*** empty log message ***

This commit is contained in:
Mark Mine 2002-02-14 02:46:32 +00:00
parent 7cb2d0d19f
commit 3f4ad61a6d
6 changed files with 1042 additions and 639 deletions

View File

@ -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('<ButtonPress-3>', 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('<Return>', self.validateEntryInput)
self._entry.bind('<ButtonPress-3>', 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('<Double-ButtonPress-1>', 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 <Return> in entry """
if self['preCallback']:
apply(self['preCallback'], self['callbackData'])
def _onReturnRelease(self, *args):
""" User redefinable callback executed on <Return> 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', '<ButtonPress-1>', self.mouseDown)
self._canvas.tag_bind('dial', '<B1-Motion>', self.mouseMotion)
@ -356,12 +244,10 @@ class DialWidget(Pmw.MegaWidget):
self.shiftMouseMotion)
self._canvas.tag_bind('dial', '<ButtonRelease-1>', self.mouseUp)
self._canvas.tag_bind('knob', '<ButtonPress-1>', self.knobMouseDown)
self._canvas.tag_bind('knob', '<B1-Motion>', self.knobMouseMotion)
self._canvas.tag_bind('knob', '<B1-Motion>', self.updateDialSF)
self._canvas.tag_bind('knob', '<ButtonRelease-1>', self.knobMouseUp)
self._canvas.tag_bind('knob', '<Enter>', self.highlightKnob)
self._canvas.tag_bind('knob', '<Leave>', self.restoreKnob)
self._canvas.bind('<Double-ButtonPress-1>', self.mouseReset)
self._canvas.bind('<ButtonPress-3>', 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']:

View File

@ -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),

View File

@ -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('<Double-ButtonPress-1>', 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('<ButtonPress-1>', self.mouseDown)
self._canvas.bind('<B1-Motion>', self.mouseMotion)
self._canvas.bind('<B1-Motion>', self.updateFloaterSF)
self._canvas.bind('<ButtonRelease-1>', self.mouseUp)
self._canvas.bind('<ButtonPress-3>', self.popupFloaterMenu)
self._canvas.bind('<Double-ButtonPress-1>', self.mouseReset)
self._canvas.bind('<ButtonPress-3>', self.popupFloaterMenu)
self._canvas.bind('<Enter>', self.highlightIcon)
self._canvas.bind('<Leave>', self.restoreIcon)
self._canvas.tag_bind('floater', '<ButtonPress-1>', self.mouseDown)
self._canvas.tag_bind('floater', '<B1-Motion>', self.mouseMotion)
self._canvas.tag_bind('floater', '<ButtonRelease-1>', self.mouseUp)
self._canvas.bind('<Enter>', self.highlightWidget)
self._canvas.bind('<Leave>', 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('<ButtonPress-3>', 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('<Return>', self.validateEntryInput)
self._entry.bind('<ButtonPress-3>', 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 <Return> in entry """
if self['preCallback']:
apply(self['preCallback'], self['callbackData'])
def _onReturnRelease(self, *args):
""" User redefinable callback executed on <Return> 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

View File

@ -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('<Double-ButtonPress-1>', 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('<ButtonPress-1>', self.mouseDown)
self._canvas.bind('<B1-Motion>', self.updateSliderSF)
self._canvas.bind('<ButtonRelease-1>', self.mouseUp)
self._canvas.bind('<Enter>', self.highlightWidget)
self._canvas.bind('<Leave>', 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)

View File

@ -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('<Return>', 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(
'<ButtonPress-3>', self._popupValuatorMenu)
self._entry.bind(
'<ButtonPress-3>', self._popupValuatorMenu)
self._valuator._canvas.bind(
'<ButtonPress-3>', 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')

View File

@ -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')