Image
-- **************************************************
-- Provide Moho with the name of this script object
-- **************************************************

ScriptName = "MR_Path"

-- **************************************************
-- General information about this script
-- **************************************************

MR_Path = {}

function MR_Path:Name()
	return self:Localize('UILabel')
end

function MR_Path:Version()
	return '1.4.3'
end

function MR_Path:UILabel()
	return self:Localize('UILabel')
end

function MR_Path:Creator()
	return 'Eugene Babich'
end

function MR_Path:Description()
	return self:Localize('Description')
end

-- **************************************************
-- Is Relevant / Is Enabled
-- **************************************************

function MR_Path:IsRelevant(moho)
	return true
end

function MR_Path:IsEnabled(moho)
	return true
end

-- **************************************************
-- Recurring Values
-- **************************************************

MR_Path.pathOpacity = 100
MR_Path.width = 1
MR_Path.pathColorR = 84
MR_Path.pathColorG = 212
MR_Path.pathColorB = 196
MR_Path.pathColorA = 255
MR_Path.centerLine = false
MR_Path.autoRandomizeColor = true
MR_Path.drawTimingMark = true
MR_Path.timingMarksSize = 0.5
MR_Path.trackBoneTip = false
MR_Path.adjacentFramesRange = false
MR_Path.adjacentFrames = 10
MR_Path.playbackRange = true
MR_Path.fromToRange = false
MR_Path.from = 1
MR_Path.to = 20
MR_Path.interval = 1
MR_Path.maxTargets = 60
MR_Path.isMouseDragging = false
MR_Path.selRect = LM.Rect:new_local()
MR_Path.trackingList = {}
MR_Path.trackingList.target = {}
MR_Path.trackingList.visibility = {}
MR_Path.trackingList.targetType = {}
MR_Path.trackingList.targetID = {}
MR_Path.trackingList.targetName = {}
MR_Path.trackingList.layerName = {}
MR_Path.trackingList.layerUUID = {}
MR_Path.trackingList.layer = {}
MR_Path.trackingList.boneTip = {}
MR_Path.trackingList.color = {}
MR_Path.trackingList.description = {}
MR_Path.trackingList.pos = {}
MR_Path.isTracking = false
MR_Path.scriptDataName = 'MR_Path_'

-- **************************************************
-- Prefs
-- **************************************************

function MR_Path:LoadPrefs(prefs)
	self.pathOpacity = prefs:GetInt("MR_Path.pathOpacity", 100)
	self.width = prefs:GetFloat("MR_Path.width", 1)
	self.pathColorR = prefs:GetInt("MR_Path.pathColorR", 84)
	self.pathColorG = prefs:GetInt("MR_Path.pathColorG", 212)
	self.pathColorB = prefs:GetInt("MR_Path.pathColorB", 196)
	self.pathColorA = prefs:GetInt("MR_Path.pathColorA", 255)
	self.autoRandomizeColor = prefs:GetBool("MR_Path.autoRandomizeColor", true)
	self.centerLine = prefs:GetBool("MR_Path.centerLine", false)
	self.drawTimingMark = prefs:GetBool("MR_Path.drawTimingMark", true)
	self.timingMarksSize = prefs:GetFloat("MR_Path.timingMarksSize", 0.5)
	self.trackBoneTip = prefs:GetBool("MR_Path.trackBoneTip", false)
	self.adjacentFramesRange = prefs:GetBool("MR_Path.adjacentFramesRange", false)
	self.adjacentFrames = prefs:GetInt("MR_Path.adjacentFrames", 10)
	self.playbackRange = prefs:GetBool("MR_Path.playbackRange", true)
	self.fromToRange = prefs:GetBool("MR_Path.fromToRange", false)
	self.from = prefs:GetInt("MR_Path.from", 1)
	self.to = prefs:GetInt("MR_Path.to", 20)
	self.interval = prefs:GetInt("MR_Path.interval", 1)
end

function MR_Path:SavePrefs(prefs)
	prefs:SetInt("MR_Path.pathOpacity", self.pathOpacity)
	prefs:SetFloat("MR_Path.width", self.width)
	prefs:SetInt("MR_Path.pathColorR", self.pathColorR)
	prefs:SetInt("MR_Path.pathColorG", self.pathColorG)
	prefs:SetInt("MR_Path.pathColorB", self.pathColorB)
	prefs:SetInt("MR_Path.pathColorA", self.pathColorA)
	prefs:SetBool("MR_Path.autoRandomizeColor", self.autoRandomizeColor)
	prefs:SetBool("MR_Path.centerLine", self.centerLine)
	prefs:SetBool("MR_Path.drawTimingMark", self.drawTimingMark)
	prefs:SetFloat("MR_Path.timingMarksSize", self.timingMarksSize)
	prefs:SetBool("MR_Path.trackBoneTip", self.trackBoneTip)
	prefs:SetBool("MR_Path.adjacentFramesRange", self.adjacentFramesRange)
	prefs:SetInt("MR_Path.adjacentFrames", self.adjacentFrames)
	prefs:SetBool("MR_Path.playbackRange", self.playbackRange)
	prefs:SetBool("MR_Path.fromToRange", self.fromToRange)
	prefs:SetInt("MR_Path.from", self.from)
	prefs:SetInt("MR_Path.to", self.to)
	prefs:SetInt("MR_Path.interval", self.interval)
end

function MR_Path:ResetPrefs()
	self.pathOpacity = 100
	self.width = 1
	self.pathColorR = 84
	self.pathColorG = 212
	self.pathColorB = 196
	self.pathColorA = 255
	self.autoRandomizeColor = true
	self.centerLine = false
	self.drawTimingMark = true
	self.timingMarksSize = 0.5
	self.trackBoneTip = false
	self.adjacentFramesRange = false
	self.adjacentFrames = 10
	self.playbackRange = true
	self.fromToRange = false
	self.from = 1
	self.to = 20
	self.interval = 1
end

function MR_PathUpdateDialog(moho)
	if MR_Path.floatingPanelDlog ~= nil then
		MR_Path.floatingPanelDlog:UpdateWidgets()
	end
end

table.insert(MOHO.UpdateTable, MR_PathUpdateDialog)

-- **************************************************
-- Keyboard/Mouse Control
-- **************************************************

function MR_Path:OnMouseDown(moho, mouseEvent)
	local skel = moho:Skeleton()
	local mesh = moho:Mesh()
	if not skel and not mesh then
		return
	end
	
	self.isMouseDragging = true
	if not mouseEvent.shiftKey and not mouseEvent.altKey then
		if skel then
			skel:SelectNone()
		elseif mesh then
			mesh:SelectNone()
		end	
	end
	
	self.selRect.left = mouseEvent.startPt.x
	self.selRect.top = mouseEvent.startPt.y
	self.selRect.right = mouseEvent.pt.x
	self.selRect.bottom = mouseEvent.pt.y
	mouseEvent.view:Graphics():SelectionRect(self.selRect)
	mouseEvent.view:DrawMe()
end

function MR_Path:OnMouseMoved(moho, mouseEvent)
	local skel = moho:Skeleton()
	local mesh = moho:Mesh()
	if not skel and not mesh then
		return
	end

	mouseEvent.view:Graphics():SelectionRect(self.selRect)
	self.selRect.right = mouseEvent.pt.x
	self.selRect.bottom = mouseEvent.pt.y
	mouseEvent.view:Graphics():SelectionRect(self.selRect)
	mouseEvent.view:RefreshView()
	mouseEvent.view:DrawMe()
end

function MR_Path:OnMouseUp(moho, mouseEvent)
	local skel = moho:Skeleton()
	local mesh = moho:Mesh()
	local mouseDist = math.abs(mouseEvent.pt.x - mouseEvent.startPt.x) + math.abs(mouseEvent.pt.y - mouseEvent.startPt.y)

	local id
	
	if skel then
		id = mouseEvent.view:PickBone(mouseEvent.pt, mouseEvent.vec, moho.layer, true)
		if not mouseEvent.shiftKey and not mouseEvent.altKey then
			skel:SelectNone()
		end	
		if id ~= -1 then
			skel:Bone(id).fSelected = not mouseEvent.altKey
		end
	elseif mesh then
		id = mouseEvent.view:PickPoint(mouseEvent.pt)
		if not mouseEvent.shiftKey and not mouseEvent.altKey then
			mesh:SelectNone()
		end	
		if id ~= -1 then
			mesh:Point(id).fSelected = not mouseEvent.altKey
		end
	end

	self.isMouseDragging = false

	local v = LM.Vector2:new_local()
	local screenPt = LM.Point:new_local()
	local m = LM.Matrix:new_local()

	if	moho.layer:LayerType() == MOHO.LT_BONE then
		if (skel ~= nil) then
			self.selRect:Normalize()
			moho.layer:GetFullTransform(moho.frame, m, moho.document)
			for i = 0, skel:CountBones() - 1 do
				local bone = skel:Bone(i)
				local boneMatrix = bone.fMovedMatrix
				for j = 0, 10 do
					v:Set(bone.fLength * j / 10.0, 0)
					boneMatrix:Transform(v)
					m:Transform(v)
					mouseEvent.view:Graphics():WorldToScreen(v, screenPt)
					if (self.selRect:Contains(screenPt)) then
						if (mouseEvent.altKey) then
							bone.fSelected = false
						else
							bone.fSelected = true
						end
						break
					end
				end
			end
		end
	elseif	moho.layer:LayerType() == MOHO.LT_VECTOR then
		if (mesh ~= nil) then
			local screenPt = LM.Point:new_local()
			local m = LM.Matrix:new_local()
			self.selRect:Normalize()
			moho.drawingLayer:GetFullTransform(moho.frame, m, moho.document)
			local v = LM.Vector2:new_local()
			for i = 0, mesh:CountPoints() - 1 do
				local pt = mesh:Point(i)
				if (not pt.fHidden) then
					v:Set(pt.fPos)
					m:Transform(v)
					mouseEvent.view:Graphics():WorldToScreen(v, screenPt)
					if (self.selRect:Contains(screenPt)) then
						if (mouseEvent.altKey) then
							pt.fSelected = false
						else
							pt.fSelected = true
						end
					end
				end
			end
			self.isMouseDragging = false
		end	
	end
	moho:UpdateSelectedChannels()
end

function MR_Path:DrawMe(moho, view)
	if self.isMouseDragging then
		local g = view:Graphics()
		g:SelectionRect(self.selRect)
	end
end

local MR_PathFloatingPanelDialog = {}

MR_PathFloatingPanelDialog.ADD_TO_TRACKING = MOHO.MSG_BASE
MR_PathFloatingPanelDialog.ADD_TO_TRACKING_ALT = MOHO.MSG_BASE + 1
MR_PathFloatingPanelDialog.REMOVE_FROM_TRACKING = MOHO.MSG_BASE + 2
MR_PathFloatingPanelDialog.REMOVE_FROM_TRACKING_ALT = MOHO.MSG_BASE + 3
MR_PathFloatingPanelDialog.CLEAR_TRECKING_LIST = MOHO.MSG_BASE + 4
MR_PathFloatingPanelDialog.CREATE_PATH = MOHO.MSG_BASE + 5
MR_PathFloatingPanelDialog.CLEAR_PATH = MOHO.MSG_BASE + 6
MR_PathFloatingPanelDialog.DELETE_PATH_LAYER = MOHO.MSG_BASE + 7
MR_PathFloatingPanelDialog.SHOW_HIDE_PATH = MOHO.MSG_BASE + 8
MR_PathFloatingPanelDialog.PATH_OPACITY = MOHO.MSG_BASE + 9
MR_PathFloatingPanelDialog.WIDTH = MOHO.MSG_BASE + 10
MR_PathFloatingPanelDialog.AUTO_RANDOMIZE_COLOR = MOHO.MSG_BASE + 11
MR_PathFloatingPanelDialog.RANDOMIZE_COLOR = MOHO.MSG_BASE + 12
MR_PathFloatingPanelDialog.RANDOMIZE_COLOR_ALT = MOHO.MSG_BASE + 13
MR_PathFloatingPanelDialog.COLOR = MOHO.MSG_BASE + 14
MR_PathFloatingPanelDialog.DRAW_CENTER_LINE = MOHO.MSG_BASE + 15
MR_PathFloatingPanelDialog.DRAW_TIMING_MARK = MOHO.MSG_BASE + 16
MR_PathFloatingPanelDialog.TIMING_MARKS_SIZE = MOHO.MSG_BASE + 17
MR_PathFloatingPanelDialog.TRACK_BONE_TIP = MOHO.MSG_BASE + 18
MR_PathFloatingPanelDialog.ADJACENT_FRAMES_RANGE = MOHO.MSG_BASE + 19
MR_PathFloatingPanelDialog.ADJACENT_FRAMES = MOHO.MSG_BASE + 20
MR_PathFloatingPanelDialog.PLAYBACK_RANGE = MOHO.MSG_BASE + 21
MR_PathFloatingPanelDialog.FROM_TO_RANGE = MOHO.MSG_BASE + 22
MR_PathFloatingPanelDialog.FROM = MOHO.MSG_BASE + 23
MR_PathFloatingPanelDialog.TO = MOHO.MSG_BASE + 24
MR_PathFloatingPanelDialog.INTERVAL_1 = MOHO.MSG_BASE + 25
MR_PathFloatingPanelDialog.INTERVAL_2 = MOHO.MSG_BASE + 26
MR_PathFloatingPanelDialog.PATH_TARGET_MANAGER = MOHO.MSG_BASE + 27

