ソースを参照

*** empty log message ***

Mark Mine 24 年 前
コミット
d98d9cc244

+ 0 - 2
direct/src/tkpanels/DirectSessionPanel.py

@@ -989,7 +989,5 @@ class DirectSessionPanel(AppShell):
         
     def onDestroy(self, event):
         # Remove hooks
-        print 'here'
         for event, method in self.actionEvents:
             self.ignore(event)
-        print 'there'

+ 53 - 237
direct/src/tkwidgets/Dial.py

@@ -1,13 +1,11 @@
 from Tkinter import *
-from tkSimpleDialog import askfloat
 from PandaModules import ClockObject
-import Task
+from WidgetPropertiesDialog import *
 import Pmw
-import tkMessageBox
+import Task
 import math
 import string
 import operator
-import types
 
 TWO_PI = 2.0 * math.pi
 ONEPOINTFIVE_PI = 1.5 * math.pi
@@ -25,187 +23,6 @@ globalClock = ClockObject.getGlobalClock()
 
 from tkSimpleDialog import Dialog
 
-class WidgetPropertiesDialog(Toplevel):
-    """Class to open dialogs to adjust widget properties."""
-    def __init__(self, widget, propertyList, title = None, parent = None):
-        """Initialize a dialog.
-        Arguments:
-            propertyList -- a list of properties to be edited
-            parent -- a parent window (the application window)
-            title -- the dialog title
-        """
-        # Record widget and property list
-        self.widget = widget
-        self.propertyList = propertyList
-        # Use default parent if none specified
-        if not parent:
-            import Tkinter
-            parent = Tkinter._default_root
-        # Create toplevel window
-        Toplevel.__init__(self, parent)
-        self.transient(parent)
-        # Set title
-        if title:
-            self.title(title)
-        # Record parent
-        self.parent = parent
-        # Initialize result
-        self.result = None
-        # Create body
-        body = Frame(self)
-        self.initial_focus = self.body(body)
-        body.pack(padx=5, pady=5)
-        # Create OK Cancel button
-        self.buttonbox()
-        # Initialize window state
-        self.grab_set()
-        self.protocol("WM_DELETE_WINDOW", self.cancel)
-        self.geometry("+%d+%d" % (parent.winfo_rootx()+50,
-                                  parent.winfo_rooty()+50))
-        self.initial_focus.focus_set()
-        self.wait_window(self)
-        
-    def destroy(self):
-        """Destroy the window"""
-        self.propertyList = []
-        self.entryList = []
-        self.initial_focus = None
-        Toplevel.destroy(self)
-        
-    #
-    # construction hooks
-    def body(self, master):
-        """create dialog body.
-        return widget that should have initial focus. 
-        This method should be overridden, and is called
-        by the __init__ method.
-        """
-        self.labelList = []
-        self.entryList = []
-        count = 0
-        for propertySet in self.propertyList:
-            # Make singletons into lists
-            if type(propertySet) is not types.ListType:
-                propertySet = [propertySet]
-            # Name of widget property
-            property = propertySet[0]
-            initialvalue = self.widget[property]
-            try:
-                entryType = propertySet[1]
-            except IndexError:
-                entryType = 'float'
-            try:
-                fAllowNone = propertySet[2]
-            except IndexError:
-                fAllowNone = 0
-            # Create label
-            label = Label(master, text=property, justify=LEFT)
-            label.grid(row=count, col = 0, padx=5, sticky=W)
-            self.labelList.append(label)
-            # Create entry
-            entry = Entry(master)
-            entry.grid(row=count, col = 1, padx=5, sticky=W+E)
-            if initialvalue is None:
-                entry.insert(0, 'None')
-            else:
-                entry.insert(0, initialvalue)
-            if entryType == 'float':
-                validateFunc = self.validateFloat
-            elif entryType == 'int':
-                validateFunc = self.validateInt
-            else:
-                validateFunc = self.validateString
-            callback = (lambda event, vf = validateFunc,
-                        e=entry,p=property,fn = fAllowNone,: vf(e, p, fn))
-            entry.bind('<Return>', callback)
-            self.entryList.append(entry)
-            count += 1
-        # Set initial focus
-        if len(self.entryList) > 0:
-            entry = self.entryList[0]
-            entry.select_range(0, END)
-            # Set initial focus to first entry in the list
-            return self.entryList[0]
-        else:
-            # Just set initial focus to self
-            return self
-        
-    def buttonbox(self):
-        """add standard button box buttons. 
-        """
-        box = Frame(self)
-        # Create buttons
-        w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
-        w.pack(side=LEFT, padx=5, pady=5)
-        w = Button(box, text="Cancel", width=10, command=self.cancel)
-        w.pack(side=LEFT, padx=5, pady=5)
-        # Bind commands
-        self.bind("<Escape>", self.cancel)
-        # Pack
-        box.pack()
-        
-    #
-    # standard button semantics
-    def ok(self, event=None):
-        self.withdraw()
-        self.update_idletasks()
-        self.apply()
-        self.cancel()
-        
-    def cancel(self, event=None):
-        # put focus back to the parent window
-        self.parent.focus_set()
-        self.destroy()
-
-    def validateFloat(self, entry, property, fAllowNone):
-        value = entry.get()
-        errormsg =  "Please enter a floating point value"
-        if fAllowNone:
-            errormsg += "\nor the string 'None'"
-        try:
-            value = string.atof(value)
-        except ValueError:
-            if fAllowNone and (value == 'None'):
-                value = None
-            else:
-                tkMessageBox.showwarning(
-                    "Illegal value", errormsg, parent = self)
-                return 0
-        self.widget[property] = value
-        return 1
-
-    def validateInt(self, entry, property, fAllowNone):
-        value = entry.get()
-        errormsg =  "Please enter an integer value"
-        if fAllowNone:
-            errormsg += "\nor the string 'None'"
-        try:
-            value = string.atoi(value)
-        except ValueError:
-            if fAllowNone and (value == 'None'):
-                value = None
-            else:
-                tkMessageBox.showwarning(
-                    "Illegal value", errormsg, parent = self)
-                return 0
-        self.widget[property] = value
-        return 1
-
-    def validateString(self, entry, property, fAllowNone):
-        value = entry.get()
-        if fAllowNone and (value == 'None'):
-            value = None
-        self.widget[property] = value
-
-    def apply(self):
-        """process the data
-
-        This method is called automatically to process the data, *after*
-        the dialog is destroyed. By default, it does nothing.
-        """
-        pass # override
-
-
 
 
 class Dial(Pmw.MegaWidget):
