diff --git a/direct/src/tkwidgets/Dial.py b/direct/src/tkwidgets/Dial.py index 4a554d3d10..b8a1cf042b 100644 --- a/direct/src/tkwidgets/Dial.py +++ b/direct/src/tkwidgets/Dial.py @@ -1,17 +1,20 @@ +""" +Dial Class: Velocity style controller for floating point values with + a label, entry (validated), and scale +""" from Tkinter import * -from PandaModules import ClockObject -import WidgetPropertiesDialog +from Valuator import * import Pmw import Task import math import string import operator +from PandaModules import ClockObject TWO_PI = 2.0 * math.pi ONEPOINTFIVE_PI = 1.5 * math.pi POINTFIVE_PI = 0.5 * math.pi INNER_SF = 0.2 -MAX_EXP = 5 DIAL_FULL = 'full' DIAL_MINI = 'mini' @@ -21,178 +24,113 @@ DIAL_MINI_SIZE = 30 globalClock = ClockObject.getGlobalClock() -from tkSimpleDialog import Dialog - - -class Dial(Pmw.MegaWidget): +class Dial(Valuator): + """ + Valuator widget which includes an angle dial and an entry for setting + floating point values + """ def __init__(self, parent = None, **kw): - #define the megawidget options INITOPT = Pmw.INITOPT optiondefs = ( ('style', DIAL_FULL, INITOPT), - # Widget relief - ('relief', GROOVE, None), - # Widget borderwidth - ('borderwidth', 2, None), - ('value', 0.0, INITOPT), - ('resetValue', 0.0, self.setResetValue), - ('text', 'Dial Widget', self.setLabel), - ('numDigits', 2, self.setEntryFormat), - ('command', None, None), - ('commandData', [], None), - ('min', None, self.setMin), - ('max', None, self.setMax), ('base', 0.0, self.setBase), ('delta', 1.0, self.setDelta), - # Callbacks to execute when updating widget's value - ('preCallback', None, self.setPreCallbackCmd), - ('postCallback', None, self.setPostCallbackCmd), - # Extra data to be passed to callback function, needs to be a list - ('callbackData', [], self.setCallbackData), + ('fSnap', 0, self.setSnap), + ('fRollover', 1, self.setRollover), ) self.defineoptions(kw, optiondefs) - - # Initialize the superclass - Pmw.MegaWidget.__init__(self, parent) - - # Override size if style specified by size is not - if not kw.has_key('dial_size'): - if self['style'] == DIAL_FULL: - dialSize = DIAL_FULL_SIZE - else: - dialSize = DIAL_MINI_SIZE - else: - dialSize = DIAL_FULL_SIZE - - # Create the components - interior = self.interior() - interior.configure(relief = self['relief'], bd = self['borderwidth']) - - # The Dial - self._dial = self.createcomponent('dial', (), None, - DialWidget, (interior,), - size = dialSize, - command = self.setEntry, - value = self['value']) - - self._dial.addPropertyToDialog( - 'text', - {'widget' : self, - 'type' : 'string', - 'help' : 'Enter label text for Dial.' - } - ) - self._dial.addPropertyToDialog( - 'numDigits', - {'widget' : self, - 'type' : 'integer', - 'help' : 'Enter number of digits after decimal point.' - } - ) - - # The Label - self._label = self.createcomponent('label', (), None, - Label, (interior,), - text = self['text'], - font = ('MS Sans Serif',12,'bold'), - anchor = CENTER) - self._label.bind('', self._dial.popupDialMenu) - - # The entry - self._entryVal = StringVar() - self._entry = self.createcomponent('entry', (), None, - Entry, (interior,), - justify = RIGHT, - width = 12, - textvariable = self._entryVal) - self._entry.bind('', self.validateEntryInput) - self._entry.bind('', self._dial.popupDialMenu) - self._entryBackground = self._entry.cget('background') - - if self['style'] == DIAL_FULL: - # Attach dial to entry - self._dial.grid(rowspan = 2, columnspan = 2, padx = 2, pady = 2) - self._label.grid(row = 0, col = 2, sticky = EW) - self._entry.grid(row = 1, col = 2, sticky = EW) - interior.columnconfigure(2, weight = 1) - else: - self._label.grid(row=0,col=0, sticky = EW) - self._entry.grid(row=0,col=1, sticky = EW) - self._dial.grid(row=0,col=2, padx = 2, pady = 2) - interior.columnconfigure(0, weight = 1) - - # Make sure input variables processed + Valuator.__init__(self, parent) self.initialiseoptions(Dial) - def set(self, value, fCommand = 1): - # Pass fCommand to dial which will return it to self.setEntry - self._dial['commandData'] = [fCommand] - self._dial.set(value) - - def get(self): - return self._dial.get() + def createValuator(self): + self._valuator = self.createcomponent( + 'valuator', + (('dial', 'valuator'),), + None, + DialWidget, + (self.interior(),), + style = self['style'], + command = self.setEntry, + value = self['value']) + self._valuator._canvas.bind('', self.mouseReset) - def setEntry(self, value, fCommand = 1): - self._entryVal.set(self.entryFormat % value) - # Execute command - if fCommand and (self['command'] != None): - apply(self['command'], [value] + self['commandData']) - - def setEntryFormat(self): - self.entryFormat = "%." + "%df" % self['numDigits'] - self.setEntry(self.get()) - self._dial['numDigits'] = self['numDigits'] - - def validateEntryInput(self, event): - input = self._entryVal.get() - try: - self._onReturnPress() - self._entry.configure(background = self._entryBackground) - newValue = string.atof(input) - self.set(newValue) - self._onReturnRelease() - except ValueError: - self._entry.configure(background = 'Pink') - - def _onReturnPress(self, *args): - """ User redefinable callback executed on in entry """ - if self['preCallback']: - apply(self['preCallback'], self['callbackData']) - - def _onReturnRelease(self, *args): - """ User redefinable callback executed on release in entry """ - if self['postCallback']: - apply(self['postCallback'], self['callbackData']) - - # Pass settings down to dial - def setCallbackData(self): - # Pass callback data down to dial - self._dial['callbackData'] = self['callbackData'] - - def setResetValue(self): - self._dial['resetValue'] = self['resetValue'] - - def setMin(self): - self._dial['min'] = self['min'] - - def setMax(self): - self._dial['max'] = self['max'] + def packValuator(self): + if self['style'] == DIAL_FULL: + self._valuator.grid(rowspan = 2, columnspan = 2, + padx = 2, pady = 2) + if self._label: + self._label.grid(row = 0, col = 2, sticky = EW) + self._entry.grid(row = 1, col = 2, sticky = EW) + self.interior().columnconfigure(2, weight = 1) + else: + if self._label: + self._label.grid(row=0,col=0, sticky = EW) + self._entry.grid(row=0,col=1, sticky = EW) + self._valuator.grid(row=0,col=2, padx = 2, pady = 2) + self.interior().columnconfigure(0, weight = 1) + def addValuatorPropertiesToDialog(self): + self.addPropertyToDialog( + 'base', + { 'widget' : self._valuator, + 'type' : 'real', + 'help' : 'Dial value = base + delta * numRevs'}) + self.addPropertyToDialog( + 'delta', + { 'widget' : self._valuator, + 'type' : 'real', + 'help' : 'Dial value = base + delta * numRevs'}) + self.addPropertyToDialog( + 'numSegments', + { 'widget' : self._valuator, + 'type' : 'integer', + 'help' : 'Number of segments to divide dial into.'}) + + def addValuatorMenuEntries(self): + # The popup menu + self._fSnap = IntVar() + self._fSnap.set(self['fSnap']) + self._popupMenu.add_checkbutton(label = 'Snap', + variable = self._fSnap, + command = self._setSnap) + self._fRollover = IntVar() + self._fRollover.set(self['fRollover']) + if self['fAdjustable']: + self._popupMenu.add_checkbutton(label = 'Rollover', + variable = self._fRollover, + command = self._setRollover) + def setBase(self): - self._dial['base'] = self['base'] + """ Set Dial base value: value = base + delta * numRevs """ + self._valuator['base'] = self['base'] def setDelta(self): - self._dial['delta'] = self['delta'] + """ Set Dial delta value: value = base + delta * numRevs """ + self._valuator['delta'] = self['delta'] - def setLabel(self): - self._label['text'] = self['text'] + def _setSnap(self): + """ Menu command to turn Dial angle snap on/off """ + self._valuator['fSnap'] = self._fSnap.get() - def setPreCallbackCmd(self): - self._dial['preCallback'] = self['preCallback'] + def setSnap(self): + """ Turn Dial angle snap on/off """ + self._fSnap.set(self['fSnap']) + # Call menu command to send down to valuator + self._setSnap() - def setPostCallbackCmd(self): - self._dial['postCallback'] = self['postCallback'] + def _setRollover(self): + """ + Menu command to turn Dial rollover on/off (i.e. does value accumulate + every time you complete a revolution of the dial?) + """ + self._valuator['fRollover'] = self._fRollover.get() + + def setRollover(self): + """ Turn Dial rollover (accumulation of a sum) on/off """ + self._fRollover.set(self['fRollover']) + # Call menu command to send down to valuator + self._setRollover() class AngleDial(Dial): @@ -200,7 +138,7 @@ class AngleDial(Dial): # Set the typical defaults for a 360 degree angle dial optiondefs = ( ('delta', 360.0, None), - ('dial_fRollover', 0, None), + ('fRollover', 0, None), ('dial_numSegments', 12, None), ) self.defineoptions(kw, optiondefs) @@ -212,37 +150,25 @@ class AngleDial(Dial): class DialWidget(Pmw.MegaWidget): - sfBase = 3.0 - sfDist = 15 - deadband = 10 def __init__(self, parent = None, **kw): #define the megawidget options INITOPT = Pmw.INITOPT optiondefs = ( - ## Appearance - # Edge size of the dial - ('size', DIAL_FULL_SIZE, INITOPT), - # Widget relief + # Appearance + ('style', DIAL_FULL, INITOPT), + ('size', None, INITOPT), ('relief', SUNKEN, self.setRelief), - # Widget borderwidth ('borderwidth', 2, self.setBorderwidth), - ('background', 'white', INITOPT), + ('background', 'white', self.setBackground), # Number of segments the dial is divided into ('numSegments', 10, self.setNumSegments), - ## Values + # Behavior # Initial value of dial, use self.set to change value ('value', 0.0, INITOPT), + ('numDigits', 2, self.setNumDigits), + # Dial specific options ('base', 0.0, None), ('delta', 1.0, None), - ('min', None, None), - ('max', None, None), - ('resolution', None, None), - ('numDigits', 2, self.setNumDigits), - # Value dial jumps to on reset - ('resetValue', 0.0, None), - ## Behavior - # Able to adjust max/min - ('fAdjustable', 1, None), # Snap to angle on/off ('fSnap', 0, None), # Do values rollover (i.e. accumulate) with multiple revolutions @@ -263,24 +189,33 @@ class DialWidget(Pmw.MegaWidget): Pmw.MegaWidget.__init__(self, parent) # Set up some local and instance variables + # Create the components + interior = self.interior() - # Running total which increments/decrements every time around dial - self.rollCount = 0 # Current value self.value = self['value'] - # Create the components - interior = self.interior() - dim = self['size'] + # Running total which increments/decrements every time around dial + self.rollCount = 0 + + # Base dial size on style, if size not specified, + if not self['size']: + if self['style'] == DIAL_FULL: + size = DIAL_FULL_SIZE + else: + size = DIAL_MINI_SIZE + else: + size = self['size'] + # Radius of the dial - radius = self.radius = int(dim/2.0) + radius = self.radius = int(size/2.0) # Radius of the inner knob inner_radius = max(3,radius * INNER_SF) # The canvas self._canvas = self.createcomponent('canvas', (), None, Canvas, (interior,), - width = dim, height = dim, + width = size, height = size, background = self['background'], highlightthickness = 0, scrollregion = (-radius,-radius, @@ -302,53 +237,6 @@ class DialWidget(Pmw.MegaWidget): fill = '#A0A0A0', tags = ('knob',)) - # A Dictionary of dictionaries used for the popup property dialog - self.propertyDict = { - 'min' : { 'widget' : self, - 'type' : 'real', - 'fNone' : 1, - 'help' : 'Minimum allowable dial value, Enter None for no minimum'}, - 'max' : { 'widget' : self, - 'type' : 'real', - 'fNone' : 1, - 'help' : 'Maximum allowable dial value, Enter None for no maximum'}, - 'base' : { 'widget' : self, - 'type' : 'real', - 'help' : 'Dial value = base + delta * numRevs'}, - 'delta' : { 'widget' : self, - 'type' : 'real', - 'help' : 'Dial value = base + delta * numRevs'}, - 'numSegments' : { 'widget' : self, - 'type' : 'integer', - 'help' : 'Number of segments to divide dial into'}, - 'resetValue' : { 'widget' : self, - 'type' : 'real', - 'help' : 'Enter value to set dial to on reset.'} - } - self.propertyList = ['min', 'max', 'base', 'delta', - 'resetValue', 'numSegments'] - - # The popup menu - self._popupMenu = Menu(interior, tearoff = 0) - self._fSnap = IntVar() - self._fSnap.set(self['fSnap']) - self._popupMenu.add_checkbutton(label = 'Snap', - variable = self._fSnap, - command = self.setSnap) - self._fRollover = IntVar() - self._fRollover.set(self['fRollover']) - if self['fAdjustable']: - self._popupMenu.add_checkbutton(label = 'Rollover', - variable = self._fRollover, - command = self.setRollover) - self._popupMenu.add_command( - label = 'Properties...', - command = self.popupPropertiesDialog) - self._popupMenu.add_command(label = 'Zero Dial', - command = self.zero) - self._popupMenu.add_command(label = 'Reset Dial', - command = self.reset) - # Add event bindings self._canvas.tag_bind('dial', '', self.mouseDown) self._canvas.tag_bind('dial', '', self.mouseMotion) @@ -356,12 +244,10 @@ class DialWidget(Pmw.MegaWidget): self.shiftMouseMotion) self._canvas.tag_bind('dial', '', self.mouseUp) self._canvas.tag_bind('knob', '', self.knobMouseDown) - self._canvas.tag_bind('knob', '', self.knobMouseMotion) + self._canvas.tag_bind('knob', '', self.updateDialSF) self._canvas.tag_bind('knob', '', self.knobMouseUp) self._canvas.tag_bind('knob', '', self.highlightKnob) self._canvas.tag_bind('knob', '', self.restoreKnob) - self._canvas.bind('', self.mouseReset) - self._canvas.bind('', self.popupDialMenu) # Make sure input variables processed self.initialiseoptions(DialWidget) @@ -371,49 +257,16 @@ class DialWidget(Pmw.MegaWidget): self.set(value, fCommand = 1) Set dial to new value, execute command if fCommand == 1 """ - # Clamp value - if self['min'] is not None: - if value < self['min']: - value = self['min'] - if self['max'] is not None: - if value > self['max']: - value = self['max'] - # Round by resolution - if self['resolution'] is not None: - value = round(value / self['resolution']) * self['resolution'] # Adjust for rollover if not self['fRollover']: if value > self['delta']: self.rollCount = 0 value = self['base'] + ((value - self['base']) % self['delta']) - # Update indicator to reflect adjusted value - self.updateIndicator(value) # Send command if any if fCommand and (self['command'] != None): apply(self['command'], [value] + self['commandData']) # Record value self.value = value - - # Set floater to zero - def zero(self): - """ - self.reset() - Set dial to zero - """ - self.set(0.0) - - # Reset dial to reset value - def reset(self): - """ - self.reset() - Reset dial to reset value - """ - self.set(self['resetValue']) - - def mouseReset(self,event): - # If not over any canvas item - if not self._canvas.find_withtag(CURRENT): - self.reset() def get(self): """ @@ -486,10 +339,10 @@ class DialWidget(Pmw.MegaWidget): def knobMouseDown(self,event): self._onButtonPress() self.knobSF = 0.0 - t = taskMgr.add(self.knobComputeVelocity, 'cv') - t.lastTime = globalClock.getFrameTime() + self.updateTask = taskMgr.add(self.updateDialTask, 'updateDial') + self.updateTask.lastTime = globalClock.getFrameTime() - def knobComputeVelocity(self, state): + def updateDialTask(self, state): # Update value currT = globalClock.getFrameTime() dt = currT - state.lastTime @@ -497,35 +350,39 @@ class DialWidget(Pmw.MegaWidget): state.lastTime = currT return Task.cont - def knobMouseMotion(self, event): - # What is the current knob angle - self.knobSF = self.computeKnobSF(event) - - def computeKnobSF(self, event): + def updateDialSF(self, event): x = self._canvas.canvasx(event.x) y = self._canvas.canvasy(event.y) - offset = max(0, abs(x) - DialWidget.deadband) + offset = max(0, abs(x) - Valuator.deadband) if offset == 0: return 0 - sf = math.pow(DialWidget.sfBase, - self.minExp + offset/DialWidget.sfDist) + sf = math.pow(Valuator.sfBase, + self.minExp + offset/Valuator.sfDist) if x > 0: - return sf + self.knobSF = sf else: - return -sf + self.knobSF = -sf def knobMouseUp(self, event): - taskMgr.remove('cv') + taskMgr.remove(self.updateTask) self.knobSF = 0.0 self._onButtonRelease() - def highlightKnob(self, event): - self._canvas.itemconfigure('knob', fill = 'black') - - def restoreKnob(self, event): - self._canvas.itemconfigure('knob', fill = '#A0A0A0') + def setNumDigits(self): + # Set minimum exponent to use in velocity task + self.minExp = math.floor(-self['numDigits']/ + math.log10(Valuator.sfBase)) # Methods to modify dial characteristics + def setRelief(self): + self.interior()['relief'] = self['relief'] + + def setBorderwidth(self): + self.interior()['borderwidth'] = self['borderwidth'] + + def setBackground(self): + self._canvas['background'] = self['background'] + def setNumSegments(self): self._canvas.delete('ticks') # Based upon input snap angle, how many ticks @@ -550,44 +407,13 @@ class DialWidget(Pmw.MegaWidget): self._canvas.create_line(startx, starty, endx, endy, tags = ('ticks','dial')) - def setRelief(self): - self.interior()['relief'] = self['relief'] + def highlightKnob(self, event): + self._canvas.itemconfigure('knob', fill = 'black') - def setBorderwidth(self): - self.interior()['borderwidth'] = self['borderwidth'] + def restoreKnob(self, event): + self._canvas.itemconfigure('knob', fill = '#A0A0A0') - def setNumDigits(self): - # Set minimum exponent to use in velocity task - self.minExp = math.floor(-self['numDigits']/ - math.log10(DialWidget.sfBase)) - - # The following methods are used to handle the popup menu - def popupDialMenu(self,event): - self._popupMenu.post(event.widget.winfo_pointerx(), - event.widget.winfo_pointery()) - - # Turn angle snap on/off - def setSnap(self): - self['fSnap'] = self._fSnap.get() - - # Turn rollover (accumulation of a sum) on/off - def setRollover(self): - self['fRollover'] = self._fRollover.get() - - # This handles the popup dial min dialog - def popupPropertiesDialog(self): - # Popup dialog to adjust widget properties - WidgetPropertiesDialog.WidgetPropertiesDialog( - self.propertyDict, - propertyList = self.propertyList, - title = 'Dial Widget Properties', - parent = self._canvas) - - def addPropertyToDialog(self, property, pDict): - self.propertyDict[property] = pDict - self.propertyList.append(property) - - # User callbacks + # To call user callbacks def _onButtonPress(self, *args): """ User redefinable callback executed on button press """ if self['preCallback']: diff --git a/direct/src/tkwidgets/EntryScale.py b/direct/src/tkwidgets/EntryScale.py index 3dd6f908bb..38118220c8 100644 --- a/direct/src/tkwidgets/EntryScale.py +++ b/direct/src/tkwidgets/EntryScale.py @@ -19,6 +19,7 @@ class EntryScale(Pmw.MegaWidget): # Define the megawidget options. optiondefs = ( + ('state', None, None), ('value', 0.0, Pmw.INITOPT), ('resolution', 0.001, None), ('command', None, None), diff --git a/direct/src/tkwidgets/Floater.py b/direct/src/tkwidgets/Floater.py index 079e09f09e..33b8722938 100644 --- a/direct/src/tkwidgets/Floater.py +++ b/direct/src/tkwidgets/Floater.py @@ -4,80 +4,88 @@ Floater Class: Velocity style controller for floating point values with """ from PandaObject import * from Tkinter import * +from Valuator import * import Pmw -import WidgetPropertiesDialog +import Task +import math import string globalClock = ClockObject.getGlobalClock() +FLOATER_WIDTH = 22 +FLOATER_HEIGHT = 18 -FLOATER_FULL = 'full' -FLOATER_MINI = 'mini' +class Floater(Valuator): + def __init__(self, parent = None, **kw): + optiondefs = () + self.defineoptions(kw, optiondefs) + # Initialize the superclass + Valuator.__init__(self, parent) + self.initialiseoptions(Floater) + + def createValuator(self): + self._valuator = self.createcomponent('valuator', + (('floater', 'valuator'),), + None, + FloaterWidget, + (self.interior(),), + command = self.setEntry, + value = self['value']) + self._valuator._canvas.bind('', self.mouseReset) + + def packValuator(self): + # Position components + if self._label: + self._label.grid(row=0,col=0, sticky = EW) + self._entry.grid(row=0,col=1, sticky = EW) + self._valuator.grid(row=0,col=2, padx = 2, pady = 2) + self.interior().columnconfigure(0, weight = 1) -FLOATER_WIDTH = 25 -FLOATER_HEIGHT = 20 class FloaterWidget(Pmw.MegaWidget): - sfBase = 3.0 - sfDist = 15 - deadband = 10 def __init__(self, parent = None, **kw): #define the megawidget options INITOPT = Pmw.INITOPT optiondefs = ( - ## Appearance - # Edge size of the floater + # Appearance ('width', FLOATER_WIDTH, INITOPT), ('height', FLOATER_HEIGHT, INITOPT), - # Color - ('background', 'white', INITOPT), - # Widget relief ('relief', SUNKEN, self.setRelief), - # Widget borderwidth ('borderwidth', 2, self.setBorderwidth), - ## Values + ('background', 'white', self.setBackground), + # Behavior # Initial value of floater, use self.set to change value ('value', 0.0, INITOPT), - ('min', None, None), - ('max', None, None), - ('resolution', None, None), ('numDigits', 2, self.setNumDigits), - # Value floater jumps to on reset - ('resetValue', 0.0, None), - ## Behavior - # Able to adjust max/min - ('fAdjustable', 1, None), # Command to execute on floater updates ('command', None, None), # Extra data to be passed to command function ('commandData', [], None), # Callback's to execute during mouse interaction - ('preCallback', None, None), - ('postCallback', None, None), + ('preCallback', None, None), + ('postCallback', None, None), # Extra data to be passed to callback function, needs to be a list ('callbackData', [], None), ) self.defineoptions(kw, optiondefs) - #print 'FLOATER WIDGET', self['resetValue'] - # Initialize the superclass Pmw.MegaWidget.__init__(self, parent) # Set up some local and instance variables - # Current value - self.value = self['value'] - # Create the components interior = self.interior() + # Current value + self.value = self['value'] + # The canvas width = self['width'] height = self['height'] self._canvas = self.createcomponent('canvas', (), None, Canvas, (interior,), - width = self['width'], - height = self['height'], + width = width, + height = height, background = self['background'], highlightthickness = 0, scrollregion = (-width/2.0, @@ -96,46 +104,12 @@ class FloaterWidget(Pmw.MegaWidget): fill = '#A0A0A0', tags = ('floater',)) - # A Dictionary of dictionaries for the popup property dialog - self.propertyDict = { - 'min' : { 'widget' : self, - 'type' : 'real', - 'fNone' : 1, - 'help' : 'Minimum allowable floater value, Enter None for no minimum'}, - 'max' : { 'widget' : self, - 'type' : 'real', - 'fNone' : 1, - 'help' : 'Maximum allowable floater value, Enter None for no maximum'}, - 'resetValue' : { 'widget' : self, - 'type' : 'real', - 'help' : 'Enter value to set floater to on reset.'} - } - self.propertyList = ['min', 'max', 'resetValue'] - - # The popup menu - self._popupMenu = Menu(interior, tearoff = 0) - - if self['fAdjustable']: - self._popupMenu.add_command( - label = 'Properties...', - command = self.popupPropertiesDialog) - self._popupMenu.add_command(label = 'Zero Floater', - command = self.zero) - self._popupMenu.add_command(label = 'Reset Floater', - command = self.reset) - # Add event bindings self._canvas.bind('', self.mouseDown) - self._canvas.bind('', self.mouseMotion) + self._canvas.bind('', self.updateFloaterSF) self._canvas.bind('', self.mouseUp) - self._canvas.bind('', self.popupFloaterMenu) - self._canvas.bind('', self.mouseReset) - self._canvas.bind('', self.popupFloaterMenu) - self._canvas.bind('', self.highlightIcon) - self._canvas.bind('', self.restoreIcon) - self._canvas.tag_bind('floater', '', self.mouseDown) - self._canvas.tag_bind('floater', '', self.mouseMotion) - self._canvas.tag_bind('floater', '', self.mouseUp) + self._canvas.bind('', self.highlightWidget) + self._canvas.bind('', self.restoreWidget) # Make sure input variables processed self.initialiseoptions(FloaterWidget) @@ -145,44 +119,16 @@ class FloaterWidget(Pmw.MegaWidget): self.set(value, fCommand = 1) Set floater to new value, execute command if fCommand == 1 """ - # Clamp value - if self['min'] is not None: - if value < self['min']: - value = self['min'] - if self['max'] is not None: - if value > self['max']: - value = self['max'] - # Round by resolution - if self['resolution'] is not None: - value = round(value / self['resolution']) * self['resolution'] - # Send command if any if fCommand and (self['command'] != None): apply(self['command'], [value] + self['commandData']) # Record value self.value = value + + def updateIndicator(self, value): + # Nothing visible to update on this type of widget + pass - # Set floater to zero - def zero(self): - """ - self.reset() - Set floater to zero - """ - self.set(0.0) - - # Reset floater to reset value - def reset(self): - """ - self.reset() - Reset floater to reset value - """ - self.set(self['resetValue']) - - def mouseReset(self,event): - # If not over any canvas item - #if not self._canvas.find_withtag(CURRENT): - self.reset() - def get(self): """ self.get() @@ -193,46 +139,56 @@ class FloaterWidget(Pmw.MegaWidget): ## Canvas callback functions # Floater velocity controller def mouseDown(self,event): - self._onButtonPress() + """ Begin mouse interaction """ + # Exectute user redefinable callback function (if any) + if self['preCallback']: + apply(self['preCallback'], self['callbackData']) self.velocitySF = 0.0 - t = taskMgr.add(self.computeVelocity, 'cv') - t.lastTime = globalClock.getFrameTime() + self.updateTask = taskMgr.add(self.updateFloaterTask, + 'updateFloater') + self.updateTask.lastTime = globalClock.getFrameTime() - def computeVelocity(self, state): - # Update value + def updateFloaterTask(self, state): + """ + Update floaterWidget value based on current scaleFactor + Adjust for time to compensate for fluctuating frame rates + """ currT = globalClock.getFrameTime() dt = currT - state.lastTime self.set(self.value + self.velocitySF * dt) state.lastTime = currT return Task.cont - def mouseMotion(self, event): - # What is the current knob angle - self.velocitySF = self.computeVelocitySF(event) - - def computeVelocitySF(self, event): + def updateFloaterSF(self, event): + """ + Update velocity scale factor based of mouse distance from origin + """ x = self._canvas.canvasx(event.x) y = self._canvas.canvasy(event.y) - offset = max(0, abs(x) - FloaterWidget.deadband) + offset = max(0, abs(x) - Valuator.deadband) if offset == 0: return 0 - sf = math.pow(FloaterWidget.sfBase, - self.minExp + offset/FloaterWidget.sfDist) + sf = math.pow(Valuator.sfBase, + self.minExp + offset/Valuator.sfDist) if x > 0: - return sf + self.velocitySF = sf else: - return -sf + self.velocitySF = -sf def mouseUp(self, event): - taskMgr.remove('cv') + taskMgr.remove(self.updateTask) self.velocitySF = 0.0 - self._onButtonRelease() + # Execute user redefinable callback function (if any) + if self['postCallback']: + apply(self['postCallback'], self['callbackData']) - def highlightIcon(self, event): - self._canvas.itemconfigure('floater', fill = 'black') - - def restoreIcon(self, event): - self._canvas.itemconfigure('floater', fill = '#A0A0A0') + def setNumDigits(self): + """ + Adjust minimum exponent to use in velocity task based + upon the number of digits to be displayed in the result + """ + self.minExp = math.floor(-self['numDigits']/ + math.log10(Valuator.sfBase)) # Methods to modify floater characteristics def setRelief(self): @@ -241,194 +197,14 @@ class FloaterWidget(Pmw.MegaWidget): def setBorderwidth(self): self.interior()['borderwidth'] = self['borderwidth'] - def setNumDigits(self): - # Set minimum exponent to use in velocity task - self.minExp = math.floor(-self['numDigits']/ - math.log10(FloaterWidget.sfBase)) + def setBackground(self): + self._canvas['background'] = self['background'] - # The following methods are used to handle the popup menu - def popupFloaterMenu(self,event): - self._popupMenu.post(event.widget.winfo_pointerx(), - event.widget.winfo_pointery()) + def highlightWidget(self, event): + self._canvas.itemconfigure('floater', fill = 'black') - # Popup dialog to adjust widget properties - def popupPropertiesDialog(self): - WidgetPropertiesDialog.WidgetPropertiesDialog( - self.propertyDict, - propertyList = self.propertyList, - title = 'Floater Widget Properties', - parent = self._canvas) - - def addPropertyToDialog(self, property, pDict): - self.propertyDict[property] = pDict - self.propertyList.append(property) - - # User callbacks - def _onButtonPress(self, *args): - """ User redefinable callback executed on button press """ - if self['preCallback']: - apply(self['preCallback'], self['callbackData']) - - def _onButtonRelease(self, *args): - """ User redefinable callback executed on button release """ - if self['postCallback']: - apply(self['postCallback'], self['callbackData']) - - -class Floater(Pmw.MegaWidget): - def __init__(self, parent = None, **kw): - #define the megawidget options - INITOPT = Pmw.INITOPT - optiondefs = ( - # Widget relief - ('relief', GROOVE, None), - # Widget borderwidth - ('borderwidth', 2, None), - ('value', 0.0, INITOPT), - ('resetValue', 0.0, self.setResetValue), - ('text', 'Floater', self.setLabel), - ('numDigits', 2, self.setEntryFormat), - ('command', None, None), - ('commandData', [], None), - ('min', None, self.setMin), - ('max', None, self.setMax), - # Callbacks to execute when updating widget's value - ('preCallback', None, self.setButtonPressCmd), - ('postCallback', None, self.setButtonReleaseCmd), - # Extra data to be passed to callback function, needs to be a list - ('callbackData', [], self.setCallbackData), - ) - self.defineoptions(kw, optiondefs) - - # Initialize the superclass - Pmw.MegaWidget.__init__(self, parent) - - # Create the components - interior = self.interior() - interior.configure(relief = self['relief'], bd = self['borderwidth']) - - # The Floater - #print self['text'], self['value'], self['resetValue'] - self._floater = self.createcomponent('floater', (), None, - FloaterWidget, (interior,), - command = self.setEntry, - resetValue = self['value'], - value = self['value']) - - if not kw.has_key('resetValue'): - self['resetValue'] = self['value'] - self._floater.addPropertyToDialog( - 'text', - {'widget' : self, - 'type' : 'string', - 'help' : 'Enter label text for Floater.' - } - ) - self._floater.addPropertyToDialog( - 'numDigits', - {'widget' : self, - 'type' : 'integer', - 'help' : 'Enter number of digits after decimal point.' - } - ) - - # The Label - self._label = self.createcomponent('label', (), None, - Label, (interior,), - text = self['text'], - font = ('MS Sans Serif',12,'bold'), - anchor = CENTER) - self._label.bind('', self._floater.popupFloaterMenu) - - # The entry - self._entryVal = StringVar() - self._entry = self.createcomponent('entry', (), None, - Entry, (interior,), - justify = RIGHT, - width = 12, - textvariable = self._entryVal) - self._entry.bind('', self.validateEntryInput) - self._entry.bind('', self._floater.popupFloaterMenu) - self._entryBackground = self._entry.cget('background') - - # Position components - self._label.grid(row=0,col=0, sticky = EW) - self._entry.grid(row=0,col=1, sticky = EW) - self._floater.grid(row=0,col=2, padx = 2, pady = 2) - interior.columnconfigure(0, weight = 1) - - # Make sure input variables processed - self.fInit = 0 - self.initialiseoptions(Floater) - self.fInit = 1 - - def set(self, value, fCommand = 1): - # Pass fCommand to user specified data (to control if command - # is executed or not) to floater which will return it to self.setEntry - self._floater['commandData'] = [fCommand] - self._floater.set(value) - # Restore commandData to 1 so that interaction via floater widget - # will result in command being executed, otherwise a set with - # commandData == 0 will stick and commands will not be executed - self._floater['commandData'] = [1] - - def get(self): - return self._floater.get() - - def setEntry(self, value, fCommand = 1): - self._entryVal.set(self.entryFormat % value) - # Execute command - if self.fInit and fCommand and (self['command'] != None): - apply(self['command'], [value] + self['commandData']) - - def setEntryFormat(self): - self.entryFormat = "%." + "%df" % self['numDigits'] - self.setEntry(self.get()) - self._floater['numDigits'] = self['numDigits'] - - def validateEntryInput(self, event): - input = self._entryVal.get() - try: - self._onReturnPress() - self._entry.configure(background = self._entryBackground) - newValue = string.atof(input) - self.set(newValue) - self._onReturnRelease() - except ValueError: - self._entry.configure(background = 'Pink') - - def _onReturnPress(self, *args): - """ User redefinable callback executed on in entry """ - if self['preCallback']: - apply(self['preCallback'], self['callbackData']) - - def _onReturnRelease(self, *args): - """ User redefinable callback executed on release in entry """ - if self['postCallback']: - apply(self['postCallback'], self['callbackData']) - - # Pass settings down to floater - def setCallbackData(self): - # Pass callback data down to floater - self._floater['callbackData'] = self['callbackData'] - - def setResetValue(self): - self._floater['resetValue'] = self['resetValue'] - - def setMin(self): - self._floater['min'] = self['min'] - - def setMax(self): - self._floater['max'] = self['max'] - - def setLabel(self): - self._label['text'] = self['text'] - - def setButtonPressCmd(self): - self._floater['preCallback'] = self['preCallback'] - - def setButtonReleaseCmd(self): - self._floater['postCallback'] = self['postCallback'] + def restoreWidget(self, event): + self._canvas.itemconfigure('floater', fill = '#A0A0A0') class FloaterGroup(Pmw.MegaToplevel): @@ -568,9 +344,6 @@ if __name__ == '__main__': # Starting value for floater mega1['value'] = 123.456 mega1['text'] = 'Drive delta X' - mega1['min'] = 0.0 - mega1['max'] = 1000.0 - mega1['resolution'] = 1.0 # To change the color of the label: mega1.label['foreground'] = 'Red' # Max change/update, default is 100 diff --git a/direct/src/tkwidgets/Slider.py b/direct/src/tkwidgets/Slider.py new file mode 100644 index 0000000000..eeb3893b69 --- /dev/null +++ b/direct/src/tkwidgets/Slider.py @@ -0,0 +1,270 @@ +""" +Slider Class: Velocity style controller for floating point values with + a label, entry (validated), and min/max slider +""" +from Tkinter import * +from Valuator import * +import Pmw +import Task +import math +import string +import operator +from PandaModules import ClockObject + +SLIDER_FULL = 'full' +SLIDER_MINI = 'mini' + +SLIDER_FULL_WIDTH = 50 +SLIDER_FULL_HEIGHT = 25 + +SLIDER_MINI_WIDTH = 22 +SLIDER_MINI_HEIGHT = 18 + +globalClock = ClockObject.getGlobalClock() + + +class Slider(Valuator): + """ + Valuator widget which includes an min/max slider and an entry for setting + floating point values in a range + """ + def __init__(self, parent = None, **kw): + INITOPT = Pmw.INITOPT + optiondefs = ( + ('style', SLIDER_FULL, INITOPT), + ) + self.defineoptions(kw, optiondefs) + Valuator.__init__(self, parent) + self.initialiseoptions(Slider) + + def createValuator(self): + self._valuator = self.createcomponent( + 'valuator', + (('slider', 'valuator'),), + None, + SliderWidget, + (self.interior(),), + style = self['style'], + command = self.setEntry, + value = self['value']) + self._valuator._canvas.bind('', self.mouseReset) + + def packValuator(self): + if self['style'] == SLIDER_FULL: + if self._label: + self._label.grid(row = 0, col = 0, sticky = EW) + self._entry.grid(row = 0, col = 1, sticky = EW) + self._valuator.grid(row = 1, columnspan = 2, + padx = 2, pady = 2) + self.interior().columnconfigure(0, weight = 1) + else: + if self._label: + self._label.grid(row=0,col=0, sticky = EW) + self._entry.grid(row=0,col=1, sticky = EW) + self._valuator.grid(row=0,col=2, padx = 2, pady = 2) + self.interior().columnconfigure(0, weight = 1) + + +class SliderWidget(Pmw.MegaWidget): + def __init__(self, parent = None, **kw): + #define the megawidget options + INITOPT = Pmw.INITOPT + optiondefs = ( + # Appearance + ('style', SLIDER_MINI, INITOPT), + ('width', SLIDER_MINI_WIDTH, INITOPT), + ('height', SLIDER_MINI_HEIGHT, INITOPT), + ('relief', SUNKEN, self.setRelief), + ('borderwidth', 2, self.setBorderwidth), + ('background', 'white', self.setBackground), + # Behavior + # Initial value of slider, use self.set to change value + ('value', 0.0, INITOPT), + ('numDigits', 2, self.setNumDigits), + # Command to execute on slider updates + ('command', None, None), + # Extra data to be passed to command function + ('commandData', [], None), + # Callback's to execute during mouse interaction + ('preCallback', None, None), + ('postCallback', None, None), + # Extra data to be passed to callback function, needs to be a list + ('callbackData', [], None), + ) + self.defineoptions(kw, optiondefs) + + # Initialize the superclass + Pmw.MegaWidget.__init__(self, parent) + + # Set up some local and instance variables + # Create the components + interior = self.interior() + + # Current value + self.value = self['value'] + + # Base slider size on style, if size not specified, + if not self['width']: + if self['style'] == SLIDER_FULL_SIZE: + width = SLIDER_FULL_WIDTH + else: + width = SLIDER_MINI_WIDTH + else: + width = self['width'] + + if not self['height']: + if self['style'] == SLIDER_FULL_SIZE: + height = SLIDER_FULL_HEIGHT + else: + height = SLIDER_MINI_HEIGHT + else: + height = self['height'] + + halfWidth = width/2.0 + left = -(halfWidth - 2) + right = halfWidth - 2 + halfHeight = height/2.0 + top = -(halfHeight - 2) + bottom = halfHeight - 2 + + print left, right,bottom,top + + # The canvas + self._canvas = self.createcomponent('canvas', (), None, + Canvas, (interior,), + width = width, + height = height, + background = self['background'], + highlightthickness = 0, + scrollregion = (-halfWidth, + -halfHeight, + halfWidth, + halfHeight)) + self._canvas.pack(expand = 1, fill = BOTH) + + self._canvas.create_polygon(left,top, + 0, bottom, + right, top, + fill = '#A0A0A0', + tags = ('slider',)) + + # The indicator + self._canvas.create_line(left, bottom, + right, bottom, + width = 2) + + # Add event bindings + self._canvas.bind('', self.mouseDown) + self._canvas.bind('', self.updateSliderSF) + self._canvas.bind('', self.mouseUp) + self._canvas.bind('', self.highlightWidget) + self._canvas.bind('', self.restoreWidget) + + # Make sure input variables processed + self.initialiseoptions(SliderWidget) + + def set(self, value, fCommand = 1): + """ + self.set(value, fCommand = 1) + Set slider to new value, execute command if fCommand == 1 + """ + # Send command if any + if fCommand and (self['command'] != None): + apply(self['command'], [value] + self['commandData']) + # Record value + self.value = value + + def updateIndicator(self, value): + # Nothing visible to update on this type of widget + pass + + def get(self): + """ + self.get() + Get current slider value + """ + return self.value + + ## Canvas callback functions + # Slider velocity controller + def mouseDown(self,event): + """ Begin mouse interaction """ + # Exectute user redefinable callback function (if any) + if self['preCallback']: + apply(self['preCallback'], self['callbackData']) + self.velocitySF = 0.0 + self.updateTask = taskMgr.add(self.updateSliderTask, + 'updateSlider') + self.updateTask.lastTime = globalClock.getFrameTime() + + def updateSliderTask(self, state): + """ + Update sliderWidget value based on current scaleFactor + Adjust for time to compensate for fluctuating frame rates + """ + currT = globalClock.getFrameTime() + dt = currT - state.lastTime + self.set(self.value + self.velocitySF * dt) + state.lastTime = currT + return Task.cont + + def updateSliderSF(self, event): + """ + Update velocity scale factor based of mouse distance from origin + """ + x = self._canvas.canvasx(event.x) + y = self._canvas.canvasy(event.y) + offset = max(0, abs(x) - Valuator.deadband) + if offset == 0: + return 0 + sf = math.pow(Valuator.sfBase, + self.minExp + offset/Valuator.sfDist) + if x > 0: + self.velocitySF = sf + else: + self.velocitySF = -sf + + def mouseUp(self, event): + taskMgr.remove(self.updateTask) + self.velocitySF = 0.0 + # Execute user redefinable callback function (if any) + if self['postCallback']: + apply(self['postCallback'], self['callbackData']) + + def setNumDigits(self): + """ + Adjust minimum exponent to use in velocity task based + upon the number of digits to be displayed in the result + """ + self.minExp = math.floor(-self['numDigits']/ + math.log10(Valuator.sfBase)) + + # Methods to modify slider characteristics + def setRelief(self): + self.interior()['relief'] = self['relief'] + + def setBorderwidth(self): + self.interior()['borderwidth'] = self['borderwidth'] + + def setBackground(self): + self._canvas['background'] = self['background'] + + def highlightWidget(self, event): + self._canvas.itemconfigure('slider', fill = 'black') + + def restoreWidget(self, event): + self._canvas.itemconfigure('slider', fill = '#A0A0A0') + +if __name__ == '__main__': + tl = Toplevel() + d = Slider(tl) + d2 = Slider(tl, slider_numSegments = 12, max = 360, + slider_fRollover = 0, value = 180) + d3 = Slider(tl, slider_numSegments = 12, max = 90, min = -90, + slider_fRollover = 0) + d4 = Slider(tl, slider_numSegments = 16, max = 256, + slider_fRollover = 0) + d.pack(expand = 1, fill = X) + d2.pack(expand = 1, fill = X) + d3.pack(expand = 1, fill = X) + d4.pack(expand = 1, fill = X) diff --git a/direct/src/tkwidgets/Valuator.py b/direct/src/tkwidgets/Valuator.py new file mode 100644 index 0000000000..2b369e44d5 --- /dev/null +++ b/direct/src/tkwidgets/Valuator.py @@ -0,0 +1,536 @@ +from PandaObject import * +from Tkinter import * +import Pmw +import WidgetPropertiesDialog +import string + +class Valuator(Pmw.MegaWidget): + sfBase = 3.0 + sfDist = 7 + deadband = 5 + """ Base class for widgets used to interactively adjust numeric values """ + def __init__(self, parent = None, **kw): + #define the megawidget options + INITOPT = Pmw.INITOPT + optiondefs = ( + ('state', NORMAL, self.setState), + # Widget appearance + ('relief', GROOVE, None), + ('borderwidth', 2, None), + ('text', 'Valuator', self.setLabel), + # Initial and reset values + ('value', 0.0, INITOPT), + ('resetValue', 0.0, None), + # Behavior + ('min', None, None), + ('max', None, None), + ('resolution', None, None), + ('numDigits', 2, self.setEntryFormat), + # Enable/disable popup menu + ('fAdjustable', 1, None), + # Actions + ('command', None, None), + ('commandData', [], None), + ('fCommandOnInit', 0, INITOPT), + # Callbacks to execute when updating widget's value + ('preCallback', None, None), + ('postCallback', None, None), + # Extra data to be passed to callback function, needs to be a list + ('callbackData', [], None), + ) + self.defineoptions(kw, optiondefs) + + # Initialize the superclass + Pmw.MegaWidget.__init__(self, parent) + + # Current adjusted (for min/max/resolution) value + self.adjustedValue = 0.0 + + # Create the components + interior = self.interior() + interior.configure(relief = self['relief'], bd = self['borderwidth']) + + # The Valuator + self.createValuator() + # Set valuator callbacks for mouse start/stop + self._valuator['preCallback'] = self._mouseDown + self._valuator['postCallback'] = self._mouseUp + + # The Label + if self['text'] is not None: + self._label = self.createcomponent('label', (), None, + Label, (interior,), + text = self['text'], + font = ('MS Sans Serif',12), + anchor = CENTER) + else: + self._label = None + + # The entry + self._entryVal = StringVar() + self._entry = self.createcomponent('entry', (), None, + Entry, (interior,), + justify = RIGHT, + width = 12, + textvariable = self._entryVal) + self._entry.bind('', self.validateEntryInput) + self._entryBackground = self._entry.cget('background') + + # Pack Valuator Widget + self.packValuator() + + # Set reset value if none specified + if not kw.has_key('resetValue'): + self['resetValue'] = self['value'] + + if self['fAdjustable']: + # The popup menu + self._popupMenu = Menu(interior, tearoff = 0) + self.addValuatorMenuEntries() + self._popupMenu.add_command(label = 'Reset', + command = self.reset) + self._popupMenu.add_command(label = 'Set to Zero', + command = self.zero) + self._popupMenu.add_command( + label = 'Properties...', + command = self._popupPropertiesDialog) + # Add key bindings + if self._label: + self._label.bind( + '', self._popupValuatorMenu) + self._entry.bind( + '', self._popupValuatorMenu) + self._valuator._canvas.bind( + '', self._popupValuatorMenu) + + # A Dictionary of dictionaries for the popup property dialog + self.propertyDict = { + 'state' : + {'widget': self, + 'type': 'string', + 'help': 'Enter state: normal or disabled.' + }, + + 'text' : + {'widget': self, + 'type': 'string', + 'help': 'Enter label text.' + }, + + 'min' : + { 'widget' : self, + 'type': 'real', + 'fNone': 1, + 'help': 'Minimum allowable value. Enter None for no minimum.'}, + 'max' : + { 'widget': self, + 'type': 'real', + 'fNone': 1, + 'help': 'Maximum allowable value. Enter None for no maximum.'}, + 'numDigits' : + {'widget': self, + 'type': 'integer', + 'help': 'Number of digits after decimal point.' + }, + + 'resolution' : + {'widget': self, + 'type': 'real', + 'fNone': 1, + 'help':'Widget resolution. Enter None for no resolution .' + }, + + 'resetValue' : + { 'widget': self, + 'type': 'real', + 'help': 'Enter value to set widget to on reset.'} + } + # Property list defines the display order of the properties + self.propertyList = [ + 'state', 'text', 'min', 'max', 'numDigits', + 'resolution', 'resetValue'] + # Add any valuator specific properties + self.addValuatorPropertiesToDialog() + + # Make sure input variables processed + self.fInit = self['fCommandOnInit'] + self.initialiseoptions(Valuator) + + def set(self, value, fCommand = 1): + """ + Update widget's value by setting valuator, which will in + turn update the entry. fCommand flag (which is passed to the + valuator as commandData, which is then passed in turn to + self.setEntry) controls command execution. + """ + self._valuator['commandData'] = [fCommand] + self._valuator.set(value) + # Restore commandData to 1 so that interaction via valuator widget + # will result in command being executed, otherwise a set with + # commandData == 0 will stick and commands will not be executed + self._valuator['commandData'] = [1] + + def get(self): + """ Return current widget value """ + return self.adjustedValue + + def setEntry(self, value, fCommand = 1): + """ + Update value displayed in entry, fCommand flag controls + command execution + """ + # Clamp value + if self['min'] is not None: + if value < self['min']: + value = self['min'] + if self['max'] is not None: + if value > self['max']: + value = self['max'] + # Round by resolution + if self['resolution'] is not None: + value = round(value / self['resolution']) * self['resolution'] + # Format value and use it to set entry + self._entryVal.set(self.entryFormat % value) + # Update indicator (if any) to reflect new adjusted value + self._valuator.updateIndicator(value) + # Execute command if required + if fCommand and self.fInit and (self['command'] is not None): + apply(self['command'], [value] + self['commandData']) + # Record adjusted value + self.adjustedValue = value + # Once initialization is finished, allow commands to execute + self.fInit = 1 + + def setEntryFormat(self): + """ + Change the number of significant digits in entry + """ + # Create new format string + self.entryFormat = "%." + "%df" % self['numDigits'] + # Update entry to reflect new format + self.setEntry(self.get()) + # Pass info down to valuator to adjust valuator sensitivity + self._valuator['numDigits'] = self['numDigits'] + + def validateEntryInput(self, event): + """ Check validity of entry and if valid pass along to valuator """ + input = self._entryVal.get() + try: + # Reset background + self._entry.configure(background = self._entryBackground) + # Get new value and check validity + newValue = string.atof(input) + # If OK, execute preCallback if one defined + self._preCallback() + # Call set to update valuator + self.set(newValue) + # Execute callback + self._postCallback() + # Update valuator to reflect adjusted value + # Don't execute command + self._valuator.set(self.adjustedValue, 0) + except ValueError: + # Invalid entry, flash background + self._entry.configure(background = 'Pink') + + # Callbacks executed on mouse down/up + def _mouseDown(self): + """ Function to execute at start of mouse interaction """ + # Execute pre interaction callback + self._preCallback() + + def _mouseUp(self): + """ Function to execute at end of mouse interaction """ + # Execute post interaction callback + self._postCallback() + # Update valuator to reflect adjusted value + # Don't execute command + self._valuator.set(self.adjustedValue, 0) + + # Callback functions + def _preCallback(self): + if self['preCallback']: + apply(self['preCallback'], self['callbackData']) + + def _postCallback(self): + # Exectute post callback if one defined + if self['postCallback']: + apply(self['postCallback'], self['callbackData']) + + def setState(self): + """ Enable/disable widget """ + if self['state'] == NORMAL: + self._entry['state'] = NORMAL + self._entry['background'] = self._entryBackground + self._valuator._canvas['state'] = NORMAL + elif self['state'] == DISABLED: + self._entry['background'] = 'grey75' + self._entry['state'] = DISABLED + self._valuator._canvas['state'] = DISABLED + + def setLabel(self): + """ Update label's text """ + if self._label: + self._label['text'] = self['text'] + + def zero(self): + """ + self.zero() + Set valuator to zero + """ + self.set(0.0) + + def reset(self): + """ + self.reset() + Reset valuator to reset value + """ + self.set(self['resetValue']) + + def mouseReset(self,event): + """ + Reset valuator to resetValue + """ + # If not over any canvas item + #if not self._canvas.find_withtag(CURRENT): + self.reset() + + # Popup dialog to adjust widget properties + def _popupValuatorMenu(self,event): + self._popupMenu.post(event.widget.winfo_pointerx(), + event.widget.winfo_pointery()) + + + def _popupPropertiesDialog(self): + WidgetPropertiesDialog.WidgetPropertiesDialog( + self.propertyDict, + propertyList = self.propertyList, + title = 'Widget Properties', + parent = self.interior()) + + def addPropertyToDialog(self, property, pDict): + self.propertyDict[property] = pDict + self.propertyList.append(property) + + # Virtual functions to be redefined by subclass + def createValuator(self): + """ Function used by subclass to create valuator geometry """ + pass + + def packValuator(self): + """ Function used by subclass to pack widget """ + pass + + def addValuatorMenuEntries(self): + """ Function used by subclass to add menu entries to popup menu """ + pass + + def addValuatorPropertiesToDialog(self): + """ Function used by subclass to add properties to property dialog """ + pass + + +FLOATER = 'floater' +DIAL = 'dial' +SLIDER = 'slider' + +class ValuatorGroup(Pmw.MegaWidget): + def __init__(self, parent = None, **kw): + + # Default group size + DEFAULT_DIM = 1 + # Default value depends on *actual* group size, test for user input + DEFAULT_VALUE = [0.0] * kw.get('dim', DEFAULT_DIM) + DEFAULT_LABELS = map(lambda x: 'v[%d]' % x, + range(kw.get('dim', DEFAULT_DIM))) + + #define the megawidget options + INITOPT = Pmw.INITOPT + optiondefs = ( + ('type', FLOATER, INITOPT), + ('dim', DEFAULT_DIM, INITOPT), + ('side', TOP, INITOPT), + # A list of initial values, one for each valuator + ('value', DEFAULT_VALUE, INITOPT), + ('numDigits', 2, self._setNumDigits), + # A tuple of labels, one for each valuator + ('labels', DEFAULT_LABELS, self._updateLabels), + # The command to be executed when one of the valuators is updated + ('command', None, None), + ) + self.defineoptions(kw, optiondefs) + + # Initialize the toplevel widget + Pmw.MegaWidget.__init__(self, parent) + + # Create the components + interior = self.interior() + # Get a copy of the initial value (making sure its a list) + self._value = list(self['value']) + + # Create the valuators + self._valuatorList = [] + for index in range(self['dim']): + # Add a group alias so you can configure the valuators via: + # fg.configure(Valuator_XXX = YYY) + if self['type'] == DIAL: + import Dial + valuatorType = Dial.Dial + else: + import Floater + valuatorType = Floater.Floater + f = self.createcomponent( + 'valuator%d' % index, (), 'valuator', valuatorType, + (interior,), value = self._value[index], + text = self['labels'][index], + command = lambda val, i = index: self._valuatorSetAt(i, val) + ) + f.pack(side = self['side'], expand = 1, fill = X) + self._valuatorList.append(f) + + # Make sure valuators are initialized + self.set(self['value'], fCommand = 0) + + # Make sure input variables processed + self.initialiseoptions(ValuatorGroup) + + # This is the command is used to set the groups value + def set(self, value, fCommand = 1): + for i in range(self['dim']): + self._value[i] = value[i] + # Update valuator, but don't execute its command + self._valuatorList[i].set(value[i], 0) + if fCommand and (self['command'] is not None): + self['command'](self._value) + + def setAt(self, index, value): + # Update valuator and execute its command + self._valuatorList[index].set(value) + + # This is the command used by the valuator + def _valuatorSetAt(self, index, value): + self._value[index] = value + if self['command']: + self['command'](self._value) + + def get(self): + return self._value + + def getAt(self,index): + return self._value[index] + + def _setNumDigits(self): + self['valuator_numDigits'] = self['numDigits'] + self.formatString = '%0.' + '%df' % self['numDigits'] + + def _updateLabels(self): + if self['labels']: + for index in range(self['dim']): + self._valuatorList[index]['text'] = self['labels'][index] + + def __len__(self): + return self['dim'] + + def __repr__(self): + str = '[' + self.formatString % self._value[0] + for val in self._value[1:]: + str += ', ' + self.formatString % val + str += ']' + return str + + + +class ValuatorGroupPanel(Pmw.MegaToplevel): + def __init__(self, parent = None, **kw): + + # Default group size + DEFAULT_DIM = 1 + # Default value depends on *actual* group size, test for user input + DEFAULT_VALUE = [0.0] * kw.get('dim', DEFAULT_DIM) + DEFAULT_LABELS = map(lambda x: 'v[%d]' % x, + range(kw.get('dim', DEFAULT_DIM))) + + #define the megawidget options + INITOPT = Pmw.INITOPT + optiondefs = ( + ('type', FLOATER, INITOPT), + ('dim', DEFAULT_DIM, INITOPT), + ('side', TOP, INITOPT), + ('title', 'Valuator Group', None), + # A list of initial values, one for each floater + ('value', DEFAULT_VALUE, INITOPT), + # A tuple of labels, one for each floater + ('labels', DEFAULT_LABELS, self._updateLabels), + ('numDigits', 2, self._setNumDigits), + # The command to be executed when one of the floaters is updated + ('command', None, self._setCommand), + ) + self.defineoptions(kw, optiondefs) + + # Initialize the toplevel widget + Pmw.MegaToplevel.__init__(self, parent) + + # Create the components + interior = self.interior() + + # The Menu Bar + self.balloon = Pmw.Balloon() + menubar = self.createcomponent('menubar',(), None, + Pmw.MenuBar, (interior,), + balloon = self.balloon) + menubar.pack(fill=X) + + # ValuatorGroup Menu + menubar.addmenu('Valuator Group', 'Valuator Group Operations') + menubar.addmenuitem( + 'Valuator Group', 'command', 'Reset the Valuator Group panel', + label = 'Reset', + command = lambda s = self: s.reset()) + menubar.addmenuitem( + 'Valuator Group', 'command', 'Dismiss Valuator Group panel', + label = 'Dismiss', command = self.withdraw) + + menubar.addmenu('Help', 'Valuator Group Help Operations') + self.toggleBalloonVar = IntVar() + self.toggleBalloonVar.set(0) + menubar.addmenuitem('Help', 'checkbutton', + 'Toggle balloon help', + label = 'Balloon Help', + variable = self.toggleBalloonVar, + command = self.toggleBalloon) + + # Create the valuator group + self.valuatorGroup = self.createcomponent( + 'valuatorGroup', + (('valuator', 'valuatorGroup_valuator'),), + None, ValuatorGroup, + (interior,), + type = self['type'], + dim = self['dim'], + value = self['value'], + labels = self['labels'], + command = self['command']) + self.valuatorGroup.pack(expand = 1, fill = X) + + # Make sure input variables processed + self.initialiseoptions(ValuatorGroupPanel) + + def toggleBalloon(self): + if self.toggleBalloonVar.get(): + self.balloon.configure(state = 'balloon') + else: + self.balloon.configure(state = 'none') + + def _updateLabels(self): + self.valuatorGroup['labels'] = self['labels'] + + def _setNumDigits(self): + self.valuatorGroup['numDigits'] = self['numDigits'] + + def _setCommand(self): + self.valuatorGroup['command'] = self['command'] + + def reset(self): + self.set(self['value']) + +Pmw.forwardmethods(ValuatorGroupPanel, ValuatorGroup, 'valuatorGroup') diff --git a/direct/src/tkwidgets/VectorWidgets.py b/direct/src/tkwidgets/VectorWidgets.py index 886ffd2c16..173b6d58a0 100644 --- a/direct/src/tkwidgets/VectorWidgets.py +++ b/direct/src/tkwidgets/VectorWidgets.py @@ -1,5 +1,6 @@ from Tkinter import * import Pmw +import Valuator import Floater import EntryScale import string @@ -232,10 +233,8 @@ class VectorEntry(Pmw.MegaWidget): self.configure(Entry_entry_state = 'disabled') self.configure(Entry_entry_background = '#C0C0C0') # Disable floater Group scale - """ self.component('fGroup').configure( - Valuator_scale_state = 'disabled') - """ + Valuator_state = 'disabled') # Disable floater group entry self.component('fGroup').configure( Valuator_entry_state = 'disabled') @@ -246,10 +245,8 @@ class VectorEntry(Pmw.MegaWidget): self.configure(Entry_entry_state = 'normal') self.configure(Entry_entry_background = self.entryBackground) # Disable floater Group scale - """ self.component('fGroup').configure( - Valuator_scale_state = 'normal') - """ + Valuator_state = 'normal') # Disable floater group entry self.component('fGroup').configure( Valuator_entry_state = 'normal')