function MR_PathFloatingPanelDialog:new(moho)
    local d = LM.GUI.SimpleDialog(MR_Path:Localize('UILabel'), MR_PathFloatingPanelDialog)
    local l = d:GetLayout()
	
	l:PushH()
	
		l:AddPadding(2)
	
		d.addToTrackingButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_add_to_tracking', MR_Path:Localize('Add to tracking'), false, self.ADD_TO_TRACKING, false)
		d.addToTrackingButton:SetAlternateMessage(self.ADD_TO_TRACKING_ALT)
		l:AddChild(d.addToTrackingButton, LM.GUI.ALIGN_LEFT, 0)

		d.removeFromTrackingButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_remove_from_tracking', MR_Path:Localize('Remove from tracking'), false, self.REMOVE_FROM_TRACKING, false)
		d.removeFromTrackingButton:SetAlternateMessage(self.REMOVE_FROM_TRACKING_ALT)
		l:AddChild(d.removeFromTrackingButton, LM.GUI.ALIGN_LEFT, 0)

		d.clearTrackingListButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_clear_tracking_list', MR_Path:Localize('Clear tracking list'), false, self.CLEAR_TRECKING_LIST, false)
		l:AddChild(d.clearTrackingListButton, LM.GUI.ALIGN_LEFT, 0)
		
		l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)

		d.createPathButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_create_path', MR_Path:Localize('Create path'), false, self.CREATE_PATH, false)
		l:AddChild(d.createPathButton, LM.GUI.ALIGN_LEFT, 0)

		d.clearPathButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_clear_path', MR_Path:Localize('Clear path'), false, self.CLEAR_PATH, false)
		l:AddChild(d.clearPathButton, LM.GUI.ALIGN_LEFT, 0)

		d.deletePathLayerButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_delete_path_layer', MR_Path:Localize('Delete path layer'), false, self.DELETE_PATH_LAYER, false)
		l:AddChild(d.deletePathLayerButton, LM.GUI.ALIGN_LEFT, 0)
		
		l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)

		d.showHidePathCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_visibility', MR_Path:Localize('Show hide'), true, self.SHOW_HIDE_PATH, false)
		l:AddChild(d.showHidePathCheck, LM.GUI.ALIGN_LEFT, 0)

		d.pathOpacityInput = LM.GUI.TextControl(0, '100', self.PATH_OPACITY, LM.GUI.FIELD_INT, MR_Path:Localize('Path Opacity:'))
		d.pathOpacityInput:SetToolTip(MR_Path:Localize('Opacity for all Paths'))
		l:AddChild(d.pathOpacityInput, LM.GUI.ALIGN_LEFT, 0)

		d.widthInput = LM.GUI.TextControl(0, '100', self.WIDTH, LM.GUI.FIELD_UFLOAT, MR_Path:Localize('Width:'))
		d.widthInput:SetWheelInc(1.0)
		d.widthInput:SetWheelInteger(true)
		d.widthInput:SetToolTip(MR_Path:Localize('Width for all Paths'))
		l:AddChild(d.widthInput, LM.GUI.ALIGN_LEFT, 0)
		
		d.colorText = LM.GUI.DynamicText(MR_Path:Localize('Color'), 0)
		l:AddChild(d.colorText, LM.GUI.ALIGN_LEFT, 0)
		
		d.autoRandomColorButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_auto_random', MR_Path:Localize('Auto randomize color'), true, self.AUTO_RANDOMIZE_COLOR, false)
		l:AddChild(d.autoRandomColorButton, LM.GUI.ALIGN_LEFT, 0)
		
		l:PushV()
							
			l:AddPadding(-7)
		
			d.randomColorButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_random', MR_Path:Localize('Randomize color'), false, self.RANDOMIZE_COLOR, false)
			d.randomColorButton:SetAlternateMessage(self.RANDOMIZE_COLOR_ALT)
			l:AddChild(d.randomColorButton, LM.GUI.ALIGN_LEFT, 0)
		
		l:Pop()
		
		d.pathColorSwatch = LM.GUI.ShortColorSwatch(true, self.COLOR)
		l:AddChild(d.pathColorSwatch, LM.GUI.ALIGN_LEFT)
		d.pathColorSwatch:SetToolTip(MR_Path:Localize('Path Color Tooltip'))

	l:Pop()
	
	l:AddChild(LM.GUI.Divider(false), LM.GUI.ALIGN_FILL)
	
	l:PushH()
	
		d.centerLineCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_center_line', MR_Path:Localize('Draw center line'), true, self.DRAW_CENTER_LINE, false)
		l:AddChild(d.centerLineCheck, LM.GUI.ALIGN_LEFT, 0)
		
		d.drawTimingMarkCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_draw_timing_marks', MR_Path:Localize('Draw timing marks'), true, self.DRAW_TIMING_MARK, false)
		l:AddChild(d.drawTimingMarkCheck, LM.GUI.ALIGN_LEFT, 0)
		
		d.timingMarksSizeInput = LM.GUI.TextControl(0, '100', self.TIMING_MARKS_SIZE, LM.GUI.FIELD_FLOAT, MR_Path:Localize('Size:'))
		d.timingMarksSizeInput:SetWheelInc(0.01)
		d.timingMarksSizeInput:SetToolTip(MR_Path:Localize('Timing marks size'))
		l:AddChild(d.timingMarksSizeInput, LM.GUI.ALIGN_LEFT, 0)
		
		d.trackBoneTipCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_track_bone_tip', MR_Path:Localize('Track bone tip'), true, self.TRACK_BONE_TIP, false)
		l:AddChild(d.trackBoneTipCheck, LM.GUI.ALIGN_LEFT, 0)
		
		l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
		
		d.adjacentFramesRangeCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_adjacent_frames', MR_Path:Localize('Adjacent frames range'), true, self.ADJACENT_FRAMES_RANGE, false)
		l:AddChild(d.adjacentFramesRangeCheck, LM.GUI.ALIGN_LEFT, 0)
		
		d.adjacentFramesInput = LM.GUI.TextControl(0, '100', self.ADJACENT_FRAMES, LM.GUI.FIELD_INT, '')
		d.adjacentFramesInput:SetToolTip(MR_Path:Localize('Adjacent frames'))
		l:AddChild(d.adjacentFramesInput, LM.GUI.ALIGN_LEFT, 0)
		
		l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
		
		d.playbackRangeCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_playback_range', MR_Path:Localize('Playback range'), true, self.PLAYBACK_RANGE, false)
		l:AddChild(d.playbackRangeCheck, LM.GUI.ALIGN_LEFT, 0)
		
		l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
		
		d.fromToRangeCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_from_to', MR_Path:Localize('From to range'), true, self.FROM_TO_RANGE, false)
		l:AddChild(d.fromToRangeCheck, LM.GUI.ALIGN_LEFT, 0)
		
		d.fromInput = LM.GUI.TextControl(0, '100', self.FROM, LM.GUI.FIELD_INT, '')
		d.fromInput:SetToolTip(MR_Path:Localize('From'))
		l:AddChild(d.fromInput, LM.GUI.ALIGN_LEFT, 0)
		
		d.toInput = LM.GUI.TextControl(0, '100', self.TO, LM.GUI.FIELD_INT, '')
		d.toInput:SetToolTip(MR_Path:Localize('To'))
		l:AddChild(d.toInput, LM.GUI.ALIGN_LEFT, 0)
		
		l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
		
		l:AddChild(LM.GUI.StaticText(MR_Path:Localize("IntervalText")))

		d.intervalMenu = LM.GUI.Menu(MOHO.Localize("Interval=Interval"))
		d.intervalMenu:AddItem(MOHO.Localize("1=1"), 0, self.INTERVAL_1)
		d.intervalMenu:AddItemAlphabetically(MOHO.Localize("2=2"), 0, self.INTERVAL_2)

		d.intervalPopup = LM.GUI.PopupMenu(50, true)
		d.intervalPopup:SetMenu(d.intervalMenu)
		l:AddChild(d.intervalPopup)
		
		l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
		
		d.PathTargetManagerButton = LM.GUI.Button('TM', self.PATH_TARGET_MANAGER)
		d.PathTargetManagerButton:SetToolTip(MR_Path:Localize('Path Target Manager'))
		l:AddChild(d.PathTargetManagerButton, LM.GUI.ALIGN_LEFT, 0)
	
	l:Pop()
	
	return d
end

function MR_PathFloatingPanelDialog:UpdateWidgets(moho)
	if MR_Path.isTracking then
		return
	end
	
	local helper = MOHO.ScriptInterfaceHelper:new_local()
	local moho = helper:MohoObject()
	if moho.document == nil then
		helper:delete()
		return
	end
	
	local pathLayer, targetLayer = MR_Path:CheckExistPathLayer(moho)
	
	helper:delete()
	
	local isPathLayer = false
	local isTarget = false
	if pathLayer then
		isPathLayer = true
		local scriptInfo = pathLayer:ScriptData()
		local targetIdKey = MR_Path.scriptDataName..'targetID'
		local testList = {}
		if scriptInfo:HasKey(targetIdKey) then
			local keyValue = scriptInfo:GetString(targetIdKey)
			MR_Utilities:StringToTable(keyValue, testList, 'n')
		end
		
		if #testList > 0 then
			isTarget = true
		end
	end
	
	self.createPathButton:Enable(isTarget)
	self.clearPathButton:Enable(isPathLayer)
	self.deletePathLayerButton:Enable(isPathLayer)
	self.showHidePathCheck:Enable(isPathLayer)
	if pathLayer then
		self.showHidePathCheck:SetValue(pathLayer:IsVisible())
		pathLayer.fAlpha:GetValue(0)
	else
		self.pathOpacityInput:SetValue(MR_Path.pathOpacity)
		self.showHidePathCheck:SetValue(true)
	end
	self.widthInput:SetValue(MR_Path.width)
	local pathColor = LM.rgb_color:new_local()
	pathColor.r = MR_Path.pathColorR
	pathColor.g = MR_Path.pathColorG
	pathColor.b = MR_Path.pathColorB
	pathColor.a = MR_Path.pathColorA
	self.pathColorSwatch:SetValue(pathColor)
	self.pathColorSwatch:Enable(not MR_Path.autoRandomizeColor)
	self.randomColorButton:Enable(not MR_Path.autoRandomizeColor)
	self.autoRandomColorButton:SetValue(MR_Path.autoRandomizeColor)
	self.centerLineCheck:SetValue(MR_Path.centerLine)
	self.drawTimingMarkCheck:SetValue(MR_Path.drawTimingMark)
	self.timingMarksSizeInput:SetValue(MR_Path.timingMarksSize)
	self.trackBoneTipCheck:SetValue(MR_Path.trackBoneTip)
	self.adjacentFramesRangeCheck:SetValue(MR_Path.adjacentFramesRange)
	self.adjacentFramesInput:SetValue(MR_Path.adjacentFrames)
	self.adjacentFramesInput:Enable(MR_Path.adjacentFramesRange)
	self.playbackRangeCheck:SetValue(MR_Path.playbackRange)
	self.fromToRangeCheck:SetValue(MR_Path.fromToRange)
	self.fromInput:SetValue(MR_Path.from)
	self.fromInput:Enable(MR_Path.fromToRange)
	self.toInput:SetValue(MR_Path.to)
	self.toInput:Enable(MR_Path.fromToRange)
	
	self.intervalMenu:SetChecked(MR_Path.INTERVAL_1, false)
	self.intervalMenu:SetChecked(MR_Path.INTERVAL_2, false)
	if (MR_Path.interval == 1) then
		self.intervalMenu:SetChecked(MR_Path.INTERVAL_1, true)
	elseif (MR_Path.interval == 2) then
		self.intervalMenu:SetChecked(MR_Path.INTERVAL_2, true)
	end
	self.intervalPopup:Redraw()
end

