Floater.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. """
  2. Floater Class: Velocity style controller for floating point values with
  3. a label, entry (validated), and scale
  4. """
  5. __all__ = ['Floater', 'FloaterWidget', 'FloaterGroup']
  6. from direct.showbase.TkGlobal import *
  7. from Tkinter import *
  8. from Valuator import Valuator, VALUATOR_MINI, VALUATOR_FULL
  9. from direct.task import Task
  10. import math, sys, string, Pmw
  11. FLOATER_WIDTH = 22
  12. FLOATER_HEIGHT = 18
  13. class Floater(Valuator):
  14. def __init__(self, parent = None, **kw):
  15. INITOPT = Pmw.INITOPT
  16. optiondefs = (
  17. ('style', VALUATOR_MINI, INITOPT),
  18. )
  19. self.defineoptions(kw, optiondefs)
  20. # Initialize the superclass
  21. Valuator.__init__(self, parent)
  22. self.initialiseoptions(Floater)
  23. def createValuator(self):
  24. self._valuator = self.createcomponent('valuator',
  25. (('floater', 'valuator'),),
  26. None,
  27. FloaterWidget,
  28. (self.interior(),),
  29. command = self.setEntry,
  30. value = self['value'])
  31. self._valuator._widget.bind('<Double-ButtonPress-1>', self.mouseReset)
  32. def packValuator(self):
  33. # Position components
  34. if self._label:
  35. self._label.grid(row=0, column=0, sticky = EW)
  36. self._entry.grid(row=0, column=1, sticky = EW)
  37. self._valuator.grid(row=0, column=2, padx = 2, pady = 2)
  38. self.interior().columnconfigure(0, weight = 1)
  39. class FloaterWidget(Pmw.MegaWidget):
  40. def __init__(self, parent = None, **kw):
  41. #define the megawidget options
  42. INITOPT = Pmw.INITOPT
  43. optiondefs = (
  44. # Appearance
  45. ('width', FLOATER_WIDTH, INITOPT),
  46. ('height', FLOATER_HEIGHT, INITOPT),
  47. ('relief', RAISED, self.setRelief),
  48. ('borderwidth', 2, self.setBorderwidth),
  49. ('background', 'grey75', self.setBackground),
  50. # Behavior
  51. # Initial value of floater, use self.set to change value
  52. ('value', 0.0, INITOPT),
  53. ('numDigits', 2, self.setNumDigits),
  54. # Command to execute on floater updates
  55. ('command', None, None),
  56. # Extra data to be passed to command function
  57. ('commandData', [], None),
  58. # Callback's to execute during mouse interaction
  59. ('preCallback', None, None),
  60. ('postCallback', None, None),
  61. # Extra data to be passed to callback function, needs to be a list
  62. ('callbackData', [], None),
  63. )
  64. self.defineoptions(kw, optiondefs)
  65. # Initialize the superclass
  66. Pmw.MegaWidget.__init__(self, parent)
  67. # Set up some local and instance variables
  68. # Create the components
  69. interior = self.interior()
  70. # Current value
  71. self.value = self['value']
  72. # The canvas
  73. width = self['width']
  74. height = self['height']
  75. self._widget = self.createcomponent('canvas', (), None,
  76. Canvas, (interior,),
  77. width = width,
  78. height = height,
  79. background = self['background'],
  80. highlightthickness = 0,
  81. scrollregion = (-width/2.0,
  82. -height/2.0,
  83. width/2.0,
  84. height/2.0))
  85. self._widget.pack(expand = 1, fill = BOTH)
  86. # The floater icon
  87. self._widget.create_polygon(-width/2.0, 0, -2.0, -height/2.0,
  88. -2.0, height/2.0,
  89. fill = 'grey50',
  90. tags = ('floater',))
  91. self._widget.create_polygon(width/2.0, 0, 2.0, height/2.0,
  92. 2.0, -height/2.0,
  93. fill = 'grey50',
  94. tags = ('floater',))
  95. # Add event bindings
  96. self._widget.bind('<ButtonPress-1>', self.mouseDown)
  97. self._widget.bind('<B1-Motion>', self.updateFloaterSF)
  98. self._widget.bind('<ButtonRelease-1>', self.mouseUp)
  99. self._widget.bind('<Enter>', self.highlightWidget)
  100. self._widget.bind('<Leave>', self.restoreWidget)
  101. # Make sure input variables processed
  102. self.initialiseoptions(FloaterWidget)
  103. def set(self, value, fCommand = 1):
  104. """
  105. self.set(value, fCommand = 1)
  106. Set floater to new value, execute command if fCommand == 1
  107. """
  108. # Send command if any
  109. if fCommand and (self['command'] != None):
  110. apply(self['command'], [value] + self['commandData'])
  111. # Record value
  112. self.value = value
  113. def updateIndicator(self, value):
  114. # Nothing visible to update on this type of widget
  115. pass
  116. def get(self):
  117. """
  118. self.get()
  119. Get current floater value
  120. """
  121. return self.value
  122. ## Canvas callback functions
  123. # Floater velocity controller
  124. def mouseDown(self, event):
  125. """ Begin mouse interaction """
  126. # Exectute user redefinable callback function (if any)
  127. self['relief'] = SUNKEN
  128. if self['preCallback']:
  129. apply(self['preCallback'], self['callbackData'])
  130. self.velocitySF = 0.0
  131. self.updateTask = taskMgr.add(self.updateFloaterTask,
  132. 'updateFloater')
  133. self.updateTask.lastTime = globalClock.getFrameTime()
  134. def updateFloaterTask(self, state):
  135. """
  136. Update floaterWidget value based on current scaleFactor
  137. Adjust for time to compensate for fluctuating frame rates
  138. """
  139. currT = globalClock.getFrameTime()
  140. dt = currT - state.lastTime
  141. self.set(self.value + self.velocitySF * dt)
  142. state.lastTime = currT
  143. return Task.cont
  144. def updateFloaterSF(self, event):
  145. """
  146. Update velocity scale factor based of mouse distance from origin
  147. """
  148. x = self._widget.canvasx(event.x)
  149. y = self._widget.canvasy(event.y)
  150. offset = max(0, abs(x) - Valuator.deadband)
  151. if offset == 0:
  152. return 0
  153. sf = math.pow(Valuator.sfBase,
  154. self.minExp + offset/Valuator.sfDist)
  155. if x > 0:
  156. self.velocitySF = sf
  157. else:
  158. self.velocitySF = -sf
  159. def mouseUp(self, event):
  160. taskMgr.remove(self.updateTask)
  161. self.velocitySF = 0.0
  162. # Execute user redefinable callback function (if any)
  163. if self['postCallback']:
  164. apply(self['postCallback'], self['callbackData'])
  165. self['relief'] = RAISED
  166. def setNumDigits(self):
  167. """
  168. Adjust minimum exponent to use in velocity task based
  169. upon the number of digits to be displayed in the result
  170. """
  171. self.minExp = math.floor(-self['numDigits']/
  172. math.log10(Valuator.sfBase))
  173. # Methods to modify floater characteristics
  174. def setRelief(self):
  175. self.interior()['relief'] = self['relief']
  176. def setBorderwidth(self):
  177. self.interior()['borderwidth'] = self['borderwidth']
  178. def setBackground(self):
  179. self._widget['background'] = self['background']
  180. def highlightWidget(self, event):
  181. self._widget.itemconfigure('floater', fill = 'black')
  182. def restoreWidget(self, event):
  183. self._widget.itemconfigure('floater', fill = 'grey50')
  184. class FloaterGroup(Pmw.MegaToplevel):
  185. def __init__(self, parent = None, **kw):
  186. # Default group size
  187. DEFAULT_DIM = 1
  188. # Default value depends on *actual* group size, test for user input
  189. DEFAULT_VALUE = [0.0] * kw.get('dim', DEFAULT_DIM)
  190. DEFAULT_LABELS = ['v[%d]' % x for x in range(kw.get('dim', DEFAULT_DIM))]
  191. #define the megawidget options
  192. INITOPT = Pmw.INITOPT
  193. optiondefs = (
  194. ('dim', DEFAULT_DIM, INITOPT),
  195. ('side', TOP, INITOPT),
  196. ('title', 'Floater Group', None),
  197. # A tuple of initial values, one for each floater
  198. ('value', DEFAULT_VALUE, INITOPT),
  199. # The command to be executed any time one of the floaters is updated
  200. ('command', None, None),
  201. # A tuple of labels, one for each floater
  202. ('labels', DEFAULT_LABELS, self._updateLabels),
  203. )
  204. self.defineoptions(kw, optiondefs)
  205. # Initialize the toplevel widget
  206. Pmw.MegaToplevel.__init__(self, parent)
  207. # Create the components
  208. interior = self.interior()
  209. # Get a copy of the initial value (making sure its a list)
  210. self._value = list(self['value'])
  211. # The Menu Bar
  212. self.balloon = Pmw.Balloon()
  213. menubar = self.createcomponent('menubar', (), None,
  214. Pmw.MenuBar, (interior,),
  215. balloon = self.balloon)
  216. menubar.pack(fill=X)
  217. # FloaterGroup Menu
  218. menubar.addmenu('Floater Group', 'Floater Group Operations')
  219. menubar.addmenuitem(
  220. 'Floater Group', 'command', 'Reset the Floater Group panel',
  221. label = 'Reset',
  222. command = lambda s = self: s.reset())
  223. menubar.addmenuitem(
  224. 'Floater Group', 'command', 'Dismiss Floater Group panel',
  225. label = 'Dismiss', command = self.withdraw)
  226. menubar.addmenu('Help', 'Floater Group Help Operations')
  227. self.toggleBalloonVar = IntVar()
  228. self.toggleBalloonVar.set(0)
  229. menubar.addmenuitem('Help', 'checkbutton',
  230. 'Toggle balloon help',
  231. label = 'Balloon Help',
  232. variable = self.toggleBalloonVar,
  233. command = self.toggleBalloon)
  234. self.floaterList = []
  235. for index in range(self['dim']):
  236. # Add a group alias so you can configure the floaters via:
  237. # fg.configure(Valuator_XXX = YYY)
  238. f = self.createcomponent(
  239. 'floater%d' % index, (), 'Valuator', Floater,
  240. (interior,), value = self._value[index],
  241. text = self['labels'][index])
  242. # Do this separately so command doesn't get executed during construction
  243. f['command'] = lambda val, s=self, i=index: s._floaterSetAt(i, val)
  244. f.pack(side = self['side'], expand = 1, fill = X)
  245. self.floaterList.append(f)
  246. # Make sure floaters are initialized
  247. self.set(self['value'])
  248. # Make sure input variables processed
  249. self.initialiseoptions(FloaterGroup)
  250. def _updateLabels(self):
  251. if self['labels']:
  252. for index in range(self['dim']):
  253. self.floaterList[index]['text'] = self['labels'][index]
  254. def toggleBalloon(self):
  255. if self.toggleBalloonVar.get():
  256. self.balloon.configure(state = 'balloon')
  257. else:
  258. self.balloon.configure(state = 'none')
  259. def get(self):
  260. return self._value
  261. def getAt(self, index):
  262. return self._value[index]
  263. # This is the command is used to set the groups value
  264. def set(self, value, fCommand = 1):
  265. for i in range(self['dim']):
  266. self._value[i] = value[i]
  267. # Update floater, but don't execute its command
  268. self.floaterList[i].set(value[i], 0)
  269. if fCommand and (self['command'] is not None):
  270. self['command'](self._value)
  271. def setAt(self, index, value):
  272. # Update floater and execute its command
  273. self.floaterList[index].set(value)
  274. # This is the command used by the floater
  275. def _floaterSetAt(self, index, value):
  276. self._value[index] = value
  277. if self['command']:
  278. self['command'](self._value)
  279. def reset(self):
  280. self.set(self['value'])
  281. ## SAMPLE CODE
  282. if __name__ == '__main__':
  283. # Initialise Tkinter and Pmw.
  284. root = Toplevel()
  285. root.title('Pmw Floater demonstration')
  286. # Dummy command
  287. def printVal(val):
  288. print val
  289. # Create and pack a Floater megawidget.
  290. mega1 = Floater(root, command = printVal)
  291. mega1.pack(side = 'left', expand = 1, fill = 'x')
  292. """
  293. # These are things you can set/configure
  294. # Starting value for floater
  295. mega1['value'] = 123.456
  296. mega1['text'] = 'Drive delta X'
  297. # To change the color of the label:
  298. mega1.label['foreground'] = 'Red'
  299. # Max change/update, default is 100
  300. # To have really fine control, for example
  301. # mega1['maxVelocity'] = 0.1
  302. # Number of digits to the right of the decimal point, default = 2
  303. # mega1['numDigits'] = 5
  304. """
  305. # To create a floater group to set an RGBA value:
  306. group1 = FloaterGroup(root, dim = 4,
  307. title = 'Simple RGBA Panel',
  308. labels = ('R', 'G', 'B', 'A'),
  309. Valuator_min = 0.0,
  310. Valuator_max = 255.0,
  311. Valuator_resolution = 1.0,
  312. command = printVal)
  313. # Uncomment this if you aren't running in IDLE
  314. #root.mainloop()