|
|
@@ -1,87 +1,93 @@
|
|
|
-title = 'DIRECT Floater megawidget'
|
|
|
+"""
|
|
|
+Floater Class: Velocity style controller for floating point values with
|
|
|
+ a label, entry (validated), and scale
|
|
|
+"""
|
|
|
|
|
|
-import string
|
|
|
-import Tkinter
|
|
|
+from Tkinter import *
|
|
|
import Pmw
|
|
|
-
|
|
|
-OK = 1
|
|
|
-ERROR = 0
|
|
|
+import string
|
|
|
|
|
|
class Floater(Pmw.MegaWidget):
|
|
|
- """ Megawidget containing a label, an entry, and a scale.
|
|
|
- Used as a velocity style controller for floating point values
|
|
|
- """
|
|
|
+ "Velocity style floating point controller"
|
|
|
|
|
|
def __init__(self, parent = None, **kw):
|
|
|
|
|
|
# Define the megawidget options.
|
|
|
optiondefs = (
|
|
|
- ('command', None, None),
|
|
|
- ('value', 0.0, self._updateValue),
|
|
|
- ('text', '', self._updateLabelText),
|
|
|
- ('min', None, None),
|
|
|
- ('max', None, None),
|
|
|
- ('resolution', None, None),
|
|
|
- ('maxVelocity', 100.0, None),
|
|
|
- ('errorbackground', 'pink', None),
|
|
|
- ('significantDigits', 2, self._setSigDigits),
|
|
|
+ ('initialValue', 0.0, Pmw.INITOPT),
|
|
|
+ ('resolution', None, None),
|
|
|
+ ('command', None, None),
|
|
|
+ ('maxVelocity', 100.0, None),
|
|
|
+ ('min', None, self._updateValidate),
|
|
|
+ ('max', None, self._updateValidate),
|
|
|
+ ('text', 'Floater', self._updateLabelText),
|
|
|
+ ('significantDigits', 2, self._setSigDigits),
|
|
|
)
|
|
|
self.defineoptions(kw, optiondefs)
|
|
|
|
|
|
- # Initialise base class (after defining options).
|
|
|
+ # Initialise superclass
|
|
|
Pmw.MegaWidget.__init__(self, parent)
|
|
|
|
|
|
- # Initialize some variables
|
|
|
- self.value = 0.0
|
|
|
+ # Initialize some class variables
|
|
|
+ self.value = self['initialValue']
|
|
|
self.velocity = 0.0
|
|
|
- self.entryValue = Tkinter.StringVar()
|
|
|
self.entryFormat = '%.2f'
|
|
|
- self.normalBackground = None
|
|
|
|
|
|
# Create the components.
|
|
|
+
|
|
|
+ # Setup up container
|
|
|
interior = self.interior()
|
|
|
- interior['relief'] = 'groove'
|
|
|
- interior['borderwidth'] = 2
|
|
|
+ interior.configure(relief = GROOVE, borderwidth = 2)
|
|
|
|
|
|
- self.infoFrame = self.createcomponent('frame',
|
|
|
- (), None,
|
|
|
- Tkinter.Frame, interior)
|
|
|
- self.infoFrame.pack(expand = 1, fill = 'both')
|
|
|
-
|
|
|
+ # Create a label and an entry
|
|
|
+ self.labelFrame = self.createcomponent('frame', (), None,
|
|
|
+ Frame, interior)
|
|
|
+ # Create an entry field to display and validate the floater's value
|
|
|
+ self.entryValue = StringVar()
|
|
|
+ self.entryValue.set(self['initialValue'])
|
|
|
+ self.entry = self.createcomponent('entryField',
|
|
|
+ # Access floaters entry using "entry"
|
|
|
+ (('entry', 'entryField_entry'),),
|
|
|
+ None,
|
|
|
+ Pmw.EntryField, self.labelFrame,
|
|
|
+ entry_width = 10,
|
|
|
+ validate = { 'validator' : 'real',
|
|
|
+ 'min' : self['min'],
|
|
|
+ 'max' : self['max'],
|
|
|
+ 'minstrict' : 0,
|
|
|
+ 'maxstrict' : 0},
|
|
|
+ entry_justify = 'right',
|
|
|
+ entry_textvar = self.entryValue,
|
|
|
+ command = self._entryCommand)
|
|
|
+ self.entry.pack(side='left',padx = 4)
|
|
|
+
|
|
|
# Create the Floater's label
|
|
|
- self.label = self.createcomponent('label',
|
|
|
- (), None,
|
|
|
- Tkinter.Label, self.infoFrame,
|
|
|
+ self.label = self.createcomponent('label', (), None,
|
|
|
+ Label, self.labelFrame,
|
|
|
text = self['text'],
|
|
|
- anchor = 'center',
|
|
|
width = 12,
|
|
|
+ anchor = 'center',
|
|
|
font = "Arial 12 bold")
|
|
|
self.label.pack(side='left', expand = 1, fill = 'x')
|
|
|
|
|
|
- # Create an entry field to display and validate the floater's value
|
|
|
- self.entry = self.createcomponent('entry',
|
|
|
- (), None,
|
|
|
- Tkinter.Entry, self.infoFrame,
|
|
|
- width = 10,
|
|
|
- justify = 'right',
|
|
|
- textvar = self.entryValue)
|
|
|
- self.entry.pack(side='left',padx = 4)
|
|
|
- self.entry.bind('<Return>', self._entryCommand)
|
|
|
-
|
|
|
+ # Now pack the frame
|
|
|
+ self.labelFrame.pack(expand = 1, fill = 'both')
|
|
|
+
|
|
|
# Create the scale component.
|
|
|
- self.scale = self.createcomponent('scale',
|
|
|
- (), None,
|
|
|
- Tkinter.Scale, interior,
|
|
|
+ self.scale = self.createcomponent('scale', (), None,
|
|
|
+ Scale, interior,
|
|
|
command = self._scaleToVelocity,
|
|
|
orient = 'horizontal',
|
|
|
length = 150,
|
|
|
from_ = -1.0,
|
|
|
to = 1.0,
|
|
|
- resolution = 0.01,
|
|
|
+ resolution = 0.001,
|
|
|
showvalue = 0)
|
|
|
self.scale.pack(expand = 1, fill = 'x')
|
|
|
+ # Set scale to the middle of its range
|
|
|
self.scale.set(0.0)
|
|
|
- # When interacting with mouse:
|
|
|
+
|
|
|
+ # Add scale bindings: When interacting with mouse:
|
|
|
self.scale.bind('<Button-1>', self._startFloaterTask)
|
|
|
self.scale.bind('<ButtonRelease-1>', self._floaterReset)
|
|
|
# In case you wish to interact using keys
|
|
|
@@ -90,17 +96,31 @@ class Floater(Pmw.MegaWidget):
|
|
|
self.scale.bind('<KeyPress-Left>', self._floaterKeyCommand)
|
|
|
self.scale.bind('<KeyRelease-Left>', self._floaterReset)
|
|
|
|
|
|
- # Check keywords and initialise options.
|
|
|
+ # Check keywords and initialise options based on input values.
|
|
|
self.initialiseoptions(Floater)
|
|
|
|
|
|
- # Now that the widgets have been created, update significant digits
|
|
|
- self._setSigDigits()
|
|
|
- self._updateValue()
|
|
|
+ def label(self):
|
|
|
+ return self.label
|
|
|
+ def scale(self):
|
|
|
+ return self.scale
|
|
|
+ def entry(self):
|
|
|
+ return self.entry
|
|
|
+
|
|
|
+ def _updateLabelText(self):
|
|
|
+ self.label['text'] = self['text']
|
|
|
+
|
|
|
+ def _updateValidate(self):
|
|
|
+ self.configure(entryField_validate = {
|
|
|
+ 'validator' : 'real',
|
|
|
+ 'min' : self['min'],
|
|
|
+ 'max' : self['max'],
|
|
|
+ 'minstrict' : 0,
|
|
|
+ 'maxstrict' : 0})
|
|
|
|
|
|
def _scaleToVelocity(self, strVal):
|
|
|
# convert scale val to float
|
|
|
val = string.atof(strVal)
|
|
|
- # retain sign of velocity by only calling abs once
|
|
|
+ # Square val, but retain sign of velocity by only calling abs once
|
|
|
self.velocity = self['maxVelocity'] * val * abs(val)
|
|
|
|
|
|
def _startFloaterTask(self,event):
|
|
|
@@ -109,7 +129,7 @@ class Floater(Pmw.MegaWidget):
|
|
|
|
|
|
def _floaterTask(self):
|
|
|
if self.velocity != 0.0:
|
|
|
- self.setValue( self.value + self.velocity )
|
|
|
+ self.set( self.value + self.velocity )
|
|
|
if self._fFloaterTask:
|
|
|
self.after(50, self._floaterTask)
|
|
|
|
|
|
@@ -120,90 +140,205 @@ class Floater(Pmw.MegaWidget):
|
|
|
|
|
|
def _floaterKeyCommand(self, event):
|
|
|
if self.velocity != 0.0:
|
|
|
- self.setValue( self.value + self.velocity )
|
|
|
+ self.set( self.value + self.velocity )
|
|
|
|
|
|
def _entryCommand(self, event = None):
|
|
|
try:
|
|
|
val = string.atof( self.entryValue.get() )
|
|
|
- self.setValue( val )
|
|
|
+ self.set( val )
|
|
|
except ValueError:
|
|
|
- # invalid entry, ring bell set background to warning color
|
|
|
- self.entry.bell()
|
|
|
- if self.normalBackground is None:
|
|
|
- self.normalBackground = self.entry.cget('background')
|
|
|
- self.entry.configure( background = self['errorbackground'] )
|
|
|
-
|
|
|
- def _updateValue(self):
|
|
|
- self.setValue(self['value'])
|
|
|
-
|
|
|
- def setValue(self, newVal):
|
|
|
+ pass
|
|
|
+
|
|
|
+ def _setSigDigits(self):
|
|
|
+ sd = self['significantDigits']
|
|
|
+ self.entryFormat = '%.' + '%d' % sd + 'f'
|
|
|
+ # And reset value to reflect change
|
|
|
+ self.entryValue.set( self.entryFormat % self.value )
|
|
|
+
|
|
|
+ def get(self):
|
|
|
+ return self.value
|
|
|
+
|
|
|
+ def set(self, newVal, fCommand = 1):
|
|
|
+ # Clamp value
|
|
|
if self['min'] is not None:
|
|
|
if newVal < self['min']:
|
|
|
newVal = self['min']
|
|
|
if self['max'] is not None:
|
|
|
if newVal > self['max']:
|
|
|
newVal = self['max']
|
|
|
+ # Round by resolution
|
|
|
if self['resolution'] is not None:
|
|
|
newVal = round(newVal / self['resolution']) * self['resolution']
|
|
|
+
|
|
|
# Update floater's value
|
|
|
self.value = newVal
|
|
|
# Update entry to reflect formatted value
|
|
|
self.entryValue.set( self.entryFormat % self.value )
|
|
|
- # Reset background
|
|
|
- if self.normalBackground is not None:
|
|
|
- self.entry.configure(background = self.normalBackground)
|
|
|
- self.normalBackground = None
|
|
|
+ self.entry.checkentry()
|
|
|
+
|
|
|
# execute command
|
|
|
- command = self['command']
|
|
|
- if command is not None:
|
|
|
- command( newVal )
|
|
|
+ if fCommand & (self['command'] is not None):
|
|
|
+ self['command']( newVal )
|
|
|
|
|
|
- def _setSigDigits(self):
|
|
|
- sd = self['significantDigits']
|
|
|
- self.entryFormat = '%.' + '%d' % sd + 'f'
|
|
|
- self.scale['resolution'] = 10 ** (-1.0 * sd)
|
|
|
- # And reset value to reflect change
|
|
|
- self.entryValue.set( self.entryFormat % self.value )
|
|
|
|
|
|
- def _updateLabelText(self):
|
|
|
- self.label['text'] = self['text']
|
|
|
+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 = map(lambda x: 'v[%d]' % x,
|
|
|
+ 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
|
|
|
+ ('initialValue', 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['initialValue'])
|
|
|
+
|
|
|
+ # 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(Floater_XXX = YYY)
|
|
|
+ f = self.createcomponent(
|
|
|
+ 'floater%d' % index, (), 'Floater', Floater,
|
|
|
+ (interior,), initialValue = 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['initialValue'])
|
|
|
|
|
|
-Pmw.forwardmethods(Floater, Tkinter.Scale, 'scale')
|
|
|
+ # 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 & (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['initialValue'])
|
|
|
+
|
|
|
+
|
|
|
|
|
|
## SAMPLE CODE
|
|
|
if __name__ == '__main__':
|
|
|
# Initialise Tkinter and Pmw.
|
|
|
- root = Pmw.initialise()
|
|
|
+ root = Toplevel()
|
|
|
root.title('Pmw Floater demonstration')
|
|
|
|
|
|
# Dummy command
|
|
|
def printVal(val):
|
|
|
- print `val`
|
|
|
+ print val
|
|
|
|
|
|
# Create and pack a Floater megawidget.
|
|
|
- mega1 = Floater(root)
|
|
|
+ mega1 = Floater(root, command = printVal)
|
|
|
mega1.pack(side = 'left', expand = 1, fill = 'x')
|
|
|
|
|
|
+ """
|
|
|
# These are things you can set/configure
|
|
|
- mega1['command'] = printVal
|
|
|
+ # Starting value for floater
|
|
|
+ mega1['initialValue'] = 123.456
|
|
|
mega1['text'] = 'Drive delta X'
|
|
|
mega1['min'] = 0.0
|
|
|
- #mega1['max'] = 1000.0
|
|
|
+ mega1['max'] = 1000.0
|
|
|
mega1['resolution'] = 1.0
|
|
|
-
|
|
|
- # UNCOMMENT THESE TO CUSTOMIZE THE FLOATER
|
|
|
# To change the color of the label:
|
|
|
- # mega1.label['foreground'] = 'Red'
|
|
|
-
|
|
|
+ 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['significantDigits'] = 5
|
|
|
+ """
|
|
|
|
|
|
- # Starting value for floater
|
|
|
- # mega1['value'] = 123.4557
|
|
|
-
|
|
|
- # Let's go.
|
|
|
+ # To create a floater group to set an RGBA value:
|
|
|
+ group1 = FloaterGroup(root, dim = 4,
|
|
|
+ title = 'Simple RGBA Panel',
|
|
|
+ labels = ('R', 'G', 'B', 'A'),
|
|
|
+ Floater_min = 0.0,
|
|
|
+ Floater_max = 255.0,
|
|
|
+ Floater_resolution = 1.0,
|
|
|
+ command = printVal)
|
|
|
+
|
|
|
+ # Uncomment this if you aren't running in IDLE
|
|
|
#root.mainloop()
|