*** empty log message ***

This commit is contained in:
Mark Mine 2002-02-12 22:03:30 +00:00
parent 0f4d293597
commit 235cd02cb7
11 changed files with 683 additions and 443 deletions

View File

@ -4435,7 +4435,7 @@ class LevelEditorPanel(Pmw.MegaToplevel):
self.fUpdateSelected = 1
# Handle to the toplevels hull
hull = self.component('hull')
hull.geometry('400x550')
hull.geometry('400x515')
balloon = self.balloon = Pmw.Balloon(hull)
# Start with balloon help disabled
@ -4737,63 +4737,73 @@ class LevelEditorPanel(Pmw.MegaToplevel):
self.addKernFloater = Floater.Floater(
gridFrame,
text='Kern',
maxVelocity=1.0,
#maxVelocity=1.0,
command=self.setSignBaselineKern)
self.addKernFloater.grid(row=4, column=0, rowspan=2, columnspan=3)
self.addKernFloater.grid(row=4, column=0, rowspan=2, columnspan=3,
sticky = EW)
self.addWiggleFloater = Floater.Floater(
gridFrame,
text='Wiggle',
maxVelocity=10.0,
#maxVelocity=10.0,
command=self.setSignBaselineWiggle)
self.addWiggleFloater.grid(row=6, column=0, rowspan=2, columnspan=3)
self.addWiggleFloater.grid(row=6, column=0, rowspan=2, columnspan=3,
sticky = EW)
self.addStumbleFloater = Floater.Floater(
gridFrame,
text='Stumble',
maxVelocity=1.0,
#maxVelocity=1.0,
command=self.setSignBaselineStumble)
self.addStumbleFloater.grid(row=8, column=0, rowspan=2, columnspan=3)
self.addStumbleFloater.grid(row=8, column=0, rowspan=2, columnspan=3,
sticky = EW)
self.addStompFloater = Floater.Floater(
gridFrame,
text='Stomp',
maxVelocity=1.0,
#maxVelocity=1.0,
command=self.setSignBaselineStomp)
self.addStompFloater.grid(row=10, column=0, rowspan=2, columnspan=3)
self.addStompFloater.grid(row=10, column=0, rowspan=2, columnspan=3,
sticky = EW)
self.addCurveFloater = Floater.Floater(
gridFrame,
text='Curve',
maxVelocity = 1.0,
#maxVelocity = 1.0,
command=self.setSignBaselineCurve)
self.addCurveFloater.grid(row=12, column=0, rowspan=2, columnspan=3)
self.addCurveFloater.grid(row=12, column=0, rowspan=2, columnspan=3,
sticky = EW)
self.addXFloater = Floater.Floater(
gridFrame,
text='X',
maxVelocity=1.0,
#maxVelocity=1.0,
command=self.setDNATargetX)
self.addXFloater.grid(row=4, column=3, rowspan=2, columnspan=3)
self.addXFloater.grid(row=4, column=3, rowspan=2, columnspan=3,
sticky = EW)
self.addZFloater = Floater.Floater(
gridFrame,
text='Z',
maxVelocity=1.0,
#maxVelocity=1.0,
command=self.setDNATargetZ)
self.addZFloater.grid(row=6, column=3, rowspan=2, columnspan=3)
self.addZFloater.grid(row=6, column=3, rowspan=2, columnspan=3,
sticky = EW)
self.addScaleXFloater = Floater.Floater(
gridFrame,
text='Scale X',
maxVelocity=1.0,
#maxVelocity=1.0,
command=self.setDNATargetScaleX)
self.addScaleXFloater.grid(row=8, column=3, rowspan=2, columnspan=3)
self.addScaleXFloater.grid(row=8, column=3, rowspan=2, columnspan=3,
sticky = EW)
self.addScaleZFloater = Floater.Floater(
gridFrame,
text='Scale Z',
maxVelocity=1.0,
#maxVelocity=1.0,
command=self.setDNATargetScaleZ)
self.addScaleZFloater.grid(row=10, column=3, rowspan=2, columnspan=3)
self.addScaleZFloater.grid(row=10, column=3, rowspan=2, columnspan=3,
sticky = EW)
self.addRollFloater = Floater.Floater(
gridFrame,
text='Roll',
maxVelocity=10.0,
#maxVelocity=10.0,
command=self.setDNATargetRoll)
self.addRollFloater.grid(row=12, column=3, rowspan=2, columnspan=3)
self.addRollFloater.grid(row=12, column=3, rowspan=2, columnspan=3,
sticky = EW)
gridFrame.pack(fill=BOTH)

View File

