diff --git a/direct/src/tkwidgets/Slider.py b/direct/src/tkwidgets/Slider.py index f7b3f4e1cb..29d4e231e2 100644 --- a/direct/src/tkwidgets/Slider.py +++ b/direct/src/tkwidgets/Slider.py @@ -17,8 +17,8 @@ SLIDER_MINI = 'mini' SLIDER_FULL_WIDTH = 50 SLIDER_FULL_HEIGHT = 25 -SLIDER_MINI_WIDTH = 22 -SLIDER_MINI_HEIGHT = 18 +SLIDER_MINI_WIDTH = 16 +SLIDER_MINI_HEIGHT = 16 globalClock = ClockObject.getGlobalClock() @@ -121,14 +121,12 @@ class SliderWidget(Pmw.MegaWidget): height = self['height'] halfWidth = width/2.0 + halfHeight = height/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,), @@ -275,30 +273,453 @@ class PopupSliderWidget(Pmw.MegaToplevel): def __init__(self, parent = None, **kw): optiondefs = ( ('width', 150, None), - ('height', 30, None), + ('height', 25, None), ('xoffset', 0, None), # pixels ('yoffset', 1, None), # pixels ) self.defineoptions(kw, optiondefs) Pmw.MegaToplevel.__init__(self, parent) + interior = self.interior() self.withdraw() self.overrideredirect(1) - self.interior()['relief'] = RAISED - self.interior()['borderwidth'] = 3 - self.b = Button(self.interior(), text = 'hello') + interior['relief'] = RAISED + interior['borderwidth'] = 2 + left = -self['width']/2.0 + right = self['width']/2.0 + top = -10 + bottom = top + self['height'] + self._canvas = self.createcomponent('canvas', (), None, + Canvas, (interior,), + relief = FLAT, + width = self['width'], + height = self['height'], + highlightthickness = 0, + scrollregion = (left, top, + right, bottom) + ) + self._canvas.pack(expand = 1, fill = BOTH) + self.marker = self._canvas.create_polygon(-6.9 + 1,12, + 6.9+1,12, + 1, 0, + fill = 'black', + tags = ('slider',)) + self._canvas.create_polygon(-5.75,10, + 5.75,10, + 0, 0, + fill = 'grey85', + outline = 'black', + tags = ('slider',)) + # The indicator + self.lineLeft = lineLeft = left + 10 + self.lineRight = lineRight = right -10 + self._canvas.create_line(lineLeft, 1, + lineRight, 1, + width = 2) + + self.b = Button(interior, text = 'hello') self.b['command'] = self.withdraw self.b.pack() self.initialiseoptions(PopupSliderWidget) + def showPopup(self, widget): x = widget.winfo_rootx() + widget.winfo_width() - self['width'] y = widget.winfo_rooty() + widget.winfo_height() Pmw.setgeometryanddeiconify(self, '%dx%d+%d+%d' % (self['width'], self['height'],x,y)) + """ + pw = PopupSliderWidget() tl = Toplevel() -b = Button(tl, text = 'hello') -b.pack(expand = 1, fill = X) -b.bind('', lambda event, s = b: pw.showPopup(b)) -#b.bind('', lambda event: pw.withdraw()) +b = Button(tl, text = 'V') +b.pack() + +fCrossedLine = 0 +def move(event): + global fCrossedLine + if fCrossedLine: + newX = pw._canvas.canvasx(event.x_root) - pw.winfo_rootx() + if newX < pw.lineLeft: + newX = pw.lineLeft + elif newX > pw.lineRight: + newX = pw.lineRight + print (newX - pw.lineLeft)/(pw.lineRight - pw.lineLeft) + startX = getX() + dx = newX - startX + pw._canvas.move('slider', dx, 0) + else: + if event.y_root >= pw.winfo_rooty() + 10: + fCrossedLine = 1 + +def getX(): + c = pw._canvas.coords(pw.marker) + return c[4] + +def press(event): + global fCrossedLine + print 'press' + fCrossedLine = 0 + pw.showPopup(b) + b.bind('', move) + +def unpostCanvas(event): + print 'unpostCanvas', event.x_root, pw._canvas.winfo_rootx() + if event.x_root < pw._canvas.winfo_rootx(): + print 'blah' + Pmw.popgrab(pw._canvas) + pw._canvas.withdraw() + +def popupPress(event): + global fCrossedLine + print 'popupPress' + fCrossedLine = 1 + pw._canvas.bind('', move) + +def release(event): + if fCrossedLine: + pw.withdraw() + b.unbind('') + pw._canvas.unbind('') + else: + Pmw.pushgrab(pw._canvas, 1, unpostCanvas) + pw._canvas.focus_set() + +b.bind('', press) +b.bind('', release) +pw._canvas.bind('', popupPress) +pw._canvas.bind('', release) + + """ + + +# Based on Pmw ComboBox code. +class PopupSlider(Pmw.MegaWidget): + def __init__(self, parent = None, **kw): + + # Define the megawidget options. + INITOPT = Pmw.INITOPT + optiondefs = ( + ('dropdown', 1, INITOPT), + ('buttonaspect', 1.0, INITOPT), + ('fliparrow', 0, INITOPT), + ('labelmargin', 0, INITOPT), + ('labelpos', None, INITOPT), + # 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) + + # 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'] + + # Interaction flags + self._fUpdate = 0 + self._fUnpost = 0 + self._firstPress = 1 + self._fPressInsde = 0 + + self._entryfield = self.createcomponent('entryfield', + (('entry', 'entryfield_entry'),), None, + Pmw.EntryField, (interior,)) + self._entryfield.grid(column=2, row=2, sticky='nsew') + interior.grid_columnconfigure(2, weight = 1) + self._entryWidget = self._entryfield.component('entry') + + # Slider dimensions + width = 100 + xPad = 10 + canvasWidth = width + 2 * xPad + height = 20 + self.left = left = -(width/2.0) + self.right = right = (width/2.0) + top = -5 + bottom = top + height + + # Create slider + if self['dropdown']: + self._isPosted = 0 + interior.grid_rowconfigure(2, weight = 1) + + # Create the arrow button. + self._arrowBtn = self.createcomponent('arrowbutton', + (), None, + Canvas, (interior,), borderwidth = 2, + relief = 'raised', + width = 16, height = 16) + self._arrowBtn.grid(column=3, row=2) + self._arrowRelief = self._arrowBtn.cget('relief') + + # Create the label. + self.createlabel(interior, childCols=2) + + # Create the dropdown window. + self._popup = self.createcomponent( + 'popup', + (), None, + Toplevel, (interior,), + relief = RAISED, borderwidth = 2) + self._popup.withdraw() + self._popup.overrideredirect(1) + + # Create the canvas inside the dropdown window. + # Min label + self._minLabel = Label(self._popup, text = 'MINAAAAAA') + self._minLabel.pack(side = LEFT) + # Slider + self._canvas = self.createcomponent( + 'canvas', (), None, + Canvas, (self._popup,), + width = canvasWidth, + height = height, + bd = 3, + highlightthickness = 0, + scrollregion = (left - xPad, top, right + xPad, bottom)) + self._canvas.pack(side = LEFT, expand=1, fill='both') + # Max label + self._maxLabel = Label(self._popup, text = 'MAX') + self._maxLabel.pack(side = LEFT) + + # Bind events to the arrow button. + self._arrowBtn.bind('<1>', self._postCanvas) + self._arrowBtn.bind('', self._drawArrow) + + # Bind events to the dropdown window. + self._popup.bind('', self._unpostCanvas) + self._popup.bind('', self._dropdownBtnRelease) + self._popup.bind('', self._dropdownBtnPress) + self._popup.bind('', self._dropdownMove) + + # Bind events to the Tk listbox. + #self._canvas.bind('', self._unpostOnNextRelease) + + # Bind events to the Tk entry widget. + self._entryWidget.bind('', self._resizeArrow) + + # Need to unpost the popup if the entryfield is unmapped (eg: + # its toplevel window is withdrawn) while the popup canvas is + # displayed. + self._entryWidget.bind('', self._unpostCanvas) + else: + # Create the slider below the entry field. + self._canvas = self.createcomponent( + 'canvas', (), None, + Canvas, (interior,), + width = canvasWidth, + height = height, + highlightthickness = 0, + scrollregion = (left - xPad, top, right + xPad, bottom)) + self._canvas.grid(column=2, row=3, sticky='nsew') + + # The scrolled canvas should expand vertically. + interior.grid_rowconfigure(3, weight = 1) + + # Create the label. + self.createlabel(interior, childRows=2) + + # Interaction marker + xShift = 1 + # Shadow arrow + self._marker = self._canvas.create_polygon(-7 + xShift, 12, + 7 + xShift, 12, + xShift, 0, + fill = 'black', + tags = ('slider',)) + # Arrow + self._canvas.create_polygon(-6.0, 10, + 6.0, 10, + 0, 0, + fill = 'grey85', + outline = 'black', + tags = ('slider',)) + # The indicator + self._canvas.create_line(left, 0, + right, 0, + width = 2) + + + # Check keywords and initialise options. + self.initialiseoptions(PopupSlider) + + def destroy(self): + if self['dropdown'] 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 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 + + #====================================================================== + + # Private methods for dropdown canvas. + + def _setNumDigits(self): + pass + + def _drawArrow(self, event=None, sunken=0): + arrow = self._arrowBtn + if sunken: + self._arrowRelief = arrow.cget('relief') + arrow.configure(relief = 'sunken') + else: + arrow.configure(relief = self._arrowRelief) + + if self._isPosted and self['fliparrow']: + direction = 'up' + else: + direction = 'down' + Pmw.drawarrow(arrow, self['entry_foreground'], direction, 'arrow') + + def _postCanvas(self, event = None): + self._isPosted = 1 + self._fUpdate = 0 + self._drawArrow(sunken=1) + + # Make sure that the arrow is displayed sunken. + self.update_idletasks() + + x = self._entryfield.winfo_rootx() + y = self._entryfield.winfo_rooty() + self._entryfield.winfo_height() + w = self._entryfield.winfo_width() + self._arrowBtn.winfo_width() + minW = self._minLabel.winfo_width() + cw = self._canvas.winfo_width() + maxW = self._maxLabel.winfo_width() + pw = minW + cw + maxW + ch = self._canvas.winfo_height() + sh = self.winfo_screenheight() + + # Compensate if too close to edge of screen + if y + ch > sh and y > sh / 2: + y = self._entryfield.winfo_rooty() - ch + + Pmw.setgeometryanddeiconify(self._popup, '+%d+%d' % (x + w - pw, y)) + + # Grab the popup, so that all events are delivered to it, and + # set focus to the canvas, to make keyboard navigation + # easier. + Pmw.pushgrab(self._popup, 1, self._unpostCanvas) + self._canvas.focus_set() + + self._drawArrow() + + # Ignore the first release of the mouse button after posting the + # dropdown canvas, unless the mouse enters the dropdown canvas. + self._fUpdate = 0 + self._fUnpost = 0 + self._firstPress = 1 + self._fPressInsde = 0 + + def _updateValue(self,event): + canvasX = self._canvas.canvasx( + event.x_root - self._canvas.winfo_rootx()) + if canvasX < self.left: + canvasX = self.left + if canvasX > self.right: + canvasX = self.right + # Get current marker position + currX = self._getMarkerX() + dx = canvasX - currX + self._canvas.move('slider', dx, 0) + + def _dropdownBtnPress(self, event): + self._fUpdate = 1 + self._fPressInside = 1 + self._updateValue(event) + + def _dropdownMove(self, event): + if self._firstPress and not self._fUpdate: + canvasY = self._canvas.canvasy( + event.y_root - self._canvas.winfo_rooty()) + if canvasY > 0: + self._fUpdate = 1 + self._unpostOnNextRelease() + elif self._fUpdate: + self._updateValue(event) + + def _dropdownBtnRelease(self, event): + if (self._fUnpost or + (not (self._firstPress or self._fPressInside))): + self._unpostCanvas() + # Otherwise, continue + self._fUpdate = 0 + self._firstPress = 0 + self._fPressInside = 0 + + def _unpostOnNextRelease(self, event = None): + self._fUnpost = 1 + + def _resizeArrow(self, event): + bw = (string.atoi(self._arrowBtn['borderwidth']) + + string.atoi(self._arrowBtn['highlightthickness'])) + newHeight = self._entryfield.winfo_reqheight() - 2 * bw + newWidth = int(newHeight * self['buttonaspect']) + self._arrowBtn.configure(width=newWidth, height=newHeight) + self._drawArrow() + + def _unpostCanvas(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 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 + self._drawArrow() + + def _getMarkerX(self): + # Get marker triangle coordinates + c = self._canvas.coords(self._marker) + # Marker postion defined as X position of third vertex + return c[4] + +Pmw.forwardmethods(PopupSlider, Pmw.EntryField, '_entryfield')