*** empty log message ***
This commit is contained in:
parent
7cb2d0d19f
commit
3f4ad61a6d
|
|
@ -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']:
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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')
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
Loading…
Reference in New Issue