function MR_PathFloatingPanelDialog:HandleMessage(msg)
	if MR_ActivityTimeTracker then
		MR_ActivityTimeTracker.propogateTool = 'MR_Path'
	end
	
	local helper = MOHO.ScriptInterfaceHelper:new_local()
	local moho = helper:MohoObject()
	local document = moho.document
	
	local pathLayer, targetLayer = MR_Path:CheckExistPathLayer(moho)
	local scriptInfo
	local isTarget = false
	if pathLayer then
		isPathLayer = true
		scriptInfo = pathLayer:ScriptData()
		local targetIdKey = MR_Path.scriptDataName..'targetID'
		local testList = {}
		if scriptInfo:HasKey(targetIdKey) then
			local keyValue = scriptInfo:GetString(targetIdKey)
			MR_Utilities:StringToTable(keyValue, testList, 'n')
		end
		
		if #testList > 0 then
			isTarget = true
		end
	end
	
	if msg == self.ADD_TO_TRACKING then
		if document then
			MR_Path:AddToTracking(moho, false)
			self.createPathButton:Enable(isTarget)
		end
	elseif msg == self.ADD_TO_TRACKING_ALT then
		if document then
			MR_Path:AddToTracking(moho, true)
			self.createPathButton:Enable(isTarget)
		end
	elseif msg == self.REMOVE_FROM_TRACKING then
		if document then
			MR_Path:RemoveFromTracking(moho, false)
			self.createPathButton:Enable(isTarget)
		end
	elseif msg == self.REMOVE_FROM_TRACKING_ALT then
		if document then
			MR_Path:RemoveFromTracking(moho, true)
			self.createPathButton:Enable(isTarget)
		end
	elseif msg == self.CLEAR_TRECKING_LIST then
		if document then
			if scriptInfo then
				if pathLayer then
					moho.document:SetDirty()
					moho.document:PrepUndo(pathLayer, true)
				end
				
				local targetIdKey = MR_Path.scriptDataName..'targetID'
				scriptInfo:Remove(targetIdKey)

				local layerUUIDKey = MR_Path.scriptDataName..'layerUUID'
				scriptInfo:Remove(layerUUIDKey)

				local colorKey = MR_Path.scriptDataName..'colorKey'
				scriptInfo:Remove(colorKey)

				local boneTipKey = MR_Path.scriptDataName..'boneTip'
				scriptInfo:Remove(boneTipKey)
				
				local targetNameKey = MR_Path.scriptDataName..'targetName'
				scriptInfo:Remove(targetNameKey)
				
				local visibilityKey = MR_Path.scriptDataName..'visibility'
				scriptInfo:Remove(visibilityKey)
			end
			self.createPathButton:Enable(isTarget)
		end
	elseif msg == self.CREATE_PATH then
		if document then
			MR_Path:CreatePath(moho)
		end
	elseif msg == self.CLEAR_PATH then
		if document then
			MR_Path:ClearPath(moho)
		end
	elseif msg == self.DELETE_PATH_LAYER then
		if document then
			MR_Path:CleanUpInvalidTargets(moho)
			MR_Path:DeletePathLayer(moho)
		end
	elseif msg == self.SHOW_HIDE_PATH then
		if document then
			local pathLayer, targetLayer = MR_Path:CheckExistPathLayer(moho)
			if pathLayer then
				pathLayer:SetVisible(self.showHidePathCheck:Value())
				moho:UpdateUI()
				local drawingToolsNonZero = MOHO.MohoGlobals.DisableDrawingToolsNonZero
				if not drawingToolsNonZero then
					MOHO.MohoGlobals.DisableDrawingToolsNonZero = true
				end
				local frame = moho.frame
				if frame == 0 then
					moho:SetCurFrame(1)
					moho:SetCurFrame(0)
				elseif frame ~= 0 then
					moho:SetCurFrame(0)
					moho:SetCurFrame(frame)
				end
				if not drawingToolsNonZero then
					MOHO.MohoGlobals.DisableDrawingToolsNonZero = drawingToolsNonZero
				end
			end
		end
	elseif msg == self.PATH_OPACITY then
		if document then
			MR_Path.pathOpacity = LM.Clamp(self.pathOpacityInput:Value(),0 , 100)
			self.pathOpacityInput:SetValue(MR_Path.pathOpacity)
			local pathLayer, targetLayer = MR_Path:CheckExistPathLayer(moho)
			if pathLayer then
				moho.document:SetDirty()
				moho.document:PrepUndo(pathLayer, true)
				pathLayer.fAlpha:SetValue(0, MR_Path.pathOpacity/100)
				moho:UpdateUI()
				moho.view:DrawMe()
				pathLayer:UpdateCurFrame()
			end
		end
	elseif msg == self.WIDTH then
		MR_Path.width = LM.Clamp(self.widthInput:Value(), 0.25, 256)
		self.widthInput:SetValue(MR_Path.width)
	elseif msg == self.AUTO_RANDOMIZE_COLOR then
		MR_Path.autoRandomizeColor = self.autoRandomColorButton:Value()
		self.pathColorSwatch:Enable(not self.autoRandomizeColor)
		self.randomColorButton:Enable(not self.autoRandomizeColor)
	elseif msg == self.RANDOMIZE_COLOR then
		local oldColor = self.pathColorSwatch:Value()
		local pathColor = MR_Utilities:GenerateDistinctColor(oldColor)
		self.randomColorButton:SetValue(pathColor)
		MR_Path.pathColorR = pathColor.r
		MR_Path.pathColorG = pathColor.g
		MR_Path.pathColorB = pathColor.b
		MR_Path.pathColorA = pathColor.a
	elseif msg == self.RANDOMIZE_COLOR_ALT then
		local oldColor = self.pathColorSwatch:Value()
		local pathColor =  MR_Utilities:RandomizeBrightness(oldColor)
		self.randomColorButton:SetValue(pathColor)
		MR_Path.pathColorR = pathColor.r
		MR_Path.pathColorG = pathColor.g
		MR_Path.pathColorB = pathColor.b
		MR_Path.pathColorA = pathColor.a
	elseif msg == self.COLOR then
        local colorSwatchValue = self.pathColorSwatch:Value()
		MR_Path.pathColorR = colorSwatchValue.r
		MR_Path.pathColorG = colorSwatchValue.g
		MR_Path.pathColorB = colorSwatchValue.b
		MR_Path.pathColorA = colorSwatchValue.a	
	elseif msg == self.DRAW_CENTER_LINE then
		MR_Path.centerLine = self.centerLineCheck:Value()
	elseif msg == self.DRAW_TIMING_MARK then
		MR_Path.drawTimingMark = self.drawTimingMarkCheck:Value()
	elseif msg == self.TIMING_MARKS_SIZE then
		MR_Path.timingMarksSize = LM.Clamp(self.timingMarksSizeInput:Value(), 0.1, 6)
		self.timingMarksSizeInput:SetValue(MR_Path.timingMarksSize)
	elseif msg == self.TRACK_BONE_TIP then
		MR_Path.trackBoneTip = self.trackBoneTipCheck:Value()
	elseif msg == self.ADJACENT_FRAMES_RANGE then
		MR_Path.adjacentFramesRange = true
		self.adjacentFramesRangeCheck:SetValue(true)
		MR_Path.playbackRange = false
		self.playbackRangeCheck:SetValue(false)
		MR_Path.fromToRange = false
		self.fromToRangeCheck:SetValue(false)
		self.adjacentFramesInput:Enable(true)
		self.fromInput:Enable(false)
		self.toInput:Enable(false)
	elseif msg == self.ADJACENT_FRAMES then
		MR_Path.adjacentFrames = LM.Clamp(self.adjacentFramesInput:Value(), 1, 200)
		self.adjacentFramesInput:SetValue(MR_Path.adjacentFrames)
	elseif msg == self.PLAYBACK_RANGE then
		MR_Path.playbackRange = true
		self.playbackRangeCheck:SetValue(true)
		MR_Path.adjacentFramesRange = false
		self.adjacentFramesRangeCheck:SetValue(false)
		MR_Path.fromToRange = false
		self.fromToRangeCheck:SetValue(false)
		self.adjacentFramesInput:Enable(false)
		self.fromInput:Enable(false)
		self.toInput:Enable(false)
	elseif msg == self.FROM_TO_RANGE then
		MR_Path.fromToRange = true
		self.fromToRangeCheck:SetValue(true)
		MR_Path.adjacentFramesRange = false
		self.adjacentFramesRangeCheck:SetValue(false)
		MR_Path.playbackRange = false
		self.playbackRangeCheck:SetValue(false)
		self.adjacentFramesInput:Enable(false)
		self.fromInput:Enable(true)
		self.toInput:Enable(true)
	elseif msg == self.FROM then
		MR_Path.from = LM.Clamp(self.fromInput:Value(), 1, self.toInput:Value() - 1)
		self.fromInput:SetValue(MR_Path.from)
	elseif msg == self.TO then
		MR_Path.to = LM.Clamp(self.toInput:Value(), self.fromInput:Value() + 1, 2000)
		self.toInput:SetValue(MR_Path.to)
	elseif (msg >= self.INTERVAL_1 and msg <= self.INTERVAL_2) then
		local int = 1
		if (msg == self.INTERVAL_1) then
			int = 1
		elseif (msg == self.INTERVAL_2) then
			int = 2
		end	
		MR_Path.interval = int
	elseif msg == self.PATH_TARGET_MANAGER then
		if document then
			MR_Path:BuildDialog(moho)
			self.createPathButton:Enable(isTarget)
		end
	end
	
	if document then
		if moho:CurrentTool() == 'MR_Path' then
			MR_Path:UpdateWidgets(moho)
		end
		self:UpdateWidgets(moho)
	end
	if MR_ActivityTimeTracker then
		MR_ActivityTimeTracker:UpdateTime(moho)
	end
	helper:delete()
end	

function MR_PathFloatingPanelDialog:OnOK(moho)
	MR_Path.floatingPanelDlog = nil
end

local MR_PathManagerDialog = {}

MR_PathManagerDialog.dynamicNumberText = {}
MR_PathManagerDialog.visibility = {}
MR_PathManagerDialog.resetButton = {}
MR_PathManagerDialog.dynamicSymbolText = {}
MR_PathManagerDialog.targetType = {}
MR_PathManagerDialog.targetID = {}
MR_PathManagerDialog.layer = {}
MR_PathManagerDialog.boneTip = {}
MR_PathManagerDialog.randomColor = {}
MR_PathManagerDialog.color = {}
MR_PathManagerDialog.removeCheck = {}
MR_PathManagerDialog.editedStatus = {}
MR_PathManagerDialog.edited = '*'
MR_PathManagerDialog.notEdited = ' '

MR_PathManagerDialog.CHANGE = MOHO.MSG_BASE
MR_PathManagerDialog.RANDOMIZE_COLORS = MOHO.MSG_BASE + 1
MR_PathManagerDialog.RANDOMIZE_COLORS_ALT = MOHO.MSG_BASE + 2
MR_PathManagerDialog.RANDOMIZE_COLOR = MOHO.MSG_BASE + 3
MR_PathManagerDialog.RANDOMIZE_COLOR_ALT = MOHO.MSG_BASE + MR_Path.maxTargets + 4
MR_PathManagerDialog.RESET_SETTINGS = MOHO.MSG_BASE + (MR_Path.maxTargets * 2) + 5
MR_PathManagerDialog.COPY_SETTINGS = MOHO.MSG_BASE + (MR_Path.maxTargets * 3) + 6
MR_PathManagerDialog.VISIBILITY = MOHO.MSG_BASE + (MR_Path.maxTargets * 4) + 7
MR_PathManagerDialog.VISIBILITY_ALT = MOHO.MSG_BASE + (MR_Path.maxTargets * 5) + 8

function MR_PathManagerDialog:new(moho)
    local d = LM.GUI.SimpleDialog(MR_Path:Localize('UILabel'), MR_PathManagerDialog)
    local l = d:GetLayout()
	
	l:AddPadding(-12)
	
	d.dynamicHeaderText = LM.GUI.DynamicText(MR_Path:Localize('Path Target Manager'), 0)
	l:AddChild(d.dynamicHeaderText, LM.GUI.ALIGN_CENTER, 0)
	
	l:AddPadding(-12)
	
	l:AddChild(LM.GUI.Divider(false), LM.GUI.ALIGN_FILL)
	local listLength = #MR_Path.trackingList.targetID
	if not MR_Path.trackingList.targetID[1] then
		d.dynamicText = LM.GUI.DynamicText(MR_Path:Localize('Path target list is empty'), 0)
		l:AddChild(d.dynamicText, LM.GUI.ALIGN_LEFT, 0)
	else
		l:PushH()
			for i = 1, listLength do
				d.editedStatus[i] = d.notEdited
				
				if i == 1 then
					l:PushV()
				elseif i == 16 then
					l:Pop()
					l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
					l:PushV()
				elseif i == 31 or i == 46 then
					l:Pop()
					l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
					l:PushV()
				elseif i == 61 then
					l:Pop()	
				end
				if i <= MR_Path.maxTargets then
					l:PushH()
						local numberText
						if i < 10 then
							numberText = '  '..i
						else
							numberText = ''..i
						end	
						
						l:AddPadding(1)
						
						local dynamicTxt = LM.GUI.DynamicText(numberText, 10)
						d.dynamicNumberText[i] = dynamicTxt
						l:AddChild(d.dynamicNumberText[i], LM.GUI.ALIGN_LEFT, 0)
						
						local symbolText = d.editedStatus[i]
						
						d.dynamicSymbolText[i] = LM.GUI.DynamicText(symbolText, 10)
						d.dynamicSymbolText[i]:SetToolTip(MR_Path:Localize('Settings not changed'))
						l:AddChild(d.dynamicSymbolText[i], LM.GUI.ALIGN_LEFT, 0)
						
						
						d.visibility[i] = LM.GUI.ImageButton('ScriptResources/mr_path/mr_visibility', MR_Path:Localize('Visibility'), true, self.VISIBILITY + i, false)
						d.visibility[i]:SetAlternateMessage(self.VISIBILITY_ALT + i)
						l:AddChild(d.visibility[i], LM.GUI.ALIGN_LEFT, 0)
						
						local typeText = ''
						
						local targetID = MR_Path.trackingList.targetID[i]
						local targetIDText = targetID
						local layerNameText = ' Layer: '.. MR_Path.trackingList.layer[i]:Name()
						if MR_Path.trackingList.boneTip[i] == 'nil' then
							targetIDText = ' ID: '.. targetIDText
							typeText = 'Point '..targetID..' '..MR_Path.trackingList.layer[i]:Name()
							if #typeText > 26 then
								typeText = typeText:sub(1, 22) .. "..."
							end
						else
							typeText = MR_Path.trackingList.description[i]
							if #typeText > 26 then
								typeText = typeText:sub(1, 22) .. "..."
							end
							targetIDText = 'ID: '.. targetIDText
						end
						
						d.targetType[i] = LM.GUI.DynamicText(typeText, 150)

						d.targetType[i]:SetToolTip(targetIDText..layerNameText)
						l:AddChild(d.targetType[i], LM.GUI.ALIGN_LEFT, 0)
						
						d.resetButton[i] = LM.GUI.Button('R', self.RESET_SETTINGS + i)
						d.resetButton[i]:SetToolTip(MR_Path:Localize('Reset settings'))
						d.resetButton[i]:SetAlternateMessage(self.COPY_SETTINGS + i)
						l:AddChild(d.resetButton[i], LM.GUI.ALIGN_LEFT, 0)
						
						l:PushV()
						
							l:AddPadding(-5)
						
							d.randomColor[i] = LM.GUI.ImageButton('ScriptResources/mr_path/mr_random', MR_Path:Localize('Randomize color'), false, self.RANDOMIZE_COLOR + i, false)
							d.randomColor[i]:SetAlternateMessage(self.RANDOMIZE_COLOR_ALT + i)
							l:AddChild(d.randomColor[i], LM.GUI.ALIGN_LEFT, 0)
							
						l:Pop()
						
						l:PushV()
						
							l:AddPadding(-6)
						
							d.color[i] = LM.GUI.ShortColorSwatch(true, self.CHANGE)
							d.color[i]:SetToolTip(MR_Path:Localize('Path Color Tooltip'))
							l:AddChild(d.color[i], LM.GUI.ALIGN_LEFT)
							
						l:Pop()
						
						if MR_Path.trackingList.boneTip[i] == nil then
							d.boneTip[i] = LM.GUI.CheckBox(MR_Path:Localize(''), d.CHANGE)
							d.boneTip[i]:SetToolTip(MR_Path:Localize('Track bone tip'))
							l:AddChild(d.boneTip[i], LM.GUI.ALIGN_LEFT, 0)
							d.boneTip[i]:Enable(false)
						elseif MR_Path.trackingList.boneTip[i] ~= nil then
							d.boneTip[i] = LM.GUI.CheckBox(MR_Path:Localize(''), d.CHANGE)
							d.boneTip[i]:SetToolTip(MR_Path:Localize('Track bone tip'))
							l:AddChild(d.boneTip[i], LM.GUI.ALIGN_LEFT, 0)
						end
						
						l:AddPadding(-14)
						
						l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
						
						l:AddPadding(-8)
						
						d.removeCheck[i] = LM.GUI.CheckBox(MR_Path:Localize(''), d.CHANGE)
						d.removeCheck[i]:SetToolTip(MR_Path:Localize('Remove from tracking'))
						l:AddChild(d.removeCheck[i], LM.GUI.ALIGN_LEFT, 0)
						
					l:Pop()
					
					l:AddPadding(-13)
					
					l:AddChild(LM.GUI.Divider(false), LM.GUI.ALIGN_FILL)
					
					l:AddPadding(-13)
					
					if i == listLength then
						l:Pop()
					end
				end
			end
		l:Pop()
		
		d.randomizeColorsButton = LM.GUI.Button(MR_Path:Localize('Randomize colors'), self.RANDOMIZE_COLORS)
		d.randomizeColorsButton:SetAlternateMessage(self.RANDOMIZE_COLORS_ALT)
		d.randomizeColorsButton:SetToolTip(MR_Path:Localize('Randomize colors tooltip'))
		l:AddChild(d.randomizeColorsButton, LM.GUI.ALIGN_FILL, 0)
	end	
	return d
end	

function MR_PathManagerDialog:UpdateWidgets(moho)
	for i = 1, #MR_Path.trackingList.targetID do
		for t = 1, #MR_Path.trackingList.targetID do
			if MR_Path.trackingList.targetID[t] == MR_Path.trackingList.targetID[i] and i ~= t then
				self.boneTip[i]:Enable(false)
				self.boneTip[t]:Enable(false)
			end
		end
		local r, g, b, a = MR_Utilities:HexToRgba(MR_Path.trackingList.color[i])
		
		local pathColor = LM.rgb_color:new_local()
		pathColor.r = r
		pathColor.g = g
		pathColor.b = b
		pathColor.a = a
		self.color[i]:SetValue(pathColor)
		local boneTipVal = true
		if MR_Path.trackingList.boneTip[i] == 'true' then
			boneTipVal = true
		elseif MR_Path.trackingList.boneTip[i] == 'false' then
			boneTipVal = false
		end
		
		self.boneTip[i]:SetValue(boneTipVal)
		local visibilityVal = MR_Path.trackingList.visibility[i]
		if visibilityVal == nil then
			visibilityVal = true
		end
		self.visibility[i]:SetValue(visibilityVal)
	end
end

