|
@@ -3,9 +3,11 @@ from tkSimpleDialog import askfloat
|
|
|
from PandaModules import ClockObject
|
|
from PandaModules import ClockObject
|
|
|
import Task
|
|
import Task
|
|
|
import Pmw
|
|
import Pmw
|
|
|
|
|
+import tkMessageBox
|
|
|
import math
|
|
import math
|
|
|
import string
|
|
import string
|
|
|
import operator
|
|
import operator
|
|
|
|
|
+import types
|
|
|
|
|
|
|
|
TWO_PI = 2.0 * math.pi
|
|
TWO_PI = 2.0 * math.pi
|
|
|
ONEPOINTFIVE_PI = 1.5 * math.pi
|
|
ONEPOINTFIVE_PI = 1.5 * math.pi
|
|
@@ -13,13 +15,210 @@ POINTFIVE_PI = 0.5 * math.pi
|
|
|
INNER_SF = 0.2
|
|
INNER_SF = 0.2
|
|
|
MAX_EXP = 5
|
|
MAX_EXP = 5
|
|
|
|
|
|
|
|
|
|
+DIAL_FULL = 'full'
|
|
|
|
|
+DIAL_MINI = 'mini'
|
|
|
|
|
+
|
|
|
|
|
+DIAL_FULL_SIZE = 45
|
|
|
|
|
+DIAL_MINI_SIZE = 20
|
|
|
|
|
+
|
|
|
globalClock = ClockObject.getGlobalClock()
|
|
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):
|
|
class Dial(Pmw.MegaWidget):
|
|
|
def __init__(self, parent = None, **kw):
|
|
def __init__(self, parent = None, **kw):
|
|
|
#define the megawidget options
|
|
#define the megawidget options
|
|
|
INITOPT = Pmw.INITOPT
|
|
INITOPT = Pmw.INITOPT
|
|
|
|
|
+ if 'full' == kw.get('style', DIAL_FULL):
|
|
|
|
|
+ DIAL_SIZE = DIAL_FULL_SIZE
|
|
|
|
|
+ else:
|
|
|
|
|
+ DIAL_SIZE = DIAL_MINI_SIZE
|
|
|
optiondefs = (
|
|
optiondefs = (
|
|
|
|
|
+ ('style', DIAL_FULL, INITOPT),
|
|
|
|
|
+ ('dial_size', DIAL_SIZE, None),
|
|
|
# Widget relief
|
|
# Widget relief
|
|
|
('relief', GROOVE, None),
|
|
('relief', GROOVE, None),
|
|
|
# Widget borderwidth
|
|
# Widget borderwidth
|
|
@@ -31,8 +230,10 @@ class Dial(Pmw.MegaWidget):
|
|
|
('command', None, None),
|
|
('command', None, None),
|
|
|
('commandData', [], None),
|
|
('commandData', [], None),
|
|
|
('callbackData', [], self.setCallbackData),
|
|
('callbackData', [], self.setCallbackData),
|
|
|
- ('min', 0.0, self.setMin),
|
|
|
|
|
- ('max', 1.0, self.setMax),
|
|
|
|
|
|
|
+ ('min', None, self.setMin),
|
|
|
|
|
+ ('max', None, self.setMax),
|
|
|
|
|
+ ('base', 0.0, self.setBase),
|
|
|
|
|
+ ('delta', 1.0, self.setDelta),
|
|
|
('onReturnPress', None, None),
|
|
('onReturnPress', None, None),
|
|
|
('onReturnRelease', None, None),
|
|
('onReturnRelease', None, None),
|
|
|
('onButtonPress', None, self.setButtonPressCmd),
|
|
('onButtonPress', None, self.setButtonPressCmd),
|
|
@@ -49,11 +250,9 @@ class Dial(Pmw.MegaWidget):
|
|
|
|
|
|
|
|
# The Dial
|
|
# The Dial
|
|
|
self._dial = self.createcomponent('dial', (), None,
|
|
self._dial = self.createcomponent('dial', (), None,
|
|
|
- MiniDial, (interior,),
|
|
|
|
|
|
|
+ DialWidget, (interior,),
|
|
|
|
|
+ command = self.setEntry,
|
|
|
value = self['value'])
|
|
value = self['value'])
|
|
|
- # Attach dial to entry
|
|
|
|
|
- self._dial['command'] = self.setEntry
|
|
|
|
|
- self._dial.grid(rowspan = 2, columnspan = 2)
|
|
|
|
|
|
|
|
|
|
# The Label
|
|
# The Label
|
|
|
self._label = self.createcomponent('label', (), None,
|
|
self._label = self.createcomponent('label', (), None,
|
|
@@ -61,18 +260,28 @@ class Dial(Pmw.MegaWidget):
|
|
|
text = self['text'],
|
|
text = self['text'],
|
|
|
font = ('MS Sans Serif',12,'bold'),
|
|
font = ('MS Sans Serif',12,'bold'),
|
|
|
anchor = CENTER)
|
|
anchor = CENTER)
|
|
|
- self._label.grid(row = 0, col = 2, sticky = EW)
|
|
|
|
|
|
|
|
|
|
# The entry
|
|
# The entry
|
|
|
self._entryVal = StringVar()
|
|
self._entryVal = StringVar()
|
|
|
self._entry = self.createcomponent('entry', (), None,
|
|
self._entry = self.createcomponent('entry', (), None,
|
|
|
Entry, (interior,),
|
|
Entry, (interior,),
|
|
|
justify = RIGHT,
|
|
justify = RIGHT,
|
|
|
|
|
+ width = 12,
|
|
|
textvariable = self._entryVal)
|
|
textvariable = self._entryVal)
|
|
|
- self._entry.grid(row = 1, col = 2, sticky = EW)
|
|
|
|
|
self._entry.bind('<Return>', self.validateEntryInput)
|
|
self._entry.bind('<Return>', self.validateEntryInput)
|
|
|
self._entryBackground = self._entry.cget('background')
|
|
self._entryBackground = self._entry.cget('background')
|
|
|
- interior.columnconfigure(2, weight = 1)
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if self['style'] == DIAL_FULL:
|
|
|
|
|
+ # Attach dial to entry
|
|
|
|
|
+ self._dial.grid(rowspan = 2, columnspan = 2)
|
|
|
|
|
+ self._label.grid(row = 0, col = 2, sticky = EW)
|
|
|
|
|
+ self._entry.grid(row = 1, col = 2, sticky = EW)
|
|
|
|
|
+ interior.columnconfigure(2, weight = 1)
|
|
|
|
|
+ else:
|
|
|
|
|
+ self._label.grid(row=0,col=0, sticky = EW)
|
|
|
|
|
+ self._entry.grid(row=0,col=1, sticky = EW)
|
|
|
|
|
+ self._dial.grid(row=0,col=2)
|
|
|
|
|
+ interior.columnconfigure(0, weight = 1)
|
|
|
|
|
|
|
|
# Make sure input variables processed
|
|
# Make sure input variables processed
|
|
|
self.initialiseoptions(Dial)
|
|
self.initialiseoptions(Dial)
|
|
@@ -94,6 +303,7 @@ class Dial(Pmw.MegaWidget):
|
|
|
def setEntryFormat(self):
|
|
def setEntryFormat(self):
|
|
|
self.entryFormat = "%." + "%df" % self['numDigits']
|
|
self.entryFormat = "%." + "%df" % self['numDigits']
|
|
|
self.setEntry(self.get())
|
|
self.setEntry(self.get())
|
|
|
|
|
+ self._dial['numDigits'] = self['numDigits']
|
|
|
|
|
|
|
|
def validateEntryInput(self, event):
|
|
def validateEntryInput(self, event):
|
|
|
input = self._entryVal.get()
|
|
input = self._entryVal.get()
|
|
@@ -129,6 +339,12 @@ class Dial(Pmw.MegaWidget):
|
|
|
|
|
|
|
|
def setMax(self):
|
|
def setMax(self):
|
|
|
self._dial['max'] = self['max']
|
|
self._dial['max'] = self['max']
|
|
|
|
|
+
|
|
|
|
|
+ def setBase(self):
|
|
|
|
|
+ self._dial['base'] = self['base']
|
|
|
|
|
+
|
|
|
|
|
+ def setDelta(self):
|
|
|
|
|
+ self._dial['delta'] = self['delta']
|
|
|
|
|
|
|
|
def setLabel(self):
|
|
def setLabel(self):
|
|
|
self._label['text'] = self['text']
|
|
self._label['text'] = self['text']
|
|
@@ -143,7 +359,7 @@ class AngleDial(Dial):
|
|
|
def __init__(self, parent = None, **kw):
|
|
def __init__(self, parent = None, **kw):
|
|
|
# Set the typical defaults for a 360 degree angle dial
|
|
# Set the typical defaults for a 360 degree angle dial
|
|
|
optiondefs = (
|
|
optiondefs = (
|
|
|
- ('max', 360.0, None),
|
|
|
|
|
|
|
+ ('delta', 360.0, None),
|
|
|
('dial_fRollover', 0, None),
|
|
('dial_fRollover', 0, None),
|
|
|
('dial_numSegments', 12, None),
|
|
('dial_numSegments', 12, None),
|
|
|
)
|
|
)
|
|
@@ -155,7 +371,7 @@ class AngleDial(Dial):
|
|
|
self.initialiseoptions(AngleDial)
|
|
self.initialiseoptions(AngleDial)
|
|
|
|
|
|
|
|
|
|
|
|
|
-class MiniDial(Pmw.MegaWidget):
|
|
|
|
|
|
|
+class DialWidget(Pmw.MegaWidget):
|
|
|
sfBase = 3.0
|
|
sfBase = 3.0
|
|
|
sfDist = 15
|
|
sfDist = 15
|
|
|
def __init__(self, parent = None, **kw):
|
|
def __init__(self, parent = None, **kw):
|
|
@@ -164,18 +380,23 @@ class MiniDial(Pmw.MegaWidget):
|
|
|
optiondefs = (
|
|
optiondefs = (
|
|
|
## Appearance
|
|
## Appearance
|
|
|
# Edge size of the dial
|
|
# Edge size of the dial
|
|
|
- ('size', 40, INITOPT),
|
|
|
|
|
|
|
+ ('size', DIAL_FULL_SIZE, INITOPT),
|
|
|
# Widget relief
|
|
# Widget relief
|
|
|
- ('relief', GROOVE, self.setRelief),
|
|
|
|
|
|
|
+ ('relief', SUNKEN, self.setRelief),
|
|
|
# Widget borderwidth
|
|
# Widget borderwidth
|
|
|
('borderwidth', 2, self.setBorderwidth),
|
|
('borderwidth', 2, self.setBorderwidth),
|
|
|
|
|
+ ('background', 'white', INITOPT),
|
|
|
# Number of segments the dial is divided into
|
|
# Number of segments the dial is divided into
|
|
|
('numSegments', 10, self.setNumSegments),
|
|
('numSegments', 10, self.setNumSegments),
|
|
|
## Values
|
|
## Values
|
|
|
# Initial value of dial, use self.set to change value
|
|
# Initial value of dial, use self.set to change value
|
|
|
('value', 0.0, INITOPT),
|
|
('value', 0.0, INITOPT),
|
|
|
- ('min', 0.0, self.setDelta),
|
|
|
|
|
- ('max', 1.0, self.setDelta),
|
|
|
|
|
|
|
+ ('base', 0.0, None),
|
|
|
|
|
+ ('delta', 1.0, None),
|
|
|
|
|
+ ('min', None, None),
|
|
|
|
|
+ ('max', None, None),
|
|
|
|
|
+ ('resolution', None, None),
|
|
|
|
|
+ ('numDigits', 2, None),
|
|
|
# Value dial jumps to on reset
|
|
# Value dial jumps to on reset
|
|
|
('resetValue', 0.0, None),
|
|
('resetValue', 0.0, None),
|
|
|
## Behavior
|
|
## Behavior
|
|
@@ -203,8 +424,6 @@ class MiniDial(Pmw.MegaWidget):
|
|
|
|
|
|
|
|
# Running total which increments/decrements every time around dial
|
|
# Running total which increments/decrements every time around dial
|
|
|
self.rollCount = 0
|
|
self.rollCount = 0
|
|
|
- # Current angle of the dial
|
|
|
|
|
- self.dialAngle = None
|
|
|
|
|
# Current value
|
|
# Current value
|
|
|
self.value = self['value']
|
|
self.value = self['value']
|
|
|
|
|
|
|
@@ -220,6 +439,8 @@ class MiniDial(Pmw.MegaWidget):
|
|
|
self._canvas = self.createcomponent('canvas', (), None,
|
|
self._canvas = self.createcomponent('canvas', (), None,
|
|
|
Canvas, (interior,),
|
|
Canvas, (interior,),
|
|
|
width = dim, height = dim,
|
|
width = dim, height = dim,
|
|
|
|
|
+ background = self['background'],
|
|
|
|
|
+ highlightthickness = 0,
|
|
|
scrollregion = (-radius,-radius,
|
|
scrollregion = (-radius,-radius,
|
|
|
radius, radius))
|
|
radius, radius))
|
|
|
self._canvas.pack(expand = 1, fill = BOTH)
|
|
self._canvas.pack(expand = 1, fill = BOTH)
|
|
@@ -252,12 +473,8 @@ class MiniDial(Pmw.MegaWidget):
|
|
|
self._popupMenu.add_checkbutton(label = 'Rollover',
|
|
self._popupMenu.add_checkbutton(label = 'Rollover',
|
|
|
variable = self._fRollover,
|
|
variable = self._fRollover,
|
|
|
command = self.setRollover)
|
|
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 = 'Properties...',
|
|
|
|
|
+ command = self.getProperties)
|
|
|
self._popupMenu.add_command(label = 'Reset Dial',
|
|
self._popupMenu.add_command(label = 'Reset Dial',
|
|
|
command = self.reset)
|
|
command = self.reset)
|
|
|
|
|
|
|
@@ -276,23 +493,31 @@ class MiniDial(Pmw.MegaWidget):
|
|
|
self._canvas.bind('<ButtonPress-3>', self.popupDialMenu)
|
|
self._canvas.bind('<ButtonPress-3>', self.popupDialMenu)
|
|
|
|
|
|
|
|
# Make sure input variables processed
|
|
# Make sure input variables processed
|
|
|
- self.initialiseoptions(MiniDial)
|
|
|
|
|
|
|
+ self.initialiseoptions(DialWidget)
|
|
|
|
|
|
|
|
def set(self, value, fCommand = 1):
|
|
def set(self, value, fCommand = 1):
|
|
|
"""
|
|
"""
|
|
|
self.set(value, fCommand = 1)
|
|
self.set(value, fCommand = 1)
|
|
|
Set dial to new value, execute command if fCommand == 1
|
|
Set dial to new value, execute command if fCommand == 1
|
|
|
"""
|
|
"""
|
|
|
|
|
+ # Clamp value
|
|
|
|
|
+ if self['min'] is not None:
|
|
|
|
|
+ if value < self['min']:
|
|
|
|
|
+ value = self['min']
|
|
|
|
|
+ if self['max'] is not None:
|
|
|
|
|
+ if value > self['max']:
|
|
|
|
|
+ value = self['max']
|
|
|
|
|
+ # Round by resolution
|
|
|
|
|
+ if self['resolution'] is not None:
|
|
|
|
|
+ value = round(value / self['resolution']) * self['resolution']
|
|
|
# Adjust for rollover
|
|
# Adjust for rollover
|
|
|
if not self['fRollover']:
|
|
if not self['fRollover']:
|
|
|
- if value > self['max']:
|
|
|
|
|
|
|
+ if value > self['delta']:
|
|
|
self.rollCount = 0
|
|
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)
|
|
|
|
|
|
|
+ value = self['base'] + ((value - self['base']) % self['delta'])
|
|
|
|
|
+ # Update indicator to reflect adjusted value
|
|
|
|
|
+ self.updateIndicator(value)
|
|
|
|
|
+ # Send command if any
|
|
|
if fCommand and (self['command'] != None):
|
|
if fCommand and (self['command'] != None):
|
|
|
apply(self['command'], [value] + self['commandData'])
|
|
apply(self['command'], [value] + self['commandData'])
|
|
|
# Record value
|
|
# Record value
|
|
@@ -346,7 +571,7 @@ class MiniDial(Pmw.MegaWidget):
|
|
|
return dialAngle
|
|
return dialAngle
|
|
|
|
|
|
|
|
def computeValueFromAngle(self, dialAngle):
|
|
def computeValueFromAngle(self, dialAngle):
|
|
|
- delta = self.delta
|
|
|
|
|
|
|
+ delta = self['delta']
|
|
|
dialAngle = dialAngle % TWO_PI
|
|
dialAngle = dialAngle % TWO_PI
|
|
|
# Check for rollover, if necessary
|
|
# Check for rollover, if necessary
|
|
|
if (self.lastAngle > ONEPOINTFIVE_PI) and (dialAngle < POINTFIVE_PI):
|
|
if (self.lastAngle > ONEPOINTFIVE_PI) and (dialAngle < POINTFIVE_PI):
|
|
@@ -355,14 +580,13 @@ class MiniDial(Pmw.MegaWidget):
|
|
|
self.rollCount -= 1
|
|
self.rollCount -= 1
|
|
|
self.lastAngle = dialAngle
|
|
self.lastAngle = dialAngle
|
|
|
# Update value
|
|
# Update value
|
|
|
- newValue = self['min'] + (self.rollCount + (dialAngle/TWO_PI)) * delta
|
|
|
|
|
- self.dialAngle = dialAngle
|
|
|
|
|
|
|
+ newValue = self['base'] + (self.rollCount + (dialAngle/TWO_PI)) * delta
|
|
|
self.set(newValue)
|
|
self.set(newValue)
|
|
|
|
|
|
|
|
def updateIndicator(self, value):
|
|
def updateIndicator(self, value):
|
|
|
# compute new indicator angle
|
|
# compute new indicator angle
|
|
|
- delta = self.delta
|
|
|
|
|
- factors = divmod(value - self['min'], delta)
|
|
|
|
|
|
|
+ delta = self['delta']
|
|
|
|
|
+ factors = divmod(value - self['base'], delta)
|
|
|
self.rollCount = factors[0]
|
|
self.rollCount = factors[0]
|
|
|
self.updateIndicatorRadians( (factors[1]/delta) * TWO_PI )
|
|
self.updateIndicatorRadians( (factors[1]/delta) * TWO_PI )
|
|
|
|
|
|
|
@@ -389,7 +613,8 @@ class MiniDial(Pmw.MegaWidget):
|
|
|
# Update value
|
|
# Update value
|
|
|
currT = globalClock.getFrameTime()
|
|
currT = globalClock.getFrameTime()
|
|
|
dt = currT - state.lastTime
|
|
dt = currT - state.lastTime
|
|
|
- self.set(self.value + self.delta * self.knobSF * dt)
|
|
|
|
|
|
|
+ #self.set(self.value + self['delta'] * self.knobSF * dt)
|
|
|
|
|
+ self.set(self.value + self.knobSF * dt)
|
|
|
state.lastTime = currT
|
|
state.lastTime = currT
|
|
|
return Task.cont
|
|
return Task.cont
|
|
|
|
|
|
|
@@ -400,7 +625,8 @@ class MiniDial(Pmw.MegaWidget):
|
|
|
def computeKnobSF(self, event):
|
|
def computeKnobSF(self, event):
|
|
|
x = self._canvas.canvasx(event.x)
|
|
x = self._canvas.canvasx(event.x)
|
|
|
y = self._canvas.canvasy(event.y)
|
|
y = self._canvas.canvasy(event.y)
|
|
|
- sf = math.pow(MiniDial.sfBase, -10 + (abs(x) / MiniDial.sfDist))
|
|
|
|
|
|
|
+ minExp = math.floor(-self['numDigits']/math.log10(DialWidget.sfBase))
|
|
|
|
|
+ sf = math.pow(DialWidget.sfBase, minExp + (abs(x) / DialWidget.sfDist))
|
|
|
if x > 0:
|
|
if x > 0:
|
|
|
return sf
|
|
return sf
|
|
|
else:
|
|
else:
|
|
@@ -448,9 +674,6 @@ class MiniDial(Pmw.MegaWidget):
|
|
|
def setBorderwidth(self):
|
|
def setBorderwidth(self):
|
|
|
self.interior()['borderwidth'] = self['borderwidth']
|
|
self.interior()['borderwidth'] = self['borderwidth']
|
|
|
|
|
|
|
|
- def setDelta(self):
|
|
|
|
|
- self.delta = self['max'] - self['min']
|
|
|
|
|
-
|
|
|
|
|
# The following methods are used to handle the popup menu
|
|
# The following methods are used to handle the popup menu
|
|
|
def popupDialMenu(self,event):
|
|
def popupDialMenu(self,event):
|
|
|
self._popupMenu.post(event.widget.winfo_pointerx(),
|
|
self._popupMenu.post(event.widget.winfo_pointerx(),
|
|
@@ -465,20 +688,47 @@ class MiniDial(Pmw.MegaWidget):
|
|
|
self['fRollover'] = self._fRollover.get()
|
|
self['fRollover'] = self._fRollover.get()
|
|
|
|
|
|
|
|
# This handles the popup dial min dialog
|
|
# 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]])
|
|
|
|
|
+
|
|
|
def getMin(self):
|
|
def getMin(self):
|
|
|
newMin = askfloat('Dial Min', 'Min:',
|
|
newMin = askfloat('Dial Min', 'Min:',
|
|
|
initialvalue = `self['min']`,
|
|
initialvalue = `self['min']`,
|
|
|
parent = self.interior())
|
|
parent = self.interior())
|
|
|
- if newMin:
|
|
|
|
|
|
|
+ if newMin is not None:
|
|
|
self['min'] = newMin
|
|
self['min'] = newMin
|
|
|
self.updateIndicator(self.value)
|
|
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
|
|
# This handles the popup dial max dialog
|
|
|
def getMax(self):
|
|
def getMax(self):
|
|
|
newMax = askfloat('Dial Max', 'Max:',
|
|
newMax = askfloat('Dial Max', 'Max:',
|
|
|
initialvalue = `self['max']`,
|
|
initialvalue = `self['max']`,
|
|
|
parent = self.interior())
|
|
parent = self.interior())
|
|
|
- if newMax:
|
|
|
|
|
|
|
+ if newMax is not None:
|
|
|
self['max'] = newMax
|
|
self['max'] = newMax
|
|
|
self.updateIndicator(self.value)
|
|
self.updateIndicator(self.value)
|
|
|
|
|
|
|
@@ -487,7 +737,7 @@ class MiniDial(Pmw.MegaWidget):
|
|
|
newResetValue = askfloat('Dial ResetValue', 'ResetValue:',
|
|
newResetValue = askfloat('Dial ResetValue', 'ResetValue:',
|
|
|
initialvalue = `self['resetValue']`,
|
|
initialvalue = `self['resetValue']`,
|
|
|
parent = self.interior())
|
|
parent = self.interior())
|
|
|
- if newResetValue:
|
|
|
|
|
|
|
+ if newResetValue is not None:
|
|
|
self['resetValue'] = newResetValue
|
|
self['resetValue'] = newResetValue
|
|
|
|
|
|
|
|
# User callbacks
|
|
# User callbacks
|
|
@@ -500,14 +750,17 @@ class MiniDial(Pmw.MegaWidget):
|
|
|
""" User redefinable callback executed on button release """
|
|
""" User redefinable callback executed on button release """
|
|
|
if self['onButtonRelease']:
|
|
if self['onButtonRelease']:
|
|
|
apply(self['onButtonRelease'], self['callbackData'])
|
|
apply(self['onButtonRelease'], self['callbackData'])
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if __name__ == '__main__':
|
|
|
tl = Toplevel()
|
|
tl = Toplevel()
|
|
|
d = Dial(tl)
|
|
d = Dial(tl)
|
|
|
- d2 = Dial(tl, numTicks = 12, max = 360, fRollover = 0, initialValue = 180)
|
|
|
|
|
- d3 = Dial(tl, numTicks = 12, max = 90, min = -90, fRollover = 0)
|
|
|
|
|
- d4 = Dial(tl, numTicks = 16, max = 256, fRollover = 0)
|
|
|
|
|
|
|
+ d2 = Dial(tl, dial_numSegments = 12, max = 360,
|
|
|
|
|
+ dial_fRollover = 0, value = 180)
|
|
|
|
|
+ d3 = Dial(tl, dial_numSegments = 12, max = 90, min = -90,
|
|
|
|
|
+ dial_fRollover = 0)
|
|
|
|
|
+ d4 = Dial(tl, dial_numSegments = 16, max = 256,
|
|
|
|
|
+ dial_fRollover = 0)
|
|
|
d.pack(expand = 1, fill = X)
|
|
d.pack(expand = 1, fill = X)
|
|
|
d2.pack(expand = 1, fill = X)
|
|
d2.pack(expand = 1, fill = X)
|
|
|
d3.pack(expand = 1, fill = X)
|
|
d3.pack(expand = 1, fill = X)
|