瀏覽代碼

*** empty log message ***

Mark Mine 24 年之前
父節點
當前提交
958232dc2d
共有 2 個文件被更改,包括 354 次插入6 次删除
  1. 350 2
      direct/src/tkwidgets/Dial.py
  2. 4 4
      direct/src/tkwidgets/EntryScale.py

+ 350 - 2
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', '<ButtonPress-1>', self.mouseDown)
+        self._canvas.tag_bind('dial', '<B1-Motion>', self.mouseMotion)
+        self._canvas.tag_bind('dial', '<Shift-B1-Motion>',
+                              self.shiftMouseMotion)
+        self._canvas.tag_bind('dial', '<ButtonRelease-1>', self.mouseUp)
+        self._canvas.tag_bind('knob', '<ButtonPress-1>', self.knobMouseDown)
+        self._canvas.tag_bind('knob', '<B1-Motion>', self.knobMouseMotion)
+        self._canvas.tag_bind('knob', '<ButtonRelease-1>', self.knobMouseUp)
+        self._canvas.tag_bind('knob', '<Enter>', self.highlightKnob)
+        self._canvas.tag_bind('knob', '<Leave>', self.restoreKnob)
+        self._canvas.bind('<Double-ButtonPress-1>', self.mouseReset)
+        self._canvas.bind('<ButtonPress-3>', self.popupDialMenu)
+
+        # Make sure input variables processed 
+        self.initialiseoptions(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()

+ 4 - 4
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