function MR_PathManagerDialog:HandleMessage(msg)
	local targetListLength = #MR_Path.trackingList.targetID
    if msg == self.CHANGE then
		self:CheckTargetStatus(moho)
    elseif msg == self.RANDOMIZE_COLORS then
		local colorList = {}
		for i = 1, targetListLength do
			local pathColor = MR_Utilities:GenerateDistinctColor(colorList)
			table.insert(colorList, pathColor)
		end
		
		for i = 1, targetListLength do
			self.color[i]:SetValue(colorList[i])
		end
		self:CheckTargetStatus(moho)
    elseif msg == self.RANDOMIZE_COLORS_ALT then
		for i = 1, targetListLength do
			local oldColor = self.color[i]:Value()
			local pathColor = MR_Utilities:RandomizeBrightness(oldColor)
			self.color[i]:SetValue(pathColor)
		end
		self:CheckTargetStatus(moho)
    elseif msg >= self.RESET_SETTINGS and msg <= self.RESET_SETTINGS + targetListLength then
		local index = msg - self.RESET_SETTINGS
		local r, g, b, a = MR_Utilities:HexToRgba(MR_Path.trackingList.color[index])
		
		local pathColor = LM.rgb_color:new_local()
		pathColor.r = r
		pathColor.g = g
		pathColor.b = b
		pathColor.a = a
		
		self.color[index]:SetValue(pathColor)
		self.boneTip[index]:SetValue(MR_Path.trackingList.boneTip[index])
		self.removeCheck[index]:SetValue(false)
		self:CheckTargetStatus(moho)
	elseif msg >= self.RANDOMIZE_COLOR and msg <= self.RANDOMIZE_COLOR + targetListLength then
		local index = msg - self.RANDOMIZE_COLOR
		local oldColor = self.color[index]:Value()
		local pathColor = MR_Utilities:GenerateDistinctColor(oldColor)
		self.color[index]:SetValue(pathColor)
		self:CheckTargetStatus(moho)
	elseif msg >= self.RANDOMIZE_COLOR_ALT and msg <= self.RANDOMIZE_COLOR_ALT + targetListLength then
		local index = msg - self.RANDOMIZE_COLOR_ALT
		local oldColor = self.color[index]:Value()
		local pathColor = MR_Utilities:RandomizeBrightness(oldColor)
		self.color[index]:SetValue(pathColor)
		self:CheckTargetStatus(moho)	
    elseif msg >= self.COPY_SETTINGS and msg <= self.COPY_SETTINGS + targetListLength then
		local index = msg - self.COPY_SETTINGS
		for i = 1, targetListLength do
			self.color[i]:SetValue(self.color[index]:Value())
		end
		self:CheckTargetStatus(moho)
    elseif msg >= self.VISIBILITY_ALT and msg <= self.VISIBILITY_ALT + targetListLength then
		local index = msg - self.VISIBILITY_ALT
		local val = not self.visibility[index]:Value()

		for i = 1, targetListLength do
			self.visibility[i]:SetValue(val)
		end
	end
end

function MR_PathManagerDialog:CheckTargetStatus(moho)
	for i = 1, #MR_Path.trackingList.targetID do
		local isSettingsChanged = false
		
		local r, g, b, a = MR_Utilities:HexToRgba(MR_Path.trackingList.color[i])
		
		local pathColor = LM.rgb_color:new_local()
		pathColor.r = r
		pathColor.g = g
		pathColor.b = b
		pathColor.a = a
		
		local color = self.color[i]:Value()
		if color.r ~= pathColor.r
		or color.g ~= pathColor.g
		or color.b ~= pathColor.b
		or color.a ~= pathColor.a then
			isSettingsChanged = true
		end	

		if MR_Path.trackingList.boneTip[i] ~= 'nil' then
			if tostring(self.boneTip[i]:Value()) ~= MR_Path.trackingList.boneTip[i] then	
				isSettingsChanged = true
			end
		end
			
		if isSettingsChanged then
			self.editedStatus[i] = self.edited
			self.dynamicSymbolText[i]:SetValue(self.editedStatus[i])
			self.dynamicSymbolText[i]:SetToolTip(MR_Path:Localize('Settings changed'))
		else
			self.editedStatus[i] = self.notEdited
			self.dynamicSymbolText[i]:SetValue(self.editedStatus[i])
			self.dynamicSymbolText[i]:SetToolTip(MR_Path:Localize('Settings not changed'))
		end
	end
end

function MR_PathManagerDialog:OnOK(moho)
	for i = 1, #MR_Path.trackingList.targetID do
		local colorSwatchValue = self.color[i]:Value()
		self.pathColorR = colorSwatchValue.r
		self.pathColorG = colorSwatchValue.g
		self.pathColorB = colorSwatchValue.b
		self.pathColorA = colorSwatchValue.a
		
		local colorVal = MR_Utilities:RgbaToHex(colorSwatchValue.r, colorSwatchValue.g, colorSwatchValue.b, colorSwatchValue.a)
		
		MR_Path.trackingList.color[i] = colorVal
		if MR_Path.trackingList.boneTip[i] ~= 'nil' then
			MR_Path.trackingList.boneTip[i] = tostring(self.boneTip[i]:Value())
		end
		MR_Path.removeList[i] = self.removeCheck[i]:Value()
		MR_Path.trackingList.visibility[i] = self.visibility[i]:Value()
	end	
end

-- **************************************************
-- Tool Panel Layout
-- **************************************************

MR_Path.ADD_TO_TRACKING = MOHO.MSG_BASE
MR_Path.ADD_TO_TRACKING_ALT = MOHO.MSG_BASE + 1
MR_Path.REMOVE_FROM_TRACKING = MOHO.MSG_BASE + 2
MR_Path.REMOVE_FROM_TRACKING_ALT = MOHO.MSG_BASE + 3
MR_Path.CLEAR_TRECKING_LIST = MOHO.MSG_BASE + 4
MR_Path.CREATE_PATH = MOHO.MSG_BASE + 5
MR_Path.CLEAR_PATH = MOHO.MSG_BASE + 6
MR_Path.DELETE_PATH_LAYER = MOHO.MSG_BASE + 7
MR_Path.SHOW_HIDE_PATH = MOHO.MSG_BASE + 8
MR_Path.PATH_OPACITY = MOHO.MSG_BASE + 9
MR_Path.WIDTH = MOHO.MSG_BASE + 10
MR_Path.AUTO_RANDOMIZE_COLOR = MOHO.MSG_BASE + 11
MR_Path.RANDOMIZE_COLOR = MOHO.MSG_BASE + 12
MR_Path.RANDOMIZE_COLOR_ALT = MOHO.MSG_BASE + 13
MR_Path.COLOR = MOHO.MSG_BASE + 14
MR_Path.DRAW_CENTER_LINE = MOHO.MSG_BASE + 15
MR_Path.DRAW_TIMING_MARK = MOHO.MSG_BASE + 16
MR_Path.TIMING_MARKS_SIZE = MOHO.MSG_BASE + 17
MR_Path.TRACK_BONE_TIP = MOHO.MSG_BASE + 18
MR_Path.ADJACENT_FRAMES_RANGE = MOHO.MSG_BASE + 19
MR_Path.ADJACENT_FRAMES = MOHO.MSG_BASE + 20
MR_Path.PLAYBACK_RANGE = MOHO.MSG_BASE + 21
MR_Path.FROM_TO_RANGE = MOHO.MSG_BASE + 22
MR_Path.FROM = MOHO.MSG_BASE + 23
MR_Path.TO = MOHO.MSG_BASE + 24
MR_Path.INTERVAL_1 = MOHO.MSG_BASE + 25
MR_Path.INTERVAL_2 = MOHO.MSG_BASE + 26
MR_Path.PATH_TARGET_MANAGER = MOHO.MSG_BASE + 27
MR_Path.OPEN_IN_PANEL = MOHO.MSG_BASE + 28

function MR_Path:DoLayout(moho, layout)
	self.addToTrackingButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_add_to_tracking', self:Localize('Add to tracking'), false, self.ADD_TO_TRACKING, false)
	self.addToTrackingButton:SetAlternateMessage(self.ADD_TO_TRACKING_ALT)
	layout:AddChild(self.addToTrackingButton, LM.GUI.ALIGN_LEFT, 0)

	self.removeFromTrackingButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_remove_from_tracking', self:Localize('Remove from tracking'), false, self.REMOVE_FROM_TRACKING, false)
	self.removeFromTrackingButton:SetAlternateMessage(self.REMOVE_FROM_TRACKING_ALT)
	layout:AddChild(self.removeFromTrackingButton, LM.GUI.ALIGN_LEFT, 0)

	self.clearTrackingListButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_clear_tracking_list', self:Localize('Clear tracking list'), false, self.CLEAR_TRECKING_LIST, false)
	layout:AddChild(self.clearTrackingListButton, LM.GUI.ALIGN_LEFT, 0)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)

	self.createPathButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_create_path', self:Localize('Create path'), false, self.CREATE_PATH, false)
	layout:AddChild(self.createPathButton, LM.GUI.ALIGN_LEFT, 0)

	self.clearPathButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_clear_path', self:Localize('Clear path'), false, self.CLEAR_PATH, false)
	layout:AddChild(self.clearPathButton, LM.GUI.ALIGN_LEFT, 0)

	self.deletePathLayerButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_delete_path_layer', self:Localize('Delete path layer'), false, self.DELETE_PATH_LAYER, false)
	layout:AddChild(self.deletePathLayerButton, LM.GUI.ALIGN_LEFT, 0)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)

	self.showHidePathCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_visibility', self:Localize('Show hide'), true, self.SHOW_HIDE_PATH, false)
	layout:AddChild(self.showHidePathCheck, LM.GUI.ALIGN_LEFT, 0)

	self.pathOpacityInput = LM.GUI.TextControl(0, '100', self.PATH_OPACITY, LM.GUI.FIELD_INT, self:Localize('Path Opacity:'))
	self.pathOpacityInput:SetToolTip(self:Localize('Opacity for all Paths'))
	layout:AddChild(self.pathOpacityInput, LM.GUI.ALIGN_LEFT, 0)

	self.widthInput = LM.GUI.TextControl(0, '100', self.WIDTH, LM.GUI.FIELD_UFLOAT, self:Localize('Width:'))
	self.widthInput:SetWheelInc(1.0)
	self.widthInput:SetWheelInteger(true)
	self.widthInput:SetToolTip(self:Localize('Width for all Paths'))
	layout:AddChild(self.widthInput, LM.GUI.ALIGN_LEFT, 0)
	
	self.colorText = LM.GUI.DynamicText(self:Localize('Color'), 0)
    layout:AddChild(self.colorText, LM.GUI.ALIGN_LEFT, 0)
	
	self.autoRandomColorButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_auto_random', MR_Path:Localize('Auto randomize color'), true, self.AUTO_RANDOMIZE_COLOR, false)
	layout:AddChild(self.autoRandomColorButton, LM.GUI.ALIGN_LEFT, 0)
	
	layout:PushV()
						
		layout:AddPadding(-7)
	
		self.randomColorButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_random', MR_Path:Localize('Randomize color'), false, self.RANDOMIZE_COLOR, false)
		self.randomColorButton:SetAlternateMessage(self.RANDOMIZE_COLOR_ALT)
		layout:AddChild(self.randomColorButton, LM.GUI.ALIGN_LEFT, 0)
		
	layout:Pop()
	
	self.pathColorSwatch = LM.GUI.ShortColorSwatch(true, self.COLOR)
	layout:AddChild(self.pathColorSwatch, LM.GUI.ALIGN_LEFT)
	self.pathColorSwatch:SetToolTip(MR_Path:Localize('Path Color Tooltip'))
	
	layout:AddPadding(-12)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	
	self.centerLineCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_center_line', self:Localize('Draw center line'), true, self.DRAW_CENTER_LINE, false)
	layout:AddChild(self.centerLineCheck, LM.GUI.ALIGN_LEFT, 0)
	
	self.drawTimingMarkCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_draw_timing_marks', self:Localize('Draw timing marks'), true, self.DRAW_TIMING_MARK, false)
	layout:AddChild(self.drawTimingMarkCheck, LM.GUI.ALIGN_LEFT, 0)
	
	self.timingMarksSizeInput = LM.GUI.TextControl(0, '100', self.TIMING_MARKS_SIZE, LM.GUI.FIELD_FLOAT, self:Localize('Size:'))
	self.timingMarksSizeInput:SetWheelInc(0.01)
	self.timingMarksSizeInput:SetToolTip(self:Localize('Timing marks size'))
	layout:AddChild(self.timingMarksSizeInput, LM.GUI.ALIGN_LEFT, 0)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	
	self.trackBoneTipCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_track_bone_tip', self:Localize('Track bone tip'), true, self.TRACK_BONE_TIP, false)
	layout:AddChild(self.trackBoneTipCheck, LM.GUI.ALIGN_LEFT, 0)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	
	self.adjacentFramesRangeCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_adjacent_frames', self:Localize('Adjacent frames range'), true, self.ADJACENT_FRAMES_RANGE, false)
	layout:AddChild(self.adjacentFramesRangeCheck, LM.GUI.ALIGN_LEFT, 0)
	
	self.adjacentFramesInput = LM.GUI.TextControl(0, '100', self.ADJACENT_FRAMES, LM.GUI.FIELD_INT, '')
	self.adjacentFramesInput:SetToolTip(self:Localize('Adjacent frames'))
	layout:AddChild(self.adjacentFramesInput, LM.GUI.ALIGN_LEFT, 0)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	
	self.playbackRangeCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_playback_range', self:Localize('Playback range'), true, self.PLAYBACK_RANGE, false)
	layout:AddChild(self.playbackRangeCheck, LM.GUI.ALIGN_LEFT, 0)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	
	self.fromToRangeCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_from_to', self:Localize('From to range'), true, self.FROM_TO_RANGE, false)
	layout:AddChild(self.fromToRangeCheck, LM.GUI.ALIGN_LEFT, 0)
	
	self.fromInput = LM.GUI.TextControl(0, '100', self.FROM, LM.GUI.FIELD_INT, '')
	self.fromInput:SetToolTip(self:Localize('From'))
	layout:AddChild(self.fromInput, LM.GUI.ALIGN_LEFT, 0)
	
	self.toInput = LM.GUI.TextControl(0, '100', self.TO, LM.GUI.FIELD_INT, '')
	self.toInput:SetToolTip(self:Localize('To'))
	layout:AddChild(self.toInput, LM.GUI.ALIGN_LEFT, 0)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	
	layout:AddChild(LM.GUI.StaticText(self:Localize("IntervalText")))

	self.intervalMenu = LM.GUI.Menu(MOHO.Localize("Interval=Interval"))
	self.intervalMenu:AddItem(MOHO.Localize("1=1"), 0, self.INTERVAL_1)
	self.intervalMenu:AddItemAlphabetically(MOHO.Localize("2=2"), 0, self.INTERVAL_2)

	self.intervalPopup = LM.GUI.PopupMenu(50, true)
	self.intervalPopup:SetMenu(self.intervalMenu)
	layout:AddChild(self.intervalPopup)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	
	self.PathTargetManagerButton = LM.GUI.Button(self:Localize('Path Target Manager'), self.PATH_TARGET_MANAGER)
	layout:AddChild(self.PathTargetManagerButton, LM.GUI.ALIGN_LEFT, 0)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	
	local v1, v2, v3 = MR_Utilities:GetMohoVersion(moho)
	if not v3 then
		v3 = 0
	end
	
	local modelessDialogSupport = false
	if v1 >= 14 and v2 >= 1 then
		modelessDialogSupport = true
	end
	
	if modelessDialogSupport then
		self.openInPanelButton = LM.GUI.Button(self:Localize('Open in panel'), self.OPEN_IN_PANEL)
		layout:AddChild(self.openInPanelButton, LM.GUI.ALIGN_LEFT, 0)
		
		layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	end
	
	layout:AddChild(LM.GUI.StaticText('v'..MR_Path:Version()))