@@ -254,12 +71,20 @@ class Dial(Pmw.MegaWidget):
                                           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')
+
         # 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._dial.popupDialMenu)
 
         # The entry
         self._entryVal = StringVar()
@@ -269,6 +94,7 @@ class Dial(Pmw.MegaWidget):
                                            width = 12,
                                            textvariable = self._entryVal)
         self._entry.bind('<Return>', self.validateEntryInput)
+        self._entry.bind('<ButtonPress-3>', self._dial.popupDialMenu)
         self._entryBackground = self._entry.cget('background')
 
         if self['style'] == DIAL_FULL:
@@ -374,6 +200,7 @@ class AngleDial(Dial):
 class DialWidget(Pmw.MegaWidget):
     sfBase = 3.0
     sfDist = 15
+    deadband = 10
     def __init__(self, parent = None, **kw):
         #define the megawidget options
         INITOPT = Pmw.INITOPT
@@ -396,7 +223,7 @@ class DialWidget(Pmw.MegaWidget):
             ('min',             None,           None),
             ('max',             None,           None),
             ('resolution',      None,           None),
-            ('numDigits',       2,              None),
+            ('numDigits',       2,              self.setNumDigits),
             # Value dial jumps to on reset
             ('resetValue',      0.0,            None),
             ## Behavior
@@ -435,6 +262,31 @@ 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,),
@@ -625,8 +477,11 @@ class DialWidget(Pmw.MegaWidget):
     def computeKnobSF(self, event):
         x = self._canvas.canvasx(event.x)
         y = self._canvas.canvasy(event.y)
-        minExp = math.floor(-self['numDigits']/math.log10(DialWidget.sfBase))
-        sf = math.pow(DialWidget.sfBase, minExp + (abs(x) / DialWidget.sfDist))
+        offset = max(0, abs(x) - DialWidget.deadband)
+        if offset == 0:
+            return 0
+        sf = math.pow(DialWidget.sfBase,
+                      self.minExp + offset/DialWidget.sfDist)
         if x > 0:
             return sf
         else:
@@ -674,6 +529,11 @@ class DialWidget(Pmw.MegaWidget):
     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(DialWidget.sfBase))        
