| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- """
- Floater Class: Velocity style controller for floating point values with
- a label, entry (validated), and scale
- """
- __all__ = ['Floater', 'FloaterWidget', 'FloaterGroup']
- from direct.showbase.TkGlobal import *
- from Tkinter import *
- from Valuator import Valuator, VALUATOR_MINI, VALUATOR_FULL
- from direct.task import Task
- import math, sys, string, Pmw
- FLOATER_WIDTH = 22
- FLOATER_HEIGHT = 18
- class Floater(Valuator):
- def __init__(self, parent = None, **kw):
- INITOPT = Pmw.INITOPT
- optiondefs = (
- ('style', VALUATOR_MINI, INITOPT),
- )
- 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._widget.bind('<Double-ButtonPress-1>', self.mouseReset)
- def packValuator(self):
- # Position components
- if self._label:
- self._label.grid(row=0, column=0, sticky = EW)
- self._entry.grid(row=0, column=1, sticky = EW)
- self._valuator.grid(row=0, column=2, padx = 2, pady = 2)
- self.interior().columnconfigure(0, weight = 1)
- class FloaterWidget(Pmw.MegaWidget):
- def __init__(self, parent = None, **kw):
- #define the megawidget options
- INITOPT = Pmw.INITOPT
- optiondefs = (
- # Appearance
- ('width', FLOATER_WIDTH, INITOPT),
- ('height', FLOATER_HEIGHT, INITOPT),
- ('relief', RAISED, self.setRelief),
- ('borderwidth', 2, self.setBorderwidth),
- ('background', 'grey75', self.setBackground),
- # Behavior
- # Initial value of floater, use self.set to change value
- ('value', 0.0, INITOPT),
- ('numDigits', 2, self.setNumDigits),
- # 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),
- # 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']
- # The canvas
- width = self['width']
- height = self['height']
- self._widget = self.createcomponent('canvas', (), None,
- Canvas, (interior,),
- width = width,
- height = height,
- background = self['background'],
- highlightthickness = 0,
- scrollregion = (-width/2.0,
- -height/2.0,
- width/2.0,
- height/2.0))
- self._widget.pack(expand = 1, fill = BOTH)
- # The floater icon
- self._widget.create_polygon(-width/2.0, 0, -2.0, -height/2.0,
- -2.0, height/2.0,
- fill = 'grey50',
- tags = ('floater',))
- self._widget.create_polygon(width/2.0, 0, 2.0, height/2.0,
- 2.0, -height/2.0,
- fill = 'grey50',
- tags = ('floater',))
- # Add event bindings
- self._widget.bind('<ButtonPress-1>', self.mouseDown)
- self._widget.bind('<B1-Motion>', self.updateFloaterSF)
- self._widget.bind('<ButtonRelease-1>', self.mouseUp)
- self._widget.bind('<Enter>', self.highlightWidget)
- self._widget.bind('<Leave>', self.restoreWidget)
- # Make sure input variables processed
- self.initialiseoptions(FloaterWidget)
- def set(self, value, fCommand = 1):
- """
- self.set(value, fCommand = 1)
- Set floater 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 floater value
- """
- return self.value
- ## Canvas callback functions
- # Floater velocity controller
- def mouseDown(self, event):
- """ Begin mouse interaction """
- # Exectute user redefinable callback function (if any)
- self['relief'] = SUNKEN
- if self['preCallback']:
- apply(self['preCallback'], self['callbackData'])
- self.velocitySF = 0.0
- self.updateTask = taskMgr.add(self.updateFloaterTask,
- 'updateFloater')
- self.updateTask.lastTime = globalClock.getFrameTime()
- 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 updateFloaterSF(self, event):
- """
- Update velocity scale factor based of mouse distance from origin
- """
- x = self._widget.canvasx(event.x)
- y = self._widget.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'])
- self['relief'] = RAISED
- 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):
- self.interior()['relief'] = self['relief']
- def setBorderwidth(self):
- self.interior()['borderwidth'] = self['borderwidth']
- def setBackground(self):
- self._widget['background'] = self['background']
- def highlightWidget(self, event):
- self._widget.itemconfigure('floater', fill = 'black')
- def restoreWidget(self, event):
- self._widget.itemconfigure('floater', fill = 'grey50')
- class FloaterGroup(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 = ['v[%d]' % x for x in range(kw.get('dim', DEFAULT_DIM))]
- #define the megawidget options
- INITOPT = Pmw.INITOPT
- optiondefs = (
- ('dim', DEFAULT_DIM, INITOPT),
- ('side', TOP, INITOPT),
- ('title', 'Floater Group', None),
- # A tuple of initial values, one for each floater
- ('value', DEFAULT_VALUE, INITOPT),
- # The command to be executed any time one of the floaters is updated
- ('command', None, None),
- # A tuple of labels, one for each floater
- ('labels', DEFAULT_LABELS, self._updateLabels),
- )
- self.defineoptions(kw, optiondefs)
- # Initialize the toplevel widget
- Pmw.MegaToplevel.__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'])
- # The Menu Bar
- self.balloon = Pmw.Balloon()
- menubar = self.createcomponent('menubar', (), None,
- Pmw.MenuBar, (interior,),
- balloon = self.balloon)
- menubar.pack(fill=X)
- # FloaterGroup Menu
- menubar.addmenu('Floater Group', 'Floater Group Operations')
- menubar.addmenuitem(
- 'Floater Group', 'command', 'Reset the Floater Group panel',
- label = 'Reset',
- command = lambda s = self: s.reset())
- menubar.addmenuitem(
- 'Floater Group', 'command', 'Dismiss Floater Group panel',
- label = 'Dismiss', command = self.withdraw)
- menubar.addmenu('Help', 'Floater 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)
- self.floaterList = []
- for index in range(self['dim']):
- # Add a group alias so you can configure the floaters via:
- # fg.configure(Valuator_XXX = YYY)
- f = self.createcomponent(
- 'floater%d' % index, (), 'Valuator', Floater,
- (interior,), value = self._value[index],
- text = self['labels'][index])
- # Do this separately so command doesn't get executed during construction
- f['command'] = lambda val, s=self, i=index: s._floaterSetAt(i, val)
- f.pack(side = self['side'], expand = 1, fill = X)
- self.floaterList.append(f)
- # Make sure floaters are initialized
- self.set(self['value'])
- # Make sure input variables processed
- self.initialiseoptions(FloaterGroup)
- def _updateLabels(self):
- if self['labels']:
- for index in range(self['dim']):
- self.floaterList[index]['text'] = self['labels'][index]
- def toggleBalloon(self):
- if self.toggleBalloonVar.get():
- self.balloon.configure(state = 'balloon')
- else:
- self.balloon.configure(state = 'none')
- def get(self):
- return self._value
- def getAt(self, index):
- return self._value[index]
- # 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 floater, but don't execute its command
- self.floaterList[i].set(value[i], 0)
- if fCommand and (self['command'] is not None):
- self['command'](self._value)
- def setAt(self, index, value):
- # Update floater and execute its command
- self.floaterList[index].set(value)
- # This is the command used by the floater
- def _floaterSetAt(self, index, value):
- self._value[index] = value
- if self['command']:
- self['command'](self._value)
- def reset(self):
- self.set(self['value'])
- ## SAMPLE CODE
- if __name__ == '__main__':
- # Initialise Tkinter and Pmw.
- root = Toplevel()
- root.title('Pmw Floater demonstration')
- # Dummy command
- def printVal(val):
- print val
- # Create and pack a Floater megawidget.
- mega1 = Floater(root, command = printVal)
- mega1.pack(side = 'left', expand = 1, fill = 'x')
- """
- # These are things you can set/configure
- # Starting value for floater
- mega1['value'] = 123.456
- mega1['text'] = 'Drive delta X'
- # To change the color of the label:
- mega1.label['foreground'] = 'Red'
- # Max change/update, default is 100
- # To have really fine control, for example
- # mega1['maxVelocity'] = 0.1
- # Number of digits to the right of the decimal point, default = 2
- # mega1['numDigits'] = 5
- """
- # To create a floater group to set an RGBA value:
- group1 = FloaterGroup(root, dim = 4,
- title = 'Simple RGBA Panel',
- labels = ('R', 'G', 'B', 'A'),
- Valuator_min = 0.0,
- Valuator_max = 255.0,
- Valuator_resolution = 1.0,
- command = printVal)
- # Uncomment this if you aren't running in IDLE
- #root.mainloop()
|