VectorWidgets.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. """Undocumented Module"""
  2. __all__ = ['VectorEntry', 'Vector2Entry', 'Vector3Entry', 'Vector4Entry', 'ColorEntry']
  3. from . import Valuator
  4. import Pmw
  5. import tkinter as tk
  6. from tkinter.colorchooser import askcolor
  7. class VectorEntry(Pmw.MegaWidget):
  8. def __init__(self, parent = None, **kw):
  9. # Default vector size
  10. DEFAULT_DIM = 3
  11. # Default value depends on *actual* vector size, test for user input
  12. DEFAULT_VALUE = [0.0] * kw.get('dim', DEFAULT_DIM)
  13. DEFAULT_LABELS = ['v[%d]' % x for x in range(kw.get('dim', DEFAULT_DIM))]
  14. # Process options
  15. INITOPT = Pmw.INITOPT
  16. optiondefs = (
  17. ('dim', DEFAULT_DIM, INITOPT),
  18. ('value', DEFAULT_VALUE, INITOPT),
  19. ('resetValue', DEFAULT_VALUE, None),
  20. ('label_width', 12, None),
  21. ('labelIpadx', 2, None),
  22. ('command', None, None),
  23. ('entryWidth', 8, self._updateEntryWidth),
  24. ('relief', tk.GROOVE, self._updateRelief),
  25. ('bd', 2, self._updateBorderWidth),
  26. ('text', 'Vector:', self._updateText),
  27. ('min', None, self._updateValidate),
  28. ('max', None, self._updateValidate),
  29. ('numDigits', 2, self._setSigDigits),
  30. ('type', 'floater', None),
  31. ('state', 'normal', self._setState),
  32. )
  33. self.defineoptions(kw, optiondefs)
  34. # Initialize superclass
  35. Pmw.MegaWidget.__init__(self, parent)
  36. # Initialize value
  37. # Make sure its a list (and as a byproduct, make a distinct copy)
  38. self._value = list(self['value'])
  39. self['resetValue'] = self['value']
  40. self._floaters = None
  41. self.entryFormat = '%.2f'
  42. # Get a handle on the parent container
  43. interior = self.interior()
  44. # This does double duty as a menu button
  45. self._label = self.createcomponent('label', (), None,
  46. tk.Menubutton, (interior,),
  47. text = self['text'],
  48. activebackground = '#909090')
  49. self.menu = self._label['menu'] = tk.Menu(self._label)
  50. self.menu.add_command(label = 'Reset', command = self.reset)
  51. self.menu.add_command(label = 'Popup sliders', command = self.popupSliders)
  52. self._label.pack(side = tk.LEFT, fill = tk.X, ipadx = self['labelIpadx'])
  53. self.variableList = []
  54. self.entryList = []
  55. for index in range(self['dim']):
  56. var = tk.StringVar()
  57. self.variableList.append(var)
  58. # To set the configuration of all entrys in a vector use:
  59. # ve.configure(Entry_XXX = YYY)
  60. # To configure an individual entryfield's entry use:
  61. # ve.configure(entry0_XXX = YYY)
  62. entry = self.createcomponent(
  63. 'entryField%d' % index,
  64. (('entry%d' % index,
  65. 'entryField%d_entry' % index),),
  66. 'Entry',
  67. Pmw.EntryField, (interior,),
  68. entry_justify = tk.RIGHT,
  69. entry_textvariable = var,
  70. command = lambda s = self, i = index: s._entryUpdateAt(i))
  71. entry.pack(side = tk.LEFT, expand = 1, fill = tk.X)
  72. self.entryList.append(entry)
  73. # To configure the floaterGroup use:
  74. # ve.configure(floaterGroup_XXX = YYY)
  75. # ve.configure(fGroup_XXX = YYY) or
  76. # To set the configuration all floaters in a group use:
  77. # ve.configure(Valuator_XXX = YYY)
  78. # To configure an individual floater in a group use:
  79. # ve.configure(floaterGroup_floater0_XXX = YYY) or
  80. # ve.configure(fGroup_floater0_XXX = YYY)
  81. self._floaters = self.createcomponent(
  82. 'floaterGroup',
  83. (('fGroup', 'floaterGroup'),
  84. ('valuator', 'floaterGroup_valuator'),), None,
  85. Valuator.ValuatorGroupPanel, (self.interior(),),
  86. dim = self['dim'],
  87. #title = self['text'],
  88. type = self['type'],
  89. command = self.set)
  90. # Note: This means the 'X' on the menu bar doesn't really destroy
  91. # the panel, just withdraws it. This is to avoid problems which occur
  92. # if the user kills the floaterGroup and then tries to pop it open again
  93. self._floaters.userdeletefunc(self._floaters.withdraw)
  94. self._floaters.withdraw()
  95. # Make sure entries are updated
  96. self.set(self['value'])
  97. # Record entry color
  98. self.entryBackground = self.cget('Entry_entry_background')
  99. # Make sure input variables processed
  100. self.initialiseoptions(VectorEntry)
  101. def label(self):
  102. return self._label
  103. def entry(self, index):
  104. return self.entryList[index]
  105. def floaters(self):
  106. return self._floaters
  107. def _clearFloaters(self):
  108. self._floaters.withdraw()
  109. def _updateText(self):
  110. self._label['text'] = self['text']
  111. def _updateRelief(self):
  112. self.interior()['relief'] = self['relief']
  113. def _updateBorderWidth(self):
  114. self.interior()['bd'] = self['bd']
  115. def _updateEntryWidth(self):
  116. self['Entry_entry_width'] = self['entryWidth']
  117. def _setSigDigits(self):
  118. sd = self['numDigits']
  119. self.entryFormat = '%.' + '%d' % sd + 'f'
  120. self.configure(valuator_numDigits = sd)
  121. # And refresh value to reflect change
  122. for index in range(self['dim']):
  123. self._refreshEntry(index)
  124. def _updateValidate(self):
  125. # Update entry field to respect new limits
  126. self.configure(Entry_validate = {
  127. 'validator': 'real',
  128. 'min': self['min'],
  129. 'max': self['max'],
  130. 'minstrict': 0,
  131. 'maxstrict': 0})
  132. # Reflect changes in floaters
  133. self.configure(valuator_min = self['min'],
  134. valuator_max = self['max'])
  135. def get(self):
  136. return self._value
  137. def getAt(self, index):
  138. return self._value[index]
  139. def set(self, value, fCommand = 1):
  140. if type(value) in (float, int):
  141. value = [value] * self['dim']
  142. for i in range(self['dim']):
  143. self._value[i] = value[i]
  144. self.variableList[i].set(self.entryFormat % value[i])
  145. self.action(fCommand)
  146. def setAt(self, index, value, fCommand = 1):
  147. self.variableList[index].set(self.entryFormat % value)
  148. self._value[index] = value
  149. self.action(fCommand)
  150. def _entryUpdateAt(self, index):
  151. entryVar = self.variableList[index]
  152. # Did we get a valid float?
  153. try:
  154. newVal = float(entryVar.get())
  155. except ValueError:
  156. return
  157. # Clamp value
  158. if self['min'] is not None:
  159. if newVal < self['min']:
  160. newVal = self['min']
  161. if self['max'] is not None:
  162. if newVal > self['max']:
  163. newVal = self['max']
  164. # Update vector's value
  165. self._value[index] = newVal
  166. # refresh entry to reflect formatted value
  167. self._refreshEntry(index)
  168. # Update the floaters and call the command
  169. self.action()
  170. def _refreshEntry(self, index):
  171. self.variableList[index].set(self.entryFormat % self._value[index])
  172. self.entryList[index].checkentry()
  173. def _refreshFloaters(self):
  174. if self._floaters:
  175. self._floaters.set(self._value, 0)
  176. def action(self, fCommand = 1):
  177. self._refreshFloaters()
  178. if fCommand and (self['command'] is not None):
  179. self['command'](self._value)
  180. def reset(self):
  181. self.set(self['resetValue'])
  182. def addMenuItem(self, label = '', command = None):
  183. self.menu.add_command(label = label, command = command)
  184. def popupSliders(self):
  185. self._floaters.set(self.get()[:])
  186. self._floaters.show()
  187. def _setState(self):
  188. if self['state'] == 'disabled':
  189. # Disable entry
  190. self.configure(Entry_entry_state = 'disabled')
  191. self.configure(Entry_entry_background = '#C0C0C0')
  192. # Disable floater Group scale
  193. self.component('fGroup').configure(
  194. valuator_state = 'disabled')
  195. # Disable floater group entry
  196. self.component('fGroup').configure(
  197. valuator_entry_state = 'disabled')
  198. self.component('fGroup').configure(
  199. valuator_entry_background = '#C0C0C0')
  200. else:
  201. # Disable entry
  202. self.configure(Entry_entry_state = 'normal')
  203. self.configure(Entry_entry_background = self.entryBackground)
  204. # Disable floater Group scale
  205. self.component('fGroup').configure(
  206. valuator_state = 'normal')
  207. # Disable floater group entry
  208. self.component('fGroup').configure(
  209. valuator_entry_state = 'normal')
  210. self.component('fGroup').configure(
  211. valuator_entry_background = self.entryBackground)
  212. class Vector2Entry(VectorEntry):
  213. def __init__(self, parent = None, **kw):
  214. # Initialize options for the class
  215. optiondefs = (
  216. ('dim', 2, Pmw.INITOPT),
  217. ('fGroup_labels', ('X','Y','Z'), None),
  218. )
  219. self.defineoptions(kw, optiondefs)
  220. # Initialize the superclass, make sure dim makes it to superclass
  221. VectorEntry.__init__(self, parent, dim = self['dim'])
  222. # Needed because this method checks if self.__class__ is myClass
  223. # where myClass is the argument passed into inialiseoptions
  224. self.initialiseoptions(Vector2Entry)
  225. class Vector3Entry(VectorEntry):
  226. def __init__(self, parent = None, **kw):
  227. # Initialize options for the class
  228. optiondefs = (
  229. ('dim', 3, Pmw.INITOPT),
  230. ('fGroup_labels', ('X','Y','Z'), None),
  231. )
  232. self.defineoptions(kw, optiondefs)
  233. # Initialize the superclass, make sure dim makes it to superclass
  234. VectorEntry.__init__(self, parent, dim = self['dim'])
  235. # Needed because this method checks if self.__class__ is myClass
  236. # where myClass is the argument passed into inialiseoptions
  237. self.initialiseoptions(Vector3Entry)
  238. class Vector4Entry(VectorEntry):
  239. def __init__(self, parent = None, **kw):
  240. # Initialize options for the class
  241. optiondefs = (
  242. ('dim', 4, Pmw.INITOPT),
  243. ('fGroup_labels', ('X','Y','Z','W'), None),
  244. )
  245. self.defineoptions(kw, optiondefs)
  246. # Initialize the superclass, make sure dim makes it to superclass
  247. VectorEntry.__init__(self, parent, dim = self['dim'])
  248. # Needed because this method checks if self.__class__ is myClass
  249. # where myClass is the argument passed into inialiseoptions
  250. self.initialiseoptions(Vector4Entry)
  251. class ColorEntry(VectorEntry):
  252. def __init__(self, parent = None, **kw):
  253. # Initialize options for the class (overriding some superclass options)
  254. optiondefs = (
  255. ('dim', 4, Pmw.INITOPT),
  256. ('type', 'slider', Pmw.INITOPT),
  257. ('fGroup_labels', ('R','G','B','A'), None),
  258. ('min', 0.0, None),
  259. ('max', 255.0, None),
  260. ('nuDigits', 0, None),
  261. ('valuator_resolution', 1.0, None),
  262. )
  263. self.defineoptions(kw, optiondefs)
  264. # Initialize the superclass, make sure dim makes it to superclass
  265. VectorEntry.__init__(self, parent, dim = self['dim'])
  266. # Add menu item to popup color picker
  267. self.addMenuItem(
  268. 'Popup color picker',
  269. command = lambda s = self: s.popupColorPicker())
  270. # Needed because this method checks if self.__class__ is myClass
  271. # where myClass is the argument passed into inialiseoptions
  272. self.initialiseoptions(ColorEntry)
  273. def popupColorPicker(self):
  274. # Can pass in current color with: color = (255, 0, 0)
  275. color = askcolor(
  276. parent = self.interior(),
  277. # Initialize it to current color
  278. initialcolor = tuple(self.get()[:3]))[0]
  279. if color:
  280. self.set((color[0], color[1], color[2], self.getAt(3)))