# Change the selected parameter(s) via the mouse wheel v3.0
# and much more see http://neocron.lunarpages.com/alforum/index.php?topic=1511.0
# - poor python coding by Dizzi
import poser
import wx
import wx.aui
import os.path
import random
import wx.lib.masked.numctrl

class WheelPosing(wx.Panel):
	selectedParamName=""
#	multi=1
	value=1.0
	Amount="1"
	Digit=1
	Actors=[]
	RotTrans=[poser.kParmCodeXROT, poser.kParmCodeYROT, poser.kParmCodeZROT, poser.kParmCodeXTRAN, poser.kParmCodeYTRAN, poser.kParmCodeZTRAN]
	RotTransString=["Rotate X", "Rotate Y", "Rotate Z", "Translate X", "Translate Y", "Translate Z"]
	InternalItems=["GoalCenterOfMass","CenterOfMass", "faceRoomActor", "faceRoomTexActor"]
	def __init__(self,parent,title):
		wx.Panel.__init__(self, parent, -1, wx.Point(-1, -1), wx.DefaultSize, wx.WANTS_CHARS)
		self.sizer = wx.BoxSizer(wx.VERTICAL)
		self.SetSizer(self.sizer)
		#self.SetAutoLayout(1)
		#self.sizer.SetSizeHints(self)
		self.sizer2 = wx.BoxSizer(wx.HORIZONTAL)
		self.sizer.Add(self.sizer2)
		list=self.GetActorParamList()
		radioList = ['Actor', 'Figure', 'All']
		self.rbType = wx.RadioBox(self, 40, "Type", wx.Point(-1, -1), wx.DefaultSize, radioList, 1, wx.RA_SPECIFY_COLS)
		self.Bind(wx.EVT_RADIOBOX, self.EvtRbType, self.rbType)
		self.sizer2.Add(self.rbType)
		
		staticBox = wx.StaticBox(self, -1, "Actors")
		self.szActorType = wx.StaticBoxSizer(staticBox, wx.VERTICAL)
		self.cbCameras = wx.CheckBox(self, 21, "Cameras")
		self.cbCameras.Bind(wx.EVT_CHECKBOX, self.EvtChkboxActorType)
		self.szActorType.Add(self.cbCameras)
		self.cbLights = wx.CheckBox(self, 22, "Lights")
		self.cbLights.Bind(wx.EVT_CHECKBOX, self.EvtChkboxActorType)
		self.szActorType.Add(self.cbLights)
		self.cbMagnets = wx.CheckBox(self, 23, "Magnets")
		self.cbMagnets.Bind(wx.EVT_CHECKBOX, self.EvtChkboxActorType)
		self.szActorType.Add(self.cbMagnets)
		self.cbProps = wx.CheckBox(self, 23, "Props")
		self.cbProps.Bind(wx.EVT_CHECKBOX, self.EvtChkboxActorType)
		self.cbProps.SetValue(True)
		self.szActorType.Add(self.cbProps)
		self.cbHidden = wx.CheckBox(self, 24, "Hidden")
		self.cbHidden.Bind(wx.EVT_CHECKBOX, self.EvtChkboxActorType)
		#self.cbHidden.SetValue(True)
		self.szActorType.Add(self.cbHidden)
		self.sizer2.Add(self.szActorType)
		self.sizer2.Hide(self.szActorType)
		
		self.rbTransType = wx.RadioBox(self, 44, "Translate", wx.Point(-1, -1), wx.DefaultSize, ["Equally", "N*A", "Random"], 1, wx.RA_SPECIFY_COLS)
		self.Bind(wx.EVT_RADIOBOX, self.EvtRbTransType, self.rbTransType)
		self.rbTransType.Show(0)
		self.sizer2.Add(self.rbTransType)

		self.lbActors = wx.ListBox(self, 50, (-1, -1), (175, 100), list, wx.LB_EXTENDED|wx.LB_NEEDED_SB)
		self.lbActors.Multiselect=1
		self.sizer.Add(self.lbActors)
		self.lbActors.Show(0);
		self.lbParams = wx.ListBox(self, 60, (-1, -1), (175, 100), list, wx.LB_EXTENDED|wx.LB_NEEDED_SB)
		self.Bind(wx.EVT_LISTBOX, self.EvtListBoxClick, self.lbParams)
		self.sizer.Add(self.lbParams)
		
		fgsGridInputs=wx.FlexGridSizer(2,3,2,1)
		self.sizer.Add(fgsGridInputs)
		# input value
		lblValue = wx.StaticText(self, -1, "Value: ", wx.Point(-1, -1), wx.DefaultSize, wx.ALIGN_RIGHT)
		self.txtValue = wx.lib.masked.NumCtrl(self, 0, "0.0")
		self.txtValue.SetFractionWidth(6)
		self.txtValue.SetIntegerWidth(6)
		self.txtValue.SetAllowNone(False)
		self.txtValue.SetAllowNegative(True)
		self.txtValue.SetGroupDigits(False)
		self.txtValue.SetEditable(False)
		fgsGridInputs.Add(lblValue, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
		fgsGridInputs.Add(self.txtValue)
		fgsGridInputs.Add(wx.StaticText(self, -1, ""))
		
		# amount label and input
		lblAmount = wx.StaticText(self, -1, "Amount: ", wx.Point(-1, -1), wx.DefaultSize, wx.ALIGN_RIGHT)
		self.txtAmount = wx.lib.masked.NumCtrl(self, 0)
		self.txtAmount.SetFractionWidth(6)
		self.txtAmount.SetIntegerWidth(6)
		self.txtAmount.SetAllowNone(True)
		self.txtAmount.SetAllowNegative(True)
		self.txtAmount.SetGroupDigits(False)
		self.txtAmount.SetEditable(False)
		self.txtAmount.SetLimited(True)
		self.txtAmount.SetLimitOnFieldChange(True)
		self.txtAmount.SetMax("999999.999999")
		self.txtAmount.SetValue(self.Amount)
		self.lblDigit = wx.StaticText(self, -1, "+1", wx.Point(-1, -1), wx.DefaultSize, wx.ALIGN_LEFT)
		fgsGridInputs.Add(lblAmount, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
		fgsGridInputs.Add(self.txtAmount)
		fgsGridInputs.Add(self.lblDigit, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
		
		#bind mousewheel on listboxes so moving the wheel without pressing will work
		self.Bind(wx.EVT_MOUSEWHEEL, self.EvtWheel)
		self.lbParams.Bind(wx.EVT_MOUSEWHEEL, self.EvtWheel)
		self.lbActors.Bind(wx.EVT_MOUSEWHEEL, self.EvtWheel)

		#bind keyevents so Poser doesn't get them. Ha!
		self.Bind(wx.EVT_CHAR, self.EvtKey)
		self.lbParams.Bind(wx.EVT_CHAR, self.EvtKey)
		self.lbActors.Bind(wx.EVT_CHAR, self.EvtKey)
		self.rbType.Bind(wx.EVT_CHAR, self.EvtKey)
		self.rbTransType.Bind(wx.EVT_CHAR, self.EvtKey)
		self.txtValue.Bind(wx.EVT_CHAR, self.EvtKey)
		
		self.Layout()
		poser.Scene().SetEventCallback(self.EvcbParam)
	def __del__(self):
		poser.Scene().ClearEventCallback()
	def EvtKey(self,event):
		event.StopPropagation()
		code=event.GetKeyCode()
		if code==wx.WXK_F1:
			self.SetDigit(6,1e5)
		elif code==wx.WXK_F2:
			self.SetDigit(5,1e4)
		elif code==wx.WXK_F3:
			self.SetDigit(4,1e3)
		elif code==wx.WXK_F4:
			self.SetDigit(3,1e2)
		elif code==wx.WXK_F5:
			self.SetDigit(2,1e1)
		elif code==wx.WXK_F6:
			self.SetDigit(1,1e0)
		elif code==wx.WXK_F7:
			self.SetDigit(-1,1e-1)
		elif code==wx.WXK_F8:
			self.SetDigit(-2,1e-2)
		elif code==wx.WXK_F9:
			self.SetDigit(-3,1e-3)
		elif code==wx.WXK_F10:
			self.SetDigit(-4,1e-4)
		elif code==wx.WXK_F11:
			self.SetDigit(-5,1e-5)
		elif code==wx.WXK_F12:
			self.SetDigit(-6,1e-6)
		if code>255:
			return
		char=chr(code)
		if char.isdigit() or char==',' or char=='.':
			if char==',':
				char="."
			val=self.Amount
			if val=='' and char=='.':
				val="0.";
			else:
				val=val+char
			if self.IsNumber(val):
				self.SetAmount(val)
				
		elif code==wx.WXK_BACK:
			val=self.Amount
			if (len(val)>0):
				val=val[0:len(val)-1]
				self.SetAmount(val)
		elif code==wx.WXK_SPACE:
			self.SetAmount("")
		elif code==wx.WXK_RETURN:
			print 'x'

#	def EvtEnter(self,event):
#		event.StopPropagation()
#		self.UpdateAmount()
#	def UpdateAmount(self):
#		val=self.txtNewAmount.GetValue()
#		if (self.IsNumber(val)):
#			self.txtAmount.SetValue(str(float(val)))
#		self.txtNewAmount.SetValue("")
	def SetAmount(self, val):
		val=str(val)
		self.Amount=val
		if len(val)==0:
			self.txtAmount.SetValue(None)
		else:
			if self.txtAmount.IsInBounds(val):
				self.txtAmount.SetValue(val)
	def SetDigit(self, displayValue, mult):
		self.Digit=mult
		if (displayValue>0):
			self.lblDigit.SetLabel("+"+str(val))
		else:
			self.lblDigit.SetLabel(str(displayValue))
	def IsNumber(self, s):
		try:
			float(s)
			return True
		except ValueError:
			return False
	def EvtRbType(self, event):
		self.CheckList('T')
		self.lbParams.SetFocus()
	def EvtRbTransType(self, event):
		self.lbParams.SetFocus()
	def EvtListBoxClick(self, event):
		self.SelectedParametersChanged()
	def EvtChkboxActorType(self, event):
		self.CheckList('A')
	def CheckList(self, type):
		if self.rbType.GetSelection()==0 and (type=='C' or type=='T'):
			self.UpdateParams(self.GetActorParamList())
			self.sizer2.Hide(self.szActorType)
			self.lbActors.Show(0)
			self.rbTransType.Show(0)
		elif (self.rbType.GetSelection()==1):
			self.UpdateActors(self.GetFigureActors())
			self.UpdateParams(self.RotTransString[:3])
			self.sizer2.Show(self.szActorType)
			self.lbActors.Show(1)
			self.rbTransType.Show(0)
		elif (self.rbType.GetSelection()==2):
			self.UpdateActors(self.GetUniverseActors())
			self.UpdateParams(self.RotTransString)
			self.sizer2.Show(self.szActorType)
			self.lbActors.Show(1)
			self.rbTransType.Show(1)
		else:
			return
		self.sizer.Layout()
		self.Layout()
	def GetActorParamList(self):
		list=[]
		Param=[]
		for parm in poser.Scene().CurrentActor().Parameters():
			if not parm.Hidden():
			   list = list + [parm.Name()]
			   Param = Param + [parm.InternalName()]
		return list
	def UpdateParams(self, lNewNames):
		lCurrentNames=self.lbParams.GetItems()
		bSame=True #old==new actors?
		for param in lCurrentNames:
			if not param in lNewNames:
				bSame=False
				break
		for param in lNewNames:
			if not param in lCurrentNames:
				bSame=False
				break
		if bSame:
			return
		lSelectedIx=self.lbParams.GetSelections()
		self.lbParams.Set(lNewNames)
		for i in range(len(lSelectedIx)): #select the previous selected actors
			try:
				ix=lNewNames.index(lCurrentNames[lSelectedIx[i]])
			except ValueError:
				continue
			self.lbParams.Select(ix)
	def UpdateActors(self, lNewActors):
		bSame=True #old==new actors?
		for actor in self.Actors:
			if not actor in lNewActors[0]:
				bSame=False
				break
		for actor in lNewActors[0]:
			if not actor in self.Actors:
				bSame=False
				break
		if bSame:
			return
		lSelectedIx=self.lbActors.GetSelections()
		self.lbActors.Clear() #clear list
		self.lbActors.AppendItems(lNewActors[1]) #add all new actors
		for i in range(len(lSelectedIx)): #select the previous selected actors
			try:
				ix=lNewActors[0].index(self.Actors[lSelectedIx[i]])
			except ValueError:
				continue
			self.lbActors.Select(ix)
		self.Actors=lNewActors[0]
		
	def SelectedParametersChanged(self):
		if (self.rbType.GetSelection()==0):
			list=self.lbParams.GetSelections()
#			for ix in self.lbParams.GetSelections():
#				list = list + [self.lbParams.GetString(ix)]
			if len(list)==1:
				self.selectedParamName=self.lbParams.GetString(list[0])
				self.value=str(poser.Scene().CurrentActor().Parameter(self.selectedParamName).Value())
			else:
				self.selectedParamName=""
				self.value=0
			self.txtValue.SetValue(str(self.value))
		elif (self.rbType.GetSelection()==1):
			self.value=0
			self.txtValue.SetValue(str(self.value))
		elif (self.rbType.GetSelection()==2):
			self.value=0
			self.txtValue.SetValue(str(self.value))
	def GetFigureActors(self):
		list=([],[])
#		try:
		fig=poser.Scene().CurrentFigure()
		if fig==None:
			return list
		name=fig.Name()
		for actor in fig.Actors():
			if self.IsInternalItem(actor.InternalName()):
				continue
			if self.ActorIsBody(actor) or (actor.InternalName()==actor.ItsFigure().InternalName()) or ((actor.IsCamera() and self.cbCameras.GetValue()) or (actor.IsLight() and self.cbLights.GetValue()) or actor.IsBodyPart() or ((actor.IsProp() or actor.IsHairProp()) and self.cbProps.GetValue())) and (self.cbHidden.GetValue() or actor.OnOff()==1):
				list[0].append(actor.InternalName())
				list[1].append(name + " -> " + actor.Name())
#		except:
#			pass
		return list
	def GetUniverseActors(self):
		list=([],[])
		for actor in poser.Scene().Actor("UNIVERSE").Children():
			#print actor.Name(), actor.IsBodyPart(), actor.OnOff()
			if self.IsInternalItem(actor.InternalName()):
				continue
			if (actor.IsCamera() and self.cbCameras.GetValue()) or (actor.IsLight() and self.cbLights.GetValue()) or (actor.IsProp() and self.cbProps.GetValue()) and (self.cbHidden.GetValue() or actor.OnOff()==1):
				list[0].append(actor.InternalName())
				list[1].append(actor.Name())
			elif actor.IsBodyPart():
				if actor.ItsFigure().ConformTarget()!=None:
					continue
				if self.IsFigurePartVisible(actor.ItsFigure())==1:
					list[0].append(actor.InternalName())
					list[1].append(actor.ItsFigure().Name() + " -> " +actor.Name())
				else:
					continue
		return list
	def IsFigurePartVisible(self, figure):
		for actor in figure.Actors():
			if (self.cbHidden.GetValue() or actor.OnOff()==1):
				return 1
		return 0
	def EvtWheel(self, event):
#		delta = event.GetWheelDelta()
		rotation = event.GetWheelRotation()
		
#		print 'delta: ' + repr(delta) + ' rotation:' + repr(rotation)
		if (event.ShiftDown() and not event.CmdDown()):
			if rotation>0:
				self.lbParams.ScrollLines(-1*event.GetLinesPerAction())
			else:
				self.lbParams.ScrollLines(event.GetLinesPerAction())
		elif (event.CmdDown() and not event.ShiftDown()):
			val=self.txtAmount.GetValue()
			if rotation>0:
				self.SetAmount(val+self.Digit)
			else:
				val=val-self.Digit
				if (val>=0):
					self.SetAmount(val)
		elif (event.CmdDown() and event.ShiftDown()):
			if not self.IsNumber(self.Amount):
				return
			if rotation>0:
				self.SetAmount(float(self.Amount)*10)
			else:
				self.SetAmount(float(self.Amount)/10)
		else:
			if not self.IsNumber(self.Amount):
				return
			amount=float(self.Amount)
			if rotation<0:
				amount=-1.0*amount
			if (self.rbType.GetSelection()==0):
				self.AddToDial(amount)
			elif (self.rbType.GetSelection()==1):
				self.AddRot(amount)
			elif (self.rbType.GetSelection()==2):
				self.AddRotTrans(amount)
	def AddToDial(self, amount):
		actor = poser.Scene().CurrentActor()
		l = self.lbParams.GetSelections()
		if len(l)==0:
			return
		param=None
		for ix in self.lbParams.GetSelections():
			param=actor.Parameter(self.lbParams.GetString(ix))
			param.SetValue(param.Value()+amount)
		if len(l)==1:
			self.txtValue.SetValue(str(param.Value()))
		else:
			self.value = self.value + amount
			self.txtValue.SetValue(str(self.value))
		poser.Scene().Draw()
	
	def AddRot(self, amount):
		fig=poser.Scene().CurrentFigure()
		if fig==None:
			return
		params=[]
		for ix in self.lbParams.GetSelections():
			params = params + [self.RotTrans[ix]]
		for actor in fig.Actors():
			if actor.IsCamera() or actor.IsLight() or actor.IsBodyPart() or actor.IsProp() or actor.IsHairProp():
				for ix in self.lbActors.GetSelections():
					if actor.InternalName()==self.Actors[ix]:
						for param in params:
							parm=actor.ParameterByCode(param)
							parm.SetValue(parm.Value()+amount)
		poser.Scene().Draw()
		self.value = self.value + amount
		self.txtValue.SetValue(str(self.value))
	
	def AddRotTrans(self, amount):
		params=[]
		transtype=self.rbTransType.GetSelection()
		for ix in self.lbParams.GetSelections():
			followTransType = 0
			if transtype==1 or transtype==2:
				followTransType = 1
			params = params + [[self.RotTrans[ix],followTransType]]
		selectedActors=self.lbActors.GetSelections()
		cntAct=len(selectedActors)
		for actor in poser.Scene().Actor("UNIVERSE").Children():
			ixAct=0
			for ix in selectedActors:
				if actor.InternalName()==self.Actors[ix]:
					for param in params:
						parm=actor.ParameterByCode(param[0])
						val=parm.Value()
						if param[1]==1:
							if transtype==1:
								val=val+ixAct*amount
							if transtype==2:
								val=val+random.random()*amount
						else:
							val=val+amount
						parm.SetValue(val)
				ixAct=ixAct+1
		poser.Scene().Draw()
		self.value = self.value + amount
		self.txtValue.SetValue(str(self.value))
			
	
	def EvcbParam(self, iScene, iEventType):
		if (iEventType & poser.kEventCodeACTORSELECTIONCHANGED):
			self.CheckList('C')
		if (iEventType & poser.kEventCodeACTORDELETED):
			self.CheckList('D')
		if (iEventType & poser.kEventCodeITEMRENAMED):
			self.CheckList('R')

	def IsInternalItem(self, internalName):
		return internalName.split(":")[0] in self.InternalItems
	def ActorIsBody(self, actor):
		return actor.InternalName().split(":")[0]=="BODY"
name = "Wheel Posing"
man = poser.WxAuiManager()
root = man.GetManagedWindow()

win = WheelPosing(root, name)
pane = wx.aui.AuiPaneInfo()
pane.Caption(name).CaptionVisible().CloseButton().Resizable().DestroyOnClose()
pane.FloatingSize(wx.Size(150, 125)).BestSize(wx.Size(50, 50)).Right().PinButton().Dock()
man.AddPane(win, pane)
pane.Show()
man.Update()


