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