end

function MR_Path:UpdateWidgets(moho)
	if self.isTracking then
		return
	end

	if moho.document == nil then
		return
	end

	local pathLayer, targetLayer = self:CheckExistPathLayer(moho)
	local isPathLayer = false
	local isTarget = false
	if pathLayer then
		isPathLayer = true
		local scriptInfo = pathLayer:ScriptData()
		local targetIdKey = self.scriptDataName..'targetID'
		local testList = {}
		if scriptInfo:HasKey(targetIdKey) then
			local keyValue = scriptInfo:GetString(targetIdKey)
			MR_Utilities:StringToTable(keyValue, testList, 'n')
		end
		
		if #testList > 0 then
			isTarget = true
		end
	end
	
	self.createPathButton:Enable(isTarget)
	self.clearPathButton:Enable(isPathLayer)
	self.deletePathLayerButton:Enable(isPathLayer)
	self.showHidePathCheck:Enable(isPathLayer)
	if pathLayer then
		self.showHidePathCheck:SetValue(pathLayer:IsVisible())
		pathLayer.fAlpha:GetValue(0)
	else
		self.pathOpacityInput:SetValue(self.pathOpacity)
		self.showHidePathCheck:SetValue(true)
	end
	self.widthInput:SetValue(self.width)
	local pathColor = LM.rgb_color:new_local()
	pathColor.r = self.pathColorR
	pathColor.g = self.pathColorG
	pathColor.b = self.pathColorB
	pathColor.a = self.pathColorA
	self.pathColorSwatch:SetValue(pathColor)
	self.pathColorSwatch:Enable(not self.autoRandomizeColor)
	self.randomColorButton:Enable(not self.autoRandomizeColor)
	self.autoRandomColorButton:SetValue(self.autoRandomizeColor)
	self.centerLineCheck:SetValue(self.centerLine)
	self.drawTimingMarkCheck:SetValue(self.drawTimingMark)
	self.timingMarksSizeInput:SetValue(self.timingMarksSize)
	self.trackBoneTipCheck:SetValue(self.trackBoneTip)
	self.adjacentFramesRangeCheck:SetValue(self.adjacentFramesRange)
	self.adjacentFramesInput:SetValue(self.adjacentFrames)
	self.adjacentFramesInput:Enable(self.adjacentFramesRange)
	self.playbackRangeCheck:SetValue(self.playbackRange)
	self.fromToRangeCheck:SetValue(self.fromToRange)
	self.fromInput:SetValue(self.from)
	self.fromInput:Enable(self.fromToRange)
	self.toInput:SetValue(self.to)
	self.toInput:Enable(self.fromToRange)
	
	self.intervalMenu:SetChecked(self.INTERVAL_1, false)
	self.intervalMenu:SetChecked(self.INTERVAL_2, false)
	if (self.interval == 1) then
		self.intervalMenu:SetChecked(self.INTERVAL_1, true)
	elseif (self.interval == 2) then
		self.intervalMenu:SetChecked(self.INTERVAL_2, true)
	end
	self.intervalPopup:Redraw()
end

function MR_Path:HandleMessage(moho, view, msg)
	if MR_ActivityTimeTracker then
		MR_ActivityTimeTracker.propogateTool = 'MR_Path'
	end
	
	local pathLayer, targetLayer = MR_Path:CheckExistPathLayer(moho)
	local scriptInfo
	local isTarget = false
	if pathLayer then
		isPathLayer = true
		scriptInfo = pathLayer:ScriptData()
		local targetIdKey = MR_Path.scriptDataName..'targetID'
		local testList = {}
		if scriptInfo:HasKey(targetIdKey) then
			local keyValue = scriptInfo:GetString(targetIdKey)
			MR_Utilities:StringToTable(keyValue, testList, 'n')
		end
		
		if #testList > 0 then
			isTarget = true
		end
	end
	
	if msg == self.ADD_TO_TRACKING then
		self:AddToTracking(moho, false)
		self.createPathButton:Enable(isTarget)
	elseif msg == self.ADD_TO_TRACKING_ALT then
		self:AddToTracking(moho, true)
		self.createPathButton:Enable(isTarget)
	elseif msg == self.REMOVE_FROM_TRACKING then
		self:RemoveFromTracking(moho, false)
		self.createPathButton:Enable(isTarget)
	elseif msg == self.REMOVE_FROM_TRACKING_ALT then
		self:RemoveFromTracking(moho, true)
		self.createPathButton:Enable(isTarget)
	elseif msg == self.CLEAR_TRECKING_LIST then
		if scriptInfo then
			if pathLayer then
				moho.document:SetDirty()
				moho.document:PrepUndo(pathLayer, true)
			end
			
			local targetIdKey = self.scriptDataName..'targetID'
			scriptInfo:Remove(targetIdKey)

			local layerUUIDKey = self.scriptDataName..'layerUUID'
			scriptInfo:Remove(layerUUIDKey)

			local colorKey = self.scriptDataName..'colorKey'
			scriptInfo:Remove(colorKey)

			local boneTipKey = self.scriptDataName..'boneTip'
			scriptInfo:Remove(boneTipKey)
			
			local targetNameKey = self.scriptDataName..'targetName'
			scriptInfo:Remove(targetNameKey)
			
			local visibilityKey = self.scriptDataName..'visibility'
			scriptInfo:Remove(visibilityKey)
		end
		self.createPathButton:Enable(isTarget)
	elseif msg == self.CREATE_PATH then
		self:CreatePath(moho)
	elseif msg == self.CLEAR_PATH then
		self:ClearPath(moho)
	elseif msg == self.DELETE_PATH_LAYER then
		self:CleanUpInvalidTargets(moho)
		self:DeletePathLayer(moho)
	elseif msg == self.SHOW_HIDE_PATH then
		if pathLayer then
			pathLayer:SetVisible(self.showHidePathCheck:Value())
			moho:UpdateUI()
		end	
	elseif msg == self.PATH_OPACITY then
		self.pathOpacity = LM.Clamp(self.pathOpacityInput:Value(),0 , 100)
		self.pathOpacityInput:SetValue(self.pathOpacity)
		if pathLayer then
			moho.document:SetDirty()
			moho.document:PrepUndo(pathLayer, true)
			pathLayer.fAlpha:SetValue(0, self.pathOpacity/100)
			moho:UpdateUI()
			moho.view:DrawMe()
			pathLayer:UpdateCurFrame()
		end
	elseif msg == self.WIDTH then
		self.width = LM.Clamp(self.widthInput:Value(), 0.25, 256)
		self.widthInput:SetValue(self.width)
	elseif msg == self.AUTO_RANDOMIZE_COLOR then
		self.autoRandomizeColor = self.autoRandomColorButton:Value()
		self.pathColorSwatch:Enable(not self.autoRandomizeColor)
		self.randomColorButton:Enable(not self.autoRandomizeColor)
	elseif msg == self.RANDOMIZE_COLOR then
		local oldColor = self.pathColorSwatch:Value()
		local pathColor = MR_Utilities:GenerateDistinctColor(oldColor)
		self.randomColorButton:SetValue(pathColor)
		self.pathColorR = pathColor.r
		self.pathColorG = pathColor.g
		self.pathColorB = pathColor.b
		self.pathColorA = pathColor.a
	elseif msg == self.RANDOMIZE_COLOR_ALT then
		local oldColor = self.pathColorSwatch:Value()
		local pathColor =  MR_Utilities:RandomizeBrightness(oldColor)
		self.randomColorButton:SetValue(pathColor)
		self.pathColorR = pathColor.r
		self.pathColorG = pathColor.g
		self.pathColorB = pathColor.b
		self.pathColorA = pathColor.a
	elseif msg == self.COLOR then
        local colorSwatchValue = self.pathColorSwatch:Value()
		self.pathColorR = colorSwatchValue.r
		self.pathColorG = colorSwatchValue.g
		self.pathColorB = colorSwatchValue.b
		self.pathColorA = colorSwatchValue.a	
	elseif msg == self.DRAW_CENTER_LINE then
		self.centerLine = self.centerLineCheck:Value()
	elseif msg == self.DRAW_TIMING_MARK then
		self.drawTimingMark = self.drawTimingMarkCheck:Value()
	elseif msg == self.TIMING_MARKS_SIZE then
		self.timingMarksSize = LM.Clamp(self.timingMarksSizeInput:Value(), 0.1, 6)
		self.timingMarksSizeInput:SetValue(self.timingMarksSize)
	elseif msg == self.TRACK_BONE_TIP then
		self.trackBoneTip = self.trackBoneTipCheck:Value()
	elseif msg == self.ADJACENT_FRAMES_RANGE then
		self.adjacentFramesRange = true
		self.adjacentFramesRangeCheck:SetValue(true)
		self.playbackRange = false
		self.playbackRangeCheck:SetValue(false)
		self.fromToRange = false
		self.fromToRangeCheck:SetValue(false)
		self.adjacentFramesInput:Enable(true)
		self.fromInput:Enable(false)
		self.toInput:Enable(false)
	elseif msg == self.ADJACENT_FRAMES then
		self.adjacentFrames = LM.Clamp(self.adjacentFramesInput:Value(), 1, 200)
		self.adjacentFramesInput:SetValue(self.adjacentFrames)
	elseif msg == self.PLAYBACK_RANGE then
		self.playbackRange = true
		self.playbackRangeCheck:SetValue(true)
		self.adjacentFramesRange = false
		self.adjacentFramesRangeCheck:SetValue(false)
		self.fromToRange = false
		self.fromToRangeCheck:SetValue(false)
		self.adjacentFramesInput:Enable(false)
		self.fromInput:Enable(false)
		self.toInput:Enable(false)
	elseif msg == self.FROM_TO_RANGE then
		self.fromToRange = true
		self.fromToRangeCheck:SetValue(true)
		self.adjacentFramesRange = false
		self.adjacentFramesRangeCheck:SetValue(false)
		self.playbackRange = false
		self.playbackRangeCheck:SetValue(false)
		self.adjacentFramesInput:Enable(false)
		self.fromInput:Enable(true)
		self.toInput:Enable(true)
	elseif msg == self.FROM then	
		self.from = LM.Clamp(self.fromInput:Value(), 1, self.toInput:Value() - 1)
		self.fromInput:SetValue(self.from)
	elseif msg == self.TO then
		self.to = LM.Clamp(self.toInput:Value(), self.fromInput:Value() + 1, 2000)
		self.toInput:SetValue(self.to)
	elseif (msg >= self.INTERVAL_1 and msg <= self.INTERVAL_2) then
		local int = 1
		if (msg == self.INTERVAL_1) then
			int = 1
		elseif (msg == self.INTERVAL_2) then
			int = 2
		end	
		self.interval = int
	elseif msg == self.PATH_TARGET_MANAGER then
		self:BuildDialog(moho)
		self.createPathButton:Enable(isTarget)
	elseif msg == self.OPEN_IN_PANEL then
		self:BuildFloatingPanelDialog(moho)
	end
	self:UpdateWidgets(moho)
	if self.floatingPanelDlog then
		self.floatingPanelDlog:UpdateWidgets(moho)
	end
	if MR_ActivityTimeTracker then
		MR_ActivityTimeTracker:UpdateTime(moho)
	end
end