@ -369,7 +369,7 @@ class DirectSessionPanel(AppShell):
self.pConstantAttenuation = EntryScale.EntryScale(
pointPage,
text = 'Constant Attenuation',
min = 0.0, max = 1.0, initialValue = 1.0)
min = 0.0, max = 1.0, value = 1.0)
self.pConstantAttenuation['command'] = self.setConstantAttenuation
self.pConstantAttenuation.pack(fill = X, expand = 0)
self.bind(self.pConstantAttenuation,
@ -378,7 +378,7 @@ class DirectSessionPanel(AppShell):
self.pLinearAttenuation = EntryScale.EntryScale(
pointPage,
text = 'Linear Attenuation',
min = 0.0, max = 1.0, initialValue = 0.0)
min = 0.0, max = 1.0, value = 0.0)
self.pLinearAttenuation['command'] = self.setLinearAttenuation
self.pLinearAttenuation.pack(fill = X, expand = 0)
self.bind(self.pLinearAttenuation,
@ -387,7 +387,7 @@ class DirectSessionPanel(AppShell):
self.pQuadraticAttenuation = EntryScale.EntryScale(
pointPage,
text = 'Quadratic Attenuation',
min = 0.0, max = 1.0, initialValue = 0.0)
min = 0.0, max = 1.0, value = 0.0)
self.pQuadraticAttenuation['command'] = self.setQuadraticAttenuation
self.pQuadraticAttenuation.pack(fill = X, expand = 0)
self.bind(self.pQuadraticAttenuation,
@ -404,7 +404,7 @@ class DirectSessionPanel(AppShell):
self.sConstantAttenuation = EntryScale.EntryScale(
spotPage,
text = 'Constant Attenuation',
min = 0.0, max = 1.0, initialValue = 1.0)
min = 0.0, max = 1.0, value = 1.0)
self.sConstantAttenuation['command'] = self.setConstantAttenuation
self.sConstantAttenuation.pack(fill = X, expand = 0)
self.bind(self.sConstantAttenuation,
@ -413,7 +413,7 @@ class DirectSessionPanel(AppShell):
self.sLinearAttenuation = EntryScale.EntryScale(
spotPage,
text = 'Linear Attenuation',
min = 0.0, max = 1.0, initialValue = 0.0)
min = 0.0, max = 1.0, value = 0.0)
self.sLinearAttenuation['command'] = self.setLinearAttenuation
self.sLinearAttenuation.pack(fill = X, expand = 0)
self.bind(self.sLinearAttenuation,
@ -422,7 +422,7 @@ class DirectSessionPanel(AppShell):
self.sQuadraticAttenuation = EntryScale.EntryScale(
spotPage,
text = 'Quadratic Attenuation',
min = 0.0, max = 1.0, initialValue = 0.0)
min = 0.0, max = 1.0, value = 0.0)
self.sQuadraticAttenuation['command'] = self.setQuadraticAttenuation
self.sQuadraticAttenuation.pack(fill = X, expand = 0)
self.bind(self.sQuadraticAttenuation,
@ -431,7 +431,7 @@ class DirectSessionPanel(AppShell):
self.sExponent = EntryScale.EntryScale(
spotPage,
text = 'Exponent',
min = 0.0, max = 1.0, initialValue = 0.0)
min = 0.0, max = 1.0, value = 0.0)
self.sExponent['command'] = self.setExponent
self.sExponent.pack(fill = X, expand = 0)
self.bind(self.sExponent,
@ -478,7 +478,7 @@ class DirectSessionPanel(AppShell):
gridPage,
text = 'Grid Spacing',
min = 0.1,
initialValue = direct.grid.getGridSpacing())
value = direct.grid.getGridSpacing())
self.gridSpacing['command'] = direct.grid.setGridSpacing
self.gridSpacing.pack(fill = X, expand = 0)
@ -486,13 +486,14 @@ class DirectSessionPanel(AppShell):
gridPage,
text = 'Grid Size',
min = 1.0,
initialValue = direct.grid.getGridSize())
value = direct.grid.getGridSize())
self.gridSize['command'] = direct.grid.setGridSize
self.gridSize.pack(fill = X, expand = 0)
self.gridSnapAngle = Dial.AngleDial(
gridPage,
text = 'Snap Angle',
style = Dial.DIAL_MINI,
value = direct.grid.getSnapAngle())
self.gridSnapAngle['command'] = direct.grid.setSnapAngle
self.gridSnapAngle.pack(fill = X, expand = 0)
@ -544,7 +545,7 @@ class DirectSessionPanel(AppShell):
self.jbXyzSF = EntryScale.EntryScale(
joyboxFrame,
text = 'XYZ Scale Factor',
initialValue = 1.0,
value = 1.0,
hull_relief = RIDGE, hull_borderwidth = 2,
min = 1.0, max = 100.0)
self.jbXyzSF['command'] = (
@ -555,7 +556,7 @@ class DirectSessionPanel(AppShell):
self.jbHprSF = EntryScale.EntryScale(
joyboxFrame,
text = 'HPR Scale Factor',
initialValue = 1.0,
value = 1.0,
hull_relief = RIDGE, hull_borderwidth = 2,
min = 1.0, max = 100.0)
self.jbHprSF['command'] = (

View File

@ -370,7 +370,7 @@ class MopathRecorder(AppShell, PandaObject):
'Number of samples in resampled curve',
resolution = 1, min = 2, max = 1000, command = self.setNumSamples)
widget.component('hull')['relief'] = RIDGE
widget.onRelease = widget.onReturnRelease = self.sampleCurve
widget['preCallback'] = widget['postCallback'] = self.sampleCurve
frame = Frame(resampleFrame)
self.createButton(
@ -395,7 +395,7 @@ class MopathRecorder(AppShell, PandaObject):
min = 1, max = 100, resolution = 1,
command = self.setDesampleFrequency)
widget.component('hull')['relief'] = RIDGE
widget.onRelease = widget.onReturnRelease = self.desampleCurve
widget['postCallback'] = self.desampleCurve
desampleFrame.pack(fill = X, expand = 0, pady = 2)
## REFINE PAGE ##
@ -410,29 +410,28 @@ class MopathRecorder(AppShell, PandaObject):
'Begin time of refine pass',
resolution = 0.01,
command = self.setRecordStart)
widget.onPress = self.setRefineMode
widget.onRelease = widget.onReturnRelease = (
lambda s = self: s.getPrePoints('Refine'))
widget['preCallback'] = self.setRefineMode
widget['postCallback'] = lambda s = self: s.getPrePoints('Refine')
widget = self.createEntryScale(
refineFrame, 'Refine Page',
'Control Start',
'Time when full control of node path is given during refine pass',
resolution = 0.01,
command = self.setControlStart)
widget.onPress = widget.onReturn = self.setRefineMode
widget['preCallback'] = self.setRefineMode
widget = self.createEntryScale(
refineFrame, 'Refine Page',
'Control Stop',
'Time when node path begins transition back to original curve',
resolution = 0.01,
command = self.setControlStop)
widget.onPress = widget.onReturn = self.setRefineMode
widget['preCallback'] = self.setRefineMode
widget = self.createEntryScale(refineFrame, 'Refine Page', 'Refine To',
'Stop time of refine pass',
resolution = 0.01,
command = self.setRefineStop)
widget.onPress = self.setRefineMode
widget.onRelease = widget.onReturnRelease = self.getPostPoints
widget['preCallback'] = self.setRefineMode
widget['postCallback'] = self.getPostPoints
refineFrame.pack(fill = X)
## EXTEND PAGE ##
@ -447,16 +446,15 @@ class MopathRecorder(AppShell, PandaObject):
'Begin time of extend pass',
resolution = 0.01,
command = self.setRecordStart)
widget.onPress = self.setExtendMode
widget.onRelease = widget.onReturnRelease = (
lambda s = self: s.getPrePoints('Extend'))
widget['preCallback'] = self.setExtendMode
widget['postCallback'] = lambda s = self: s.getPrePoints('Extend')
widget = self.createEntryScale(
extendFrame, 'Extend Page',
'Control Start',
'Time when full control of node path is given during extend pass',
resolution = 0.01,
command = self.setControlStart)
widget.onPress = widget.onReturn = self.setExtendMode
widget['preCallback'] = self.setExtendMode
extendFrame.pack(fill = X)
## CROP PAGE ##
@ -529,48 +527,48 @@ class MopathRecorder(AppShell, PandaObject):
sfFrame, 'Style', 'Num Segs',
'Set number of segments used to approximate each parametric unit',
min = 1.0, max = 400, resolution = 1.0,
initialValue = 40,
value = 40,
command = self.setNumSegs, side = TOP)
widget.component('hull')['relief'] = RIDGE
widget = self.createEntryScale(
sfFrame, 'Style', 'Num Ticks',
'Set number of tick marks drawn for each unit of time',
min = 0.0, max = 10.0, resolution = 1.0,
initialValue = 0.0,
value = 0.0,
command = self.setNumTicks, side = TOP)
widget.component('hull')['relief'] = RIDGE
widget = self.createEntryScale(
sfFrame, 'Style', 'Tick Scale',
'Set visible size of time tick marks',
min = 0.01, max = 100.0, resolution = 0.01,
initialValue = 5.0,
value = 5.0,
command = self.setTickScale, side = TOP)
widget.component('hull')['relief'] = RIDGE
self.createColorEntry(
sfFrame, 'Style', 'Path Color',
'Color of curve',
command = self.setPathColor,
initialValue = [255.0,255.0,255.0,255.0])
value = [255.0,255.0,255.0,255.0])
self.createColorEntry(
sfFrame, 'Style', 'Knot Color',
'Color of knots',
command = self.setKnotColor,
initialValue = [0,0,255.0,255.0])
value = [0,0,255.0,255.0])
self.createColorEntry(
sfFrame, 'Style', 'CV Color',
'Color of CVs',
command = self.setCvColor,
initialValue = [255.0,0,0,255.0])
value = [255.0,0,0,255.0])
self.createColorEntry(
sfFrame, 'Style', 'Tick Color',
'Color of Ticks',
command = self.setTickColor,
initialValue = [255.0,0,0,255.0])
value = [255.0,0,0,255.0])
self.createColorEntry(
sfFrame, 'Style', 'Hull Color',
'Color of Hull',
command = self.setHullColor,
initialValue = [255.0,128.0,128.0,255.0])
value = [255.0,128.0,128.0,255.0])
#drawFrame.pack(fill = X)
@ -585,7 +583,7 @@ class MopathRecorder(AppShell, PandaObject):
widget = self.createLabeledEntry(
frame, 'Recording', 'Record Hook',
'Hook used to start/stop recording',
initialValue = self.startStopHook,
value = self.startStopHook,
command = self.setStartStopHook)[0]
label = self.getWidget('Recording', 'Record Hook-Label')
label.configure(width = 16, anchor = W)
@ -593,7 +591,7 @@ class MopathRecorder(AppShell, PandaObject):
widget = self.createLabeledEntry(
frame, 'Recording', 'Keyframe Hook',
'Hook used to add a new keyframe',
initialValue = self.keyframeHook,
value = self.keyframeHook,
command = self.setKeyframeHook)[0]
label = self.getWidget('Recording', 'Keyframe Hook-Label')
label.configure(width = 16, anchor = W)
@ -1678,12 +1676,12 @@ class MopathRecorder(AppShell, PandaObject):
return self.variableDict[category + '-' + text]
def createLabeledEntry(self, parent, category, text, balloonHelp,
initialValue = '', command = None,
value = '', command = None,
relief = 'sunken', side = LEFT,
expand = 1, width = 12):
frame = Frame(parent)
variable = StringVar()
variable.set(initialValue)
variable.set(value)
label = Label(frame, text = text)
label.pack(side = LEFT, fill = X)
self.bind(label, balloonHelp)

