diff --git a/direct/src/tkwidgets/Dial.py b/direct/src/tkwidgets/Dial.py index 772997043a..7707a810d1 100644 --- a/direct/src/tkwidgets/Dial.py +++ b/direct/src/tkwidgets/Dial.py @@ -1,5 +1,7 @@ from Tkinter import * from tkSimpleDialog import askfloat +from PandaModules import ClockObject +import Task import Pmw import math import string @@ -13,9 +15,11 @@ import operator TWO_PI = 2.0 * math.pi ONEPOINTFIVE_PI = 1.5 * math.pi POINTFIVE_PI = 0.5 * math.pi -INNER_SF = 0.175 +INNER_SF = 0.2 MAX_EXP = 5 +globalClock = ClockObject.getGlobalClock() + class Dial(Pmw.MegaWidget): def __init__(self, parent = None, **kw): #define the megawidget options @@ -289,10 +293,10 @@ class Dial(Pmw.MegaWidget): def validateEntryInput(self, event): input = self._entryVal.get() try: + self._entry.configure(background = self._entryBackground) newValue = string.atof(input) apply(self.onReturn, self['callbackData']) self.set(newValue) - self._entry.configure(background = self._entryBackground) apply(self.onReturnRelease, self['callbackData']) except ValueError: self._entry.configure(background = 'Pink') @@ -524,6 +528,350 @@ class AngleDial(Dial): # Needed because this method checks if self.__class__ is myClass # where myClass is the argument passed into inialiseoptions self.initialiseoptions(AngleDial) + + +class MiniDial(Pmw.MegaWidget): + sfBase = 3.0 + sfDist = 15 + def __init__(self, parent = None, **kw): + #define the megawidget options + INITOPT = Pmw.INITOPT + optiondefs = ( + ## Appearance + # Edge size of the dial + ('size', 50, INITOPT), + # Widget relief + ('relief', GROOVE, self.setRelief), + # Widget borderwidth + ('borderwidth', 2, self.setBorderwidth), + # Number of segments the dial is divided into + ('numSegments', 10, self.setNumSegments), + ## Values + # Initial value of dial, use self.set to change value + ('value', 0.0, INITOPT), + ('min', 0.0, self.setDelta), + ('max', 1.0, self.setDelta), + # Value dial jumps to on reset + ('resetValue', 0.0, None), + ## Behavior + # Able to adjust max/min + ('fAdjustable', 1, None), + # Snap to angle on/off + ('fSnap', 0, None), + # Do values rollover (i.e. accumulate) with multiple revolutions + ('fRollover', 1, None), + # Command to execute on dial updates + ('command', None, None), + # Extra data to be passed to command function + ('commandData', [], None), + # Extra data to be passed to callback function + ('callbackData', [], None), + ) + self.defineoptions(kw, optiondefs) + + # Initialize the superclass + Pmw.MegaWidget.__init__(self, parent) + + # Set up some local and instance variables + + # Running total which increments/decrements every time around dial + self.rollCount = 0 + # Current angle of the dial + self.dialAngle = None + # Current value + self.value = self['value'] + + # Create the components + interior = self.interior() + dim = self['size'] + # Radius of the dial + radius = self.radius = int(dim/2.0) + # Radius of the inner knob + inner_radius = max(3,radius * INNER_SF) + + # The canvas + self._canvas = self.createcomponent('canvas', (), None, + Canvas, (interior,), + width = dim, height = dim, + scrollregion = (-radius,-radius, + radius, radius)) + self._canvas.pack(expand = 1, fill = BOTH) + + # The dial face (no outline/fill, primarily for binding mouse events) + self._canvas.create_oval(-radius, -radius, radius, radius, + outline = '', + tags = ('dial',)) + + # The indicator + self._canvas.create_line(0, 0, 0, -radius, width = 2, + tags = ('indicator', 'dial')) + + # The central knob + self._canvas.create_oval(-inner_radius, -inner_radius, + inner_radius, inner_radius, + fill = '#A0A0A0', + tags = ('knob',)) + + # The popup menu + self._popupMenu = Menu(interior, tearoff = 0) + self._fSnap = IntVar() + self._fSnap.set(self['fSnap']) + self._popupMenu.add_checkbutton(label = 'Snap', + variable = self._fSnap, + command = self.setSnap) + self._fRollover = IntVar() + self._fRollover.set(self['fRollover']) + if self['fAdjustable']: + self._popupMenu.add_checkbutton(label = 'Rollover', + variable = self._fRollover, + command = self.setRollover) + self._popupMenu.add_command(label = 'Min...', + command = self.getMin) + self._popupMenu.add_command(label = 'Max...', + command = self.getMax) + self._popupMenu.add_command(label = 'Reset Value...', + command = self.getResetValue) + self._popupMenu.add_command(label = 'Reset Dial', + command = self.reset) + + # Add event bindings + self._canvas.tag_bind('dial', '', self.mouseDown) + self._canvas.tag_bind('dial', '', self.mouseMotion) + self._canvas.tag_bind('dial', '', + self.shiftMouseMotion) + self._canvas.tag_bind('dial', '', self.mouseUp) + self._canvas.tag_bind('knob', '', self.knobMouseDown) + self._canvas.tag_bind('knob', '', self.knobMouseMotion) + self._canvas.tag_bind('knob', '', self.knobMouseUp) + self._canvas.tag_bind('knob', '', self.highlightKnob) + self._canvas.tag_bind('knob', '', self.restoreKnob) + self._canvas.bind('', self.mouseReset) + self._canvas.bind('', self.popupDialMenu) + + # Make sure input variables processed + self.initialiseoptions(MiniDial) + + def set(self, value, fCommand = 1): + """ + self.set(value, fCommand = 1) + Set dial to new value, execute command if fCommand == 1 + """ + # Adjust for rollover + if not self['fRollover']: + if value > self['max']: + self.rollCount = 0 + value = self['min'] + ((value - self['min']) % self.delta) + if self.dialAngle: + self.updateIndicatorRadians(self.dialAngle) + self.dialAngle = None + else: + self.updateIndicator(value) + if fCommand and (self['command'] != None): + apply(self['command'], [value] + self['commandData']) + # Record value + self.value = value + + # Reset dial to reset value + def reset(self): + """ + self.reset() + Reset dial to reset value + """ + self.set(self['resetValue']) + + def mouseReset(self,event): + if not self._canvas.find_withtag(CURRENT): + self.reset() + + def get(self): + """ + self.get() + Get current dial value + """ + return self.value + + ## Canvas callback functions + # Dial + def mouseDown(self,event): + apply(self.onPress, self['callbackData']) + self.lastAngle = dialAngle = self.computeDialAngle(event) + self.computeValueFromAngle(dialAngle) + + def mouseUp(self,event): + apply(self.onRelease, self['callbackData']) + + def shiftMouseMotion(self,event): + self.mouseMotion(event, 1) + + def mouseMotion(self, event, fShift = 0): + dialAngle = self.computeDialAngle(event, fShift) + self.computeValueFromAngle(dialAngle) + + def computeDialAngle(self,event, fShift = 0): + x = self._canvas.canvasx(event.x) + y = self._canvas.canvasy(event.y) + rawAngle = math.atan2(y,x) + # Snap to grid + # Convert to dial coords to do snapping + dialAngle = rawAngle + POINTFIVE_PI + if operator.xor(self['fSnap'], fShift): + dialAngle = round(dialAngle / self.snapAngle) * self.snapAngle + return dialAngle + + def computeValueFromAngle(self, dialAngle): + delta = self.delta + dialAngle = dialAngle % TWO_PI + # Check for rollover, if necessary + if (self.lastAngle > ONEPOINTFIVE_PI) and (dialAngle < POINTFIVE_PI): + self.rollCount += 1 + elif (self.lastAngle < POINTFIVE_PI) and (dialAngle > ONEPOINTFIVE_PI): + self.rollCount -= 1 + self.lastAngle = dialAngle + # Update value + newValue = self['min'] + (self.rollCount + (dialAngle/TWO_PI)) * delta + self.dialAngle = dialAngle + self.set(newValue) + + def updateIndicator(self, value): + # compute new indicator angle + delta = self.delta + factors = divmod(value - self['min'], delta) + self.rollCount = factors[0] + self.updateIndicatorRadians( (factors[1]/delta) * TWO_PI ) + + def updateIndicatorDegrees(self, degAngle): + self.updateIndicatorRadians(degAngle * (math.pi/180.0)) + + def updateIndicatorRadians(self,dialAngle): + rawAngle = dialAngle - POINTFIVE_PI + # Compute end points + endx = math.cos(rawAngle) * self.radius + endy = math.sin(rawAngle) * self.radius + # Draw new indicator + self._canvas.coords('indicator', endx * INNER_SF, endy * INNER_SF, + endx, endy) + + # Knob velocity controller + def knobMouseDown(self,event): + apply(self.onPress, self['callbackData']) + self.knobSF = 0.0 + t = taskMgr.spawnMethodNamed(self.knobComputeVelocity, 'cv') + t.lastTime = globalClock.getFrameTime() + + def knobComputeVelocity(self, state): + # Update value + currT = globalClock.getFrameTime() + dt = currT - state.lastTime + self.set(self.value + self.delta * self.knobSF * dt) + state.lastTime = currT + return Task.cont + + def knobMouseMotion(self, event): + # What is the current knob angle + self.knobSF = self.computeKnobSF(event) + + def computeKnobSF(self, event): + x = self._canvas.canvasx(event.x) + y = self._canvas.canvasy(event.y) + sf = math.pow(MiniDial.sfBase, -10 + (abs(x) / MiniDial.sfDist)) + if x > 0: + return sf + else: + return -sf + + def knobMouseUp(self, event): + taskMgr.removeTasksNamed('cv') + self.knobSF = 0.0 + apply(self.onRelease, self['callbackData']) + + def highlightKnob(self, event): + self._canvas.itemconfigure('knob', fill = 'black') + + def restoreKnob(self, event): + self._canvas.itemconfigure('knob', fill = '#A0A0A0') + + # Methods to modify dial characteristics + def setNumSegments(self): + self._canvas.delete('ticks') + # Based upon input snap angle, how many ticks + numSegments = self['numSegments'] + # Compute snapAngle (radians) + self.snapAngle = snapAngle = TWO_PI / numSegments + # Create the ticks at the snap angles + for ticknum in range(numSegments): + angle = snapAngle * ticknum + # convert to canvas coords + angle = angle - POINTFIVE_PI + # Compute tick endpoints + startx = math.cos(angle) * self.radius + starty = math.sin(angle) * self.radius + # Elongate ticks at 90 degree points + if (angle % POINTFIVE_PI) == 0.0: + sf = 0.6 + else: + sf = 0.8 + endx = startx * sf + endy = starty * sf + self._canvas.create_line(startx, starty, endx, endy, + tags = ('ticks','dial')) + + def setRelief(self): + self.interior()['relief'] = self['relief'] + + def setBorderwidth(self): + self.interior()['borderwidth'] = self['borderwidth'] + + def setDelta(self): + self.delta = self['max'] - self['min'] + + # The following methods are used to handle the popup menu + def popupDialMenu(self,event): + self._popupMenu.post(event.widget.winfo_pointerx(), + event.widget.winfo_pointery()) + + # Turn angle snap on/off + def setSnap(self): + self['fSnap'] = self._fSnap.get() + + # Turn rollover (accumulation of a sum) on/off + def setRollover(self): + self['fRollover'] = self._fRollover.get() + + # This handles the popup dial min dialog + def getMin(self): + newMin = askfloat('Dial Min', 'Min:', + initialvalue = `self['min']`, + parent = self.interior()) + if newMin: + self['min'] = newMin + self.updateIndicator(self.value) + + # This handles the popup dial max dialog + def getMax(self): + newMax = askfloat('Dial Max', 'Max:', + initialvalue = `self['max']`, + parent = self.interior()) + if newMax: + self['max'] = newMax + self.updateIndicator(self.value) + + # This handles the popup dial resetValue dialog + def getResetValue(self): + newResetValue = askfloat('Dial ResetValue', 'ResetValue:', + initialvalue = `self['resetValue']`, + parent = self.interior()) + if newResetValue: + self['resetValue'] = newResetValue + + # User callbacks + def onPress(self, *args): + """ User redefinable callback executed on button press """ + pass + + def onRelease(self, *args): + """ User redefinable callback executed on button release """ + pass + if __name__ == '__main__': tl = Toplevel() diff --git a/direct/src/tkwidgets/EntryScale.py b/direct/src/tkwidgets/EntryScale.py index 8c6db2ad4c..aa469f2044 100644 --- a/direct/src/tkwidgets/EntryScale.py +++ b/direct/src/tkwidgets/EntryScale.py @@ -293,7 +293,7 @@ class EntryScaleGroup(Pmw.MegaToplevel): optiondefs = ( ('dim', DEFAULT_DIM, INITOPT), ('side', TOP, INITOPT), - ('title', 'EntryScale Group', None), + ('title', 'Group', None), # A tuple of initial values, one for each entryScale ('initialValue', DEFAULT_VALUE, INITOPT), # The command to be executed any time one of the entryScales is updated @@ -537,9 +537,9 @@ if __name__ == '__main__': group1 = EntryScaleGroup(root, dim = 4, title = 'Simple RGBA Panel', labels = ('R', 'G', 'B', 'A'), - EntryScale_min = 0.0, - EntryScale_max = 255.0, - EntryScale_resolution = 1.0, + Valuator_min = 0.0, + Valuator_max = 255.0, + Valuator_resolution = 1.0, command = printVal) # Uncomment this if you aren't running in IDLE