macroScript IsolateTrack category:"MrPingouin_ToolKit" tooltip:"Isolate_Track"
(
	/*
		Copyright (c) 2010, Laurent "MrPingouin" CHEA
		All rights reserved.

		Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
		
		- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
		- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
		- Neither the name of LAURENTCHEA.COM nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

		THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
		THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS 
		BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
		GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
		STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
		OF SUCH DAMAGE.

		*********************************************************************************************************

		MrPingouin's Isolate Track 0.1
		contact@laurentchea.com
		http://www.laurentchea.com
		
		January 03, 2011

		When an object has several "object keyframes" of different parameters at the same frame time (e.g. a morpher with multiples targets, or a baseObject with radius and segments animated),
		all of these keyframes are displayed as one "grey object key" in the timeline.
		
		It is possible to isolate tracks (e.g. only one morpher target), but to do this, you have to right click the value, choose Show in Trackview, right click again with "collect parameter", 
		and finally to deal with the parameter collector options, which is not very comfortable, especially if it's for a one-shot use of the Parameter Collector.
		
		This macro popups a rollout that displays all the animatable tracks of the current modifier stack selection, and quickly isolate the selected track in the timeline.
		
		USAGE
		
			Link it to a keyboard shortcut for best use, for example : (Isolate selection shortcut) + Shift
			To exit the "isolate mode", just close the parameter collector
		
		TODO
		
			process() :
				when a parameter has no controller (UndefinedClass), we assign it a new controller, this is the only way to display it in the parameter Collector
				for the moment, we "guess" what controller is needed (from the classof value), there must be a better way to do it
		
		NOTES
		
			Max's behavior : even if a parameter uses integer values (e.g. sphere segments), the associated controller is a bezier_float 
			
			Maxscript Reference is wrong for the function "ParameterCollectorOps.addParameter" : it lacks one last string value, for the Parameter Collector rollout. 
			This sould be :
			<bool> addParameter <controller> ControllerObject <int>CollectionIndex <int>RolloutIndex <string>ParameterName

	*/
	
	local g_appName = "Isolate Track"
	local g_version = "0.1"
	
	local g_rolloutName = "Isolate Track"
	
	global isolateTrack_R
	local app
	
	struct QuickIsolateApp 
	(
		----------------------------------------------------------------------------------------------------------------------------------------
		-- <QuickIsolateApp> createApp
		-- Constructor
		-- Creates the app only if we're in Modify Mode
		-- AND if the parameter collector is closed (for toggle)
		----------------------------------------------------------------------------------------------------------------------------------------
		
		function createApp = 
		(
			if selection.count > 0 then
			(
				max modify mode
				if (modPanel.getCurrentObject()) != undefined then
				(
					if ParamCollectorOps.visible == false then
					(
						local q = quickIsolateapp()
						return q
					)
					else
					(
						ParamCollectorOps.visible = false
					)
				)
			)
				
			return undefined
		),
		
		----------------------------------------------------------------------------------------------------------------------------------------
		-- <int> getQuickRolloutIndex
		-- returns the index of "Isolate Track" rollout in the Parameter Collector (value is at least 1 for max)
		-- if not found, returns 0
		----------------------------------------------------------------------------------------------------------------------------------------
		
		function getQuickRolloutIndex = 
		(
			local index = 0
			local activeCollection = ParamCollectorOps.getActiveCollection()
			
			for i=1 to (ParamCollectorOps.numRollouts activeCollection) do
			(
				if paramCollectorOps.getRolloutName activeCollection i == g_rolloutName then
					index = i
			)
			return index
		),
		
		----------------------------------------------------------------------------------------------------------------------------------------
		-- <void> initParamCollector
		-- There is always a default collection named "XX UNNAMED XX" (index : 1)
		-- It's not apparently possible to set the active collection by script, it can only be done via user mouse interaction : We can only use the CURRENT Collection.
		-- 
		-- This method looks for an existing "Isolate Track" rollout, if it exists, it erases and recreates it.
		----------------------------------------------------------------------------------------------------------------------------------------
		
		function initParamCollector = 
		(
			if ParamCollectorOps.anyRollouts() == true then
			(
				local rolloutIndex = getQuickRolloutIndex()
				
				if rolloutIndex > 0 then
					ParamCollectorOps.deleteRollout rolloutIndex
			)
			
			ParamCollectorOps.addNewRollout g_rolloutName false
			
			ParamCollectorOps.showTrackBarKeys = true
			ParamCollectorOps.showTrackBarSelectedKeys = true
			ParamCollectorOps.hideOtherKeys = true
			
			ParamCollectorOps.refresh()
		),
		
		
		----------------------------------------------------------------------------------------------------------------------------------------
		-- <array> getTracks
		-- This method is used by the UI rollout
		-- It retrieves a list of all animatable tracks name for the current modifier (subanims)
		----------------------------------------------------------------------------------------------------------------------------------------
		function getTracks = 
		(
			try
			(
				local tracks = #()
				local obj = modPanel.getCurrentObject()
				
				if obj != undefined then
				(
					-- Morpher has *always* a hundred of empty subanims and they have crazy names. We display them in a more elegant way.
					if classof obj == Morpher then
					(
						for i = 1 to obj.numSubs do
						(
							if	WM3_MC_HasTarget  obj i then
								append tracks ("Morph Target : " + (WM3_MC_GetName  obj i))
							else
								append tracks ("--")
						)
					)
					else
					(
						-- Some tracks can't be handled by the Parameter Collector (like modifier's gizmo, as they're using PRS Controllers) : 3dsMax himself doesn't handle them.
						-- We ignore them.
						for i = 1 to obj.numSubs do
						(
							local subAnimObj = getSubAnim obj i
							local trackName = getSubAnimName obj i
							
							case (classof subAnimObj.controller) of
							(
								PRS : trackName = "-- ignored : " + trackName
								Position_XYZ : trackName = "-- ignored : " +trackName
							)
							
							-- print (classof subAnimObj.controller)
							append tracks trackName
						)
					)
					return tracks
				)
			)
			catch
			(
				messageBox (getCurrentException())
				return #()
			)
		),
		
		----------------------------------------------------------------------------------------------------------------------------------------
		-- <void> process <int> selectedSub
		-- This is the main function.
		-- It adds the selected subanim in the parameter collector, and tells the Parameter Collector to display isolated keys
		-- See NOTES to learn more about the ParameterCollectorOps.addParameter function
		----------------------------------------------------------------------------------------------------------------------------------------
		
		function process selectedSub = 
		(
			try
			(
				local activeCollection = ParamCollectorOps.getActiveCollection()
				local rolloutIndex = getQuickRolloutIndex()
				
				obj = modPanel.getCurrentObject()
				
				local subAnimName = getSubAnimName obj selectedSub
				local subAnimObj = getSubAnim obj selectedSub
				
				format "%\n% : % / %\n" subAnimObj subAnimName subAnimObj.controller subAnimObj.value
				
				local operate = true -- Always true, except for Morpher
				
				-- Again, morpher is an exception
				
				if classof obj == Morpher then
				(
					if WM3_MC_HasTarget obj selectedSub then
						ParamCollectorOps.addParameter subAnimObj.controller activeCollection rolloutIndex (WM3_MC_GetName  obj selectedSub)
					else
						operate = false
				)
				else
				(
					-- No controller has been assigned yet, we try to guess their class
					-- TODO : there must be a cleaner way to do this
					
					if classof subAnimObj.controller == UndefinedClass then
					(
						case (classof subAnimObj.value) of
						(
							-- TODO : handling worldUnits ? is it possible ? Probably no : Max himself doesn't do it.
							BooleanClass:subAnimObj.controller = Boolean_Float()
							default:subAnimObj.controller = Bezier_Float()
						)
					)
					
					ParamCollectorOps.addParameter subAnimObj.controller activeCollection rolloutIndex subAnimName
				)
				
				if operate == true then
				(
					ParamCollectorOps.selectNone()
					local paramIndex = ParamCollectorOps.getParameterIndex activeCollection rolloutIndex 1
					
					ParamCollectorOps.setParameterSelected paramIndex true
					
					ParamCollectorOps.refresh()
					ParamCollectorOps.visible = true
				)
			)
			catch
			(
				messageBox (getCurrentException())
			)
		)
	)
	
	rollout isolateTrack_R (g_appName + " " + g_version) 
	(
		multilistbox tracks_mlb height:10
		button cancel_btn "Cancel" align:#right
		
		on isolateTrack_R open do
		(
			tracks_mlb.items = app.getTracks()
		)
		
		on tracks_mlb selected val do
		(
			try
			(
				app.initParamCollector()
				app.process val

				setFocus isolateTrack_R
				destroyDialog isolateTrack_R
			)
			catch
			(
				messageBox (getCurrentException())
				destroyDialog isolateTrack_R
			)
		)
		
		on cancel_btn pressed do
		(
			destroydialog isolateTrack_R
		)
	)
	
	-- ENTRY POINT

	app = QuickIsolateApp.createApp()
	
	if app != undefined then
		createDialog isolateTrack_R 200 175 mouse.screenpos.x mouse.screenpos.y escapeEnable:true modal:true style:#(#style_sunkenedge)
		
)