View File

@ -20,7 +20,7 @@ class ParticlePanel(AppShell):
# Override class variables
appname = 'Particle Panel'
frameWidth = 375
frameHeight = 775
frameHeight = 575
usecommandarea = 0
usestatusarea = 0
balloonState = 'both'
@ -374,7 +374,7 @@ class ParticlePanel(AppShell):
self.createVector3Entry(boxPage, 'Box Emitter', 'Max',
'Max point defining emitter box',
command = self.setEmitterBoxPoint2,
initialValue = (1.0, 1.0, 1.0))
value = (1.0, 1.0, 1.0))
# Disc page #
discPage = self.emitterNotebook.add('DiscEmitter')
self.createFloater(discPage, 'Disc Emitter', 'Radius',
@ -408,7 +408,7 @@ class ParticlePanel(AppShell):
self.createVector3Entry(linePage, 'Line Emitter', 'Max',
'Max point defining emitter line',
command = self.setEmitterLinePoint2,
initialValue = (1.0, 0.0, 0.0))
value = (1.0, 0.0, 0.0))
# Point page #
emitterPointPage = self.emitterNotebook.add('PointEmitter')
self.createVector3Entry(emitterPointPage, 'Point Emitter', 'Position',
@ -736,13 +736,11 @@ class ParticlePanel(AppShell):
def createFloater(self, parent, category, text, balloonHelp,
command = None, min = 0.0, resolution = None,
significantDigits = 3,
maxVelocity = 10.0, **kw):
numDigits = 3, **kw):
kw['text'] = text
kw['min'] = min
kw['maxVelocity'] = maxVelocity
kw['resolution'] = resolution
kw['significantDigits'] = significantDigits
kw['floater_resolution'] = resolution
kw['numDigits'] = numDigits
widget = apply(Floater.Floater, (parent,), kw)
# Do this after the widget so command isn't called on creation
widget['command'] = command
@ -754,6 +752,7 @@ class ParticlePanel(AppShell):
def createAngleDial(self, parent, category, text, balloonHelp,
command = None, **kw):
kw['text'] = text
kw['style'] = Dial.DIAL_MINI
widget = apply(Dial.AngleDial,(parent,), kw)
# Do this after the widget so command isn't called on creation
widget['command'] = command
@ -1771,7 +1770,7 @@ class ParticlePanel(AppShell):
self.createFloater(frame, pageName, forceName + ' Amplitude',
'Force amplitude multiplier',
command = setAmplitude,
initialValue = force.getAmplitude())
value = force.getAmplitude())
cbf = Frame(frame, relief = FLAT)
self.createCheckbutton(cbf, pageName, forceName + ' Mass Dependent',
('On: force depends on mass; ' +
@ -1808,7 +1807,7 @@ class ParticlePanel(AppShell):
self.createVector3Entry(frame, pageName, forceName,
'Set force direction and magnitude',
command = setVec,
initialValue = [vec[0], vec[1], vec[2]])
value = [vec[0], vec[1], vec[2]])
self.createForceActiveWidget(frame, pageName, forceName, force)
def createLinearRandomForceWidget(self, forcePage, pageName, count,
@ -1828,7 +1827,7 @@ class ParticlePanel(AppShell):
self.createFloater(frame, pageName, forceName + ' Coef',
'Set linear friction force',
command = setCoef, min = None,
initialValue = force.getCoef())
value = force.getCoef())
self.createForceActiveWidget(frame, pageName, forceName, force)
def createLinearCylinderVortexForceWidget(self, forcePage, pageName,
@ -1845,15 +1844,15 @@ class ParticlePanel(AppShell):
self.createFloater(frame, pageName, forceName + ' Coef',
'Set linear cylinder vortex coefficient',
command = setCoef,
initialValue = force.getCoef())
value = force.getCoef())
self.createFloater(frame, pageName, forceName + ' Length',
'Set linear cylinder vortex length',
command = setLength,
initialValue = force.getLength())
value = force.getLength())
self.createFloater(frame, pageName, forceName + ' Radius',
'Set linear cylinder vortex radius',
command = setRadius,
initialValue = force.getRadius())
value = force.getRadius())
self.createForceActiveWidget(frame, pageName, forceName, force)
def createLinearDistanceForceWidget(self, forcePage, pageName,
@ -1896,12 +1895,12 @@ class ParticlePanel(AppShell):
'Set center of force',
command = setForceCenter,
label_width = 16,
initialValue = [vec[0], vec[1], vec[2]])
value = [vec[0], vec[1], vec[2]])
self.createFloater(frame, pageName, forceName + ' Radius',
'Set falloff radius',
command = setRadius,
min = 0.01,
initialValue = force.getRadius())
value = force.getRadius())
self.createForceActiveWidget(frame, pageName, forceName, force)
######################################################################

View File