+
     # The following methods are used to handle the popup menu
     def popupDialMenu(self,event):
         self._popupMenu.post(event.widget.winfo_pointerx(),
@@ -690,56 +550,12 @@ class DialWidget(Pmw.MegaWidget):
     # This handles the popup dial min dialog
     def getProperties(self):
         # Popup dialog to adjust widget properties
-        WidgetPropertiesDialog(self, [
-            ['min', 'float', 1],
-            ['min', 'float', 1],
-            ['base', 'float', 1],
-            ['delta', 'float', 0],
-            ['resetValue', 'float', 0]])
+        WidgetPropertiesDialog(
+            self.propertyDict,
+            propertyList = self.propertyList,
+            title = 'Dial Widget Properties',
+            parent = self._canvas)
             
-    def getMin(self):
-        newMin = askfloat('Dial Min', 'Min:',
-                          initialvalue = `self['min']`,
-                          parent = self.interior())
-        if newMin is not None:
-            self['min'] = newMin
-            self.updateIndicator(self.value)
-
-    # This handles the popup dial base value dialog
-    def getBase(self):
-        newBase = askfloat('Dial Base Value', 'Base:',
-                           initialvalue = `self['base']`,
-                           parent = self.interior())
-        if newBase is not None:
-            self['base'] = newBase
-            self.updateIndicator(self.value)
-
-    # This handles the popup dial delta dialog
-    def getDelta(self):
-        newDelta = askfloat('Delta Per Revolution', 'Delta:',
-                          initialvalue = `self['delta']`,
-                          parent = self.interior())
-        if newDelta is not None:
-            self['delta'] = newDelta
-            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 is not None:
-            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 is not None:
-            self['resetValue'] = newResetValue
-
     # User callbacks
     def _onButtonPress(self, *args):
         """ User redefinable callback executed on button press """

+ 221 - 0
direct/src/tkwidgets/WidgetPropertiesDialog.py

@@ -0,0 +1,221 @@
+from Tkinter import *
+import Pmw
+import types
+import string
+
+"""
+TODO:
+  Checkboxes for None?
+  Floaters to adjust float values
+  OK and Cancel to allow changes to be delayed
+  Something other than Return to accept a new value
+"""
+
+class WidgetPropertiesDialog(Toplevel):
+    """Class to open dialogs to adjust widget properties."""
+    def __init__(self, propertyDict, propertyList = None, parent = None,
+                 title = 'Widget Properties'):
+        """Initialize a dialog.
+        Arguments:
+            propertyDict -- a dictionary of properties to be edited
+            parent -- a parent window (the application window)
+            title -- the dialog title
+        """
+        # Record property list
+        self.propertyDict = propertyDict
+        self.propertyList = propertyList
+        if self.propertyList is None:
+            self.propertyList = self.propertyDict.keys()
+            self.propertyList.sort()
+        # Use default parent if none specified
+        if not parent:
+            import Tkinter
+            parent = Tkinter._default_root
+        # Create toplevel window
+        Toplevel.__init__(self, parent)
+        self.transient(parent)
+        # Set title
+        if title:
+            self.title(title)
+        # Record parent
+        self.parent = parent
+        # Initialize modifications
+        self.modifiedDict = {}
+        # Create body
+        body = Frame(self)
+        self.initial_focus = self.body(body)
+        body.pack(padx=5, pady=5)
+        # Create OK Cancel button
+        self.buttonbox()
+        # Initialize window state
+        self.grab_set()
+        self.protocol("WM_DELETE_WINDOW", self.cancel)
+        self.geometry("+%d+%d" % (parent.winfo_rootx()+50,
+                                  parent.winfo_rooty()+50))
+        self.initial_focus.focus_set()
+        self.wait_window(self)
+        
+    def destroy(self):
+        """Destroy the window"""
+        self.propertyDict = {}
+        self.initial_focus = None
+        # Clean up balloons!
+        for balloon in self.balloonList:
+            balloon.withdraw()
+        Toplevel.destroy(self)
+        
+    #
+    # construction hooks
+    def body(self, master):
+        """create dialog body.
+        return entry that should have initial focus. 
+        This method should be overridden, and is called
+        by the __init__ method.
+        """
+        count = 0
+        entryList = []
+        self.balloonList = []
+        for property in self.propertyList:
+            propertySet = self.propertyDict[property]
+            # Widget
+            widget = propertySet.get('widget', None)
+            # Get initial value
+            initialvalue = widget[property]
+            # Type of entry
+            entryType = propertySet.get('type', 'real')
+            # Is None an allowable value?
+            fAllowNone = propertySet.get('fNone', 0)
+            # Help string specified?
+            helpString = propertySet.get('help', None)
+            # Create label
+            label = Label(master, text=property, justify=LEFT)
+            label.grid(row=count, col = 0, padx=5, sticky=W)
+
+            # Create entry
+            entry = Pmw.EntryField(master, entry_justify = 'right')
+            entry.grid(row=count, col = 1, padx=5, sticky=W+E)
+            if initialvalue is None:
+                entry.insert(0, 'None')
+            else:
+                entry.insert(0, initialvalue)
+
+            # Create balloon for help
+            balloon = Pmw.Balloon(state = 'balloon')
+            self.balloonList.append(balloon)
+            # extra info if None is allowed value
+            if helpString is None:
+                if fAllowNone:
+                    extra = ' or None'
+                else:
+                    extra = ''
+            # Set up help string and validator based upon type
+            if entryType == 'real':
+                entry['validate'] = { 'validator' : self.realOrNone }
+                if helpString is None:
+                    helpString = 'Enter a floating point number' + extra + '.'
+            elif entryType == 'integer':
+                entry['validate'] = { 'validator' : self.intOrNone }
+                if helpString is None:
+                    helpString = 'Enter an integer' + extra + '.'
+            else:
+                entry['validate'] = { 'validator' : 'alphanumeric' }
+                if helpString is None:
+                    helpString = 'Enter a string' + extra + '.'
+            # Bind balloon with help string to entry
+            balloon.bind(entry, helpString)
+            # Create callback to execute whenever a value is changed
+            modifiedCallback = (lambda f=self.modified, w=widget, e=entry,
+                                p=property, t=entryType,fn=fAllowNone:
+                                f(w,e,p,t, fn))
+            entry['modifiedcommand'] = modifiedCallback
+            # Keep track of the entrys
+            entryList.append(entry)
+            count += 1
+        # Set initial focus
+        if len(entryList) > 0:
+            entry = entryList[0]
+            entry.select_range(0, END)
+            # Set initial focus to first entry in the list
+            return entryList[0]
+        else:
+            # Just set initial focus to self
+            return self
+
+    def modified(self, widget, entry, property, type, fNone):
+        self.modifiedDict[property] = (widget,entry,type,fNone)
+        
+    def buttonbox(self):
+        """add standard button box buttons. 
+        """
+        box = Frame(self)
+        # Create buttons
+        w = Button(box, text="OK", width=10, command=self.ok)
+        w.pack(side=LEFT, padx=5, pady=5)
+        # Create buttons
+        w = Button(box, text="Cancel", width=10, command=self.cancel)
+        w.pack(side=LEFT, padx=5, pady=5)
+        # Bind commands
+        self.bind("<Return>", self.ok)
+        self.bind("<Escape>", self.cancel)
+        # Pack
+        box.pack()
+
+    def realOrNone(self, val):
+        val = string.lower(val)
+        if string.find('none', val) != -1:
+            if val == 'none':
+                return Pmw.OK
+            else:
+                return Pmw.PARTIAL
+        return Pmw.realvalidator(val)
+
+    def intOrNone(self, val):
+        val = string.lower(val)
+        if string.find('none', val) != -1:
+            if val == 'none':
+                return Pmw.OK
+            else:
+                return Pmw.PARTIAL
+        return Pmw.integervalidator(val)
+
+    #
+    # standard button semantics
+    def ok(self, event=None):
+        self.withdraw()
+        self.update_idletasks()
+        self.validateChanges()
+        self.apply()
+        self.cancel()
+        
+    def cancel(self, event=None):
+        # put focus back to the parent window
+        self.parent.focus_set()
+        self.destroy()
+
+    def validateChanges(self):
+        for property in self.modifiedDict.keys():
+            tuple = self.modifiedDict[property]
+            widget = tuple[0]
+            entry = tuple[1]
+            type = tuple[2]
+            fNone = tuple[3]
+            value = entry.get()
+            lValue = string.lower(value)
+            if (string.find('none', lValue) != -1):
+                if fNone and (lValue == 'none'):
+                    widget[property] = None
+            else:
+                if type == 'real':
+                    value = string.atof(value)
+                elif type == 'integer':
+                    value = string.atoi(value)
+                widget[property] = value
+
+    def apply(self):
+        """process the data
+
+        This method is called automatically to process the data, *after*
+        the dialog is destroyed. By default, it does nothing.
+        """
+        pass # override
+