| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490 |
- """
- Slider Class: Velocity style controller for floating point values with
- a label, entry (validated), and min/max slider
- """
- from TkGlobal import *
- from Valuator import *
- import Task
- import math
- import string
- import operator
- from PandaModules import ClockObject
- 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 = (
- ('min', 0.0, self.setMin),
- ('max', 100.0, self.setMax),
- ('style', VALUATOR_MINI, INITOPT),
- )
- self.defineoptions(kw, optiondefs)
- Valuator.__init__(self, parent)
- # Can not enter None for min or max, update propertyDict to reflect
- self.propertyDict['min']['fNone'] = 0
- self.propertyDict['min']['help'] = 'Minimum allowable value.'
- self.propertyDict['max']['fNone'] = 0
- self.propertyDict['max']['help'] = 'Maximum allowable value.'
- 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._widget.bind('<Double-ButtonPress-1>', self.mouseReset)
- # Add popup bindings to slider widget
- try:
- self._valuator._arrowBtn.bind(
- '<ButtonPress-3>', self._popupValuatorMenu)
- except AttributeError:
- pass
- self._valuator._minLabel.bind(
- '<ButtonPress-3>', self._popupValuatorMenu)
- self._valuator._maxLabel.bind(
- '<ButtonPress-3>', self._popupValuatorMenu)
- def packValuator(self):
- if self['style'] == VALUATOR_FULL:
- if self._label:
- self._label.grid(row = 0, column = 0, sticky = EW)
- self._entry.grid(row = 0, column = 1, sticky = EW)
- self._valuator.grid(row = 1, columnspan = 2,
- padx = 2, pady = 2, sticky = 'ew')
- self.interior().columnconfigure(0, weight = 1)
- else:
- if self._label:
- self._label.grid(row=0,column=0, sticky = EW)
- self._entry.grid(row=0,column=1, sticky = EW)
- self._valuator.grid(row=0,column=2, padx = 2, pady = 2)
- self.interior().columnconfigure(0, weight = 1)
- def setMin(self):
- if self['min'] is not None:
- self._valuator['min'] = self['min']
- def setMax(self):
- if self['max'] is not None:
- self._valuator['max'] = self['max']
- # Based on Pmw ComboBox code.
- class SliderWidget(Pmw.MegaWidget):
- def __init__(self, parent = None, **kw):
- # Define the megawidget options.
- INITOPT = Pmw.INITOPT
- optiondefs = (
- # Appearance
- ('style', VALUATOR_MINI, INITOPT),
- ('relief', RAISED, self.setRelief),
- ('borderwidth', 2, self.setBorderwidth),
- ('background', 'SystemButtonFace', self.setBackground),
- ('fliparrow', 0, INITOPT),
- # Behavior
- # Bounds
- ('min', 0.0, self.setMin),
- ('max', 100.0, self.setMax),
- # 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)
- # Initialise the base class (after defining the options).
- Pmw.MegaWidget.__init__(self, parent)
- # Create the components.
- interior = self.interior()
- # Current value
- self.value = self['value']
- self.formatString = '%2f'
- self.increment = 0.01
- # Interaction flags
- self._isPosted = 0
- self._fUnpost = 0
- self._fUpdate = 0
- self._firstPress = 1
- self._fPressInsde = 0
- # Slider dimensions
- width = 100
- self.xPad = xPad = 10
- sliderWidth = width + 2 * xPad
- height = 20
- self.left = left = -(width/2.0)
- self.right = right = (width/2.0)
- top = -5
- bottom = top + height
- def createSlider(parent):
- # Create the slider inside the dropdown window.
- # Min label
- self._minLabel = Label(parent, text = self['min'], width = 8,
- anchor = W)
- self._minLabel.pack(side = LEFT)
- # Slider widget
- if self['style'] == VALUATOR_FULL:
- # Use a scale slider
- self._widgetVar = DoubleVar()
- self._widgetVar.set(self['value'])
- self._widget = self.createcomponent(
- 'slider', (), None,
- Scale, (interior,),
- variable = self._widgetVar,
- from_ = self['min'], to = self['max'],
- width = 10,
- orient = 'horizontal',
- showvalue = 0,
- length = sliderWidth,
- relief = FLAT, bd = 2,
- highlightthickness = 0)
- else:
- # Use a canvas slider
- self._widget = self.createcomponent(
- 'slider', (), None,
- Canvas, (parent,),
- width = sliderWidth,
- height = height,
- bd = 2,
- highlightthickness = 0,
- scrollregion = (left - xPad, top, right + xPad, bottom))
- # Interaction marker
- xShift = 1
- # Shadow arrow
- self._marker = self._widget.create_polygon(-7 + xShift, 12,
- 7 + xShift, 12,
- xShift, 0,
- fill = 'black',
- tags = ('marker',))
- # Arrow
- self._widget.create_polygon(-6.0, 10,
- 6.0, 10,
- 0, 0,
- fill = 'grey85',
- outline = 'black',
- tags = ('marker',))
- # The indicator
- self._widget.create_line(left, 0,
- right, 0,
- width = 2,
- tags = ('line',))
- self._widget.pack(side = LEFT, expand=1, fill=X)
- # Max label
- self._maxLabel = Label(parent, text = self['max'], width = 8,
- anchor = W)
- self._maxLabel.pack(side = LEFT)
- # Create slider
- if self['style'] == VALUATOR_MINI:
- # Create the arrow button to invoke slider
- self._arrowBtn = self.createcomponent(
- 'arrowbutton',
- (), None,
- Canvas, (interior,), borderwidth = 0,
- relief = FLAT, width = 14, height = 14,
- scrollregion = (-7,-7,7,7))
- self._arrowBtn.pack(expand = 1, fill = BOTH)
- self._arrowBtn.create_polygon(-5, -5, 5, -5, 0, 5,
- fill = 'grey50',
- tags = 'arrow')
- self._arrowBtn.create_line(-5, 5, 5, 5,
- fill = 'grey50',
- tags = 'arrow')
- # Create the dropdown window.
- self._popup = self.createcomponent(
- 'popup',
- (), None,
- Toplevel, (interior,),
- relief = RAISED, borderwidth = 2)
- self._popup.withdraw()
- self._popup.overrideredirect(1)
- # Create popup slider
- createSlider(self._popup)
- # Bind events to the arrow button.
- self._arrowBtn.bind('<1>', self._postSlider)
- self._arrowBtn.bind('<Enter>', self.highlightWidget)
- self._arrowBtn.bind('<Leave>', self.restoreWidget)
- # Need to unpost the popup if the arrow Button is unmapped (eg:
- # its toplevel window is withdrawn) while the popup slider is
- # displayed.
- self._arrowBtn.bind('<Unmap>', self._unpostSlider)
-
- # Bind events to the dropdown window.
- self._popup.bind('<Escape>', self._unpostSlider)
- self._popup.bind('<ButtonRelease-1>', self._widgetBtnRelease)
- self._popup.bind('<ButtonPress-1>', self._widgetBtnPress)
- self._popup.bind('<Motion>', self._widgetMove)
-
- self._widget.bind('<Left>', self._decrementValue)
- self._widget.bind('<Right>', self._incrementValue)
- self._widget.bind('<Shift-Left>', self._bigDecrementValue)
- self._widget.bind('<Shift-Right>', self._bigIncrementValue)
- self._widget.bind('<Home>', self._goToMin)
- self._widget.bind('<End>', self._goToMax)
- else:
- createSlider(interior)
- self._widget['command'] = self._firstScaleCommand
-
- # Check keywords and initialise options.
- self.initialiseoptions(SliderWidget)
- # Adjust relief
- if not kw.has_key('relief'):
- if self['style'] == VALUATOR_FULL:
- self['relief'] = FLAT
- self.updateIndicator(self['value'])
- def destroy(self):
- if (self['style'] == VALUATOR_MINI) and self._isPosted:
- Pmw.popgrab(self._popup)
- Pmw.MegaWidget.destroy(self)
- #======================================================================
- # Public methods
- 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 get(self):
- """
- self.get()
- Get current slider value
- """
- return self.value
- def updateIndicator(self, value):
- if self['style'] == VALUATOR_MINI:
- # Get current marker position
- percentX = (value - self['min'])/float(self['max'] - self['min'])
- newX = percentX * (self.right - self.left) + self.left
- markerX = self._getMarkerX()
- dx = newX - markerX
- self._widget.move('marker', dx, 0)
- else:
- # Update scale's variable, which update scale without
- # Calling scale's command
- self._widgetVar.set(value)
-
- #======================================================================
- # Private methods for slider.
- def _postSlider(self, event = None):
- self._isPosted = 1
- self._fUpdate = 0
- # Make sure that the arrow is displayed sunken.
- self.interior()['relief'] = SUNKEN
- self.update_idletasks()
- # Position popup so that marker is immediately below center of
- # Arrow button
- # Find screen space position of bottom/center of arrow button
- x = (self._arrowBtn.winfo_rootx() + self._arrowBtn.winfo_width()/2.0 -
- string.atoi(self.interior()['bd']))
- y = self._arrowBtn.winfo_rooty() + self._arrowBtn.winfo_height()
- # Popup border width
- bd = string.atoi(self._popup['bd'])
- # Get width of label
- minW = self._minLabel.winfo_width()
- # Width of canvas to adjust for
- cw = (self._getMarkerX() - self.left ) + self.xPad
- popupOffset = bd + minW + cw
- ch = self._widget.winfo_height()
- sh = self.winfo_screenheight()
- # Compensate if too close to edge of screen
- if y + ch > sh and y > sh / 2:
- y = self._arrowBtn.winfo_rooty() - ch
- # Popup window
- Pmw.setgeometryanddeiconify(self._popup, '+%d+%d' % (x-popupOffset, y))
- # Grab the popup, so that all events are delivered to it, and
- # set focus to the slider, to make keyboard navigation
- # easier.
- Pmw.pushgrab(self._popup, 1, self._unpostSlider)
- self._widget.focus_set()
- # Ignore the first release of the mouse button after posting the
- # dropdown slider, unless the mouse enters the dropdown slider.
- self._fUpdate = 0
- self._fUnpost = 0
- self._firstPress = 1
- self._fPressInsde = 0
- def _updateValue(self,event):
- mouseX = self._widget.canvasx(
- event.x_root - self._widget.winfo_rootx())
- if mouseX < self.left:
- mouseX = self.left
- if mouseX > self.right:
- mouseX = self.right
- # Update value
- sf = (mouseX - self.left)/(self.right - self.left)
- newVal = sf * (self['max'] - self['min']) + self['min']
- self.set(newVal)
- def _widgetBtnPress(self, event):
- # Check behavior for this button press
- widget = self._popup
- xPos = event.x_root - widget.winfo_rootx()
- yPos = event.y_root - widget.winfo_rooty()
- fInside = ((xPos > 0) and (xPos < widget.winfo_width()) and
- (yPos > 0) and (yPos < widget.winfo_height()))
- # Set flags based upon result
- if fInside:
- self._fPressInside = 1
- self._fUpdate = 1
- if self['preCallback']:
- apply(self['preCallback'], self['callbackData'])
- self._updateValue(event)
- else:
- self._fPressInside = 0
- self._fUpdate = 0
-
- def _widgetMove(self, event):
- if self._firstPress and not self._fUpdate:
- canvasY = self._widget.canvasy(
- event.y_root - self._widget.winfo_rooty())
- if canvasY > 0:
- self._fUpdate = 1
- if self['preCallback']:
- apply(self['preCallback'], self['callbackData'])
- self._unpostOnNextRelease()
- elif self._fUpdate:
- self._updateValue(event)
- def _widgetBtnRelease(self, event):
- # Do post callback if any
- if self._fUpdate and self['postCallback']:
- apply(self['postCallback'], self['callbackData'])
- if (self._fUnpost or
- (not (self._firstPress or self._fPressInside))):
- self._unpostSlider()
- # Otherwise, continue
- self._fUpdate = 0
- self._firstPress = 0
- self._fPressInside = 0
- def _unpostOnNextRelease(self, event = None):
- self._fUnpost = 1
- def _unpostSlider(self, event=None):
- if not self._isPosted:
- # It is possible to get events on an unposted popup. For
- # example, by repeatedly pressing the space key to post
- # and unpost the popup. The <space> event may be
- # delivered to the popup window even though
- # Pmw.popgrab() has set the focus away from the
- # popup window. (Bug in Tk?)
- return
- # Restore the focus before withdrawing the window, since
- # otherwise the window manager may take the focus away so we
- # can't redirect it. Also, return the grab to the next active
- # window in the stack, if any.
- Pmw.popgrab(self._popup)
- self._popup.withdraw()
- self._isPosted = 0
- # Raise up arrow button
- self.interior()['relief'] = RAISED
- def _incrementValue(self, event):
- self.set(self.value + self.increment)
- def _bigIncrementValue(self, event):
- self.set(self.value + self.increment * 10.0)
- def _decrementValue(self, event):
- self.set(self.value - self.increment)
- def _bigDecrementValue(self, event):
- self.set(self.value - self.increment * 10.0)
- def _goToMin(self, event):
- self.set(self['min'])
- def _goToMax(self, event):
- self.set(self['max'])
- def _firstScaleCommand(self, val):
- """ Hack to avoid calling command on instantiation of Scale """
- self._widget['command'] = self._scaleCommand
- def _scaleCommand(self, val):
- self.set(string.atof(val))
- # Methods to modify floater characteristics
- def setMin(self):
- self._minLabel['text'] = self.formatString % self['min']
- if self['style'] == VALUATOR_FULL:
- self._widget['from_'] = self['min']
- self.updateIndicator(self.value)
- def setMax(self):
- self._maxLabel['text'] = self.formatString % self['max']
- if self['style'] == VALUATOR_FULL:
- self._widget['to'] = self['max']
- self.updateIndicator(self.value)
- def setNumDigits(self):
- self.formatString = '%0.' + ('%d' % self['numDigits']) + 'f'
- self._minLabel['text'] = self.formatString % self['min']
- self._maxLabel['text'] = self.formatString % self['max']
- self.updateIndicator(self.value)
- self.increment = pow(10, -self['numDigits'])
- def _getMarkerX(self):
- # Get marker triangle coordinates
- c = self._widget.coords(self._marker)
- # Marker postion defined as X position of third vertex
- return c[4]
- def setRelief(self):
- self.interior()['relief'] = self['relief']
- def setBorderwidth(self):
- self.interior()['borderwidth'] = self['borderwidth']
- def setBackground(self):
- self._widget['background'] = self['background']
- def highlightWidget(self, event):
- self._arrowBtn.itemconfigure('arrow', fill = 'black')
- def restoreWidget(self, event):
- self._arrowBtn.itemconfigure('arrow', fill = 'grey50')
|