@ -18,8 +18,8 @@ class Placer(AppShell):
# Override class variables here
appname = 'Placer Panel'
frameWidth = 625
frameHeight = 290
usecommandarea = 1
frameHeight = 215
usecommandarea = 0
usestatusarea = 0
def __init__(self, parent = None, **kw):
@ -82,6 +82,7 @@ class Placer(AppShell):
def createInterface(self):
# The interior of the toplevel panel
interior = self.interior()
interior['relief'] = FLAT
# Add placer commands to menubar
self.menuBar.addmenu('Placer', 'Placer Panel Operations')
self.menuBar.addmenuitem('Placer', 'command',
@ -159,20 +160,16 @@ class Placer(AppShell):
self.redoButton.pack(side = 'left', expand = 0)
self.bind(self.redoButton, 'Redo last operation')
# The master frame for the dials
dialFrame = Frame(interior)
dialFrame.pack(fill = 'both', expand = 1)
# Create and pack the Pos Controls
posGroup = Pmw.Group(dialFrame,
posGroup = Pmw.Group(interior,
tag_pyclass = Menubutton,
tag_text = 'Position',
tag_font=('MSSansSerif', 14, 'bold'),
tag_font=('MSSansSerif', 14),
tag_activebackground = '#909090',
ring_relief = 'flat')
ring_relief = RIDGE)
posMenubutton = posGroup.component('tag')
self.bind(posMenubutton, 'Position menu operations')
posMenu = Menu(posMenubutton)
posMenu = Menu(posMenubutton, tearoff = 0)
posMenu.add_command(label = 'Set to zero', command = self.zeroPos)
posMenu.add_command(label = 'Reset initial',
command = self.resetPos)
@ -183,56 +180,50 @@ class Placer(AppShell):
# Create the dials
self.posX = self.createcomponent('posX', (), None,
Floater.Floater, (posInterior,),
text = 'X',
initialValue = 0.0,
text = 'X', relief = FLAT,
value = 0.0,
label_foreground = 'Red')
self.posX['command'] = self.xform
self.posX['commandData'] = ['x']
self.posX['preCallback'] = self.xformStart
self.posX['postCallback'] = self.xformStop
self.posX['callbackData'] = ['x']
self.posX.onReturn = self.xformStart
self.posX.onReturnRelease = self.xformStop
self.posX.onPress = self.xformStart
self.posX.onRelease = self.xformStop
self.posX.pack(expand=1,fill='both')
self.posY = self.createcomponent('posY', (), None,
Floater.Floater, (posInterior,),
text = 'Y',
initialValue = 0.0,
text = 'Y', relief = FLAT,
value = 0.0,
label_foreground = '#00A000')
self.posY['command'] = self.xform
self.posY['commandData'] = ['y']
self.posY['preCallback'] = self.xformStart
self.posY['postCallback'] = self.xformStop
self.posY['callbackData'] = ['y']
self.posY.onReturn = self.xformStart
self.posY.onReturnRelease = self.xformStop
self.posY.onPress = self.xformStart
self.posY.onRelease = self.xformStop
self.posY.pack(expand=1,fill='both')
self.posZ = self.createcomponent('posZ', (), None,
Floater.Floater, (posInterior,),
text = 'Z',
initialValue = 0.0,
text = 'Z', relief = FLAT,
value = 0.0,
label_foreground = 'Blue')
self.posZ['command'] = self.xform
self.posZ['commandData'] = ['z']
self.posZ['preCallback'] = self.xformStart
self.posZ['postCallback'] = self.xformStop
self.posZ['callbackData'] = ['z']
self.posZ.onReturn = self.xformStart
self.posZ.onReturnRelease = self.xformStop
self.posZ.onPress = self.xformStart
self.posZ.onRelease = self.xformStop
self.posZ.pack(expand=1,fill='both')
# Create and pack the Hpr Controls
hprGroup = Pmw.Group(dialFrame,
hprGroup = Pmw.Group(interior,
tag_pyclass = Menubutton,
tag_text = 'Orientation',
tag_font=('MSSansSerif', 14, 'bold'),
tag_font=('MSSansSerif', 14),
tag_activebackground = '#909090',
ring_relief = 'flat')
ring_relief = RIDGE)
hprMenubutton = hprGroup.component('tag')
self.bind(hprMenubutton, 'Orientation menu operations')
hprMenu = Menu(hprMenubutton)
hprMenu = Menu(hprMenubutton, tearoff = 0)
hprMenu.add_command(label = 'Set to zero', command = self.zeroHpr)
hprMenu.add_command(label = 'Reset initial', command = self.resetHpr)
hprMenubutton['menu'] = hprMenu
@ -242,41 +233,41 @@ class Placer(AppShell):
# Create the dials
self.hprH = self.createcomponent('hprH', (), None,
Dial.AngleDial, (hprInterior,),
style = Dial.DIAL_MINI,
text = 'H', value = 0.0,
relief = FLAT,
label_foreground = 'blue')
self.hprH['command'] = self.xform
self.hprH['commandData'] = ['h']
self.hprH['preCallback'] = self.xformStart
self.hprH['postCallback'] = self.xformStop
self.hprH['callbackData'] = ['h']
self.hprH['onReturnPress'] = self.xformStart
self.hprH['onReturnRelease'] = self.xformStop
self.hprH['onButtonPress'] = self.xformStart
self.hprH['onButtonRelease'] = self.xformStop
self.hprH.pack(expand=1,fill='both')
self.hprP = self.createcomponent('hprP', (), None,
Dial.AngleDial, (hprInterior,),
style = Dial.DIAL_MINI,
text = 'P', value = 0.0,
relief = FLAT,
label_foreground = 'red')
self.hprP['command'] = self.xform
self.hprP['commandData'] = ['p']
self.hprP['preCallback'] = self.xformStart
self.hprP['postCallback'] = self.xformStop
self.hprP['callbackData'] = ['p']
self.hprP['onReturnPress'] = self.xformStart
self.hprP['onReturnRelease'] = self.xformStop
self.hprP['onButtonPress'] = self.xformStart
self.hprP['onButtonRelease'] = self.xformStop
self.hprP.pack(expand=1,fill='both')
self.hprR = self.createcomponent('hprR', (), None,
Dial.AngleDial, (hprInterior,),
style = Dial.DIAL_MINI,
text = 'R', value = 0.0,
relief = FLAT,
label_foreground = '#00A000')
self.hprR['command'] = self.xform
self.hprR['commandData'] = ['r']
self.hprR['preCallback'] = self.xformStart
self.hprR['postCallback'] = self.xformStop
self.hprR['callbackData'] = ['r']
self.hprR['onReturnPress'] = self.xformStart
self.hprR['onReturnRelease'] = self.xformStop
self.hprR['onButtonPress'] = self.xformStart
self.hprR['onButtonRelease'] = self.xformStop
self.hprR.pack(expand=1,fill='both')
# Create and pack the Scale Controls
@ -284,18 +275,18 @@ class Placer(AppShell):
self.scalingMode = StringVar()
self.scalingMode.set('Scale Uniform')
# The scaling widgets
scaleGroup = Pmw.Group(dialFrame,
scaleGroup = Pmw.Group(interior,
tag_text = 'Scale Uniform',
tag_pyclass = Menubutton,
tag_font=('MSSansSerif', 14, 'bold'),
tag_font=('MSSansSerif', 14),
tag_activebackground = '#909090',
ring_relief = 'flat')
ring_relief = RIDGE)
self.scaleMenubutton = scaleGroup.component('tag')
self.bind(self.scaleMenubutton, 'Scale menu operations')
self.scaleMenubutton['textvariable'] = self.scalingMode
# Scaling menu
scaleMenu = Menu(self.scaleMenubutton)
scaleMenu = Menu(self.scaleMenubutton, tearoff = 0)
scaleMenu.add_command(label = 'Set to unity',
command = self.unitScale)
scaleMenu.add_command(label = 'Reset initial',
@ -313,48 +304,45 @@ class Placer(AppShell):
# Create the dials
self.scaleX = self.createcomponent('scaleX', (), None,
Dial.Dial, (scaleInterior,),
Floater.Floater, (scaleInterior,),
text = 'X Scale',
relief = FLAT,
min = 0.0001, value = 1.0,
resetValue = 1.0,
label_foreground = 'Red')
self.scaleX['command'] = self.xform
self.scaleX['commandData'] = ['sx']
self.scaleX['callbackData'] = ['sx']
self.scaleX['onReturnPress'] = self.xformStart
self.scaleX['onReturnRelease'] = self.xformStop
self.scaleX['onButtonPress'] = self.xformStart
self.scaleX['onButtonRelease'] = self.xformStop
self.scaleX['preCallback'] = self.xformStart
self.scaleX['postCallback'] = self.xformStop
self.scaleX.pack(expand=1,fill='both')
self.scaleY = self.createcomponent('scaleY', (), None,
Dial.Dial, (scaleInterior,),
Floater.Floater, (scaleInterior,),
text = 'Y Scale',
relief = FLAT,
min = 0.0001, value = 1.0,
resetValue = 1.0,
label_foreground = '#00A000')
self.scaleY['command'] = self.xform
self.scaleY['commandData'] = ['sy']
self.scaleY['callbackData'] = ['sy']
self.scaleY['onReturnPress'] = self.xformStart
self.scaleY['onReturnRelease'] = self.xformStop
self.scaleY['onButtonPress'] = self.xformStart
self.scaleY['onButtonRelease'] = self.xformStop
self.scaleY['preCallback'] = self.xformStart
self.scaleY['postCallback'] = self.xformStop
self.scaleY.pack(expand=1,fill='both')
self.scaleZ = self.createcomponent('scaleZ', (), None,
Dial.Dial, (scaleInterior,),
Floater.Floater, (scaleInterior,),
text = 'Z Scale',
relief = FLAT,
min = 0.0001, value = 1.0,
resetValue = 1.0,
label_foreground = 'Blue')
self.scaleZ['command'] = self.xform
self.scaleZ['commandData'] = ['sz']
self.scaleZ['callbackData'] = ['sz']
self.scaleZ['onReturnPress'] = self.xformStart
self.scaleZ['onReturnRelease'] = self.xformStop
self.scaleZ['onButtonPress'] = self.xformStart
self.scaleZ['onButtonRelease'] = self.xformStop
self.scaleZ['preCallback'] = self.xformStart
self.scaleZ['postCallback'] = self.xformStop
self.scaleZ.pack(expand=1,fill='both')
# Make sure appropriate labels are showing
@ -363,33 +351,6 @@ class Placer(AppShell):
self.selectNodePathNamed('init')
self.selectRefNodePathNamed('parent')
self.createButtons()
def createButtons(self):
self.buttonAdd('Zero All',
helpMessage='Zero Node Path',
statusMessage='Zero Node Path',
command=self.zeroAll)
self.buttonAdd('Reset All',
helpMessage='Reset Node Path',
statusMessage='Reset Node Path',
command=self.resetAll)
self.buttonAdd('Print Info',
helpMessage='Print Node Path Info',
statusMessage='Print Node Path Info',
command=self.printNodePathInfo)
self.buttonAdd('Toggle Widget Viz',
helpMessage='Toggle Object Handles Visability',
statusMessage='Toggle Object Handles Visability',
command=direct.toggleWidgetVis)
self.buttonAdd(
'Toggle Widget Mode',
helpMessage='Toggle Widget Move/COA Mode',
statusMessage='Toggle Widget Move/COA Mode',
command=direct.manipulationControl.toggleObjectHandlesMode)
# Make all buttons as wide as widest
self.alignbuttons()
### WIDGET OPERATIONS ###
def setMovementMode(self, movementMode):
@ -715,8 +676,17 @@ class Placer(AppShell):
def updateResetValues(self, nodePath):
self.initPos.assign(nodePath.getPos())
self.posX['resetValue'] = self.initPos[0]
self.posY['resetValue'] = self.initPos[1]
self.posZ['resetValue'] = self.initPos[2]
self.initHpr.assign(nodePath.getHpr())
self.hprH['resetValue'] = self.initHpr[0]
self.hprP['resetValue'] = self.initHpr[1]
self.hprR['resetValue'] = self.initHpr[2]
self.initScale.assign(nodePath.getScale())
self.scaleX['resetValue'] = self.initScale[0]
self.scaleY['resetValue'] = self.initScale[1]
self.scaleZ['resetValue'] = self.initScale[2]
def resetAll(self):
if self['nodePath']:

View File

@ -328,14 +328,14 @@ class AppShell(Pmw.MegaWidget, PandaObject):
return widget
def newCreateLabeledEntry(self, parent, category, text, help = '',
command = None, initialValue = '',
command = None, value = '',
width = 12, relief = SUNKEN,
side = LEFT, fill = X, expand = 0):
""" createLabeledEntry(parent, category, text, [options]) """
# Create labeled entry
frame = Frame(parent)
variable = StringVar()
variable.set(initialValue)
variable.set(value)
label = Label(frame, text = text)
label.pack(side = LEFT, fill = X, expand = 0)
entry = Entry(frame, width = width, relief = relief,

View File

@ -1,6 +1,6 @@
from Tkinter import *
from PandaModules import ClockObject
from WidgetPropertiesDialog import *
import WidgetPropertiesDialog
import Pmw
import Task
import math
@ -17,7 +17,7 @@ DIAL_FULL = 'full'
DIAL_MINI = 'mini'
DIAL_FULL_SIZE = 45
DIAL_MINI_SIZE = 20
DIAL_MINI_SIZE = 30
globalClock = ClockObject.getGlobalClock()
@ -29,13 +29,8 @@ class Dial(Pmw.MegaWidget):
def __init__(self, parent = None, **kw):
#define the megawidget options
INITOPT = Pmw.INITOPT
if 'full' == kw.get('style', DIAL_FULL):
DIAL_SIZE = DIAL_FULL_SIZE
else:
DIAL_SIZE = DIAL_MINI_SIZE
optiondefs = (
('style', DIAL_FULL, INITOPT),
('dial_size', DIAL_SIZE, None),
# Widget relief
('relief', GROOVE, None),
# Widget borderwidth
@ -46,21 +41,30 @@ class Dial(Pmw.MegaWidget):
('numDigits', 2, self.setEntryFormat),
('command', None, None),
('commandData', [], None),
('callbackData', [], self.setCallbackData),
('min', None, self.setMin),
('max', None, self.setMax),
('base', 0.0, self.setBase),
('delta', 1.0, self.setDelta),
('onReturnPress', None, None),
('onReturnRelease', None, None),
('onButtonPress', None, self.setButtonPressCmd),
('onButtonRelease', None, self.setButtonReleaseCmd),
# 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),
)
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'])
@ -68,15 +72,24 @@ class Dial(Pmw.MegaWidget):
# The Dial
self._dial = self.createcomponent('dial', (), None,
DialWidget, (interior,),
size = dialSize,
command = self.setEntry,
value = self['value'])
self._dial.propertyDict['numDigits'] = {
'widget' : self,
'type' : 'integer',
'help' : 'Enter number of digits after decimal point.'
}
self._dial.propertyList.append('numDigits')
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,
@ -99,14 +112,14 @@ class Dial(Pmw.MegaWidget):
if self['style'] == DIAL_FULL:
# Attach dial to entry
self._dial.grid(rowspan = 2, columnspan = 2)
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)
self._dial.grid(row=0,col=2, padx = 2, pady = 2)
interior.columnconfigure(0, weight = 1)
# Make sure input variables processed
@ -144,13 +157,13 @@ class Dial(Pmw.MegaWidget):
def _onReturnPress(self, *args):
""" User redefinable callback executed on <Return> in entry """
if self['onReturnPress']:
apply(self['onReturnPress'], self['callbackData'])
if self['preCallback']:
apply(self['preCallback'], self['callbackData'])
def _onReturnRelease(self, *args):
""" User redefinable callback executed on <Return> release in entry """
if self['onReturnRelease']:
apply(self['onReturnRelease'], self['callbackData'])
if self['postCallback']:
apply(self['postCallback'], self['callbackData'])
# Pass settings down to dial
def setCallbackData(self):
@ -175,11 +188,12 @@ class Dial(Pmw.MegaWidget):
def setLabel(self):
self._label['text'] = self['text']
def setButtonPressCmd(self):
self._dial['onButtonPress'] = self['onButtonPress']
def setPreCallbackCmd(self):
self._dial['preCallback'] = self['preCallback']
def setPostCallbackCmd(self):
self._dial['postCallback'] = self['postCallback']
def setButtonReleaseCmd(self):
self._dial['onButtonRelease'] = self['onButtonRelease']
class AngleDial(Dial):
def __init__(self, parent = None, **kw):
@ -237,10 +251,11 @@ class DialWidget(Pmw.MegaWidget):
('command', None, None),
# Extra data to be passed to command function
('commandData', [], None),
# Extra data to be passed to callback function
# 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),
('onButtonPress', None, None),
('onButtonRelease', None, None),
)
self.defineoptions(kw, optiondefs)
@ -262,31 +277,6 @@ class DialWidget(Pmw.MegaWidget):
# Radius of the inner knob
inner_radius = max(3,radius * INNER_SF)
# A Dictionary of dictionaries
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', 'numSegments', 'resetValue']
# The canvas
self._canvas = self.createcomponent('canvas', (), None,
Canvas, (interior,),
@ -312,6 +302,32 @@ 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()
@ -325,8 +341,11 @@ class DialWidget(Pmw.MegaWidget):
self._popupMenu.add_checkbutton(label = 'Rollover',
variable = self._fRollover,
command = self.setRollover)
self._popupMenu.add_command(label = 'Properties...',
command = self.getProperties)
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)
@ -375,6 +394,14 @@ class DialWidget(Pmw.MegaWidget):
# 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):
"""
@ -384,6 +411,7 @@ class DialWidget(Pmw.MegaWidget):
self.set(self['resetValue'])
def mouseReset(self,event):
# If not over any canvas item
if not self._canvas.find_withtag(CURRENT):
self.reset()
@ -465,7 +493,6 @@ class DialWidget(Pmw.MegaWidget):
# Update value
currT = globalClock.getFrameTime()
dt = currT - state.lastTime
#self.set(self.value + self['delta'] * self.knobSF * dt)
self.set(self.value + self.knobSF * dt)
state.lastTime = currT
return Task.cont
@ -548,24 +575,28 @@ class DialWidget(Pmw.MegaWidget):
self['fRollover'] = self._fRollover.get()
# This handles the popup dial min dialog
def getProperties(self):
def popupPropertiesDialog(self):
# Popup dialog to adjust widget properties
WidgetPropertiesDialog(
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
def _onButtonPress(self, *args):
""" User redefinable callback executed on button press """
if self['onButtonPress']:
apply(self['onButtonPress'], self['callbackData'])
if self['preCallback']:
apply(self['preCallback'], self['callbackData'])
def _onButtonRelease(self, *args):
""" User redefinable callback executed on button release """
if self['onButtonRelease']:
apply(self['onButtonRelease'], self['callbackData'])
if self['postCallback']:
apply(self['postCallback'], self['callbackData'])
if __name__ == '__main__':

View File

@ -19,14 +19,16 @@ class EntryScale(Pmw.MegaWidget):
# Define the megawidget options.
optiondefs = (
('initialValue', 0.0, Pmw.INITOPT),
('value', 0.0, Pmw.INITOPT),
('resolution', 0.001, None),
('command', None, None),
('preCallback', None, None),
('postCallback', None, None),
('callbackData', [], None),
('min', 0.0, self._updateValidate),
('max', 100.0, self._updateValidate),
('text', 'EntryScale', self._updateLabelText),
('significantDigits', 2, self._setSigDigits),
('numDigits', 2, self._setSigDigits),
)
self.defineoptions(kw, optiondefs)
@ -34,7 +36,7 @@ class EntryScale(Pmw.MegaWidget):
Pmw.MegaWidget.__init__(self, parent)
# Initialize some class variables
self.value = self['initialValue']
self.value = self['value']
self.entryFormat = '%.2f'
self.fScaleCommand = 0
@ -49,7 +51,7 @@ class EntryScale(Pmw.MegaWidget):
Frame, interior)
# Create an entry field to display and validate the entryScale's value
self.entryValue = StringVar()
self.entryValue.set(self['initialValue'])
self.entryValue.set(self['value'])
self.entry = self.createcomponent('entryField',
# Access widget's entry using "entry"
(('entry', 'entryField_entry'),),
@ -105,7 +107,7 @@ class EntryScale(Pmw.MegaWidget):
showvalue = 0)
self.scale.pack(side = 'left', expand = 1, fill = 'x')
# Set scale to the middle of its range
self.scale.set(self['initialValue'])
self.scale.set(self['value'])
self.scale.bind('<Button-1>', self.__onPress)
self.scale.bind('<ButtonRelease-1>', self.__onRelease)
self.scale.bind('<Button-3>', self.askForResolution)
@ -218,7 +220,7 @@ class EntryScale(Pmw.MegaWidget):
pass
def _setSigDigits(self):
sd = self['significantDigits']
sd = self['numDigits']
self.entryFormat = '%.' + '%d' % sd + 'f'
# And reset value to reflect change
self.entryValue.set( self.entryFormat % self.value )
@ -260,7 +262,8 @@ class EntryScale(Pmw.MegaWidget):
def __onPress(self, event):
# First execute onpress callback
apply(self.onPress, self['callbackData'])
if self['preCallback']:
apply(self['preCallback'], self['callbackData'])
# Now enable slider command
self.fScaleCommand = 1
@ -272,7 +275,8 @@ class EntryScale(Pmw.MegaWidget):
# Now disable slider command
self.fScaleCommand = 0
# First execute onpress callback
apply(self.onRelease, self['callbackData'])
if self['postCallback']:
apply(self['postCallback'], self['callbackData'])
def onRelease(self, *args):
""" User redefinable callback executed on button release """
@ -295,9 +299,11 @@ class EntryScaleGroup(Pmw.MegaToplevel):
('side', TOP, INITOPT),
('title', 'Group', None),
# A tuple of initial values, one for each entryScale
('initialValue', DEFAULT_VALUE, INITOPT),
('value', DEFAULT_VALUE, INITOPT),
# The command to be executed any time one of the entryScales is updated
('command', None, None),
('preCallback', None, None),
('postCallback', None, None),
# A tuple of labels, one for each entryScale
('labels', DEFAULT_LABELS, self._updateLabels),
# Destroy or withdraw
@ -311,7 +317,7 @@ class EntryScaleGroup(Pmw.MegaToplevel):
# Create the components
interior = self.interior()
# Get a copy of the initial value (making sure its a list)
self._value = list(self['initialValue'])
self._value = list(self['value'])
# The Menu Bar
self.balloon = Pmw.Balloon()
@ -349,7 +355,7 @@ class EntryScaleGroup(Pmw.MegaToplevel):
# fg.configure(Valuator_XXX = YYY)
f = self.createcomponent(
'entryScale%d' % index, (), 'Valuator', EntryScale,
(interior,), initialValue = self._value[index],
(interior,), value = self._value[index],
text = self['labels'][index])
# Do this separately so command doesn't get executed during construction
f['command'] = lambda val, s=self, i=index: s._entryScaleSetAt(i, val)
@ -357,13 +363,13 @@ class EntryScaleGroup(Pmw.MegaToplevel):
# Callbacks
f.onReturn = self.__onReturn
f.onReturnRelease = self.__onReturnRelease
f.onPress = self.__onPress
f.onRelease = self.__onRelease
f['preCallback'] = self.__onPress
f['postCallback'] = self.__onRelease
f.pack(side = self['side'], expand = 1, fill = X)
self.entryScaleList.append(f)
# Make sure entryScales are initialized
self.set(self['initialValue'])
self.set(self['value'])
# Make sure input variables processed
self.initialiseoptions(EntryScaleGroup)
@ -405,7 +411,7 @@ class EntryScaleGroup(Pmw.MegaToplevel):
self['command'](self._value)
def reset(self):
self.set(self['initialValue'])
self.set(self['value'])
def __onReturn(self, esg):
# Execute onReturn callback
@ -425,7 +431,8 @@ class EntryScaleGroup(Pmw.MegaToplevel):
def __onPress(self, esg):
# Execute onPress callback
apply(self.onPress, esg.get())
if self['preCallback']:
apply(self['preCallback'], esg.get())
def onPress(self, *args):
""" User redefinable callback executed on button press """
@ -433,7 +440,8 @@ class EntryScaleGroup(Pmw.MegaToplevel):
def __onRelease(self, esg):
# Execute onRelease callback
apply(self.onRelease, esg.get())
if self['postCallback']:
apply(self['postCallback'], esg.get())
def onRelease(self, *args):
""" User redefinable callback executed on button release """
@ -455,7 +463,7 @@ def rgbPanel(nodePath, callback = None):
esg = EntryScaleGroup(title = 'RGBA Panel: ' + nodePath.getName(),
dim = 4,
labels = ['R','G','B','A'],
initialValue = [int(initColor[0]),
value = [int(initColor[0]),
int(initColor[1]),
int(initColor[2]),
int(initColor[3])],
@ -499,7 +507,7 @@ def rgbPanel(nodePath, callback = None):
# Set callback
def onRelease(r,g,b,a, nodePath = nodePath):
messenger.send('RGBPanel_setColor', [nodePath, r,g,b,a])
esg.onRelease = onRelease
esg['postCallback'] = onRelease
return esg
## SAMPLE CODE
@ -519,7 +527,7 @@ if __name__ == '__main__':
"""
# These are things you can set/configure
# Starting value for entryScale
mega1['initialValue'] = 123.456
mega1['value'] = 123.456
mega1['text'] = 'Drive delta X'
mega1['min'] = 0.0
mega1['max'] = 1000.0
@ -530,7 +538,7 @@ if __name__ == '__main__':
# To have really fine control, for example
# mega1['maxVelocity'] = 0.1
# Number of digits to the right of the decimal point, default = 2
# mega1['significantDigits'] = 5
# mega1['numDigits'] = 5
"""
# To create a entryScale group to set an RGBA value:

View File

@ -5,214 +5,431 @@ Floater Class: Velocity style controller for floating point values with
from PandaObject import *
from Tkinter import *
import Pmw
import WidgetPropertiesDialog
import string
class Floater(Pmw.MegaWidget):
"Velocity style floating point controller"
def __init__(self, parent = None, **kw):
globalClock = ClockObject.getGlobalClock()
# Define the megawidget options.
FLOATER_FULL = 'full'
FLOATER_MINI = 'mini'
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 = (
('initialValue', 0.0, Pmw.INITOPT),
('resolution', None, None),
('command', None, None),
('commandData', [], None),
('callbackData', [], None),
('maxVelocity', 10.0, None),
('min', None, self._updateValidate),
('max', None, self._updateValidate),
('text', 'Floater', self._updateLabelText),
('significantDigits', 2, self._setSigDigits),
## Appearance
# Edge size of the floater
('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
# 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),
# Extra data to be passed to callback function, needs to be a list
('callbackData', [], None),
)
self.defineoptions(kw, optiondefs)
# Initialise superclass
#print 'FLOATER WIDGET', self['resetValue']
# Initialize the superclass
Pmw.MegaWidget.__init__(self, parent)
# Initialize some class variables
self.value = self['initialValue']
self.velocity = 0.0
self.entryFormat = '%.2f'
# Set up some local and instance variables
# Current value
self.value = self['value']
# Create the components.
# Setup up container
# Create the components
interior = self.interior()
interior.configure(relief = GROOVE, borderwidth = 2)
# Create a label and an entry
self.labelFrame = self.createcomponent('frame', (), None,
Frame, interior)
# Create an entry field to display and validate the floater's value
self.entryValue = StringVar()
self.entryValue.set(self['initialValue'])
self.entry = self.createcomponent('entryField',
# Access floaters entry using "entry"
(('entry', 'entryField_entry'),),
None,
Pmw.EntryField, self.labelFrame,
entry_width = 10,
validate = { 'validator' : 'real',
'min' : self['min'],
'max' : self['max'],
'minstrict' : 0,
'maxstrict' : 0},
entry_justify = 'right',
entry_textvar = self.entryValue,
command = self._entryCommand)
self.entry.pack(side='left',padx = 4)
# Create the Floater's label
self.label = self.createcomponent('label', (), None,
Label, self.labelFrame,
text = self['text'],
width = 12,
anchor = 'center',
font = "Arial 10 bold")
self.label.pack(side='left', expand = 1, fill = 'x')
# The canvas
width = self['width']
height = self['height']
self._canvas = self.createcomponent('canvas', (), None,
Canvas, (interior,),
width = self['width'],
height = self['height'],
background = self['background'],
highlightthickness = 0,
scrollregion = (-width/2.0,
-height/2.0,
width/2.0,
height/2.0))
self._canvas.pack(expand = 1, fill = BOTH)
# Now pack the frame
self.labelFrame.pack(expand = 1, fill = 'both')
# The floater icon
self._canvas.create_polygon(-width/2.0, 0, -2.0, -height/2.0,
-2.0, height/2.0,
fill = '#A0A0A0',
tags = ('floater',))
self._canvas.create_polygon(width/2.0, 0, 2.0, height/2.0,
2.0, -height/2.0,
fill = '#A0A0A0',
tags = ('floater',))
# Create the scale component.
self.scale = self.createcomponent('scale', (), None,
Scale, interior,
command = self._scaleToVelocity,
orient = 'horizontal',
length = 150,
from_ = -1.0,
to = 1.0,
resolution = 0.001,
showvalue = 0)
self.scale.pack(expand = 1, fill = 'x')
# Set scale to the middle of its range
self.scale.set(0.0)
# Add scale bindings: When interacting with mouse:
self.scale.bind('<Button-1>', self._startFloaterTask)
self.scale.bind('<ButtonRelease-1>', self._floaterReset)
# In case you wish to interact using keys
self.scale.bind('<KeyPress-Right>', self._floaterKeyCommand)
self.scale.bind('<KeyRelease-Right>', self._floaterReset)
self.scale.bind('<KeyPress-Left>', self._floaterKeyCommand)
self.scale.bind('<KeyRelease-Left>', self._floaterReset)
# Check keywords and initialise options based on input values.
self.initialiseoptions(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']
def label(self):
return self.label
def scale(self):
return self.scale
def entry(self):
return self.entry
def _updateLabelText(self):
self.label['text'] = self['text']
# The popup menu
self._popupMenu = Menu(interior, tearoff = 0)
def _updateValidate(self):
self.configure(entryField_validate = {
'validator' : 'real',
'min' : self['min'],
'max' : self['max'],
'minstrict' : 0,
'maxstrict' : 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)
def _scaleToVelocity(self, strVal):
# convert scale val to float
val = string.atof(strVal)
# Square val, but retain sign of velocity by only calling abs once
self.velocity = self['maxVelocity'] * val * abs(val)
# Add event bindings
self._canvas.bind('<ButtonPress-1>', self.mouseDown)
self._canvas.bind('<B1-Motion>', self.mouseMotion)
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)
def _startFloaterTask(self,event):
self._fFloaterTask = 1
apply(self.onPress,self['callbackData'])
taskMgr.add(self._floaterTask, 'floaterTask')
# Make sure input variables processed
self.initialiseoptions(FloaterWidget)
def _floaterTask(self, state):
if self.velocity != 0.0:
self.set( self.value + self.velocity )
return Task.cont
def _floaterReset(self, event):
taskMgr.remove('floaterTask')
self.velocity = 0.0
self.scale.set(0.0)
apply(self.onRelease, self['callbackData'])
def _floaterKeyCommand(self, event):
if self.velocity != 0.0:
self.set( self.value + self.velocity )
def _entryCommand(self, event = None):
try:
val = string.atof( self.entryValue.get() )
apply(self.onReturn,self['callbackData'])
self.set( val )
apply(self.onReturnRelease,self['callbackData'])
except ValueError:
pass
def _setSigDigits(self):
sd = self['significantDigits']
self.entryFormat = '%.' + '%d' % sd + 'f'
# And reset value to reflect change
self.entryValue.set( self.entryFormat % self.value )
def get(self):
return self.value
def set(self, newVal, fCommand = 1):
def set(self, value, fCommand = 1):
"""
self.set(value, fCommand = 1)
Set floater to new value, execute command if fCommand == 1
"""
# Clamp value
if self['min'] is not None:
if newVal < self['min']:
newVal = self['min']
if value < self['min']:
value = self['min']
if self['max'] is not None:
if newVal > self['max']:
newVal = self['max']
if value > self['max']:
value = self['max']
# Round by resolution
if self['resolution'] is not None:
newVal = round(newVal / self['resolution']) * self['resolution']
# Update floater's value
self.value = newVal
# Update entry to reflect formatted value
self.entryValue.set( self.entryFormat % self.value )
self.entry.checkentry()
# execute command
if fCommand and (self['command'] is not None):
apply(self['command'], [newVal] + self['commandData'])
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
# 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.set(self['initialValue'])
"""
self.reset()
Reset floater to reset value
"""
self.set(self['resetValue'])
def disable(self):
self.scale['state'] = 'disabled'
self.label['state'] = 'disabled'
self.component('entry')['state'] = 'disabled'
def mouseReset(self,event):
# If not over any canvas item
#if not self._canvas.find_withtag(CURRENT):
self.reset()
def get(self):
"""
self.get()
Get current floater value
"""
return self.value
def enable(self):
self.scale['state'] = 'normal'
self.label['state'] = 'normal'
self.component('entry')['state'] = 'normal'
## Canvas callback functions
# Floater velocity controller
def mouseDown(self,event):
self._onButtonPress()
self.velocitySF = 0.0
t = taskMgr.add(self.computeVelocity, 'cv')
t.lastTime = globalClock.getFrameTime()
def onReturn(self, *args):
""" User redefinable callback executed on <Return> in entry """
pass
def computeVelocity(self, state):
# Update value
currT = globalClock.getFrameTime()
dt = currT - state.lastTime
self.set(self.value + self.velocitySF * dt)
state.lastTime = currT
return Task.cont
def onReturnRelease(self, *args):
""" User redefinable callback executed on <Return> release in entry """
pass
def mouseMotion(self, event):
# What is the current knob angle
self.velocitySF = self.computeVelocitySF(event)
def onPress(self, *args):
def computeVelocitySF(self, event):
x = self._canvas.canvasx(event.x)
y = self._canvas.canvasy(event.y)
offset = max(0, abs(x) - FloaterWidget.deadband)
if offset == 0:
return 0
sf = math.pow(FloaterWidget.sfBase,
self.minExp + offset/FloaterWidget.sfDist)
if x > 0:
return sf
else:
return -sf
def mouseUp(self, event):
taskMgr.remove('cv')
self.velocitySF = 0.0
self._onButtonRelease()
def highlightIcon(self, event):
self._canvas.itemconfigure('floater', fill = 'black')
def restoreIcon(self, event):
self._canvas.itemconfigure('floater', fill = '#A0A0A0')
# Methods to modify floater characteristics
def setRelief(self):
self.interior()['relief'] = self['relief']
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))
# 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())
# 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 """
pass
if self['preCallback']:
apply(self['preCallback'], self['callbackData'])
def onRelease(self, *args):
def _onButtonRelease(self, *args):
""" User redefinable callback executed on button release """
pass
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']
class FloaterGroup(Pmw.MegaToplevel):
def __init__(self, parent = None, **kw):
@ -231,7 +448,7 @@ class FloaterGroup(Pmw.MegaToplevel):
('side', TOP, INITOPT),
('title', 'Floater Group', None),
# A tuple of initial values, one for each floater
('initialValue', DEFAULT_VALUE, INITOPT),
('value', DEFAULT_VALUE, INITOPT),
# The command to be executed any time one of the floaters is updated
('command', None, None),
# A tuple of labels, one for each floater
@ -245,7 +462,7 @@ class FloaterGroup(Pmw.MegaToplevel):
# Create the components
interior = self.interior()
# Get a copy of the initial value (making sure its a list)
self._value = list(self['initialValue'])
self._value = list(self['value'])
# The Menu Bar
self.balloon = Pmw.Balloon()
@ -279,7 +496,7 @@ class FloaterGroup(Pmw.MegaToplevel):
# fg.configure(Valuator_XXX = YYY)
f = self.createcomponent(
'floater%d' % index, (), 'Valuator', Floater,
(interior,), initialValue = self._value[index],
(interior,), value = self._value[index],
text = self['labels'][index])
# Do this separately so command doesn't get executed during construction
f['command'] = lambda val, s=self, i=index: s._floaterSetAt(i, val)
@ -287,7 +504,7 @@ class FloaterGroup(Pmw.MegaToplevel):
self.floaterList.append(f)
# Make sure floaters are initialized
self.set(self['initialValue'])
self.set(self['value'])
# Make sure input variables processed
self.initialiseoptions(FloaterGroup)
@ -329,9 +546,9 @@ class FloaterGroup(Pmw.MegaToplevel):
self['command'](self._value)
def reset(self):
self.set(self['initialValue'])
self.set(self['value'])
## SAMPLE CODE
if __name__ == '__main__':
# Initialise Tkinter and Pmw.
@ -349,7 +566,7 @@ if __name__ == '__main__':
"""
# These are things you can set/configure
# Starting value for floater
mega1['initialValue'] = 123.456
mega1['value'] = 123.456
mega1['text'] = 'Drive delta X'
mega1['min'] = 0.0
mega1['max'] = 1000.0
@ -360,7 +577,7 @@ if __name__ == '__main__':
# To have really fine control, for example
# mega1['maxVelocity'] = 0.1
# Number of digits to the right of the decimal point, default = 2
# mega1['significantDigits'] = 5
# mega1['numDigits'] = 5
"""
# To create a floater group to set an RGBA value:

View File

@ -20,7 +20,7 @@ class VectorEntry(Pmw.MegaWidget):
INITOPT = Pmw.INITOPT
optiondefs = (
('dim', DEFAULT_DIM, INITOPT),
('initialValue', DEFAULT_VALUE, INITOPT),
('value', DEFAULT_VALUE, INITOPT),
('resetValue', DEFAULT_VALUE, None),
('label_width', 12, None),
('command', None, None),
@ -30,7 +30,7 @@ class VectorEntry(Pmw.MegaWidget):
('text', 'Vector:', self._updateText),
('min', None, self._updateValidate),
('max', None, self._updateValidate),
('significantDigits', 2, self._setSigDigits),
('numDigits', 2, self._setSigDigits),
('valuatorType', VALUATOR, None),
('state', 'normal', self._setState),
)
@ -41,8 +41,8 @@ class VectorEntry(Pmw.MegaWidget):
# Initialize value
# Make sure its a list (and as a byproduct, make a distinct copy)
self._value = list(self['initialValue'])
self['resetValue'] = self['initialValue']
self._value = list(self['value'])
self['resetValue'] = self['value']
self._floaters = None
self.entryFormat = '%.2f'
@ -103,7 +103,7 @@ class VectorEntry(Pmw.MegaWidget):
# Make sure entries are updated
self.set(self['initialValue'])
self.set(self['value'])
# Record entry color
self.entryBackground = self.cget('Entry_entry_background')
@ -142,9 +142,9 @@ class VectorEntry(Pmw.MegaWidget):
self['Entry_entry_width'] = self['entryWidth']
def _setSigDigits(self):
sd = self['significantDigits']
sd = self['numDigits']
self.entryFormat = '%.' + '%d' % sd + 'f'
self.configure(Valuator_significantDigits = sd)
self.configure(Valuator_numDigits = sd)
# And refresh value to reflect change
for index in range(self['dim']):
self._refreshEntry(index)
@ -232,8 +232,10 @@ 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')
"""
# Disable floater group entry
self.component('fGroup').configure(
Valuator_entry_state = 'disabled')
@ -244,8 +246,10 @@ 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')
"""
# Disable floater group entry
self.component('fGroup').configure(
Valuator_entry_state = 'normal')
@ -302,7 +306,7 @@ class ColorEntry(VectorEntry):
('fGroup_labels', ('R','G','B','A'), None),
('min', 0.0, None),
('max', 255.0, None),
('significantDigits', 0, None),
('nuDigits', 0, None),
('Valuator_resolution', 1.0, None),
)
self.defineoptions(kw, optiondefs)

View File

@ -110,15 +110,17 @@ class WidgetPropertiesDialog(Toplevel):
extra = ''
# Set up help string and validator based upon type
if entryType == 'real':
# Only allow real numbers
entry['validate'] = { 'validator' : self.realOrNone }
if helpString is None:
helpString = 'Enter a floating point number' + extra + '.'
elif entryType == 'integer':
# Only allow integer values
entry['validate'] = { 'validator' : self.intOrNone }
if helpString is None:
helpString = 'Enter an integer' + extra + '.'
else:
entry['validate'] = { 'validator' : 'alphanumeric' }
# Anything goes with a string widget
if helpString is None:
helpString = 'Enter a string' + extra + '.'
# Bind balloon with help string to entry