function MR_Path:AddToTracking(moho, alt)
	local skel = moho:Skeleton()	
	local mesh = moho:Mesh()
	if not skel and not mesh then
		return
	end
	
	local pathLayer, targetLayer = self:CheckExistPathLayer(moho)
	local pathLayerMesh
	
	if not pathLayer then
		local curLayer = moho.layer
		moho.document:SetDirty()
		moho.document:PrepUndo(nil)
		moho:SetSelLayer(targetLayer)
		pathLayer = self:CreateNewLayer(moho)
		pathLayerMesh = moho:LayerAsVector(pathLayer):Mesh()
		moho:SetSelLayer(curLayer)
		pathLayer:UpdateCurFrame()
		curLayer:UpdateCurFrame()
		moho:UpdateUI()
	else
		moho.document:SetDirty()
		moho.document:PrepUndo(pathLayer, true)
	end
	
	self:CleanUpInvalidTargets(moho)
	
	self.trackingList = {}
	self.trackingList.targetID = {}
	self.trackingList.layerUUID = {}
	self.trackingList.boneTip = {}
	self.trackingList.color = {}
	self.trackingList.targetName = {}
	self.trackingList.visibility = {}
	
	local scriptInfo = pathLayer:ScriptData()
	local layerUUID = moho.layer:UUID()
	local targetIdKey = self.scriptDataName..'targetID'
	if scriptInfo:HasKey(targetIdKey) then
		local keyValue = scriptInfo:GetString(targetIdKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.targetID, 'n')
	end
	local layerUUIDKey = self.scriptDataName..'layerUUID'
	if scriptInfo:HasKey(layerUUIDKey) then
		local keyValue = scriptInfo:GetString(layerUUIDKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.layerUUID, 's')
	end
	
	local colorKey = self.scriptDataName..'colorKey'
	if scriptInfo:HasKey(colorKey) then
		local keyValue = scriptInfo:GetString(colorKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.color, 's')
	end
	
	local boneTipKey = self.scriptDataName..'boneTip'
	if scriptInfo:HasKey(boneTipKey) then
		local keyValue = scriptInfo:GetString(boneTipKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.boneTip, 's')
	end
	
	local targetNameKey = self.scriptDataName..'targetName'
	if scriptInfo:HasKey(targetNameKey) then
		local keyValue = scriptInfo:GetString(targetNameKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.targetName, 's')
	end
	
	local visibilityKey = self.scriptDataName..'visibility'
	if scriptInfo:HasKey(visibilityKey) then
		local keyValue = scriptInfo:GetString(visibilityKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.visibility, 'b')
	end
	
	local pathColor = LM.rgb_color:new_local()
	pathColor.r = self.pathColorR
	pathColor.g = self.pathColorG
	pathColor.b = self.pathColorB
	pathColor.a = self.pathColorA

	local colorVal = MR_Utilities:RgbaToHex(pathColor.r, pathColor.g, pathColor.b, pathColor.a)
	
	local outOfLimit = 0
	
	local currentDocName = moho.document:Name()
	local currentDoc = tostring(moho.document)
	local lastColor = LM.rgb_color:new_local()
	if mesh and moho:CountSelectedPoints() > 0 then
		for i = 0, mesh:CountPoints()-1 do
			local point = mesh:Point(i)
			if point.fSelected  then
				if self.autoRandomizeColor then
					local randomColor = MR_Utilities:GenerateDistinctColor(lastColor)
					colorVal = MR_Utilities:RgbaToHex(randomColor.r, randomColor.g, randomColor.b, randomColor.a)
					lastColor = randomColor
				end
				local existInList = MR_Utilities:ValueExists(self.trackingList.targetID, i)
				if self.trackingList.layerUUID[existInList] ~= layerUUID then
					existInList = nil
				end
				if existInList then
					self.trackingList.targetID[existInList] = i
					self.trackingList.targetName[existInList] = i
					self.trackingList.layerUUID[existInList] = layerUUID
					self.trackingList.boneTip[existInList] = 'nil'
					self.trackingList.color[existInList] = colorVal
					self.trackingList.visibility[existInList] = true
				elseif #self.trackingList.targetID < self.maxTargets then
					table.insert(self.trackingList.targetID, i)
					table.insert(self.trackingList.targetName, i)
					table.insert(self.trackingList.layerUUID, layerUUID)
					table.insert(self.trackingList.boneTip, 'nil')
					table.insert(self.trackingList.color, colorVal)
					table.insert(self.trackingList.visibility, true)
				else
					outOfLimit = outOfLimit + 1
				end
			end
		end
	end

	local trackBoneTip = self.trackBoneTip
	if alt then
		trackBoneTip = not trackBoneTip
	end

	if skel and moho:CountSelectedBones() > 0 then
		for i = 0, skel:CountBones()-1 do
			local bone = skel:Bone(i)
			if bone.fSelected then
				if self.autoRandomizeColor then
					local randomColor = MR_Utilities:GenerateDistinctColor(lastColor)
					colorVal = MR_Utilities:RgbaToHex(randomColor.r, randomColor.g, randomColor.b, randomColor.a)
					lastColor = randomColor
				end
				
				local existInList
				if bone:IsZeroLength() then
					for key, val in pairs(self.trackingList.targetID) do
						if val == i and self.trackingList.layerUUID[key] == layerUUID then
							existInList = key
							break
						end
					end
				else
					for key, val in pairs(self.trackingList.targetID) do
						if val == i and self.trackingList.boneTip[key] == tostring(trackBoneTip)
						and self.trackingList.layerUUID[key] == layerUUID then
							existInList = key
							break
						end
					end
				end

				if existInList then
					self.trackingList.targetID[existInList] = i
					self.trackingList.targetName[existInList] = bone:Name()
					self.trackingList.layerUUID[existInList] = layerUUID
					self.trackingList.boneTip[existInList] = tostring(trackBoneTip)
					self.trackingList.color[existInList] = colorVal
					self.trackingList.visibility[existInList] = true
				elseif #self.trackingList.targetID < self.maxTargets then
					table.insert(self.trackingList.targetID, i)
					table.insert(self.trackingList.targetName, bone:Name())
					table.insert(self.trackingList.layerUUID, layerUUID)
					table.insert(self.trackingList.boneTip, tostring(trackBoneTip))
					table.insert(self.trackingList.color, colorVal)
					table.insert(self.trackingList.visibility, true)
				else
					outOfLimit = outOfLimit + 1
				end
			end
		end
	end
	
	if outOfLimit > 0 then
		local infoAlert = LM.GUI.Alert(LM.GUI.ALERT_INFO, self:Localize('Targets limit exceeded alert 1'),
		self:Localize('Targets limit exceeded alert 2')..' '..outOfLimit, "", 'OK')
	end

	local targetIdKey = self.scriptDataName..'targetID'
	local keyValue = MR_Utilities:TableToString(self.trackingList.targetID)
	scriptInfo:Set(targetIdKey, keyValue)

	local layerUUIDKey = self.scriptDataName..'layerUUID'
	keyValue = MR_Utilities:TableToString(self.trackingList.layerUUID)
	scriptInfo:Set(layerUUIDKey, keyValue)

	local colorKey = self.scriptDataName..'colorKey'
	keyValue = MR_Utilities:TableToString(self.trackingList.color)
	scriptInfo:Set(colorKey, keyValue)

	local boneTipKey = self.scriptDataName..'boneTip'
	keyValue = MR_Utilities:TableToString(self.trackingList.boneTip)
	scriptInfo:Set(boneTipKey, keyValue)
	
	local targetNameKey = self.scriptDataName..'targetName'
	keyValue = MR_Utilities:TableToString(self.trackingList.targetName)
	scriptInfo:Set(targetNameKey, keyValue)
	
	local visibilityKey = self.scriptDataName..'visibility'
	keyValue = MR_Utilities:TableToString(self.trackingList.visibility)
	scriptInfo:Set(visibilityKey, keyValue)
	
	self.trackingList = {}
	self.trackingList.targetID = {}
	self.trackingList.layerUUID = {}
	self.trackingList.boneTip = {}
	self.trackingList.color = {}
	self.trackingList.targetName = {}
	self.trackingList.pos = {}
	self.trackingList.target = {}
	self.trackingList.layer = {}
	self.trackingList.visibility = {}
end

function MR_Path:RemoveFromTracking(moho, alt)
	local skel = moho:Skeleton()
	local mesh = moho:Mesh()
	if not skel and not mesh then
		return
	end
	
	local pathLayer, targetLayer = self:CheckExistPathLayer(moho)
	local pathLayerMesh
	
	if not pathLayer then
		return
	end
	
	moho.document:SetDirty()
	moho.document:PrepUndo(pathLayer, true)
	
	self:CleanUpInvalidTargets(moho)
	
	self.trackingList = {}
	self.trackingList.targetID = {}
	self.trackingList.layerUUID = {}
	self.trackingList.boneTip = {}
	self.trackingList.color = {}
	self.trackingList.targetName = {}
	self.trackingList.visibility = {}
	
	local scriptInfo = pathLayer:ScriptData()
	
	local targetIdKey = self.scriptDataName..'targetID'
	if scriptInfo:HasKey(targetIdKey) then
		local keyValue = scriptInfo:GetString(targetIdKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.targetID, 'n')
	end
	local layerUUIDKey = self.scriptDataName..'layerUUID'
	if scriptInfo:HasKey(layerUUIDKey) then
		local keyValue = scriptInfo:GetString(layerUUIDKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.layerUUID, 's')
	end
	
	local colorKey = self.scriptDataName..'colorKey'
	if scriptInfo:HasKey(colorKey) then
		local keyValue = scriptInfo:GetString(colorKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.color, 's')
	end
	
	local boneTipKey = self.scriptDataName..'boneTip'
	if scriptInfo:HasKey(boneTipKey) then
		local keyValue = scriptInfo:GetString(boneTipKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.boneTip, 's')
	end
	
	local targetNameKey = self.scriptDataName..'targetName'
	if scriptInfo:HasKey(targetNameKey) then
		local keyValue = scriptInfo:GetString(targetNameKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.targetName, 's')
	end
	
	local visibilityKey = self.scriptDataName..'visibility'
	if scriptInfo:HasKey(visibilityKey) then
		local keyValue = scriptInfo:GetString(visibilityKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.visibility, 'b')
	end
	
	if mesh and moho:CountSelectedPoints() > 0 then
		for i = 0, mesh:CountPoints()-1 do
			local point = mesh:Point(i)
			if point.fSelected then
				local existInList = MR_Utilities:ValueExists(self.trackingList.targetID, i)
				if existInList then
					self:RemoveFromTargetList(moho, existInList)
				end
			end
		end
	end
	
	if skel and moho:CountSelectedBones() > 0 then
		for i = 0, skel:CountBones()-1 do
			local bone = skel:Bone(i)
			if bone.fSelected then
				if bone:IsZeroLength() then
					for key, val in pairs(self.trackingList.targetID) do
						if val == i then
							self:RemoveFromTargetList(moho, key)
						end
					end
				else
					if alt then
						for key, val in pairs(self.trackingList.targetID) do
							if val == i and self.trackingList.boneTip[key] == tostring(not self.trackBoneTip) then
								self:RemoveFromTargetList(moho, key)
							end
						end
					else
						for key, val in pairs(self.trackingList.targetID) do
							if val == i and self.trackingList.boneTip[key] == tostring(self.trackBoneTip) then
								self:RemoveFromTargetList(moho, key)
							end
						end
					end
				end
			end
		end
	end
	
	local targetIdKey = self.scriptDataName..'targetID'
	local keyValue = MR_Utilities:TableToString(self.trackingList.targetID)
	scriptInfo:Set(targetIdKey, keyValue)

	local layerUUIDKey = self.scriptDataName..'layerUUID'
	keyValue = MR_Utilities:TableToString(self.trackingList.layerUUID)
	scriptInfo:Set(layerUUIDKey, keyValue)

	local colorKey = self.scriptDataName..'colorKey'
	keyValue = MR_Utilities:TableToString(self.trackingList.color)
	scriptInfo:Set(colorKey, keyValue)

	local boneTipKey = self.scriptDataName..'boneTip'
	keyValue = MR_Utilities:TableToString(self.trackingList.boneTip)
	scriptInfo:Set(boneTipKey, keyValue)
	
	local targetNameKey = self.scriptDataName..'targetName'
	keyValue = MR_Utilities:TableToString(self.trackingList.targetName)
	scriptInfo:Set(targetNameKey, keyValue)
	
	local visibilityKey = self.scriptDataName..'visibility'
	keyValue = MR_Utilities:TableToString(self.trackingList.visibility)
	scriptInfo:Set(visibilityKey, keyValue)
	
	self.trackingList = {}
	self.trackingList.targetID = {}
	self.trackingList.layerUUID = {}
	self.trackingList.boneTip = {}
	self.trackingList.color = {}
	self.trackingList.targetName = {}
	self.trackingList.pos = {}
	self.trackingList.target = {}
	self.trackingList.layer = {}
	self.trackingList.visibility = {}
end

function MR_Path:BuildDialog(moho)
	self:CleanUpInvalidTargets(moho)
	local pathLayer, targetLayer = self:CheckExistPathLayer(moho)
	self.trackingList = {}
	self.trackingList.targetID = {}
	self.trackingList.layerUUID = {}
	self.trackingList.boneTip = {}
	self.trackingList.color = {}
	self.trackingList.targetName = {}
	self.trackingList.target = {}
	self.trackingList.layer = {}
	self.trackingList.description = {}
	self.trackingList.visibility = {}
	local scriptInfo
	
	if pathLayer then
		scriptInfo = pathLayer:ScriptData()
		
		local targetIdKey = self.scriptDataName..'targetID'
		if scriptInfo:HasKey(targetIdKey) then
			local keyValue = scriptInfo:GetString(targetIdKey)
			MR_Utilities:StringToTable(keyValue, self.trackingList.targetID, 'n')
		end
		local layerUUIDKey = self.scriptDataName..'layerUUID'
		if scriptInfo:HasKey(layerUUIDKey) then
			local keyValue = scriptInfo:GetString(layerUUIDKey)
			MR_Utilities:StringToTable(keyValue, self.trackingList.layerUUID, 's')
		end
		
		local colorKey = self.scriptDataName..'colorKey'
		if scriptInfo:HasKey(colorKey) then
			local keyValue = scriptInfo:GetString(colorKey)
			MR_Utilities:StringToTable(keyValue, self.trackingList.color, 's')
		end
		
		local boneTipKey = self.scriptDataName..'boneTip'
		if scriptInfo:HasKey(boneTipKey) then
			local keyValue = scriptInfo:GetString(boneTipKey)
			MR_Utilities:StringToTable(keyValue, self.trackingList.boneTip, 's')
		end
		
		local targetNameKey = self.scriptDataName..'targetName'
		if scriptInfo:HasKey(targetNameKey) then
			local keyValue = scriptInfo:GetString(targetNameKey)
			MR_Utilities:StringToTable(keyValue, self.trackingList.targetName, 's')
		end
		
		local visibilityKey = self.scriptDataName..'visibility'
		if scriptInfo:HasKey(visibilityKey) then
			local keyValue = scriptInfo:GetString(visibilityKey)
			MR_Utilities:StringToTable(keyValue, self.trackingList.visibility, 'b')
		end

		for i, v in pairs(self.trackingList.targetID) do
			local n = i
			local layer = MR_Utilities:GetLayerByUUID(moho, self.trackingList.layerUUID[n])
			if self.trackingList.boneTip[n] == 'nil' then
				local mesh = moho:LayerAsVector(layer):Mesh()
				local point = mesh:Point(self.trackingList.targetID[n])
				table.insert(self.trackingList.target, point)
				
				local skel = layer:ControllingSkeleton()
				if skel and point.fParent >= 0 then
					table.insert(self.trackingList.description, 'Parent bone: \"' .. skel:Bone(point.fParent):Name()..'\" ID: '..point.fParent)
				else
					table.insert(self.trackingList.description, 'No parent bone.')
				end
			else
				local skel = moho:LayerAsBone(layer):Skeleton()
				local bone = skel:Bone(self.trackingList.targetID[n])
				table.insert(self.trackingList.target, bone)
				table.insert(self.trackingList.description, bone:Name())
			end
			table.insert(self.trackingList.layer, layer)
		end
	end
	
	self.removeList = {}
	
	local dlog = MR_PathManagerDialog:new(moho)
	if dlog then
		if (dlog:DoModal() == LM.GUI.MSG_CANCEL) then
			return false
		end
	end
	
	if pathLayer then 
		if #self.trackingList.targetID > 0 then
			moho.document:SetDirty()
			moho.document:PrepUndo(pathLayer, true)
		end

		local removeListLength = #self.removeList
		for i = 1, removeListLength do
			local n = removeListLength + 1 - i
			if self.removeList[n] then
				self:RemoveFromTargetList(moho, n)
			end	
		end
		local targetIdKey = self.scriptDataName..'targetID'
		local keyValue = MR_Utilities:TableToString(self.trackingList.targetID)
		scriptInfo:Set(targetIdKey, keyValue)

		local layerUUIDKey = self.scriptDataName..'layerUUID'
		keyValue = MR_Utilities:TableToString(self.trackingList.layerUUID)
		scriptInfo:Set(layerUUIDKey, keyValue)

		local colorKey = self.scriptDataName..'colorKey'
		keyValue = MR_Utilities:TableToString(self.trackingList.color)
		scriptInfo:Set(colorKey, keyValue)

		local boneTipKey = self.scriptDataName..'boneTip'
		keyValue = MR_Utilities:TableToString(self.trackingList.boneTip)
		scriptInfo:Set(boneTipKey, keyValue)
		
		local targetNameKey = self.scriptDataName..'targetName'
		keyValue = MR_Utilities:TableToString(self.trackingList.targetName)
		scriptInfo:Set(targetNameKey, keyValue)
		
		local visibilityKey = self.scriptDataName..'visibility'
		keyValue = MR_Utilities:TableToString(self.trackingList.visibility)
		scriptInfo:Set(visibilityKey, keyValue)
	end
	
	self.trackingList = {}
	self.trackingList.targetID = {}
	self.trackingList.layerUUID = {}
	self.trackingList.boneTip = {}
	self.trackingList.color = {}
	self.trackingList.targetName = {}
	self.trackingList.pos = {}
	self.trackingList.target = {}
	self.trackingList.layer = {}
	self.trackingList.visibility = {}
end

function MR_Path:CleanUpInvalidTargets(moho)
	self.trackingList = {}
	self.trackingList.targetID = {}
	self.trackingList.layerUUID = {}
	self.trackingList.boneTip = {}
	self.trackingList.color = {}
	self.trackingList.targetName = {}
	self.trackingList.visibility = {}
	
	local pathLayer, targetLayer = self:CheckExistPathLayer(moho)
	
	if not pathLayer then
		return
	end
	
	local scriptInfo = pathLayer:ScriptData()
	
	local targetIdKey = self.scriptDataName..'targetID'
	if scriptInfo:HasKey(targetIdKey) then
		local keyValue = scriptInfo:GetString(targetIdKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.targetID, 'n')
	end
	local layerUUIDKey = self.scriptDataName..'layerUUID'
	if scriptInfo:HasKey(layerUUIDKey) then
		local keyValue = scriptInfo:GetString(layerUUIDKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.layerUUID, 's')
	end
	
	local colorKey = self.scriptDataName..'colorKey'
	if scriptInfo:HasKey(colorKey) then
		local keyValue = scriptInfo:GetString(colorKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.color, 's')
	end
	
	local boneTipKey = self.scriptDataName..'boneTip'
	if scriptInfo:HasKey(boneTipKey) then
		local keyValue = scriptInfo:GetString(boneTipKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.boneTip, 's')
	end
	
	local targetNameKey = self.scriptDataName..'targetName'
	if scriptInfo:HasKey(targetNameKey) then
		local keyValue = scriptInfo:GetString(targetNameKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.targetName, 's')
	end
	
	local visibilityKey = self.scriptDataName..'visibility'
	if scriptInfo:HasKey(visibilityKey) then
		local keyValue = scriptInfo:GetString(visibilityKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.visibility, 'b')
	end

	local listLength = #self.trackingList.targetID
	local removeList = {}
	for i = 1, listLength do
		local n = listLength + 1 - i
		local layer = MR_Utilities:GetLayerByUUID(moho, self.trackingList.layerUUID[n])
		if not layer then
			table.insert(removeList, n)
		else
			if self.trackingList.boneTip[n] == 'nil' then -- point
				local isPointValid = false
				local vectorLayer = moho:LayerAsVector(layer)
				if vectorLayer then
					local mesh = vectorLayer:Mesh()
					if mesh then
						if self.trackingList.targetID[n] < mesh:CountPoints() then
							local point = mesh:Point(self.trackingList.targetID[n])
							if point then
								if point:CountCurves() ~= 0 then
									isPointValid = true
								end
							end
						end
					end
				end
				if not isPointValid then 
					table.insert(removeList, n)
				end
			elseif self.trackingList.boneTip[n] ~= 'nil' then -- bone
				local isBoneValid = false
				local boneLayer = moho:LayerAsBone(layer)
				if boneLayer then
					local skel = boneLayer:Skeleton()
					if skel then
						for b=0, skel:CountBones()-1 do
							local targetBone = skel:Bone(b)
							if targetBone then
								if self.trackingList.targetName[n] == targetBone:Name() then
									self.trackingList.targetID[n] = b
									isBoneValid = true
									break
								end
							end
						end
					end
				end
				if not isBoneValid then
					table.insert(removeList, n)
				end
			end
		end	
    end
	
	table.sort(removeList, function(a, b)
		return a > b
	end)
	
	for i, key in pairs(removeList) do
		self:RemoveFromTargetList(moho, key)
	end
	
	local targetIdKey = self.scriptDataName..'targetID'
	local keyValue = MR_Utilities:TableToString(self.trackingList.targetID)
	scriptInfo:Set(targetIdKey, keyValue)

	local layerUUIDKey = self.scriptDataName..'layerUUID'
	keyValue = MR_Utilities:TableToString(self.trackingList.layerUUID)
	scriptInfo:Set(layerUUIDKey, keyValue)

	local colorKey = self.scriptDataName..'colorKey'
	keyValue = MR_Utilities:TableToString(self.trackingList.color)
	scriptInfo:Set(colorKey, keyValue)

	local boneTipKey = self.scriptDataName..'boneTip'
	keyValue = MR_Utilities:TableToString(self.trackingList.boneTip)
	scriptInfo:Set(boneTipKey, keyValue)
	
	local targetNameKey = self.scriptDataName..'targetName'
	keyValue = MR_Utilities:TableToString(self.trackingList.targetName)
	scriptInfo:Set(targetNameKey, keyValue)
	
	local visibilityKey = self.scriptDataName..'visibility'
	keyValue = MR_Utilities:TableToString(self.trackingList.visibility)
	scriptInfo:Set(visibilityKey, keyValue)
end

function MR_Path:RemoveFromTargetList(moho, n)
	table.remove(self.trackingList.targetID, n)
	table.remove(self.trackingList.targetName, n)
	table.remove(self.trackingList.layerUUID, n)
	table.remove(self.trackingList.boneTip, n)
	table.remove(self.trackingList.color, n)
	table.remove(self.trackingList.visibility, n)
end

function MR_Path:CreatePath(moho)
	local pathLayer, targetLayer = self:CheckExistPathLayer(moho)
	
	if not pathLayer then
		return
	end
	
	self:CleanUpInvalidTargets(moho)
	
	self.trackingList = {}
	self.trackingList.targetID = {}
	self.trackingList.layerUUID = {}
	self.trackingList.boneTip = {}
	self.trackingList.color = {}
	self.trackingList.targetName = {}
	self.trackingList.pos = {}
	self.trackingList.target = {}
	self.trackingList.layer = {}
	self.trackingList.visibility = {}
	
	local scriptInfo = pathLayer:ScriptData()
	local targetIdKey = self.scriptDataName..'targetID'
	if scriptInfo:HasKey(targetIdKey) then
		local keyValue = scriptInfo:GetString(targetIdKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.targetID, 'n')
	end
	local layerUUIDKey = self.scriptDataName..'layerUUID'
	if scriptInfo:HasKey(layerUUIDKey) then
		local keyValue = scriptInfo:GetString(layerUUIDKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.layerUUID, 's')
	end
	
	local colorKey = self.scriptDataName..'colorKey'
	if scriptInfo:HasKey(colorKey) then
		local keyValue = scriptInfo:GetString(colorKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.color, 's')
	end
	
	local boneTipKey = self.scriptDataName..'boneTip'
	if scriptInfo:HasKey(boneTipKey) then
		local keyValue = scriptInfo:GetString(boneTipKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.boneTip, 's')
	end
	
	local targetNameKey = self.scriptDataName..'targetName'
	if scriptInfo:HasKey(targetNameKey) then
		local keyValue = scriptInfo:GetString(targetNameKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.targetName, 's')
	end
	
	local visibilityKey = self.scriptDataName..'visibility'
	if scriptInfo:HasKey(visibilityKey) then
		local keyValue = scriptInfo:GetString(visibilityKey)
		MR_Utilities:StringToTable(keyValue, self.trackingList.visibility, 'b')
	end
	
	if #self.trackingList.targetID < 1 then
		return
	end
	
	for i, v in pairs(self.trackingList.targetID) do
		local n = i
		local layer = MR_Utilities:GetLayerByUUID(moho, self.trackingList.layerUUID[n])
		if self.trackingList.boneTip[n] == 'nil' then
			local mesh = moho:LayerAsVector(layer):Mesh()
			local point = mesh:Point(self.trackingList.targetID[n])
			table.insert(self.trackingList.target, point)
		else
			local skel = moho:LayerAsBone(layer):Skeleton()
			local bone = skel:Bone(self.trackingList.targetID[n])
			table.insert(self.trackingList.target, bone)
		end
		table.insert(self.trackingList.layer, layer)
		local posList = {}
		posList.vec2 = {}
		posList.frame = {}
		table.insert(self.trackingList.pos, posList)
	end

	if MR_ActivityTimeTracker then
		MR_ActivityTimeTracker:UpdateTime(moho)
	end
	
	local curLayer = moho.layer
	local curFrame = moho.frame
	local curAction = moho.layer:CurrentAction()
	
	local currentQualityFlags = moho.view:QualityFlags()
	local wireframe = MOHO.hasbit(moho.view:QualityFlags(), MOHO.bit(MOHO.LDQ_WIREFRAME))
	if wireframe then
		moho.view:SetQualityFlags(currentQualityFlags - MOHO.LDQ_WIREFRAME)
	end
	local from = 1
	local to = 20
	
	if self.adjacentFramesRange then
		from = curFrame - self.adjacentFrames
		if from < 1 then from = 1 end
		to = curFrame + self.adjacentFrames
	elseif self.playbackRange then
		from = MOHO.MohoGlobals.PlayStart
		to = MOHO.MohoGlobals.PlayEnd
		if from < 1 then
			from = moho.document:StartFrame()
		end
		if to < 2 then
			to = moho.document:EndFrame()
		end
	elseif self.fromToRange then
		from = self.from
		to = self.to
	end

	if self.interval == 2 then
		local isCurFrameEven = curFrame % 2 == 0
		local isFromFrameEven = from % 2 == 0
		local isToFrameEven = to % 2 == 0
		if isFromFrameEven ~= isCurFrameEven then
			if from == 1 then
				from = from + 1
			elseif from > 1 then
				from = from - 1
			end
		end
		if isToFrameEven ~= isCurFrameEven then
			to = to + 1
		end
	end

	local isSoundtrackMuted = MOHO.MohoGlobals.MuteSoundtrack
	MOHO.MohoGlobals.MuteSoundtrack = true

	local targetFrame = from
	local counter = 1
	
	for i = 1, #self.trackingList.targetID do
		self.trackingList.pos[i].vec2 = {}
		self.trackingList.pos[i].frame = {}
	end
	self.isTracking = true
	if MR_ActivityTimeTracker then
		MR_ActivityTimeTracker.pauseTracking = true
		MR_ActivityTimeTracker.propogateProductionTime = true
	end
	
	repeat
		moho:SetCurFrame(targetFrame)
		for i = 1, #self.trackingList.targetID do
			local pos = LM.Vector2:new_local()
			local layer = self.trackingList.layer[i]
			if self.trackingList.boneTip[i] == 'nil' then
				local point = self.trackingList.target[i]
				pos = MR_Utilities:GetGlobalPos(moho, layer, point.fPos, true)
			elseif self.trackingList.boneTip[i] ~= 'nil' then
				local bone = self.trackingList.target[i]
				
				if self.trackingList.boneTip[i] == 'true' then
					if (bone:IsZeroLength()) then
						pos:Set(0, 0)
					else
						pos:Set(bone.fLength, 0)
					end
				else
					pos:Set(0, 0)
				end
				bone.fMovedMatrix:Transform(pos)
				pos = MR_Utilities:GetGlobalPos(moho, layer, pos, true)
			end
			self.trackingList.pos[i].vec2[counter] = pos
			self.trackingList.pos[i].frame[counter] = targetFrame
		end
		counter = counter + 1
		targetFrame = targetFrame + self.interval
	until targetFrame >= to + 1

	local pathLayerMesh
	
	if not pathLayer then
		moho.document:SetDirty()
		moho.document:PrepUndo(nil)
		moho:SetSelLayer(targetLayer)
		pathLayer = self:CreateNewLayer(moho)
		pathLayerMesh = moho:LayerAsVector(pathLayer):Mesh()
	else	
		moho.document:PrepUndo(pathLayer, true)
		moho.document:SetDirty()
		pathLayerMesh = moho:LayerAsVector(pathLayer):Mesh()
		if pathLayerMesh then
			pathLayerMesh:Clear()
			pathLayer:SetVisible(true)
			pathLayer.fAlpha:SetValue(0, self.pathOpacity / 100)
		end	
	end
	moho:SetSelLayer(pathLayer)
	moho:SetCurFrame(0)
	
	local pointList = {}
	pointList.point = {}
	pointList.color = {}
	for i = 1, #self.trackingList.targetID do
		if self.trackingList.visibility[i] then
			local pointsNumBeforeLines = pathLayerMesh:CountPoints()
			table.insert(pointList.point, pointsNumBeforeLines)
			
			local r, g, b, a = MR_Utilities:HexToRgba(self.trackingList.color[i])
			
			local pathColor = LM.rgb_color:new_local()
			pathColor.r = r
			pathColor.g = g
			pathColor.b = b
			pathColor.a = a
			
			local color = pathColor
			table.insert(pointList.color, color)
			local prewPos = LM.Vector2:new_local()
			for p = 1,  #self.trackingList.pos[i].vec2 do
				local pos = self.trackingList.pos[i].vec2[p]
				if p == 1 then
					pathLayerMesh:AddLonePoint(pos, 0)
					prewPos = self.trackingList.pos[i].vec2[p]
				else
					if not MR_Utilities:IsEqual(pos.x, prewPos.x, 0.0001) or not MR_Utilities:IsEqual(pos.y, prewPos.y, 0.0001) then
						pathLayerMesh:AppendPoint(pos, 0)
						if p == 2 and i == 1 then
							local lastPoint = pathLayerMesh:Point(pathLayerMesh:CountPoints()-2)
							lastPoint.fAnimPos:SetValue(1, lastPoint.fAnimPos:GetValue(0))
						end
					end
					prewPos = pos
				end	
			end
			
			local totalPoints = pathLayerMesh:CountPoints()
			
			for c=0, totalPoints - 1 do
				local point = pathLayerMesh:Point(c)
				point:SetCurvature(MOHO.PEAKED, 0)
			end
			pathLayerMesh:SelectConnected()
			local shapeID = moho:CreateShape(false, false, 0)
			if shapeID >= 0 then
				local shape = pathLayerMesh:Shape(shapeID)	
				local lineWidth = self.width
				lineWidth = LM.Clamp(lineWidth, 0.25, 256)
				shape.fMyStyle.fLineWidth = lineWidth / moho.document:Height()
				shape.fMyStyle.fLineCol:SetValue(0, color)
				shape.fMyStyle.fLineCaps = 0
				shape:MakePlain()
			end
			pathLayerMesh:SelectNone()
			
			if self.drawTimingMark then
				local listLength = #self.trackingList.pos[i].vec2
				local prewPos = LM.Vector2:new_local()
				for p = 1, listLength do
					local frame = self.trackingList.pos[i].frame[p]
					local pos = self.trackingList.pos[i].vec2[p]
					local nextPos = LM.Vector2:new_local()
					local counter = 1
					
					if p == 1 then
						prewPos = self.trackingList.pos[i].vec2[p]
					end
					
					if p < listLength then
						local counterNext = 1
						repeat
							nextPos = self.trackingList.pos[i].vec2[p + counterNext]
							if not MR_Utilities:IsEqual(pos.x, nextPos.x, 0.0001) or not MR_Utilities:IsEqual(pos.y, nextPos.y, 0.0001) then
								break
							end
							counterNext = counterNext + 1
						until p + counterNext > listLength	
					else	
						nextPos = self.trackingList.pos[i].vec2[p]
					end
					if p == 1 then
						local notchStart, notchEnd = self:GetTimingMarkPos(moho, nextPos, pos, nextPos, (self.timingMarksSize / 2) * self.width, true, frame)
						pathLayerMesh:AddLonePoint(notchStart, 0)
						pathLayerMesh:AppendPoint(notchEnd, 0)
					else
						if not MR_Utilities:IsEqual(pos.x, prewPos.x, 0.0001) or not MR_Utilities:IsEqual(pos.y, prewPos.y, 0.0001) then
							local notchStart, notchEnd = self:GetTimingMarkPos(moho, prewPos, pos, nextPos, (self.timingMarksSize / 2) * self.width, false, frame)
							pathLayerMesh:AddLonePoint(notchStart, 0)
							pathLayerMesh:AppendPoint(notchEnd, 0)
							prewPos = pos
						end	
					end	
					pathLayerMesh:SelectNone()
				end
				
				local totalPointsWithtimingMark = pathLayerMesh:CountPoints()
				
				for i = totalPoints, totalPointsWithtimingMark - 1 do
					local point = pathLayerMesh:Point(i)
					point.fSelected = true
				end
				
				local shapeID = moho:CreateShape(false, false, 0)
				if shapeID >= 0 then
					local shape = pathLayerMesh:Shape(shapeID)	
					local lineWidth = self.width / 2
					lineWidth = LM.Clamp(lineWidth, 0.25, 256)
					shape.fMyStyle.fLineWidth = lineWidth / moho.document:Height()
					shape.fMyStyle.fLineCol:SetValue(0, color)
					shape.fMyStyle.fLineCaps = 0	
					shape:MakePlain()
				end
				pathLayerMesh:SelectNone()
			end
		end
	end

	if self.centerLine then
		for p = 1, #pointList.point do
			local linePoint = pathLayerMesh:Point(pointList.point[p])
			linePoint.fSelected = true
			pathLayerMesh:SelectConnected()
			local pathColor = pointList.color[p]
			local secondColor = LM.rgb_color:new_local()
			if (pathColor.r + pathColor.g + pathColor.b) < 382 then
				secondColor.r = 255
				secondColor.g = 255
				secondColor.b = 255
				secondColor.a = 255
			else
				secondColor.r = 0
				secondColor.g = 0
				secondColor.b = 0
				secondColor.a = 255
			end
			local v = LM.Vector2:new_local()
			local vc1 = LM.ColorVector:new_local()
			local vc2 = LM.ColorVector:new_local()
			vc1:Set(pathColor)
			vc2:Set(secondColor)
			vc1 = (vc1 + vc2) / 2
			local newColor = vc1:AsColorStruct()
			local shapeID = moho:CreateShape(false, false, 0)
			if shapeID >= 0 then
				local shape = pathLayerMesh:Shape(shapeID)	
				local lineWidth = (self.width / 2) / 2
				lineWidth = LM.Clamp(lineWidth, 0.1, 256)
				shape.fMyStyle.fLineWidth = lineWidth / moho.document:Height()
				shape.fMyStyle.fLineCol:SetValue(0, newColor)
				shape.fMyStyle.fLineCaps = 0
				shape:MakePlain()
			end	
			pathLayerMesh:SelectNone()
		end	
	end

	if wireframe then
		moho.view:SetQualityFlags(currentQualityFlags)
	end
	
	MOHO.MohoGlobals.MuteSoundtrack = isSoundtrackMuted
	
	moho:SetSelLayer(curLayer)
	
	if curAction ~= '' then
		MR_Utilities:ReturnToAction(moho, curAction, curLayer)
	end
	
	pathLayer:ClearAnimation(true, 0, false)
	moho:SetCurFrame(curFrame)
	self.isTracking = false
	if MR_ActivityTimeTracker then
		MR_ActivityTimeTracker.pauseTracking = false
	end
	
	self.trackingList = {}
	self.trackingList.targetID = {}
	self.trackingList.layerUUID = {}
	self.trackingList.boneTip = {}
	self.trackingList.color = {}
	self.trackingList.targetName = {}
	self.trackingList.pos = {}
	self.trackingList.target = {}
	self.trackingList.layer = {}
	self.trackingList.visibility = {}
	
	pathLayer:UpdateCurFrame()
	curLayer:UpdateCurFrame()
	moho:UpdateUI()
end

function MR_Path:DeletePathLayer(moho)
	self:CleanUpInvalidTargets(moho)
	local pathLayer, targetLayer = self:CheckExistPathLayer(moho)
	if pathLayer then
		moho.document:SetDirty()
		moho.document:PrepUndo()
		local curAction = moho.layer:CurrentAction()
		local curFrame = moho.frame
		local curLayer = moho.layer
		moho:DeleteLayer(pathLayer)
		MR_Utilities:ReturnToAction(moho, curAction, curLayer)
		if curFrame > 0 then
			moho:SetCurFrame(0)
			moho:SetCurFrame(curFrame)
		elseif curFrame == 0 then
			moho:SetCurFrame(1)
			moho:SetCurFrame(curFrame)
		end
		moho.layer:UpdateCurFrame()
		moho.view:DrawMe()
		moho:UpdateUI()
		moho:UpdateUI()
		moho.view:DrawMe()
	end
end

function MR_Path:ClearPath(moho)
	self:CleanUpInvalidTargets(moho)
	local pathLayer, targetLayer = self:CheckExistPathLayer(moho)
	if pathLayer then
		moho.document:SetDirty()
		moho.document:PrepUndo(pathLayer, true)
		pathLayerMesh = moho:LayerAsVector(pathLayer):Mesh()
		pathLayerMesh:Clear()
		pathLayer:UpdateCurFrame()
		moho:UpdateUI()
		moho.view:DrawMe()
	end
end

function MR_Path:CreateNewLayer(moho)
	local pathLayer = moho:CreateNewLayer(MOHO.LT_VECTOR, false)
	local scriptData = pathLayer:ScriptData()
	scriptData:Set("MR Path Layer", true)
	
	self.numVers = {}
	local vers = moho:AppVersion()

	for n in string.gmatch (vers, "%d+") do
		table.insert(self.numVers, tonumber(n))
	end
	
	if self.numVers[1] == 13 and self.numVers[2] == 5 then
		if self.numVers[3] ~= nil then
			if self.numVers[3] >= 2 then
				pathLayer:SetIgnoredByLayerPicker(true)
			end
		end
	elseif self.numVers[1] == 13 and self.numVers[2] > 5 then
		pathLayer:SetIgnoredByLayerPicker(true)
	elseif self.numVers[1] > 13 then
		pathLayer:SetIgnoredByLayerPicker(true)	
	end
	
	pathLayer:SetName('Path')
	pathLayer.fAlpha:SetValue(0, self.pathOpacity / 100)
	pathLayer:SetEditOnly(true)
	pathLayer:SetImmuneToCamera	(true)
	
	return pathLayer
end

function MR_Path:CheckExistPathLayer(moho)
	if moho.document == nil then
		return
	end

	local topLayer = moho.document:Layer(moho.document:CountLayers()-1)
	local pathLayer = nil
	local targetLayer = nil
	local scriptData = topLayer:ScriptData()
	if (scriptData:HasKey("MR Path Layer")) then
		pathLayer = moho:LayerAsVector(topLayer)
	elseif (scriptData:HasKey("MR Guides Layer")) then
		topLayer = moho.document:Layer(moho.document:CountLayers()-2)
		scriptData = topLayer:ScriptData()
		if (scriptData:HasKey("MR Path Layer")) then
			pathLayer = moho:LayerAsVector(topLayer)
		else
			targetLayer = topLayer
		end	
	elseif (scriptData:HasKey("MR Overlay Layer")) then
		topLayer = moho.document:Layer(moho.document:CountLayers()-1)
		scriptData = topLayer:ScriptData()
		if (scriptData:HasKey("MR Path Layer")) then
			pathLayer = moho:LayerAsVector(topLayer)
		else
			targetLayer = topLayer
		end
	else
		targetLayer = topLayer
	end	
	return pathLayer, targetLayer
end

function MR_Path:GetTimingMarkPos(moho, vec1, vec2, vec3, size, firstMarker, frame)
	local adaptedSize = size / 100
	local v1 = vec1
	local v2 = vec2
	v2 = v2 - v1	
	local angle = math.atan2(v2.y, v2.x)
	
	local v2 = vec2
	local v3 = vec3
	v3 = v3 - v2

	local angle2 = math.atan2(v3.y, v3.x)

	local p1 = vec1
	local p2 = vec2
	local p3 = vec3
	
	local v1 = {x = p1.x - p2.x, y = p1.y - p2.y}
    local v2 = {x = p3.x - p2.x, y = p3.y - p2.y}

    local v1_length = math.sqrt(v1.x^2 + v1.y^2)
    local v2_length = math.sqrt(v2.x^2 + v2.y^2)
    
    local dot_product = v1.x * v2.x + v1.y * v2.y
    
    local cos_angle = dot_product / (v1_length * v2_length)
    
    cos_angle = math.max(-1, math.min(1, cos_angle))
    
    local angle3 = math.acos(cos_angle)
	if not firstMarker then
		if angle3 == 0 or (MR_Utilities:IsEqual(vec2.x, vec3.x, 0.0001) and MR_Utilities:IsEqual(vec2.y, vec3.y, 0.0001)) then
			angle = angle + math.pi / 2
		else
			local delta = angle2 - angle
			
			if delta > math.pi then
				delta = delta - 2 * math.pi
			elseif delta < -math.pi then
				delta = delta + 2 * math.pi
			elseif delta == 0 then
				delta = delta - 2 * math.pi
			end

			if delta > 0 then
				angle = angle - (angle3 / 2)
			elseif delta < 0 then
				angle = angle + (angle3 / 2)
			end
		end
	else
		angle = angle + math.pi / 2
	end

	local vec3 = LM.Vector2:new_local()
    vec3.x = vec2.x + adaptedSize * math.cos(angle)
    vec3.y = vec2.y + adaptedSize * math.sin(angle)

    local vec4 = LM.Vector2:new_local()
    vec4.x = vec2.x + adaptedSize * math.cos(angle + math.pi)
    vec4.y = vec2.y + adaptedSize * math.sin(angle + math.pi)
    return vec3, vec4
end

function MR_Path:BuildFloatingPanelDialog(moho)
	if MR_Path.floatingPanelDlog == nil then
		MR_Path.floatingPanelDlog = MR_PathFloatingPanelDialog:new()
		if MR_Path.floatingPanelDlog then
			if (MR_Path.floatingPanelDlog:DoModeless() == LM.GUI.MSG_CANCEL) then
				return false
			end
		end
	end
end

-- **************************************************
-- Localization
-- **************************************************

function MR_Path:Localize(text)
	local phrase = {}

	phrase['Description'] = 'Draw bone and point trajectories over all layers for spacing analysis.'
	phrase['UILabel'] = 'MR Path '..self:Version()

	phrase['Track bone tip'] = 'Track bone tip'
	phrase['Add to tracking'] = 'Add to tracking. Alt + Click to add with inverted \'Track bone tip\' value.'
	phrase['Remove from tracking'] = 'Remove from tracking. Alt + Click to remove with inverted \'Track bone tip\' value.'
	phrase['Clear tracking list'] = 'Clear tracking list'
	phrase['Create path'] = 'Create path'
	phrase['Clear path'] = 'Clear path'
	phrase['Delete path layer'] = 'Delete path layer'
	phrase['Show hide'] = 'Show hide'
	phrase['Path Opacity:'] = 'Opacity:'
	phrase['Opacity for all Paths'] = 'Path layer opacity'
	phrase['Width:'] = 'Width:'
	phrase['Width for all Paths'] = 'Width for all Paths'
	phrase['Color'] = 'Color:'
	phrase['Draw center line'] = 'Draw center line'
	phrase['Draw timing marks'] = 'Draw tick marks'
	phrase['Size:'] = 'Size:'
	phrase['Timing marks size'] = 'Timing marks size'
	phrase['Path Color Tooltip'] = 'Path Color'
	phrase['Adjacent frames range'] = 'Adjacent frames range'
	phrase['Adjacent frames'] = 'Adjacent frames'
	phrase['Playback range'] = 'Playback range'
	phrase['Project range'] = 'Project range'
	phrase['From to range'] = 'From/To range'
	phrase['From'] = 'From'
	phrase['To'] = 'To'
	phrase['IntervalText'] = 'Interval:'
	phrase['Panel settings'] = 'Panel settings'
	phrase['Open in panel'] = 'Open in panel'
	
	phrase['Compact'] = 'Compact'
	phrase['Mid'] = 'Mid'
	phrase['Full'] = 'Full'
	
	phrase['Path Target Manager'] = 'Target Manager'
	phrase['Path target list is empty'] = 'Path target list is empty'
	phrase['Reset settings'] = 'Click to reset settings. Alt + Click to copy color to other targets.'
	phrase['Settings changed'] = 'The settings of this target have been changed.'
	phrase['Settings not changed'] = 'The settings of this target have not been changed.'
	phrase['Randomize colors'] = 'Randomize colors'
	phrase['Randomize colors tooltip'] = 'Randomize colors. Alt + Click to randomize brightness.'
	phrase['Randomize color'] = 'Randomize color. Alt + Click to randomize brightness.'
	phrase['Auto randomize color'] = 'Auto randomize color'
	phrase['Visibility'] = 'Visibility. Alt + Click to copy to other targets.'
	
	phrase['Targets limit exceeded alert 1'] = 'Targets limit exceeded.'
	phrase['Targets limit exceeded alert 2'] = 'You have added the maximum number of targets ('..self.maxTargets..'). Number of targets not added:'

	return phrase[text]
end

Icon
MR Path
Listed

Script type: Tool

Uploaded: Jun 25 2023, 07:52

Last modified: Jul 19 2024, 07:40

Script Version: 1.4.3

Draw bone and point trajectories over all layers for spacing analysis.


Version 1.2
Added the ability to use the script in a floating panel.

Version 1.3 New Features:

1. All information about paths and their targets is now stored in the Moho project, allowing you to switch freely between projects without needing to reassign targets each time.
2. It is now possible to track both the base and the tip of a bone simultaneously.
3. Updated the 'Target Manager' window which now always generates random saturated colors.
4. Added color randomization buttons for each target individually. Alt + Click to randomize brightness.
5. Added 'Auto randomize color' option.

Version 1.4 New Features:

1. Updated the Target Manager window, allowing individual targets to be enabled and disabled without removing them from the list.
2. In the Target Manager window, targets are now labeled with bone names instead of their IDs.
3. Bug fixes.


This script, and all other scripts on this site are distributed as free software under the GNU General Public License 3.0 or later.
Downloads count: 1548