Exemple #1
0
class Dial(Tkinter.Frame, KeyboardEntry):
    """This class implements a Dial widget.
The widget has a pointer that can be moved around a circle.
The range corresponding to one full turn can be specified as well as the min
and max values that are allowed. By defaults these are set to None meaning that
there is no min and no max. One turn corresponds to 360 units by default.
A dial can also operate in discrete mode (if self.increment is set to x). In
this mode the values will be restrained to be multiples of self.increment.

The Widget has a Callback manager. Callback functions get called at every value
change if self.contiguous is set to 1, else they get called when the mouse
button is released. They always get called with the current value as an
argument.

An optional label can be displayed at the center of the Dial widget.
The size of the dial has to be specified at instanciation. Other parameters
can be set after the widget has been created.
The widget tried to adjust automatically the size of the arrow according to
the size of the dial.

The widget has a configure() method: type, min, max, increment, precision,
showLabel, value, continuous, oneTurn can be set this way.

master, labCfg and size can be passed only to the constructor.

a lock() method is used to disable the various gui components of the
options panel. Usage: <instance>.lock(<component>=<value>)
components see configure(). value is 0 or 1. 1 disables,
0 enables.

Setting values with increment enabled:
if using the method set(), the actual value will 'snap' to the next increment.
i.e., if the value is set to 3, and the increment is set to 2, setting the
value to 6 will actually result in 7  (3,5,7,9,.....)
To still be able to set the value, disregarding the current active increment,
the set method understands the optional keyword force=True, i.e.
dial.set(<value>, force=True)), which will set the value to <value>. The
increment will now be added to this new <value>
 
"""
    def __init__(self, master=None, type='float',
                 labCfg={'fg':'black','side':'left', 'text':None},
                 min=None, max=None, increment=.0, precision=2,
                 showLabel=1, value=0.0, continuous=1, oneTurn=360.,
                 size=50, callback=None,
                 lockMin=0, lockBMin=0, lockMax=0, lockBMax=0,
                 lockIncrement=0, lockBIncrement=0,
                 lockPrecision=0, lockShowLabel=0, lockValue=0,
                 lockType=0, lockContinuous=0, lockOneTurn=0, **kw):

	Tkinter.Frame.__init__(self, master)
        Tkinter.Pack.config(self)

        self.callbacks = CallbackManager() # object to manage callback
                                        # functions. They get called with the
                                        # current value as an argument

        # initialize various attributes with default values
        self.precision = 2              # decimal places
        self.min = None                 # minimum value
        self.max = None                 # maximum value
        self.increment = increment            # value increment
        self.minOld = 0.                # used to store old values 
        self.maxOld = 0.
        self.incrementOld = increment
        self.size = 50                  # defines widget size
        self.offsetValue = 0.           # used to set increment correctly
        self.lab = None                 # label
        self.callback = None            # user specified callback
        self.opPanel = None             # option panel widget
        self.oneTurn = 360.             # value increment for 1 full turn
        self.value = 0.0                # current value of widget
        self.oldValue = 0.0             # old value of widget
        self.showLabel = 1              # turn on to display label on
        self.continuous = 1             # set to 1 to call callbacks at 
                                        # each value change, else gets called 
                                        # on button release event
        self.angle = 0.                 # angle corresponding to value

        self.labCfg = labCfg            # Tkinter Label options
        self.labelFont = (
            ensureFontCase('helvetica'), 14, 'bold')    # label font
        self.labelColor = 'yellow'      # label color
        self.canvas = None              # the canvas to create the widget in
        self.usedArcColor = '#aaaaaa'   # filled arc color of used portion
        self.unusedArcColor = '#cccccc' # filled arc color of unused portion
        self.pyOver180 = math.pi/180.0  # constants used in various places
        self.threeSixtyOver1turn = 1
        self.piOver1turn = math.pi/360.

        self.lockMin = lockMin          # lock<X> vars are used in self.lock()
        self.lockMax = lockMax          # to lock/unlock entries in optionpanel
        self.lockIncrement = lockIncrement
        self.lockBMin = lockBMin
        self.lockBMax = lockBMax
        self.lockBIncrement = lockBIncrement
        self.lockPrecision = lockPrecision
        self.lockShowLabel = lockShowLabel
        self.lockValue = lockValue
        self.lockType = lockType
        self.lockContinuous = lockContinuous
        self.lockOneTurn = lockOneTurn

        self.setArrow()

        # configure with user-defined values
        self.setSize(size)
        self.setCallback(callback)     
        self.setContinuous(continuous)

        self.setType(type)              
        self.setPrecision(precision)
        self.setOneTurn(oneTurn)        
        self.setMin(min)
        self.setMax(max)
        self.setIncrement(increment)
        
        self.setShowLabel(showLabel)
        
        self.setValue(value)
        
        self.setLabel(self.labCfg)

        self.createCanvas(master)

        canvas = self.canvas
	canvas.bind("<ButtonPress-1>", self.mouseDown)
	canvas.bind("<ButtonRelease-1>", self.mouseUp)
	canvas.bind("<B1-Motion>", self.mouseMove)
        canvas.bind("<Button-3>", self.toggleOptPanel)

        if os.name == 'nt': #sys.platform == 'win32':
            canvas.bind("<MouseWheel>", self.mouseWheel)
        else:
            canvas.bind("<Button-4>", self.mouseWheel)
            canvas.bind("<Button-5>", self.mouseWheel)

        KeyboardEntry.__init__(self, (canvas,), self.setFromEntry)

        self.opPanel = OptionsPanel(master = self, title="Dial Options")
        
##         if self.callback:
##             self.callbacks.AddCallback(self.callback)


    def setFromEntry(self, valueString):
        try:
            self.set(self.type(valueString))
        except ValueError:
            # fixme we would like to pop this up in a window maybe
            import traceback
            traceback.print_stack()
            traceback.print_exc()


    def handleKeyStroke(self, event):
        # handle key strokes for numbers only in widget keyboard entry label
        key = event.keysym

        if key.isdigit() or key=='period' or key=='minus' or key=='plus':
            if key == 'period':
                key = '.'
            elif key == 'minus':
                key = '-'
            elif key == 'plus':
                key = '+'
            self.typedValue += key
            self.typedValueTK.configure(text=self.typedValue)
            
        else:
            KeyboardEntry.handleKeyStroke(self, event)


    def setSize(self, size):
        """Set widget size. Size must be of type int and greater than 0"""

        assert isinstance(size, types.IntType),\
               "Illegal size: expected type %s, got %s"%(type(1), type(size) )
        assert size > 0, "Illegal size: must be > 0, got %s"%size
        self.size = size


    def setCallback(self, cb):
        """Set widget callback. Must be callable function. Callback is called
every time the widget value is set/modified"""

        assert cb is None or callable(cb) or type(cb) is types.ListType,\
               "Illegal callback: must be either None or callable, or list. Got %s"%cb
        if cb is None: return
        elif type(cb) is types.ListType:
            for func in cb:
                assert callable(func), "Illegal callback must be callable. Got %s"%func
                self.callbacks.AddCallback(func)
        else:
            self.callbacks.AddCallback(cb)
        self.callback = cb
        

    def toggleOptPanel(self, event=None):
        if self.opPanel.flag:
           self.opPanel.Dismiss_cb()
        else:
            if not hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.displayPanel(create=1)
            else:
                self.opPanel.displayPanel(create=0)


    def setArrow(self, size=None):
        if size is not None:
            self.setSize(size)
        aS = self.size/40
        self.arrowLength = max(3, 3*aS) # arrow head length
        self.arrowWidth = max(2, aS)    # half the arrow body width
        self.arrowBorderwidth = max(1, self.arrowWidth/2)  # width of arrow
                                                           # shadow lines
        self.arrowHeadWidth = 2*self.arrowWidth # width of arrow head base


    def mouseDown(self, event):
	# remember where the mouse went down
	self.lastx = event.x
	self.lasty = event.y


    def mouseUp(self, event):
	# call callbacks if not in continuous mode
        if not self.continuous:
            self.callbacks.CallCallbacks(self.opPanel.valInput.get())
        if self.showLabel == 2:
            # no widget labels on mouse release
            self.canvas.itemconfigure(self.labelId2, text='')
            self.canvas.itemconfigure(self.labelId, text='')


    def mouseMove(self, event):
        dx = event.x-self.xm
        dy = self.ym-event.y
    
        n = math.sqrt(dx*dx+dy*dy)
        if n == 0.0: v = [0.0, 0.0]
        else: v = [dx/n, dy/n]

        # find the cosine of the angle between new hand position and previous
        # hand position
        ma = v[0]*self.vector[0] + v[1]*self.vector[1]
        
        # assure no rounding errors
        if ma > 1.0: ma = 1.0
        elif ma < -1.0: ma = -1.0

        # compute angle increment compared to current vector
        ang = math.acos(ma)

        # find the sign of the rotation, sign of z component of vector prod.
        oldv = self.vector
        normz = oldv[0]*v[1] - oldv[1]*v[0]
        if normz>0: ang = -1. * ang
        
        # compute the new value
        val = self.value + ang*self.oneTurnOver2pi

        self.set(val)

        self.lastx = event.x
        self.lasty = event.y


    def mouseWheel(self, event):
        #print "mouseWheel", event, event.num
        if os.name == 'nt': #sys.platform == 'win32':
            if event.delta > 0:
                lEventNum = 4
            else:
                lEventNum = 5
        else:
            lEventNum = event.num
        if lEventNum == 4:
            self.set(self.value+self.oneTurn)
        else:
            self.set(self.value-self.oneTurn)


    def get(self):
        return self.type(self.value)


    def printLabel(self):
        if self.canvas is None:
            return
        self.canvas.itemconfigure(self.labelId2,
                                  text=self.labelFormat%self.value)#newVal)
        self.canvas.itemconfigure(self.labelId,
                                  text=self.labelFormat%self.value)#newVal)
        
    
    def set(self, val, update=1, force=0):
        # if force is set to 1, we call this method regardless of the
        # widget configuration. This is for example the case if the dial
        # is set to continuous=0, but the value is set in the options panel

        # snap to closest increment
        if self.increment is not None and self.increment != 0. and not force:
            offset = self.offsetValue%self.increment
            dval = round(val/self.increment) * self.increment

            if val < dval:
                dval = dval + offset - self.increment
            else:
                dval = dval + offset
        
            if self.min is not None and dval < self.min:
                dval = self.min
            elif self.max is not None and dval > self.max:
                dval = self.max

            # recompute vector and angle corresponding to val
            self.angle = (dval%self.oneTurn)*self.threeSixtyOver1turn
            if dval <0.0:
                self.angle = self.angle - 360.0
            a = self.angle*self.pyOver180
            self.vector = [math.sin(a), math.cos(a)]
            self.value = dval
            self.offsetValue = dval

        else:
            # 'regular' mode, i.e. no step-wise increment
            if self.min is not None and val < self.min: val = self.min
            elif self.max is not None and val > self.max: val = self.max
            
            # recompute vector and angle corresponding to val
            self.angle = (val%self.oneTurn)*self.threeSixtyOver1turn
            if val <0.0: self.angle = self.angle - 360.0
            a = self.angle*self.pyOver180
            self.vector = [math.sin(a), math.cos(a)]
            self.value = val
            self.offsetValue = val

        #update arrow in display
        self.drawArrow()
        newVal = self.get()
        
        if self.continuous or force:
            if update and self.oldValue != newVal or force:
                self.oldValue = newVal
                self.callbacks.CallCallbacks(newVal)
            if self.showLabel==2:
                self.printLabel()
        else:
            if self.showLabel==2:
                self.printLabel()
                
        if self.showLabel==1:
            self.printLabel()
        if self.opPanel:
            self.opPanel.valInput.set(self.labelFormat%newVal)


    def drawArrow(self):
        if self.canvas is None:
            return
        # end point
        x1 = self.xm + self.vector[0]*self.rad
        y1 = self.ym - self.vector[1]*self.rad
        # point at arrow head base
        xb = self.xm + self.vector[0]*self.radNoArrow
        yb = self.xm - self.vector[1]*self.radNoArrow
        # vector orthogonal to arrow
        n = [-self.vector[1], -self.vector[0]]
        pts1 = [ self.xm+n[0]*self.arrowWidth, self.ym+n[1]*self.arrowWidth,
                 xb+n[0]*self.arrowWidth, yb+n[1]*self.arrowWidth,
                 xb+n[0]*self.arrowHeadWidth, yb+n[1]*self.arrowHeadWidth,
                 x1, y1 ]
        pts2 = [ x1, y1,
                 xb-n[0]*self.arrowHeadWidth, yb-n[1]*self.arrowHeadWidth,
                 xb-n[0]*self.arrowWidth, yb-n[1]*self.arrowWidth,
                 self.xm-n[0]*self.arrowWidth, self.ym-n[1]*self.arrowWidth ]
        canvas = self.canvas
        if self.vector[0] > 0.0:
            col1 = '#DDDDDD'
            col2 = 'black'
        else:
            col1 = 'black'
            col2 = '#DDDDDD'
        apply( canvas.coords, (self.arrowPolId,) + tuple(pts1+pts2) )
        apply( canvas.coords, (self.arrowPolborder1,) + tuple(pts1) )
        canvas.itemconfigure( self.arrowPolborder1, fill=col1 )
        apply( canvas.coords, (self.arrowPolborder2,) + tuple(pts2) )
        canvas.itemconfigure( self.arrowPolborder2, fill=col2 )
        canvas.itemconfigure(self.arcId, extent = 0.0-self.angle)

       
    def createCanvas(self, master):
        size = self.size
        self.frame = Tkinter.Frame(self, borderwidth=3, relief='sunken')

	self.canvas = Tkinter.Canvas(self.frame, width=size+2, height=size+2)

        self.xm = self.ym = size/2+2
        self.rad = size/2
        self.radNoArrow = self.rad-self.arrowLength
        self.vector = [0, 1]
        x1 = self.xm + self.vector[0]*self.rad
        y1 = self.ym + self.vector[1]*self.rad
        canvas = self.canvas
        self.circleId = canvas.create_oval(2,2,size,size, width=1,
                                           fill=self.unusedArcColor)
        self.arcId = canvas.create_arc(2,2,size,size, start=90.,
                                            extent=0, fill=self.usedArcColor)
        canvas.create_line(2, self.ym, size+2, self.ym)
        canvas.create_line(self.xm, 2, self.ym, size+2)

        self.arrowPolId = canvas.create_polygon( 0,0,0,0,0,0,0,0,
                                                 0,0,0,0,0,0,0,0,
                                                 fill='gray75' )
        self.arrowPolborder1 = canvas.create_line( 0,0,0,0,0,0,0,0,
                                                   fill='black',
                                             width = self.arrowBorderwidth)
        self.arrowPolborder2 = canvas.create_line( 0,0,0,0,0,0,0,0,
                                                   fill='white',
                                             width = self.arrowBorderwidth )

        r = size/20
        off = self.arrowBorderwidth
        canvas.create_oval(self.xm-r,self.ym-r-off/2,self.xm+r,self.ym+r-off/2,
                           fill='#DDDDDD', outline='white')

        canvas.create_oval(self.xm-r,self.ym-r+off,self.xm+r,self.ym+r+off,
                           fill='black', outline='black')
        
        canvas.create_oval(self.xm-r,self.ym-r,self.xm+r,self.ym+r,
                           fill='gray70', outline='#DDDDDD')
       
        
        self.labelId2 = canvas.create_text(self.xm+2, self.ym+2,
                                           fill='black',
                                           justify='center', text='',
                                           font = self.labelFont)
        self.labelId = canvas.create_text(self.xm, self.ym,
                                          fill=self.labelColor,
                                          justify='center', text='',
                                          font = self.labelFont)

        self.drawArrow()

        self.opPanel = OptionsPanel(master = self, title="Dial Options")

        # pack em up
        self.canvas.pack(side=Tkinter.TOP)
        self.frame.pack(expand=1, fill='x')
        self.toggleWidgetLabel(self.showLabel)


    def toggleWidgetLabel(self, val):
        if val == 0:
            # no widget labels
            self.showLabel=0
            self.canvas.itemconfigure(self.labelId2,
                                      text='')
            self.canvas.itemconfigure(self.labelId,
                                      text='')

        if val == 1:
            # show always widget labels
            self.showLabel=1
            self.printLabel()

        if val == 2:
            # show widget labels only when mouse moves
            self.showLabel=2
            self.canvas.itemconfigure(self.labelId2,
                                      text='')
            self.canvas.itemconfigure(self.labelId,
                                      text='')


    def setValue(self, val):

        if type(val) == types.StringType:
            val = float(val)

        assert type(val) in [types.IntType, types.FloatType],\
               "Illegal type for value: expected %s or %s, got %s"%(
                   type(1), type(1.0), type(val) )

        # setValue does NOT call a callback!
        if self.min is not None and val < self.min: val = self.min
        if self.max is not None and val > self.max: val = self.max
        self.value = self.type(val)
        self.offsetValue=self.value
        self.oldValue = self.value

        #update arrow in display
        self.angle = (self.value%self.oneTurn)*self.threeSixtyOver1turn
        if self.value <0.0: self.angle = self.angle - 360.0
        a = self.angle*self.pyOver180
        self.vector = [math.sin(a), math.cos(a)]
        self.drawArrow()

        if self.showLabel == 1:
            self.printLabel()
        if self.opPanel:
            self.opPanel.valInput.set(self.labelFormat%self.value)


    def setLabel(self, labCfg):
        self.labCfg = labCfg

        text = labCfg.get('text', None)
        if text is None or text=='':
            return

        d={}
        for k, w in self.labCfg.items():
            if k == 'side': continue
            else: d[k] = w
        if not 'side' in self.labCfg.keys():
            self.labCfg['side'] = 'left'

        if not self.lab:
            self.lab = Tkinter.Label(self, d)
            self.lab.pack(side=self.labCfg['side'])
            self.lab.bind("<Button-3>", self.toggleOptPanel)
        else:
            self.lab.configure(text)

            
 #####################################################################
 # the 'configure' methods:
 #####################################################################

    def configure(self, **kw):
        for key,value in kw.items():
            # the 'set' parameter callbacks
            if key=='labCfg': self.setLabel(value)
            elif key=='type': self.setType(value)
            elif key=='min': self.setMin(value)
            elif key=='max': self.setMax(value)
            elif key=='increment': self.setIncrement(value)
            elif key=='precision': self.setPrecision(value)
            elif key=='showLabel': self.setShowLabel(value)
            elif key=='continuous': self.setContinuous(value)
            elif key=='oneTurn': self.setOneTurn(value)

            # the 'lock' entries callbacks
            elif key=='lockType': self.lockTypeCB(value)
            elif key=='lockMin': self.lockMinCB(value)
            elif key=='lockBMin': self.lockBMinCB(value)
            elif key=='lockMax': self.lockMaxCB(value)
            elif key=='lockBMax': self.lockBMaxCB(value)
            elif key=='lockIncrement': self.lockIncrementCB(value)
            elif key=='lockBIncrement': self.lockBIncrementCB(value)
            elif key=='lockPrecision': self.lockPrecisionCB(value)
            elif key=='lockShowLabel': self.lockShowLabelCB(value)
            elif key=='lockValue': self.lockValueCB(value)
            elif key=='lockContinuous': self.lockContinuousCB(value)
            elif key=='lockOneTurn': self.lockOneTurnCB(value)

            
    def setType(self, Type):

        assert type(Type) in [types.StringType, types.TypeType],\
               "Illegal type for datatype. Expected %s or %s, got %s"%(
                   type('a'), type(type), type(Type) )
        
        if type(Type) == type(""): # type str
            assert Type in ('int','float'),\
            "Illegal type descriptor. Expected 'int' or 'float', got '%s'"%Type
            self.type = eval(Type)
        else:
            self.type = Type
        
        if self.type == int:
            self.labelFormat = "%d"
            self.int_value = self.value
        else:
            self.labelFormat = "%."+str(self.precision)+"f"

        if hasattr(self.opPanel, 'optionsForm'):
            w = self.opPanel.idf.entryByName['togIntFloat']['widget']
            if self.type == int:
                w.setvalue('int')
            elif self.type == 'float':
                w.setvalue('float')
            
        if self.opPanel:
            self.opPanel.updateDisplay()

        # and update the printed label
        if self.canvas and self.showLabel == 1:
            self.printLabel()


    def setMin(self, min):
        if min is not None:

            assert type(min) in [types.IntType, types.FloatType],\
                 "Illegal type for minimum. Expected type %s or %s, got %s"%(
                     type(0), type(0.0), type(min) )
            
            if self.max and min > self.max:
                min = self.max

            self.min = self.type(min)

            if self.showLabel == 1:
                self.printLabel()

            if self.value < self.min:
                self.set(self.min)

            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.minInput.set(self.labelFormat%self.min) 

                self.opPanel.toggleMin.set(1)
                self.opPanel.min_entry.configure(state='normal', fg='gray0')
            self.minOld = self.min    
                
        else:
            self.min = None
            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.toggleMin.set(0)
                self.opPanel.min_entry.configure(state='disabled',
                                                 fg='gray40')


    def setMax(self, max):
        if max is not None:

            assert type(max) in [types.IntType, types.FloatType],\
                 "Illegal type for maximum. Expected type %s or %s, got %s"%(
                     type(0), type(0.0), type(max) )
             
            if self.min and max < self.min:
                max = self.min

            self.max = self.type(max)

            if self.showLabel == 1:
                self.printLabel()

            if self.value > self.max:
                self.set(self.max)

            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.maxInput.set(self.labelFormat%self.max) 

                self.opPanel.toggleMax.set(1)
                self.opPanel.max_entry.configure(state='normal', fg='gray0')
            self.maxOld = self.max
                
        else:
            self.max = None
            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.toggleMax.set(0)
                self.opPanel.max_entry.configure(state='disabled', fg='gray40')


    def setIncrement(self, incr):
        if incr is not None:

            assert type(incr) in [types.IntType, types.FloatType],\
                "Illegal type for increment. Expected type %s or %s, got %s"%(
                    type(0), type(0.0), type(incr) )

            self.increment = self.type(incr)
            self.offsetValue = self.value
            self.incrementOld = self.increment
            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.incrInput.set(self.labelFormat%self.increment) 
                self.opPanel.toggleIncr.set(1)
                self.opPanel.incr_entry.configure(state='normal', fg='gray0')
                
        else:
            self.increment = self.type(0)
            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.toggleIncr.set(0)
                self.opPanel.incrInput.set(self.labelFormat%0)
                self.opPanel.incr_entry.configure(state='disabled',
                                                  fg='gray40')


    def setPrecision(self, val):
        assert type(val) in [types.IntType, types.FloatType],\
               "Illegal type for precision. Expected type %s or %s, got %s"%(
                     type(0), type(0.0), type(val) )
        val = int(val)
        
        if val > 10:
            val = 10
        if val < 1:
            val = 1
        self.precision = val

        if self.type == float:
            self.labelFormat = "%."+str(self.precision)+"f"
        else:
            self.labelFormat = "%d"

        if hasattr(self.opPanel, 'optionsForm'):
            w = self.opPanel.idf.entryByName['selPrec']['widget']
            w.setvalue(val)
            
        if self.opPanel:
            self.opPanel.updateDisplay()

        # and update the printed label
        if self.canvas and self.showLabel == 1:
            self.printLabel()
            
    def setContinuous(self, cont):
        """ cont can be None, 0 or 1 """

        assert cont in [None, 0, 1],\
             "Illegal value for continuous: expected None, 0 or 1, got %s"%cont

        if cont != 1:
            cont = None
        self.continuous = cont
        if hasattr(self.opPanel, 'optionsForm'):
            w = self.opPanel.idf.entryByName['togCont']['widget']
            if cont:
                w.setvalue('on')#i=1
            else:
                w.setvalue('off')#i=0

        if self.opPanel:
            self.opPanel.updateDisplay()


    def setShowLabel(self, val):
        """Show label can be 0, 1 or 2
0: no label
1: label is always shown
2: show label only when value changes"""
        
        assert val in [0,1,2],\
               "Illegal value for showLabel. Expected 0, 1 or 2, got %s"%val
        
        if val != 0 and val != 1 and val != 2:
            print "Illegal value. Must be 0, 1 or 2"
            return
        self.showLabel = val
        self.toggleWidgetLabel(val)

        if hasattr(self.opPanel, 'optionsForm'):
            w = self.opPanel.idf.entryByName['togLabel']['widget']
            if self.showLabel == 0:
                label = 'never'
            elif self.showLabel == 1:
                label = 'always'
            elif self.showLabel == 2:
                label = 'move'
            w.setvalue(label)

        if self.opPanel:
            self.opPanel.updateDisplay()


    def setOneTurn(self, oneTurn):
        assert type(oneTurn) in [types.IntType, types.FloatType],\
               "Illegal type for oneTurn. Expected %s or %s, got %s"%(
                   type(0), type(0.0), type(oneTurn) )
        
        self.oneTurn = oneTurn
        self.threeSixtyOver1turn = 360./oneTurn
        self.piOver1turn = math.pi/oneTurn
        self.oneTurnOver2pi = oneTurn / (2*math.pi)

        if self.opPanel:
            self.opPanel.updateDisplay()


 #####################################################################
 # the 'lock' methods:
 #####################################################################


    def lockTypeCB(self, mode):
        if mode != 0: mode = 1
        self.lockType = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()
        

    def lockMinCB(self, mode): #min entry field
        if mode != 0: mode = 1
        self.lockMin = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()
        

    def lockBMinCB(self, mode): # min checkbutton
        if mode != 0: mode = 1
        self.lockBMin = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()


    def lockMaxCB(self, mode): # max entry field
        if mode != 0: mode = 1
        self.lockMax = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()


    def lockBMaxCB(self, mode): # max checkbutton
        if mode != 0: mode = 1
        self.lockBMax = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()


    def lockIncrementCB(self, mode): # increment entry field
        if mode != 0: mode = 1
        self.lockIncrement = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()


    def lockBIncrementCB(self, mode): # increment checkbutton
        if mode != 0: mode = 1
        self.lockBIncrement = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()


    def lockPrecisionCB(self, mode):
        if mode != 0: mode = 1
        self.lockPrecision = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()
            

    def lockShowLabelCB(self, mode):
        if mode != 0: mode = 1
        self.lockShowLabel = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()


    def lockValueCB(self, mode):
        if mode != 0: mode = 1
        self.lockValue = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()


    def lockContinuousCB(self, mode):
        if mode != 0: mode = 1
        self.lockContinuous = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()


    def lockOneTurnCB(self, mode):
        if mode != 0: mode = 1
        self.lockOneTurn = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()
Exemple #2
0
class ThumbWheel(Tkinter.Frame, KeyboardEntry):
    """ This class builds a thumbwheel put onto a wheelpad.
constructor options:
- master: the master into the thumwheel can be packed. If one is specified,
  the widget gets packed in a toplevel() window
- height, width, orient specify the size and orientation of the widget.
- wheelpad specifies the pad onto which the thumbwheel gets packed
  optional is a canvascfg to configure the wheelpad canvas Tkinter options.
  for example if the wheelpad needs a blue background: canvascfg={'bg':'blue'}
- continuous: boolean. When set to True, continuous is 'on' and the callback functions
  will be called each time the value changes. Otherwise continuous will be 'off' and
  callback functions will be called on mouse release.
- callback: (None, callable function or list of callable functions).to specify function
  to be called when the value of the thumbwheel is modified.
- type: ('float', 'int' ...) string describing the type of the thumbwheel
- min, max, increment, precision, oneTurn specify the parameters of the thumbwheel.
- labCfg describes the label of the thumbwheel which will be packed to the left of the widget by default unless 'side' is specified. Possible keywords are text and side.

- wheelLabCfg describes the label located on the wheel where the value of the thumbwheel
  will be displayed
- showLabel Flag to specify whether to display the wheelLabel always 1, never 0,
  only on motion 2.
- canvasCfg describe the canvas containing the thumbwheel.
An option panel is available to the user to modify the thumbwheel settings by right clicking
on the widget
- lockMin, lockBMin, lockMax, lockBMax, lockIncrement, lockBIncrement,
  lockPrecision, lockShowLabel, lockValue,  lockType, lockContinuous, lockOneTurn:
  These flags specifies whether (when set to 1) or not (when set to 0) the user will be
  allowed to change the setting of the thumbwheel.
- reportDelta is flag to specify whether value differences should be reported
  rathe than absolute values
  
The widget has a configure() method: type, min, max, increment, precision,
showLabel, value, continuous, oneTurn, orient, reportDelta  can be set this
way.

a lock() method is used to disable the various gui components of the
options panel. Usage: <instance>.configure(<component>=<value>)
components see configure(). value is 0 or 1. 1 disables,0 enables.    
    """

    #FIXME: this would be the mechanism to remove all arguments from the
    # init (see DejaVu)
    #    keywords = [
    #        'master',
    #        'oneTurn',
    #        ]
    def __init__(self,
                 master=None,
                 labCfg={
                     'fg': 'black',
                     'side': 'left',
                     'text': None
                 },
                 wheelLabCfg={},
                 canvasCfg={},
                 callback=None,
                 type='float',
                 min=None,
                 max=None,
                 increment=0.0,
                 precision=2,
                 showLabel=1,
                 value=0.0,
                 continuous=True,
                 width=200,
                 height=40,
                 oneTurn=10.,
                 wheelPad=6,
                 lockMin=0,
                 lockBMin=0,
                 lockMax=0,
                 lockBMax=0,
                 lockIncrement=0,
                 lockBIncrement=0,
                 lockPrecision=0,
                 lockShowLabel=0,
                 lockValue=0,
                 lockType=0,
                 lockContinuous=0,
                 lockOneTurn=0,
                 orient=None,
                 reportDelta=0,
                 **kw):

        # See FIXME init
        #        if __debug__:
        #            checkkeywords(kw)

        Tkinter.Frame.__init__(self, master)
        Tkinter.Pack.config(self, side='left', anchor='w')

        #FIXME: nblines are not dynamically computed
        self.nblines = 30
        self.callback = None
        self.callbacks = CallbackManager()  # object to manage callback
        # functions. They get called with the
        # current value as an argument

        self.width = 200
        self.height = 40
        self.setWidth(width)
        self.setHeight(height)
        # set widget orientation: either horizontal or vertical
        self.setOrient(orient)

        # initialize various attributes with default values
        self.precision = 2  # decimal places
        self.min = None  # minimum value
        self.max = None  # maximum value
        self.increment = increment  # value increment
        self.minOld = 0.  # used to store old values
        self.maxOld = 0.
        self.incrementOld = increment
        self.size = 50  # defines widget size
        self.offsetValue = 0.  # used to set increment correctly
        self.lab = None  # label
        self.opPanel = None  # option panel widget
        self.oneTurn = 360.  # value increment for 1 full turn
        self.value = 0.0  # current value of widget
        self.oldValue = 0.0  # old value of widget
        self.showLabel = 1  # turn on to display label on
        self.continuous = True  # set to 1 to call callbacks at
        # each value change, else gets called
        # on button release event
        self.angle = 0.  # angle corresponding to value

        self.labCfg = labCfg  # Tkinter Label options
        self.labelFont = (ensureFontCase('helvetica'), 14, 'bold'
                          )  # label font
        self.labelColor = 'yellow'  # label color
        self.canvas = None  # the canvas to create the widget in
        self.usedArcColor = '#aaaaaa'  # filled arc color of used portion
        self.unusedArcColor = '#cccccc'  # filled arc color of unused portion
        self.pyOver180 = math.pi / 180.0  # constants used in various places
        self.threeSixtyOver1turn = 1
        self.piOver1turn = math.pi / 360.

        self.wheelLabCfg = wheelLabCfg  # Tkinter wheel label options
        self.canvasCfg = canvasCfg  # Tkinter Canvas options
        self.wheelPad = wheelPad  # pad between wheel and widget borders
        self.deltaVal = 0.  # delta with value at last callback

        self.valueLabel = None  # Tkinter Label
        self.setType(type)  # can be float or int

        self.discreteValue = 0.  # value in discrete space

        self.lockMin = lockMin  # lock<X> variables in configure()
        self.lockMax = lockMax  # to lock/unlock entries in options
        # panel

        self.lockMin = lockMin  # lock<X> vars are used in self.lock()
        self.lockMax = lockMax  # to lock/unlock entries in optionpanel
        self.lockIncrement = lockIncrement
        self.lockBMin = lockBMin
        self.lockBMax = lockBMax
        self.lockBIncrement = lockBIncrement
        self.lockPrecision = lockPrecision
        self.lockShowLabel = lockShowLabel
        self.lockValue = lockValue
        self.lockType = lockType
        self.lockContinuous = lockContinuous
        self.lockOneTurn = lockOneTurn
        self.reportDelta = reportDelta

        self.setLabel(self.labCfg)

        self.createCanvas(master, wheelPad=wheelPad)
        self.canvas.bind("<ButtonPress-1>", self.mouseDown)
        self.canvas.bind("<ButtonRelease-1>", self.mouseUp)
        self.canvas.bind("<B1-Motion>", self.mouseMove)
        self.canvas.bind("<Button-3>", self.toggleOptPanel)

        self.valueLabel.bind("<ButtonPress-1>", self.mouseDown)
        self.valueLabel.bind("<ButtonRelease-1>", self.mouseUp)
        self.valueLabel.bind("<B1-Motion>", self.mouseMove)
        self.valueLabel.bind("<Button-3>", self.toggleOptPanel)

        if os.name == 'nt':  #sys.platform == 'win32':
            self.canvas.bind("<MouseWheel>", self.mouseWheel)
            self.valueLabel.bind("<MouseWheel>", self.mouseWheel)
        else:
            self.canvas.bind("<Button-4>", self.mouseWheel)
            self.valueLabel.bind("<Button-4>", self.mouseWheel)
            self.canvas.bind("<Button-5>", self.mouseWheel)
            self.valueLabel.bind("<Button-5>", self.mouseWheel)

        self.bind("<Button-3>", self.toggleOptPanel)

        KeyboardEntry.__init__(self, (self, self.canvas, self.valueLabel),
                               self.setFromEntry)

        self.opPanel = OptionsPanel(master=self, title="Thumbwheel Options")

        # now set the constructor options correctly using the configure method
        self.setValue(value)
        apply(
            self.configure, (), {
                'type': type,
                'min': min,
                'max': max,
                'increment': increment,
                'precision': precision,
                'showLabel': showLabel,
                'continuous': continuous,
                'oneTurn': oneTurn,
                'lockType': lockType,
                'lockMin': lockMin,
                'lockBMin': lockBMin,
                'lockMax': lockMax,
                'lockBMax': lockBMax,
                'lockIncrement': lockIncrement,
                'lockBIncrement': lockBIncrement,
                'lockPrecision': lockPrecision,
                'lockShowLabel': lockShowLabel,
                'lockValue': lockValue,
                'lockContinuous': lockContinuous,
                'lockOneTurn': lockOneTurn,
                'orient': orient,
                'reportDelta': reportDelta,
                'callback': callback
            })

    def setFromEntry(self, valueString):
        try:
            self.set(self.type(valueString), force=1)
        except ValueError:
            # fixme we would like to pop this up in a window maybe
            import traceback
            traceback.print_stack()
            traceback.print_exc()

    def handleKeyStroke(self, event):
        # handle key strokes for numbers only in widget keyboard entry label
        key = event.keysym

        if key.isdigit() or key == 'period' or key == 'minus' or key == 'plus':
            if key == 'period':
                key = '.'
            elif key == 'minus':
                key = '-'
            elif key == 'plus':
                key = '+'
            self.typedValue += key
            self.typedValueTK.configure(text=self.typedValue)

        else:
            KeyboardEntry.handleKeyStroke(self, event)

    def setWidth(self, width):
        assert isinstance(width, types.IntType),\
               "Illegal width: expected %s, got %s"%(type(1),type(width))
        assert width > 0, "Illegal width: must be > 0, got %s" % width
        self.width = width

    def setHeight(self, height):
        assert isinstance(height, types.IntType),\
               "Illegal height: expected %s, got %s"%(type(1),type(height))
        assert height > 0, "Illegal height: must be > 0, got %s" % height
        self.height = height

    def setCallbacks(self, cb):
        """Set widget callback. Must be callable function. Callback is called
every time the widget value is set/modified"""

        assert cb is None or callable(cb) or type(cb) is types.ListType,\
               "Illegal callback: must be either None or callable. Got %s"%cb
        if cb is None: return
        elif type(cb) is types.ListType:
            for func in cb:
                assert callable(
                    func), "Illegal callback must be callable. Got %s" % func
                self.callbacks.AddCallback(func)
        else:
            self.callbacks.AddCallback(cb)
        self.callback = cb

    def toggleOptPanel(self, event=None):
        if self.opPanel.flag:
            self.opPanel.Dismiss_cb()
        else:
            if not hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.displayPanel(create=1)
            else:
                self.opPanel.displayPanel(create=0)

    def mouseDown(self, event):
        # remember where the mouse went down
        if self.orient == 'horizontal':
            self.lastx = event.x
        else:
            self.lastx = event.y

    def mouseUp(self, event):
        # call callbacks if not in continuous mode
        newVal = self.get()
        if self.oldValue != newVal:
            if not self.continuous:
                self.oldValue = newVal
                self.callbacks.CallCallbacks(newVal)
            if self.showLabel == 2:
                # no widget labels on mouse release
                self.valueLabel.place_forget()

    def mouseMove(self, event):
        if self.orient == 'horizontal':
            dx = event.x - self.lastx
            self.lastx = event.x
        else:
            dx = event.y - self.lastx
            self.lasty = event.y
        dang = dx * math.pi / 180.
        val = dang * self.oneTurnOver2pi
        self.computeNewValue(val)

    def mouseWheel(self, event):
        #print "mouseWheel", event, event.num
        if os.name == 'nt':  #sys.platform == 'win32':
            if event.delta > 0:
                lEventNum = 4
            else:
                lEventNum = 5
        else:
            lEventNum = event.num
        if lEventNum == 4:
            self.computeNewValue(self.oneTurn / 10)
        else:
            self.computeNewValue(-self.oneTurn / 10)

    def get(self):
        if self.reportDelta:
            return self.type(self.deltaVal)
        else:
            return self.type(self.value)

    def printLabel(self):
        if not self.showLabel in [1, 2]:
            return
        hl = self.valueLabel.winfo_reqheight()
        wl = self.valueLabel.winfo_reqwidth()
        h = self.canvas.winfo_reqheight()
        w = self.canvas.winfo_reqwidth()
        self.valueLabel.place(in_=self.canvas,
                              x=(w - wl) * .5,
                              y=((h - hl) * 0.5) - self.height / 4 + 2)
        self.valueLabel.configure(text=self.labelFormat % self.value)

    def set(self, val, update=1, force=0):
        # if force is set to 1, we call this method regardless of the
        # widget configuration. This is for example the case if the thumwheel
        # is set to continuous=0, but the value is set in the options panel
        if self.min is not None and val <= self.min:
            val = self.min
        elif self.max is not None and val >= self.max:
            val = self.max

        oldval = self.value
        self.value = val
        self.deltaVal = self.value - oldval

        newVal = self.get()
        if update and self.continuous or force:
            if self.oldValue != self.value or force:
                self.oldValue = newVal
                self.callbacks.CallCallbacks(newVal)
            if self.showLabel == 2:
                self.printLabel()

        if self.showLabel == 1:
            self.printLabel()

        if self.opPanel:
            self.opPanel.valInput.set(self.labelFormat % self.value)

    #FIXME: this could be renamed increment (to parallel the set method)
    # some code in this method is duplicated in set method
    def computeNewValue(self, val):
        # snap to closest increment
        oldval = self.value
        if self.increment is not None and self.increment != 0.:
            self.discreteValue = self.discreteValue + val
            if self.discreteValue >= self.increment:
                self.value = self.value + self.increment
                self.discreteValue = 0.
            elif -self.discreteValue >= self.increment:
                self.value = self.value - self.increment
                self.discreteValue = 0.
        else:
            self.value = self.value + val

        if self.min is not None and self.value <= self.min:
            self.value = self.min
            val = 0
        elif self.max is not None and self.value >= self.max:
            self.value = self.max
            val = 0

        self.deltaVal = self.value - oldval

        # self.angle is used in drawLines()
        self.angle = val / self.oneTurnOver2pi
        self.drawLines()

        if self.showLabel > 0:  # ALWAYS
            self.printLabel()

        if self.opPanel:
            self.opPanel.valInput.set(self.labelFormat % self.value)

        newVal = self.get()
        if self.oldValue != newVal and not self.reportDelta:
            # this part is called either when the value is float OR when
            # continuous is off
            if self.continuous:
                self.oldValue = newVal
                self.callbacks.CallCallbacks(newVal)
            else:
                pass
        else:
            # this part is usually called when the datatype is INT AND
            # continuous is on
            if self.oldValue != newVal:
                # only call this part when we "reach" a new value
                self.oldValue = newVal
                self.callbacks.CallCallbacks(newVal)
            else:
                # else we need nothing to do
                pass

    def createCanvas(self, master, wheelPad=6):
        bw = self.borderWidth = wheelPad  # distance between wheel and raise

        cd = {
            'width': self.width + bw,
            'height': self.height + bw,
            'relief': 'raised',
            'borderwidth': 3
        }

        for k, w in self.canvasCfg.items():
            cd[k] = w

        self.canvas = Tkinter.Canvas(self, cd)
        cbdw = int(self.canvas.cget('borderwidth'))
        bd = self.borderWidth + cbdw + 1  # +1 for pixel0 that is not drawn

        height = self.height - bw
        width = self.width - bw
        cp = self.canvas.create_polygon
        self.outline1 = cp(bd,
                           bd,
                           bd,
                           height + cbdw,
                           width + cbdw,
                           height + cbdw,
                           width + cbdw,
                           bd,
                           bd,
                           bd,
                           width=1,
                           outline='gray60',
                           fill='gray85')

        ul = bd + 1  # upper left pixel
        l = (width + cbdw - 1) - (bd + 1)  # length of the inner box
        cl25 = 2. * l / 25.
        cl = self.canvas.create_line
        self.outline2 = cl(ul + int(cl25),
                           ul,
                           ul,
                           ul,
                           ul,
                           height + cbdw - 1,
                           ul + int(cl25),
                           height + cbdw - 1,
                           width=1,
                           fill='gray20')
        self.outline3 = cl(ul + int(cl25),
                           ul,
                           ul + int(3 * cl25),
                           ul,
                           width=1,
                           fill='gray60')
        self.outline4 = cl(ul + int(cl25),
                           height + cbdw - 1,
                           ul + int(3 * cl25),
                           height + cbdw - 1,
                           width=1,
                           fill='gray60')
        self.outline5 = cl(ul + int(5 * cl25),
                           ul,
                           ul + int(7.5 * cl25),
                           ul,
                           width=1,
                           fill='white')
        self.outline4 = cl(ul + int(5 * cl25),
                           height + cbdw - 1,
                           ul + int(7.5 * cl25),
                           height + cbdw - 1,
                           width=1,
                           fill='white')
        self.outline6 = cl(ul + int(9.5 * cl25),
                           ul,
                           ul + int(11.5 * cl25),
                           ul,
                           width=1,
                           fill='gray60')
        self.outline7 = cl(ul + int(9.5 * cl25),
                           height + cbdw - 1,
                           ul + int(11.5 * cl25),
                           height + cbdw - 1,
                           width=1,
                           fill='gray60')

        re = ul + l
        self.outline8 = cl(ul + int(11.5 * cl25),
                           ul,
                           re,
                           ul,
                           re,
                           height + cbdw - 1,
                           ul + int(11.5 * cl25),
                           height + cbdw - 1,
                           width=1,
                           fill='gray20')

        # corners of the box where the lines have to be drawn
        self.innerbox = (ul + 1, ul + 1, re - 1, height + cbdw - 1)

        self.circlePtsAngles = []
        inc = 2 * math.pi / float(self.nblines)
        for i in range(self.nblines):
            self.circlePtsAngles.append(i * inc)
        self.circlePtsAngles = Numeric.array(self.circlePtsAngles, 'f')
        self.linesIds = []
        self.shLinesIds = []
        # this table should depend on the number of lines
        # currently it is of length 15 (self.nblines/2)
        # It should be resized automatically to self.nblines/2
        self.shadowLinesOptions = [
            (0, 'black', 1),  # offset, color, width
            (2, 'gray30', 1),
            (2, 'gray30', 1),
            (0, 'gray30', 1),
            (-1, 'white', 1),
            (-1, 'white', 1),
            (-1, 'white', 2),
            (-1, 'white', 2),
            (-1, 'white', 2),
            (-1, 'white', 2),
            (-1, 'white', 1),
            (-1, 'white', 1),
            (0, 'gray30', 1),
            (-2, 'gray30', 1),
            (-2, 'gray30', 1),
            (0, 'black', 1),  # offset, color, width
            (0, 'black', 1),  # offset, color, width
        ]

        for i in range(self.nblines):
            self.linesIds.append(cl(0, 0, 0, 0, width=1, fill='black'))
            self.shLinesIds.append(cl(0, 0, 0, 0))

        wlabCfg = {'padx': 0, 'pady': 0}
        wlabCfg.update(self.wheelLabCfg)
        self.valueLabel = apply(Tkinter.Label, (self.master, ), wlabCfg)

        self.drawLines()
        self.canvas.pack(side=Tkinter.LEFT)
        self.toggleWidgetLabel(self.showLabel)

    def drawLines(self):
        # angle has to be provided in radians
        angle = self.angle

        self.circlePtsAngles = self.circlePtsAngles + angle
        self.circlePtsAngles = Numeric.remainder(self.circlePtsAngles,
                                                 2 * math.pi)
        xcoords = Numeric.cos(self.circlePtsAngles)
        xsin = Numeric.sin(self.circlePtsAngles)
        if self.orient == 'horizontal':
            w = self.innerbox[2] - self.innerbox[0]
            r = w / 2
            c = self.innerbox[0] + r
            y1 = self.innerbox[1]
            y2 = self.innerbox[3]
        else:
            w = self.innerbox[3] - self.innerbox[1]
            r = w / 2
            c = self.innerbox[1] + r
            y1 = self.innerbox[0]
            y2 = self.innerbox[2]

        cl = self.canvas.create_line
        setCoords = self.canvas.coords
        setOpt = self.canvas.itemconfigure
        pi2 = math.pi / 2.
        drawLines = 0
        co = Numeric.take(xcoords,
                          Numeric.nonzero(Numeric.greater_equal(xsin, 0.0)))
        co = Numeric.sort(co)
        co = [-1] + list(co)
        for i in range(len(co)):
            x = c - int(co[i] * r)
            if self.orient == 'horizontal':
                setCoords(self.linesIds[i], x, y1, x, y2)
            else:
                setCoords(self.linesIds[i], y1, x, y2, x)
            shopt = self.shadowLinesOptions[i]
            x = x + shopt[0]
            if self.orient == 'horizontal':
                setCoords(self.shLinesIds[i], x, y1, x, y2)
            else:
                setCoords(self.shLinesIds[i], y1, x, y2, x)
            setOpt(self.shLinesIds[i], fill=shopt[1], width=shopt[2])

    def toggleWidgetLabel(self, val):
        if val == 0:
            # no widget labels
            self.showLabel = 0
            self.valueLabel.place_forget()

        if val == 1:
            # show always widget labels
            self.showLabel = 1
            self.printLabel()

        if val == 2:
            # show widget labels only when mouse moves
            self.showLabel = 2
            self.valueLabel.place_forget()

    def setValue(self, val):
        assert type(val) in [types.IntType, types.FloatType],\
               "Illegal type for value: expected %s or %s, got %s"%(
                   type(1), type(1.0), type(val) )

        # setValue does NOT call a callback!
        if self.min is not None and val < self.min: val = self.min
        if self.max is not None and val > self.max: val = self.max
        self.value = self.type(val)
        self.oldValue = self.value
        self.offsetValue = self.value
        if self.showLabel == 1:
            self.printLabel()

#####################################################################
# the 'configure' methods:
#####################################################################

    def configure(self, **kw):
        if 'type' in kw.keys():  # make sure type is set first
            self.setType(kw['type'])
            del kw['type']

        for key, value in kw.items():
            # the 'set' parameter callbacks
            if key == 'labCfg': self.setLabel(value)
            elif key == 'callback': self.setCallbacks(value)
            elif key == 'wheelLabCfg':
                self.wheelLablCfg.update(value)
                self.printLabel()
            elif key == 'min':
                self.setMin(value)
            elif key == 'max':
                self.setMax(value)
            elif key == 'increment':
                self.setIncrement(value)
            elif key == 'precision':
                self.setPrecision(value)
            elif key == 'showLabel':
                self.setShowLabel(value)
            elif key == 'continuous':
                self.setContinuous(value)
            elif key == 'oneTurn':
                self.setOneTurn(value)
            elif key == 'orient':
                self.setOrient(value)
            elif key == 'reportDelta':
                self.setReportDelta(value)

                # the 'lock' entries callbacks
            elif key == 'lockType':
                self.lockTypeCB(value)
            elif key == 'lockMin':
                self.lockMinCB(value)
            elif key == 'lockBMin':
                self.lockBMinCB(value)
            elif key == 'lockMax':
                self.lockMaxCB(value)
            elif key == 'lockBMax':
                self.lockBMaxCB(value)
            elif key == 'lockIncrement':
                self.lockIncrementCB(value)
            elif key == 'lockBIncrement':
                self.lockBIncrementCB(value)
            elif key == 'lockPrecision':
                self.lockPrecisionCB(value)
            elif key == 'lockShowLabel':
                self.lockShowLabelCB(value)
            elif key == 'lockValue':
                self.lockValueCB(value)
            elif key == 'lockContinuous':
                self.lockContinuousCB(value)
            elif key == 'lockOneTurn':
                self.lockOneTurnCB(value)

    def setLabel(self, labCfg):
        self.labCfg = labCfg

        text = labCfg.get('text', None)
        if text is None or text == '':
            return

        d = {}
        for k, w in self.labCfg.items():
            if k == 'side': continue
            else: d[k] = w
        if not 'side' in self.labCfg.keys():
            self.labCfg['side'] = 'left'

        if not self.lab:
            self.lab = Tkinter.Label(self, d)
            self.lab.pack(side=self.labCfg['side'])
            self.lab.bind("<Button-3>", self.toggleOptPanel)
        else:
            self.lab.configure(text)

    def setType(self, Type):
        assert type(Type) in [types.StringType, types.TypeType],\
               "Illegal type for datatype. Expected %s or %s, got %s"%(
                   type('a'), type(type), type(Type) )

        if type(Type) == type(""):  # type str
            assert Type in ('int','float'),\
           "Illegal type descriptor. Expected 'int' or 'float', got '%s'"%Type
            self.type = eval(Type)
        else:
            self.type = Type

        if self.type == int:
            self.labelFormat = "%d"
            self.int_value = self.value
        else:
            self.labelFormat = "%." + str(self.precision) + "f"

        if hasattr(self.opPanel, 'optionsForm'):
            w = self.opPanel.idf.entryByName['togIntFloat']['widget']
            if self.type == int:
                w.setvalue('int')
            elif self.type == 'float':
                w.setvalue('float')

        if self.opPanel:
            self.opPanel.updateDisplay()

        if self.valueLabel and self.showLabel == 1:
            self.printLabel()

    def setMin(self, min):
        if min is not None:

            assert type(min) in [types.IntType, types.FloatType],\
                 "Illegal type for minimum. Expected type %s or %s, got %s"%(
                     type(0), type(0.0), type(min) )

            if self.max and min > self.max:
                min = self.max

            self.min = self.type(min)

            if self.showLabel == 1:
                self.printLabel()

            if self.value < self.min:
                self.set(self.min)

            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.minInput.set(self.labelFormat % self.min)

                self.opPanel.toggleMin.set(1)
                self.opPanel.min_entry.configure(state='normal', fg='gray0')
            self.minOld = self.min

        else:
            self.min = None
            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.toggleMin.set(0)
                self.opPanel.min_entry.configure(state='disabled', fg='gray40')

    def setMax(self, max):
        if max is not None:

            assert type(max) in [types.IntType, types.FloatType],\
                 "Illegal type for maximum. Expected type %s or %s, got %s"%(
                     type(0), type(0.0), type(max) )

            if self.min and max < self.min:
                max = self.min

            self.max = self.type(max)

            if self.showLabel == 1:
                self.printLabel()

            if self.value > self.max:
                self.set(self.max)

            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.maxInput.set(self.labelFormat % self.max)

                self.opPanel.toggleMax.set(1)
                self.opPanel.max_entry.configure(state='normal', fg='gray0')
            self.maxOld = self.max

        else:
            self.max = None
            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.toggleMax.set(0)
                self.opPanel.max_entry.configure(state='disabled', fg='gray40')

    def setIncrement(self, incr):
        if incr is not None:

            assert type(incr) in [types.IntType, types.FloatType],\
               "Illegal type for increment. Expected type %s or %s, got %s"%(
                   type(0), type(0.0), type(incr) )

            self.increment = self.type(incr)
            self.offsetValue = self.value
            self.incrementOld = self.increment

            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.incrInput.set(self.labelFormat % self.increment)

                self.opPanel.toggleIncr.set(1)
                self.opPanel.incr_entry.configure(state='normal', fg='gray0')

        else:
            self.increment = None
            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.toggleIncr.set(0)
                self.opPanel.incr_entry.configure(state='disabled',
                                                  fg='gray40')

    def setPrecision(self, val):
        assert type(val) in [types.IntType, types.FloatType],\
               "Illegal type for precision. Expected type %s or %s, got %s"%(
                     type(0), type(0.0), type(val) )
        val = int(val)

        if val > 10:
            val = 10
        if val < 1:
            val = 1
        self.precision = val
        if self.type == float:
            self.labelFormat = "%." + str(self.precision) + "f"
        else:
            self.labelFormat = "%d"

        if hasattr(self.opPanel, 'optionsForm'):
            w = self.opPanel.idf.entryByName['selPrec']['widget']
            w.setvalue(val)

        if self.opPanel:
            self.opPanel.updateDisplay()

        # and update the printed label
        if self.canvas and self.showLabel == 1:
            self.printLabel()

    def setContinuous(self, cont):
        """ cont can be None, 0 or 1 """

        assert cont in [True, False, 0, 1],\
             "Illegal value for continuous: expecting a boolean True, False, got %s"%cont

        self.continuous = cont
        if hasattr(self.opPanel, 'optionsForm'):
            w = self.opPanel.idf.entryByName['togCont']['widget']
            if cont:
                w.setvalue('on')
            else:
                w.setvalue('off')

    def setShowLabel(self, val):
        """Show label can be 0, 1 or 2
0: no label
1: show label only when value changes
2: label is always shown"""

        assert val in [0,1,2],\
               "Illegal value for showLabel. Expected 0, 1 or 2, got %s"%val

        if val != 0 and val != 1 and val != 2:
            print "Illegal value. Must be 0, 1 or 2"
            return
        self.showLabel = val
        self.toggleWidgetLabel(val)

        if hasattr(self.opPanel, 'optionsForm'):
            w = self.opPanel.idf.entryByName['togLabel']['widget']
            if self.showLabel == 0:
                label = 'never'
            elif self.showLabel == 1:
                label = 'always'
            elif self.showLabel == 2:
                label = 'move'
            w.setvalue(label)

        if self.opPanel:
            self.opPanel.updateDisplay()

    def setOneTurn(self, oneTurn):
        assert type(oneTurn) in [types.IntType, types.FloatType],\
               "Illegal type for oneTurn. Expected %s or %s, got %s"%(
                   type(0), type(0.0), type(oneTurn) )

        self.oneTurn = oneTurn
        self.threeSixtyOver1turn = 360. / oneTurn
        self.piOver1turn = math.pi / oneTurn
        self.oneTurnOver2pi = oneTurn / (2 * math.pi)
        if self.opPanel:
            self.opPanel.updateDisplay()

    def setOrient(self, orient):
        if orient is None:
            if self.width > self.height:
                orient = 'horizontal'
            else:
                orient = 'vertical'
        assert orient in ['horizontal', 'vertical'],\
               "Expected 'vertical' or 'horizontal', got '%s'"%orient
        self.orient = orient

    def setReportDelta(self, rD):
        assert rD in [None, 0, 1],\
               "Expected None, 0 or 1, got %s"%rD
        if rD is None or rD == 0:
            self.reportDelta = 0
        else:
            self.reportDelta = 1

#####################################################################
# the 'lock' methods:
#####################################################################

    def lockTypeCB(self, mode):
        if mode != 0: mode = 1
        self.lockType = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockMinCB(self, mode):  #min entry field
        if mode != 0: mode = 1
        self.lockMin = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockBMinCB(self, mode):  # min checkbutton
        if mode != 0: mode = 1
        self.lockBMin = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockMaxCB(self, mode):  # max entry field
        if mode != 0: mode = 1
        self.lockMax = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockBMaxCB(self, mode):  # max checkbutton
        if mode != 0: mode = 1
        self.lockBMax = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockIncrementCB(self, mode):  # increment entry field
        if mode != 0: mode = 1
        self.lockIncrement = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockBIncrementCB(self, mode):  # increment checkbutton
        if mode != 0: mode = 1
        self.lockBIncrement = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockPrecisionCB(self, mode):
        if mode != 0: mode = 1
        self.lockPrecision = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockShowLabelCB(self, mode):
        if mode != 0: mode = 1
        self.lockShowLabel = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockValueCB(self, mode):
        if mode != 0: mode = 1
        self.lockValue = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockContinuousCB(self, mode):
        if mode != 0: mode = 1
        self.lockContinuous = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockOneTurnCB(self, mode):
        if mode != 0: mode = 1
        self.lockOneTurn = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()
Exemple #3
0
class Dial(Tkinter.Frame, KeyboardEntry):
    """This class implements a Dial widget.
The widget has a pointer that can be moved around a circle.
The range corresponding to one full turn can be specified as well as the min
and max values that are allowed. By defaults these are set to None meaning that
there is no min and no max. One turn corresponds to 360 units by default.
A dial can also operate in discrete mode (if self.increment is set to x). In
this mode the values will be restrained to be multiples of self.increment.

The Widget has a Callback manager. Callback functions get called at every value
change if self.contiguous is set to 1, else they get called when the mouse
button is released. They always get called with the current value as an
argument.

An optional label can be displayed at the center of the Dial widget.
The size of the dial has to be specified at instanciation. Other parameters
can be set after the widget has been created.
The widget tried to adjust automatically the size of the arrow according to
the size of the dial.

The widget has a configure() method: type, min, max, increment, precision,
showLabel, value, continuous, oneTurn can be set this way.

master, labCfg and size can be passed only to the constructor.

a lock() method is used to disable the various gui components of the
options panel. Usage: <instance>.lock(<component>=<value>)
components see configure(). value is 0 or 1. 1 disables,
0 enables.

Setting values with increment enabled:
if using the method set(), the actual value will 'snap' to the next increment.
i.e., if the value is set to 3, and the increment is set to 2, setting the
value to 6 will actually result in 7  (3,5,7,9,.....)
To still be able to set the value, disregarding the current active increment,
the set method understands the optional keyword force=True, i.e.
dial.set(<value>, force=True)), which will set the value to <value>. The
increment will now be added to this new <value>
 
"""
    def __init__(self,
                 master=None,
                 type='float',
                 labCfg={
                     'fg': 'black',
                     'side': 'left',
                     'text': None
                 },
                 min=None,
                 max=None,
                 increment=.0,
                 precision=2,
                 showLabel=1,
                 value=0.0,
                 continuous=1,
                 oneTurn=360.,
                 size=50,
                 callback=None,
                 lockMin=0,
                 lockBMin=0,
                 lockMax=0,
                 lockBMax=0,
                 lockIncrement=0,
                 lockBIncrement=0,
                 lockPrecision=0,
                 lockShowLabel=0,
                 lockValue=0,
                 lockType=0,
                 lockContinuous=0,
                 lockOneTurn=0,
                 **kw):

        Tkinter.Frame.__init__(self, master)
        Tkinter.Pack.config(self)

        self.callbacks = CallbackManager()  # object to manage callback
        # functions. They get called with the
        # current value as an argument

        # initialize various attributes with default values
        self.precision = 2  # decimal places
        self.min = None  # minimum value
        self.max = None  # maximum value
        self.increment = increment  # value increment
        self.minOld = 0.  # used to store old values
        self.maxOld = 0.
        self.incrementOld = increment
        self.size = 50  # defines widget size
        self.offsetValue = 0.  # used to set increment correctly
        self.lab = None  # label
        self.callback = None  # user specified callback
        self.opPanel = None  # option panel widget
        self.oneTurn = 360.  # value increment for 1 full turn
        self.value = 0.0  # current value of widget
        self.oldValue = 0.0  # old value of widget
        self.showLabel = 1  # turn on to display label on
        self.continuous = 1  # set to 1 to call callbacks at
        # each value change, else gets called
        # on button release event
        self.angle = 0.  # angle corresponding to value

        self.labCfg = labCfg  # Tkinter Label options
        self.labelFont = (ensureFontCase('helvetica'), 14, 'bold'
                          )  # label font
        self.labelColor = 'yellow'  # label color
        self.canvas = None  # the canvas to create the widget in
        self.usedArcColor = '#aaaaaa'  # filled arc color of used portion
        self.unusedArcColor = '#cccccc'  # filled arc color of unused portion
        self.pyOver180 = math.pi / 180.0  # constants used in various places
        self.threeSixtyOver1turn = 1
        self.piOver1turn = math.pi / 360.

        self.lockMin = lockMin  # lock<X> vars are used in self.lock()
        self.lockMax = lockMax  # to lock/unlock entries in optionpanel
        self.lockIncrement = lockIncrement
        self.lockBMin = lockBMin
        self.lockBMax = lockBMax
        self.lockBIncrement = lockBIncrement
        self.lockPrecision = lockPrecision
        self.lockShowLabel = lockShowLabel
        self.lockValue = lockValue
        self.lockType = lockType
        self.lockContinuous = lockContinuous
        self.lockOneTurn = lockOneTurn

        self.setArrow()

        # configure with user-defined values
        self.setSize(size)
        self.setCallback(callback)
        self.setContinuous(continuous)

        self.setType(type)
        self.setPrecision(precision)
        self.setOneTurn(oneTurn)
        self.setMin(min)
        self.setMax(max)
        self.setIncrement(increment)

        self.setShowLabel(showLabel)

        self.setValue(value)

        self.setLabel(self.labCfg)

        self.createCanvas(master)

        canvas = self.canvas
        canvas.bind("<ButtonPress-1>", self.mouseDown)
        canvas.bind("<ButtonRelease-1>", self.mouseUp)
        canvas.bind("<B1-Motion>", self.mouseMove)
        canvas.bind("<Button-3>", self.toggleOptPanel)

        if os.name == 'nt':  #sys.platform == 'win32':
            canvas.bind("<MouseWheel>", self.mouseWheel)
        else:
            canvas.bind("<Button-4>", self.mouseWheel)
            canvas.bind("<Button-5>", self.mouseWheel)

        KeyboardEntry.__init__(self, (canvas, ), self.setFromEntry)

        self.opPanel = OptionsPanel(master=self, title="Dial Options")

##         if self.callback:
##             self.callbacks.AddCallback(self.callback)

    def setFromEntry(self, valueString):
        try:
            self.set(self.type(valueString))
        except ValueError:
            # fixme we would like to pop this up in a window maybe
            import traceback
            traceback.print_stack()
            traceback.print_exc()

    def handleKeyStroke(self, event):
        # handle key strokes for numbers only in widget keyboard entry label
        key = event.keysym

        if key.isdigit() or key == 'period' or key == 'minus' or key == 'plus':
            if key == 'period':
                key = '.'
            elif key == 'minus':
                key = '-'
            elif key == 'plus':
                key = '+'
            self.typedValue += key
            self.typedValueTK.configure(text=self.typedValue)

        else:
            KeyboardEntry.handleKeyStroke(self, event)

    def setSize(self, size):
        """Set widget size. Size must be of type int and greater than 0"""

        assert isinstance(size, types.IntType),\
               "Illegal size: expected type %s, got %s"%(type(1), type(size) )
        assert size > 0, "Illegal size: must be > 0, got %s" % size
        self.size = size

    def setCallback(self, cb):
        """Set widget callback. Must be callable function. Callback is called
every time the widget value is set/modified"""

        assert cb is None or callable(cb) or type(cb) is types.ListType,\
               "Illegal callback: must be either None or callable, or list. Got %s"%cb
        if cb is None: return
        elif type(cb) is types.ListType:
            for func in cb:
                assert callable(
                    func), "Illegal callback must be callable. Got %s" % func
                self.callbacks.AddCallback(func)
        else:
            self.callbacks.AddCallback(cb)
        self.callback = cb

    def toggleOptPanel(self, event=None):
        if self.opPanel.flag:
            self.opPanel.Dismiss_cb()
        else:
            if not hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.displayPanel(create=1)
            else:
                self.opPanel.displayPanel(create=0)

    def setArrow(self, size=None):
        if size is not None:
            self.setSize(size)
        aS = self.size / 40
        self.arrowLength = max(3, 3 * aS)  # arrow head length
        self.arrowWidth = max(2, aS)  # half the arrow body width
        self.arrowBorderwidth = max(1, self.arrowWidth / 2)  # width of arrow
        # shadow lines
        self.arrowHeadWidth = 2 * self.arrowWidth  # width of arrow head base

    def mouseDown(self, event):
        # remember where the mouse went down
        self.lastx = event.x
        self.lasty = event.y

    def mouseUp(self, event):
        # call callbacks if not in continuous mode
        if not self.continuous:
            self.callbacks.CallCallbacks(self.opPanel.valInput.get())
        if self.showLabel == 2:
            # no widget labels on mouse release
            self.canvas.itemconfigure(self.labelId2, text='')
            self.canvas.itemconfigure(self.labelId, text='')

    def mouseMove(self, event):
        dx = event.x - self.xm
        dy = self.ym - event.y

        n = math.sqrt(dx * dx + dy * dy)
        if n == 0.0: v = [0.0, 0.0]
        else: v = [dx / n, dy / n]

        # find the cosine of the angle between new hand position and previous
        # hand position
        ma = v[0] * self.vector[0] + v[1] * self.vector[1]

        # assure no rounding errors
        if ma > 1.0: ma = 1.0
        elif ma < -1.0: ma = -1.0

        # compute angle increment compared to current vector
        ang = math.acos(ma)

        # find the sign of the rotation, sign of z component of vector prod.
        oldv = self.vector
        normz = oldv[0] * v[1] - oldv[1] * v[0]
        if normz > 0: ang = -1. * ang

        # compute the new value
        val = self.value + ang * self.oneTurnOver2pi

        self.set(val)

        self.lastx = event.x
        self.lasty = event.y

    def mouseWheel(self, event):
        #print "mouseWheel", event, event.num
        if os.name == 'nt':  #sys.platform == 'win32':
            if event.delta > 0:
                lEventNum = 4
            else:
                lEventNum = 5
        else:
            lEventNum = event.num
        if lEventNum == 4:
            self.set(self.value + self.oneTurn)
        else:
            self.set(self.value - self.oneTurn)

    def get(self):
        return self.type(self.value)

    def printLabel(self):
        if self.canvas is None:
            return
        self.canvas.itemconfigure(self.labelId2,
                                  text=self.labelFormat % self.value)  #newVal)
        self.canvas.itemconfigure(self.labelId,
                                  text=self.labelFormat % self.value)  #newVal)

    def set(self, val, update=1, force=0):
        # if force is set to 1, we call this method regardless of the
        # widget configuration. This is for example the case if the dial
        # is set to continuous=0, but the value is set in the options panel

        # snap to closest increment
        if self.increment is not None and self.increment != 0. and not force:
            offset = self.offsetValue % self.increment
            dval = round(val / self.increment) * self.increment

            if val < dval:
                dval = dval + offset - self.increment
            else:
                dval = dval + offset

            if self.min is not None and dval < self.min:
                dval = self.min
            elif self.max is not None and dval > self.max:
                dval = self.max

            # recompute vector and angle corresponding to val
            self.angle = (dval % self.oneTurn) * self.threeSixtyOver1turn
            if dval < 0.0:
                self.angle = self.angle - 360.0
            a = self.angle * self.pyOver180
            self.vector = [math.sin(a), math.cos(a)]
            self.value = dval
            self.offsetValue = dval

        else:
            # 'regular' mode, i.e. no step-wise increment
            if self.min is not None and val < self.min: val = self.min
            elif self.max is not None and val > self.max: val = self.max

            # recompute vector and angle corresponding to val
            self.angle = (val % self.oneTurn) * self.threeSixtyOver1turn
            if val < 0.0: self.angle = self.angle - 360.0
            a = self.angle * self.pyOver180
            self.vector = [math.sin(a), math.cos(a)]
            self.value = val
            self.offsetValue = val

        #update arrow in display
        self.drawArrow()
        newVal = self.get()

        if self.continuous or force:
            if update and self.oldValue != newVal or force:
                self.oldValue = newVal
                self.callbacks.CallCallbacks(newVal)
            if self.showLabel == 2:
                self.printLabel()
        else:
            if self.showLabel == 2:
                self.printLabel()

        if self.showLabel == 1:
            self.printLabel()
        if self.opPanel:
            self.opPanel.valInput.set(self.labelFormat % newVal)

    def drawArrow(self):
        if self.canvas is None:
            return
        # end point
        x1 = self.xm + self.vector[0] * self.rad
        y1 = self.ym - self.vector[1] * self.rad
        # point at arrow head base
        xb = self.xm + self.vector[0] * self.radNoArrow
        yb = self.xm - self.vector[1] * self.radNoArrow
        # vector orthogonal to arrow
        n = [-self.vector[1], -self.vector[0]]
        pts1 = [
            self.xm + n[0] * self.arrowWidth, self.ym + n[1] * self.arrowWidth,
            xb + n[0] * self.arrowWidth, yb + n[1] * self.arrowWidth,
            xb + n[0] * self.arrowHeadWidth, yb + n[1] * self.arrowHeadWidth,
            x1, y1
        ]
        pts2 = [
            x1, y1, xb - n[0] * self.arrowHeadWidth,
            yb - n[1] * self.arrowHeadWidth, xb - n[0] * self.arrowWidth,
            yb - n[1] * self.arrowWidth, self.xm - n[0] * self.arrowWidth,
            self.ym - n[1] * self.arrowWidth
        ]
        canvas = self.canvas
        if self.vector[0] > 0.0:
            col1 = '#DDDDDD'
            col2 = 'black'
        else:
            col1 = 'black'
            col2 = '#DDDDDD'
        apply(canvas.coords, (self.arrowPolId, ) + tuple(pts1 + pts2))
        apply(canvas.coords, (self.arrowPolborder1, ) + tuple(pts1))
        canvas.itemconfigure(self.arrowPolborder1, fill=col1)
        apply(canvas.coords, (self.arrowPolborder2, ) + tuple(pts2))
        canvas.itemconfigure(self.arrowPolborder2, fill=col2)
        canvas.itemconfigure(self.arcId, extent=0.0 - self.angle)

    def createCanvas(self, master):
        size = self.size
        self.frame = Tkinter.Frame(self, borderwidth=3, relief='sunken')

        self.canvas = Tkinter.Canvas(self.frame,
                                     width=size + 2,
                                     height=size + 2)

        self.xm = self.ym = size / 2 + 2
        self.rad = size / 2
        self.radNoArrow = self.rad - self.arrowLength
        self.vector = [0, 1]
        x1 = self.xm + self.vector[0] * self.rad
        y1 = self.ym + self.vector[1] * self.rad
        canvas = self.canvas
        self.circleId = canvas.create_oval(2,
                                           2,
                                           size,
                                           size,
                                           width=1,
                                           fill=self.unusedArcColor)
        self.arcId = canvas.create_arc(2,
                                       2,
                                       size,
                                       size,
                                       start=90.,
                                       extent=0,
                                       fill=self.usedArcColor)
        canvas.create_line(2, self.ym, size + 2, self.ym)
        canvas.create_line(self.xm, 2, self.ym, size + 2)

        self.arrowPolId = canvas.create_polygon(0,
                                                0,
                                                0,
                                                0,
                                                0,
                                                0,
                                                0,
                                                0,
                                                0,
                                                0,
                                                0,
                                                0,
                                                0,
                                                0,
                                                0,
                                                0,
                                                fill='gray75')
        self.arrowPolborder1 = canvas.create_line(0,
                                                  0,
                                                  0,
                                                  0,
                                                  0,
                                                  0,
                                                  0,
                                                  0,
                                                  fill='black',
                                                  width=self.arrowBorderwidth)
        self.arrowPolborder2 = canvas.create_line(0,
                                                  0,
                                                  0,
                                                  0,
                                                  0,
                                                  0,
                                                  0,
                                                  0,
                                                  fill='white',
                                                  width=self.arrowBorderwidth)

        r = size / 20
        off = self.arrowBorderwidth
        canvas.create_oval(self.xm - r,
                           self.ym - r - off / 2,
                           self.xm + r,
                           self.ym + r - off / 2,
                           fill='#DDDDDD',
                           outline='white')

        canvas.create_oval(self.xm - r,
                           self.ym - r + off,
                           self.xm + r,
                           self.ym + r + off,
                           fill='black',
                           outline='black')

        canvas.create_oval(self.xm - r,
                           self.ym - r,
                           self.xm + r,
                           self.ym + r,
                           fill='gray70',
                           outline='#DDDDDD')

        self.labelId2 = canvas.create_text(self.xm + 2,
                                           self.ym + 2,
                                           fill='black',
                                           justify='center',
                                           text='',
                                           font=self.labelFont)
        self.labelId = canvas.create_text(self.xm,
                                          self.ym,
                                          fill=self.labelColor,
                                          justify='center',
                                          text='',
                                          font=self.labelFont)

        self.drawArrow()

        self.opPanel = OptionsPanel(master=self, title="Dial Options")

        # pack em up
        self.canvas.pack(side=Tkinter.TOP)
        self.frame.pack(expand=1, fill='x')
        self.toggleWidgetLabel(self.showLabel)

    def toggleWidgetLabel(self, val):
        if val == 0:
            # no widget labels
            self.showLabel = 0
            self.canvas.itemconfigure(self.labelId2, text='')
            self.canvas.itemconfigure(self.labelId, text='')

        if val == 1:
            # show always widget labels
            self.showLabel = 1
            self.printLabel()

        if val == 2:
            # show widget labels only when mouse moves
            self.showLabel = 2
            self.canvas.itemconfigure(self.labelId2, text='')
            self.canvas.itemconfigure(self.labelId, text='')

    def setValue(self, val):

        if type(val) == types.StringType:
            val = float(val)

        assert type(val) in [types.IntType, types.FloatType],\
               "Illegal type for value: expected %s or %s, got %s"%(
                   type(1), type(1.0), type(val) )

        # setValue does NOT call a callback!
        if self.min is not None and val < self.min: val = self.min
        if self.max is not None and val > self.max: val = self.max
        self.value = self.type(val)
        self.offsetValue = self.value
        self.oldValue = self.value

        #update arrow in display
        self.angle = (self.value % self.oneTurn) * self.threeSixtyOver1turn
        if self.value < 0.0: self.angle = self.angle - 360.0
        a = self.angle * self.pyOver180
        self.vector = [math.sin(a), math.cos(a)]
        self.drawArrow()

        if self.showLabel == 1:
            self.printLabel()
        if self.opPanel:
            self.opPanel.valInput.set(self.labelFormat % self.value)

    def setLabel(self, labCfg):
        self.labCfg = labCfg

        text = labCfg.get('text', None)
        if text is None or text == '':
            return

        d = {}
        for k, w in self.labCfg.items():
            if k == 'side': continue
            else: d[k] = w
        if not 'side' in self.labCfg.keys():
            self.labCfg['side'] = 'left'

        if not self.lab:
            self.lab = Tkinter.Label(self, d)
            self.lab.pack(side=self.labCfg['side'])
            self.lab.bind("<Button-3>", self.toggleOptPanel)
        else:
            self.lab.configure(text)

#####################################################################
# the 'configure' methods:
#####################################################################

    def configure(self, **kw):
        for key, value in kw.items():
            # the 'set' parameter callbacks
            if key == 'labCfg': self.setLabel(value)
            elif key == 'type': self.setType(value)
            elif key == 'min': self.setMin(value)
            elif key == 'max': self.setMax(value)
            elif key == 'increment': self.setIncrement(value)
            elif key == 'precision': self.setPrecision(value)
            elif key == 'showLabel': self.setShowLabel(value)
            elif key == 'continuous': self.setContinuous(value)
            elif key == 'oneTurn':
                self.setOneTurn(value)

                # the 'lock' entries callbacks
            elif key == 'lockType':
                self.lockTypeCB(value)
            elif key == 'lockMin':
                self.lockMinCB(value)
            elif key == 'lockBMin':
                self.lockBMinCB(value)
            elif key == 'lockMax':
                self.lockMaxCB(value)
            elif key == 'lockBMax':
                self.lockBMaxCB(value)
            elif key == 'lockIncrement':
                self.lockIncrementCB(value)
            elif key == 'lockBIncrement':
                self.lockBIncrementCB(value)
            elif key == 'lockPrecision':
                self.lockPrecisionCB(value)
            elif key == 'lockShowLabel':
                self.lockShowLabelCB(value)
            elif key == 'lockValue':
                self.lockValueCB(value)
            elif key == 'lockContinuous':
                self.lockContinuousCB(value)
            elif key == 'lockOneTurn':
                self.lockOneTurnCB(value)

    def setType(self, Type):

        assert type(Type) in [types.StringType, types.TypeType],\
               "Illegal type for datatype. Expected %s or %s, got %s"%(
                   type('a'), type(type), type(Type) )

        if type(Type) == type(""):  # type str
            assert Type in ('int','float'),\
            "Illegal type descriptor. Expected 'int' or 'float', got '%s'"%Type
            self.type = eval(Type)
        else:
            self.type = Type

        if self.type == int:
            self.labelFormat = "%d"
            self.int_value = self.value
        else:
            self.labelFormat = "%." + str(self.precision) + "f"

        if hasattr(self.opPanel, 'optionsForm'):
            w = self.opPanel.idf.entryByName['togIntFloat']['widget']
            if self.type == int:
                w.setvalue('int')
            elif self.type == 'float':
                w.setvalue('float')

        if self.opPanel:
            self.opPanel.updateDisplay()

        # and update the printed label
        if self.canvas and self.showLabel == 1:
            self.printLabel()

    def setMin(self, min):
        if min is not None:

            assert type(min) in [types.IntType, types.FloatType],\
                 "Illegal type for minimum. Expected type %s or %s, got %s"%(
                     type(0), type(0.0), type(min) )

            if self.max and min > self.max:
                min = self.max

            self.min = self.type(min)

            if self.showLabel == 1:
                self.printLabel()

            if self.value < self.min:
                self.set(self.min)

            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.minInput.set(self.labelFormat % self.min)

                self.opPanel.toggleMin.set(1)
                self.opPanel.min_entry.configure(state='normal', fg='gray0')
            self.minOld = self.min

        else:
            self.min = None
            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.toggleMin.set(0)
                self.opPanel.min_entry.configure(state='disabled', fg='gray40')

    def setMax(self, max):
        if max is not None:

            assert type(max) in [types.IntType, types.FloatType],\
                 "Illegal type for maximum. Expected type %s or %s, got %s"%(
                     type(0), type(0.0), type(max) )

            if self.min and max < self.min:
                max = self.min

            self.max = self.type(max)

            if self.showLabel == 1:
                self.printLabel()

            if self.value > self.max:
                self.set(self.max)

            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.maxInput.set(self.labelFormat % self.max)

                self.opPanel.toggleMax.set(1)
                self.opPanel.max_entry.configure(state='normal', fg='gray0')
            self.maxOld = self.max

        else:
            self.max = None
            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.toggleMax.set(0)
                self.opPanel.max_entry.configure(state='disabled', fg='gray40')

    def setIncrement(self, incr):
        if incr is not None:

            assert type(incr) in [types.IntType, types.FloatType],\
                "Illegal type for increment. Expected type %s or %s, got %s"%(
                    type(0), type(0.0), type(incr) )

            self.increment = self.type(incr)
            self.offsetValue = self.value
            self.incrementOld = self.increment
            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.incrInput.set(self.labelFormat % self.increment)
                self.opPanel.toggleIncr.set(1)
                self.opPanel.incr_entry.configure(state='normal', fg='gray0')

        else:
            self.increment = self.type(0)
            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.toggleIncr.set(0)
                self.opPanel.incrInput.set(self.labelFormat % 0)
                self.opPanel.incr_entry.configure(state='disabled',
                                                  fg='gray40')

    def setPrecision(self, val):
        assert type(val) in [types.IntType, types.FloatType],\
               "Illegal type for precision. Expected type %s or %s, got %s"%(
                     type(0), type(0.0), type(val) )
        val = int(val)

        if val > 10:
            val = 10
        if val < 1:
            val = 1
        self.precision = val

        if self.type == float:
            self.labelFormat = "%." + str(self.precision) + "f"
        else:
            self.labelFormat = "%d"

        if hasattr(self.opPanel, 'optionsForm'):
            w = self.opPanel.idf.entryByName['selPrec']['widget']
            w.setvalue(val)

        if self.opPanel:
            self.opPanel.updateDisplay()

        # and update the printed label
        if self.canvas and self.showLabel == 1:
            self.printLabel()

    def setContinuous(self, cont):
        """ cont can be None, 0 or 1 """

        assert cont in [None, 0, 1],\
             "Illegal value for continuous: expected None, 0 or 1, got %s"%cont

        if cont != 1:
            cont = None
        self.continuous = cont
        if hasattr(self.opPanel, 'optionsForm'):
            w = self.opPanel.idf.entryByName['togCont']['widget']
            if cont:
                w.setvalue('on')  #i=1
            else:
                w.setvalue('off')  #i=0

        if self.opPanel:
            self.opPanel.updateDisplay()

    def setShowLabel(self, val):
        """Show label can be 0, 1 or 2
0: no label
1: label is always shown
2: show label only when value changes"""

        assert val in [0,1,2],\
               "Illegal value for showLabel. Expected 0, 1 or 2, got %s"%val

        if val != 0 and val != 1 and val != 2:
            print "Illegal value. Must be 0, 1 or 2"
            return
        self.showLabel = val
        self.toggleWidgetLabel(val)

        if hasattr(self.opPanel, 'optionsForm'):
            w = self.opPanel.idf.entryByName['togLabel']['widget']
            if self.showLabel == 0:
                label = 'never'
            elif self.showLabel == 1:
                label = 'always'
            elif self.showLabel == 2:
                label = 'move'
            w.setvalue(label)

        if self.opPanel:
            self.opPanel.updateDisplay()

    def setOneTurn(self, oneTurn):
        assert type(oneTurn) in [types.IntType, types.FloatType],\
               "Illegal type for oneTurn. Expected %s or %s, got %s"%(
                   type(0), type(0.0), type(oneTurn) )

        self.oneTurn = oneTurn
        self.threeSixtyOver1turn = 360. / oneTurn
        self.piOver1turn = math.pi / oneTurn
        self.oneTurnOver2pi = oneTurn / (2 * math.pi)

        if self.opPanel:
            self.opPanel.updateDisplay()

#####################################################################
# the 'lock' methods:
#####################################################################

    def lockTypeCB(self, mode):
        if mode != 0: mode = 1
        self.lockType = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockMinCB(self, mode):  #min entry field
        if mode != 0: mode = 1
        self.lockMin = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockBMinCB(self, mode):  # min checkbutton
        if mode != 0: mode = 1
        self.lockBMin = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockMaxCB(self, mode):  # max entry field
        if mode != 0: mode = 1
        self.lockMax = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockBMaxCB(self, mode):  # max checkbutton
        if mode != 0: mode = 1
        self.lockBMax = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockIncrementCB(self, mode):  # increment entry field
        if mode != 0: mode = 1
        self.lockIncrement = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockBIncrementCB(self, mode):  # increment checkbutton
        if mode != 0: mode = 1
        self.lockBIncrement = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockPrecisionCB(self, mode):
        if mode != 0: mode = 1
        self.lockPrecision = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockShowLabelCB(self, mode):
        if mode != 0: mode = 1
        self.lockShowLabel = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockValueCB(self, mode):
        if mode != 0: mode = 1
        self.lockValue = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockContinuousCB(self, mode):
        if mode != 0: mode = 1
        self.lockContinuous = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()

    def lockOneTurnCB(self, mode):
        if mode != 0: mode = 1
        self.lockOneTurn = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()
Exemple #4
0
class ThumbWheel(Tkinter.Frame, KeyboardEntry):
    """ This class builds a thumbwheel put onto a wheelpad.
constructor options:
- master: the master into the thumwheel can be packed. If one is specified,
  the widget gets packed in a toplevel() window
- height, width, orient specify the size and orientation of the widget.
- wheelpad specifies the pad onto which the thumbwheel gets packed
  optional is a canvascfg to configure the wheelpad canvas Tkinter options.
  for example if the wheelpad needs a blue background: canvascfg={'bg':'blue'}
- continuous: boolean. When set to True, continuous is 'on' and the callback functions
  will be called each time the value changes. Otherwise continuous will be 'off' and
  callback functions will be called on mouse release.
- callback: (None, callable function or list of callable functions).to specify function
  to be called when the value of the thumbwheel is modified.
- type: ('float', 'int' ...) string describing the type of the thumbwheel
- min, max, increment, precision, oneTurn specify the parameters of the thumbwheel.
- labCfg describes the label of the thumbwheel which will be packed to the left of the widget by default unless 'side' is specified. Possible keywords are text and side.

- wheelLabCfg describes the label located on the wheel where the value of the thumbwheel
  will be displayed
- showLabel Flag to specify whether to display the wheelLabel always 1, never 0,
  only on motion 2.
- canvasCfg describe the canvas containing the thumbwheel.
An option panel is available to the user to modify the thumbwheel settings by right clicking
on the widget
- lockMin, lockBMin, lockMax, lockBMax, lockIncrement, lockBIncrement,
  lockPrecision, lockShowLabel, lockValue,  lockType, lockContinuous, lockOneTurn:
  These flags specifies whether (when set to 1) or not (when set to 0) the user will be
  allowed to change the setting of the thumbwheel.
- reportDelta is flag to specify whether value differences should be reported
  rathe than absolute values
  
The widget has a configure() method: type, min, max, increment, precision,
showLabel, value, continuous, oneTurn, orient, reportDelta  can be set this
way.

a lock() method is used to disable the various gui components of the
options panel. Usage: <instance>.configure(<component>=<value>)
components see configure(). value is 0 or 1. 1 disables,0 enables.    
    """

#FIXME: this would be the mechanism to remove all arguments from the
# init (see DejaVu)
#    keywords = [
#        'master',
#        'oneTurn',
#        ]
    def __init__(self, master=None, labCfg={'fg':'black','side':'left',
                                            'text':None},
                 wheelLabCfg={}, canvasCfg={}, callback=None,
                 type='float', min=None, max=None, increment=0.0, precision=2,
                 showLabel=1, value=0.0, continuous=True, width=200,
                 height=40, oneTurn=10., wheelPad=6,
                 lockMin=0, lockBMin=0, lockMax=0,lockBMax=0,
                 lockIncrement=0, lockBIncrement=0,
                 lockPrecision=0, lockShowLabel=0, lockValue=0,
                 lockType=0, lockContinuous=0, lockOneTurn=0,
                 orient=None, reportDelta=0, **kw):

# See FIXME init
#        if __debug__:
#            checkkeywords(kw)
            
	Tkinter.Frame.__init__(self, master)
	Tkinter.Pack.config(self, side='left', anchor='w')

        #FIXME: nblines are not dynamically computed
        self.nblines = 30
        self.callback = None
        self.callbacks = CallbackManager()  # object to manage callback
                                        # functions. They get called with the
                                        # current value as an argument

        self.width = 200
        self.height = 40
        self.setWidth(width)
        self.setHeight(height)
        # set widget orientation: either horizontal or vertical
        self.setOrient(orient)


        # initialize various attributes with default values
        self.precision = 2              # decimal places
        self.min = None                 # minimum value
        self.max = None                 # maximum value
        self.increment = increment      # value increment
        self.minOld = 0.                # used to store old values 
        self.maxOld = 0.
        self.incrementOld = increment
        self.size = 50                  # defines widget size
        self.offsetValue = 0.           # used to set increment correctly
        self.lab = None                 # label
        self.opPanel = None             # option panel widget
        self.oneTurn = 360.             # value increment for 1 full turn
        self.value = 0.0                # current value of widget
        self.oldValue = 0.0             # old value of widget
        self.showLabel = 1              # turn on to display label on
        self.continuous = True          # set to 1 to call callbacks at 
                                        # each value change, else gets called 
                                        # on button release event
        self.angle = 0.                 # angle corresponding to value

        self.labCfg = labCfg            # Tkinter Label options
        self.labelFont = (
            ensureFontCase('helvetica'), 14, 'bold')    # label font
        self.labelColor = 'yellow'      # label color
        self.canvas = None              # the canvas to create the widget in
        self.usedArcColor = '#aaaaaa'   # filled arc color of used portion
        self.unusedArcColor = '#cccccc' # filled arc color of unused portion
        self.pyOver180 = math.pi/180.0  # constants used in various places
        self.threeSixtyOver1turn = 1
        self.piOver1turn = math.pi/360.

        self.wheelLabCfg = wheelLabCfg  # Tkinter wheel label options
        self.canvasCfg = canvasCfg      # Tkinter Canvas options
        self.wheelPad = wheelPad        # pad between wheel and widget borders
        self.deltaVal = 0.              # delta with value at last callback

        self.valueLabel = None          # Tkinter Label
        self.setType(type)              # can be float or int

        self.discreteValue = 0.         # value in discrete space

        self.lockMin = lockMin          # lock<X> variables in configure()
        self.lockMax = lockMax          # to lock/unlock entries in options
                                        # panel
        

        self.lockMin = lockMin          # lock<X> vars are used in self.lock()
        self.lockMax = lockMax          # to lock/unlock entries in optionpanel
        self.lockIncrement = lockIncrement
        self.lockBMin = lockBMin
        self.lockBMax = lockBMax
        self.lockBIncrement = lockBIncrement
        self.lockPrecision = lockPrecision
        self.lockShowLabel = lockShowLabel
        self.lockValue = lockValue
        self.lockType = lockType
        self.lockContinuous = lockContinuous
        self.lockOneTurn = lockOneTurn
        self.reportDelta = reportDelta

        self.setLabel(self.labCfg)

        self.createCanvas(master, wheelPad=wheelPad)        
	self.canvas.bind("<ButtonPress-1>", self.mouseDown)
	self.canvas.bind("<ButtonRelease-1>", self.mouseUp)
	self.canvas.bind("<B1-Motion>", self.mouseMove)
        self.canvas.bind("<Button-3>", self.toggleOptPanel)

 	self.valueLabel.bind("<ButtonPress-1>", self.mouseDown)
	self.valueLabel.bind("<ButtonRelease-1>", self.mouseUp)
	self.valueLabel.bind("<B1-Motion>", self.mouseMove)
        self.valueLabel.bind("<Button-3>", self.toggleOptPanel)

        if os.name == 'nt': #sys.platform == 'win32':
            self.canvas.bind("<MouseWheel>", self.mouseWheel)
            self.valueLabel.bind("<MouseWheel>", self.mouseWheel)
        else:
            self.canvas.bind("<Button-4>", self.mouseWheel)
            self.valueLabel.bind("<Button-4>", self.mouseWheel)
            self.canvas.bind("<Button-5>", self.mouseWheel)
            self.valueLabel.bind("<Button-5>", self.mouseWheel)


        self.bind("<Button-3>", self.toggleOptPanel)

        KeyboardEntry.__init__(self, (self, self.canvas, self.valueLabel),
                               self.setFromEntry)
        
        self.opPanel = OptionsPanel(master = self, title="Thumbwheel Options")

        # now set the constructor options correctly using the configure method
        self.setValue(value)
        apply( self.configure, (),
               {'type':type, 'min':min, 'max':max, 'increment':increment,
                'precision':precision, 'showLabel':showLabel,
                'continuous':continuous, 'oneTurn':oneTurn,
                'lockType':lockType, 'lockMin':lockMin, 'lockBMin':lockBMin,
                'lockMax':lockMax, 'lockBMax':lockBMax,
                'lockIncrement':lockIncrement, 'lockBIncrement':lockBIncrement,
                'lockPrecision':lockPrecision, 'lockShowLabel':lockShowLabel,
                'lockValue':lockValue, 'lockContinuous':lockContinuous,
                'lockOneTurn':lockOneTurn, 'orient':orient,
                'reportDelta':reportDelta,
                'callback':callback
                })
        

    def setFromEntry(self, valueString):
        try:
            self.set(self.type(valueString), force=1)
        except ValueError:
            # fixme we would like to pop this up in a window maybe
            import traceback
            traceback.print_stack()
            traceback.print_exc()


    def handleKeyStroke(self, event):
        # handle key strokes for numbers only in widget keyboard entry label
        key = event.keysym

        if key.isdigit() or key=='period' or key=='minus' or key=='plus':
            if key == 'period':
                key = '.'
            elif key == 'minus':
                key = '-'
            elif key == 'plus':
                key = '+'
            self.typedValue += key
            self.typedValueTK.configure(text=self.typedValue)
            
        else:
            KeyboardEntry.handleKeyStroke(self, event)


    def setWidth(self, width):
        assert isinstance(width, types.IntType),\
               "Illegal width: expected %s, got %s"%(type(1),type(width))
        assert width > 0,"Illegal width: must be > 0, got %s"%width
        self.width = width


    def setHeight(self, height):
        assert isinstance(height, types.IntType),\
               "Illegal height: expected %s, got %s"%(type(1),type(height))
        assert height > 0,"Illegal height: must be > 0, got %s"%height
        self.height = height


    def setCallbacks(self, cb):
        """Set widget callback. Must be callable function. Callback is called
every time the widget value is set/modified"""

        assert cb is None or callable(cb) or type(cb) is types.ListType,\
               "Illegal callback: must be either None or callable. Got %s"%cb
        if cb is None: return
        elif type(cb) is types.ListType:
            for func in cb:
                assert callable(func), "Illegal callback must be callable. Got %s"%func
                self.callbacks.AddCallback(func)
        else:
            self.callbacks.AddCallback(cb)
        self.callback = cb
        

    def toggleOptPanel(self, event=None):
        if self.opPanel.flag:
           self.opPanel.Dismiss_cb()
        else:
            if not hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.displayPanel(create=1)
            else:
                self.opPanel.displayPanel(create=0)

 
    def mouseDown(self, event):
	# remember where the mouse went down
        if self.orient=='horizontal':
            self.lastx = event.x
        else:
            self.lastx = event.y


    def mouseUp(self, event):
	# call callbacks if not in continuous mode
        newVal = self.get()
        if self.oldValue != newVal:
            if not self.continuous:
                self.oldValue = newVal
                self.callbacks.CallCallbacks(newVal)
            if self.showLabel == 2:
                # no widget labels on mouse release
                self.valueLabel.place_forget()


    def mouseMove(self, event):
        if self.orient=='horizontal':
            dx = event.x - self.lastx
            self.lastx = event.x
        else:
            dx = event.y - self.lastx
            self.lasty = event.y
        dang = dx * math.pi / 180.
        val = dang*self.oneTurnOver2pi
        self.computeNewValue(val)


    def mouseWheel(self, event):
        #print "mouseWheel", event, event.num
        if os.name == 'nt': #sys.platform == 'win32':
            if event.delta > 0:
                lEventNum = 4
            else:
                lEventNum = 5
        else:
            lEventNum = event.num
        if lEventNum == 4:
            self.computeNewValue(self.oneTurn/10)
        else:
            self.computeNewValue(-self.oneTurn/10)


    def get(self):
        if self.reportDelta:
            return self.type(self.deltaVal)
        else:
            return self.type(self.value)


    def printLabel(self):
        if not self.showLabel in [1,2]:
            return
        hl = self.valueLabel.winfo_reqheight()
        wl = self.valueLabel.winfo_reqwidth()
        h = self.canvas.winfo_reqheight()
        w = self.canvas.winfo_reqwidth()
        self.valueLabel.place(in_=self.canvas, x=(w-wl)*.5, y=((h-hl)*0.5)-
                              self.height/4 + 2)
        self.valueLabel.configure(text=self.labelFormat%self.value)


    def set(self, val, update=1, force=0):
        # if force is set to 1, we call this method regardless of the
        # widget configuration. This is for example the case if the thumwheel
        # is set to continuous=0, but the value is set in the options panel
        if self.min is not None and val <= self.min:
            val = self.min
        elif self.max is not None and val >= self.max:
            val = self.max
      
        oldval = self.value
        self.value = val
        self.deltaVal = self.value - oldval
        
        newVal = self.get()
        if update and self.continuous or force:
            if self.oldValue != self.value or force:
                self.oldValue = newVal
                self.callbacks.CallCallbacks(newVal)
            if self.showLabel==2:
                self.printLabel()
 
        if self.showLabel==1:
            self.printLabel()
            
        if self.opPanel:
            self.opPanel.valInput.set(self.labelFormat%self.value)
        
    #FIXME: this could be renamed increment (to parallel the set method)
    # some code in this method is duplicated in set method
    def computeNewValue(self, val):
        # snap to closest increment
        oldval = self.value
        if self.increment is not None and self.increment != 0.:
            self.discreteValue = self.discreteValue + val
            if self.discreteValue>=self.increment:
                self.value = self.value+self.increment
                self.discreteValue=0.
            elif -self.discreteValue>=self.increment:
                self.value = self.value-self.increment
                self.discreteValue=0.
        else:
            self.value = self.value + val

        if self.min is not None and self.value <= self.min:
            self.value = self.min
            val = 0
        elif self.max is not None and self.value >= self.max:
            self.value = self.max
            val = 0
        
        self.deltaVal = self.value - oldval

        # self.angle is used in drawLines()
        self.angle = val/self.oneTurnOver2pi
        self.drawLines()

        if self.showLabel>0: # ALWAYS
            self.printLabel()
            
        if self.opPanel:
            self.opPanel.valInput.set(self.labelFormat%self.value)

        newVal = self.get()
        if self.oldValue != newVal and not self.reportDelta:
            # this part is called either when the value is float OR when
            # continuous is off
            if self.continuous:
                self.oldValue = newVal
                self.callbacks.CallCallbacks(newVal)
            else:
                pass
        else:
            # this part is usually called when the datatype is INT AND
            # continuous is on
            if self.oldValue != newVal:
                # only call this part when we "reach" a new value
                self.oldValue = newVal
                self.callbacks.CallCallbacks(newVal)
            else:
                # else we need nothing to do
                pass


    def createCanvas(self, master, wheelPad=6):
        bw = self.borderWidth = wheelPad # distance between wheel and raise
        
        cd={'width':self.width+bw, 'height':self.height+bw, 'relief':'raised',
                'borderwidth':3}
        
        for k, w in self.canvasCfg.items():
            cd[k] = w

        self.canvas = Tkinter.Canvas(self, cd)
        cbdw = int(self.canvas.cget('borderwidth'))
        bd = self.borderWidth + cbdw + 1 # +1 for pixel0 that is not drawn

        height = self.height-bw
        width = self.width-bw
        cp = self.canvas.create_polygon
	self.outline1 = cp( bd, bd, bd, height+cbdw, width+cbdw, height+cbdw,
                            width+cbdw, bd, bd, bd,
                            width=1, outline='gray60', fill='gray85')

        ul = bd+1 # upper left pixel
        l = (width+cbdw-1) - (bd+1) # length of the inner box
        cl25 = 2.*l/25.
        cl = self.canvas.create_line
	self.outline2 = cl( ul+int(cl25), ul, ul, ul, ul,
                            height+cbdw-1, ul+int(cl25),
                            height+cbdw-1,
                            width=1, fill='gray20')
 	self.outline3 = cl( ul+int(cl25), ul, ul+int(3*cl25), ul,
                            width=1, fill='gray60')
 	self.outline4 = cl( ul+int(cl25), height+cbdw-1,
                            ul+int(3*cl25), height+cbdw-1,
                            width=1, fill='gray60')
 	self.outline5 = cl( ul+int(5*cl25), ul, ul+int(7.5*cl25), ul,
                            width=1, fill='white')
 	self.outline4 = cl(  ul+int(5*cl25), height+cbdw-1, ul+int(7.5*cl25),
                            height+cbdw-1,
                            width=1, fill='white')
 	self.outline6 = cl( ul+int(9.5*cl25), ul, ul+int(11.5*cl25), ul,
                            width=1, fill='gray60')
 	self.outline7 = cl( ul+int(9.5*cl25), height+cbdw-1,
                            ul+int(11.5*cl25), height+cbdw-1,
                            width=1, fill='gray60')

        re = ul+l
	self.outline8 = cl( ul+int(11.5*cl25), ul, re, ul, re,
                            height+cbdw-1, ul+int(11.5*cl25),
                            height+cbdw-1,
                            width=1, fill='gray20')

        # corners of the box where the lines have to be drawn
        self.innerbox = (ul+1, ul+1, re-1, height+cbdw-1)

        self.circlePtsAngles = []
        inc = 2*math.pi/float(self.nblines)
        for i in range(self.nblines):
            self.circlePtsAngles.append( i*inc )
        self.circlePtsAngles = Numeric.array(self.circlePtsAngles, 'f')
        self.linesIds = []
        self.shLinesIds = []
        # this table should depend on the number of lines
        # currently it is of length 15 (self.nblines/2)
        # It should be resized automatically to self.nblines/2
        self.shadowLinesOptions = [
            ( 0, 'black', 1), # offset, color, width
            ( 2, 'gray30', 1),
            ( 2, 'gray30', 1),
            ( 0, 'gray30', 1),
            (-1, 'white', 1),
            (-1, 'white', 1),
            (-1, 'white', 2),
            (-1, 'white', 2),
            (-1, 'white', 2),
            (-1, 'white', 2),
            (-1, 'white', 1),
            (-1, 'white', 1),
            ( 0, 'gray30', 1),
            (-2, 'gray30', 1),
            (-2, 'gray30', 1),
            ( 0, 'black', 1), # offset, color, width
            ( 0, 'black', 1), # offset, color, width
            ]

        for i in range(self.nblines):
            self.linesIds.append(cl(0,0,0,0,width=1, fill='black'))
            self.shLinesIds.append(cl(0,0,0,0))
        
        wlabCfg = {'padx':0,'pady':0}
        wlabCfg.update(self.wheelLabCfg)
        self.valueLabel = apply( Tkinter.Label, (self.master,), wlabCfg)

        self.drawLines()
	self.canvas.pack(side=Tkinter.LEFT)
        self.toggleWidgetLabel(self.showLabel)

        
    def drawLines(self):
        # angle has to be provided in radians
        angle = self.angle
            
        self.circlePtsAngles = self.circlePtsAngles+angle
        self.circlePtsAngles = Numeric.remainder(self.circlePtsAngles,
                                                 2*math.pi)
        xcoords = Numeric.cos(self.circlePtsAngles)
        xsin = Numeric.sin(self.circlePtsAngles)
        if self.orient=='horizontal':
            w = self.innerbox[2] - self.innerbox[0]
            r = w/2
            c = self.innerbox[0] + r
            y1 = self.innerbox[1]
            y2 = self.innerbox[3]
        else:
            w = self.innerbox[3] - self.innerbox[1]
            r = w/2
            c = self.innerbox[1] + r
            y1 = self.innerbox[0]
            y2 = self.innerbox[2]
            
        cl = self.canvas.create_line
        setCoords = self.canvas.coords
        setOpt = self.canvas.itemconfigure
        pi2 = math.pi/2.
        drawLines = 0
        co = Numeric.take(xcoords,
                          Numeric.nonzero(Numeric.greater_equal(xsin, 0.0)))
        co = Numeric.sort(co)
        co = [-1]+list(co)
        for i in range(len(co)):
            x = c - int(co[i]*r)
            if self.orient=='horizontal':
                setCoords(self.linesIds[i], x, y1, x, y2)
            else:
                setCoords(self.linesIds[i], y1, x, y2, x)
            shopt = self.shadowLinesOptions[i]
            x = x + shopt[0]
            if self.orient=='horizontal':
                setCoords(self.shLinesIds[i], x, y1, x, y2)
            else:
                setCoords(self.shLinesIds[i], y1, x, y2, x)
            setOpt(self.shLinesIds[i], fill = shopt[1], width=shopt[2])
            

    def toggleWidgetLabel(self, val):
        if val == 0:
            # no widget labels
            self.showLabel=0
            self.valueLabel.place_forget()
            
        if val == 1:
            # show always widget labels
            self.showLabel=1
            self.printLabel()

        if val == 2:
            # show widget labels only when mouse moves
            self.showLabel=2
            self.valueLabel.place_forget()

    def setValue(self, val):
        assert type(val) in [types.IntType, types.FloatType],\
               "Illegal type for value: expected %s or %s, got %s"%(
                   type(1), type(1.0), type(val) )

        # setValue does NOT call a callback!
        if self.min is not None and val < self.min: val = self.min
        if self.max is not None and val > self.max: val = self.max
        self.value = self.type(val)
        self.oldValue = self.value
        self.offsetValue=self.value
        if self.showLabel == 1:
            self.printLabel()
            

 #####################################################################
 # the 'configure' methods:
 #####################################################################

    def configure(self, **kw):
        if 'type' in kw.keys(): # make sure type is set first
            self.setType(kw['type'])
            del kw['type']
            
        for key,value in kw.items():
            # the 'set' parameter callbacks
            if key=='labCfg': self.setLabel(value)
            elif key=='callback': self.setCallbacks(value)
            elif key=='wheelLabCfg':
                self.wheelLablCfg.update(value)
                self.printLabel()
            elif key=='min': self.setMin(value)
            elif key=='max': self.setMax(value)
            elif key=='increment': self.setIncrement(value)
            elif key=='precision': self.setPrecision(value)
            elif key=='showLabel': self.setShowLabel(value)
            elif key=='continuous':
                self.setContinuous(value)
            elif key=='oneTurn': self.setOneTurn(value)
            elif key=='orient': self.setOrient(value)
            elif key=='reportDelta': self.setReportDelta(value)

            # the 'lock' entries callbacks
            elif key=='lockType': self.lockTypeCB(value)
            elif key=='lockMin': self.lockMinCB(value)
            elif key=='lockBMin': self.lockBMinCB(value)
            elif key=='lockMax': self.lockMaxCB(value)
            elif key=='lockBMax': self.lockBMaxCB(value)
            elif key=='lockIncrement': self.lockIncrementCB(value)
            elif key=='lockBIncrement': self.lockBIncrementCB(value)
            elif key=='lockPrecision': self.lockPrecisionCB(value)
            elif key=='lockShowLabel': self.lockShowLabelCB(value)
            elif key=='lockValue': self.lockValueCB(value)
            elif key=='lockContinuous': self.lockContinuousCB(value)
            elif key=='lockOneTurn': self.lockOneTurnCB(value)


    def setLabel(self, labCfg):
        self.labCfg = labCfg

        text = labCfg.get('text', None)
        if text is None or text=='':
            return

        d={}
        for k, w in self.labCfg.items():
            if k == 'side': continue
            else: d[k] = w
        if not 'side' in self.labCfg.keys():
            self.labCfg['side'] = 'left'

        if not self.lab:
            self.lab = Tkinter.Label(self, d)
            self.lab.pack(side=self.labCfg['side'])
            self.lab.bind("<Button-3>", self.toggleOptPanel)
        else:
            self.lab.configure(text)


    def setType(self, Type):
        assert type(Type) in [types.StringType, types.TypeType],\
               "Illegal type for datatype. Expected %s or %s, got %s"%(
                   type('a'), type(type), type(Type) )

        if type(Type) == type(""): # type str
            assert Type in ('int','float'),\
           "Illegal type descriptor. Expected 'int' or 'float', got '%s'"%Type
            self.type = eval(Type)
        else:
            self.type = Type

        if self.type == int:
            self.labelFormat = "%d"
            self.int_value = self.value
        else:
            self.labelFormat = "%."+str(self.precision)+"f"

        if hasattr(self.opPanel, 'optionsForm'):
            w = self.opPanel.idf.entryByName['togIntFloat']['widget']
            if self.type == int:
                w.setvalue('int')
            elif self.type == 'float':
                w.setvalue('float')

        if self.opPanel:
            self.opPanel.updateDisplay()


        if self.valueLabel and self.showLabel == 1:
            self.printLabel()


    def setMin(self, min):
        if min is not None:

            assert type(min) in [types.IntType, types.FloatType],\
                 "Illegal type for minimum. Expected type %s or %s, got %s"%(
                     type(0), type(0.0), type(min) )

            if self.max and min > self.max:
                min = self.max

            self.min = self.type(min)

            if self.showLabel == 1:
                self.printLabel()

            if self.value < self.min:
                self.set(self.min)

            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.minInput.set(self.labelFormat%self.min) 

                self.opPanel.toggleMin.set(1)
                self.opPanel.min_entry.configure(state='normal', fg='gray0')
            self.minOld = self.min    
                
        else:
            self.min = None
            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.toggleMin.set(0)
                self.opPanel.min_entry.configure(state='disabled',
                                                 fg='gray40')


    def setMax(self, max):
        if max is not None:

            assert type(max) in [types.IntType, types.FloatType],\
                 "Illegal type for maximum. Expected type %s or %s, got %s"%(
                     type(0), type(0.0), type(max) )

            if self.min and max < self.min:
                max = self.min

            self.max = self.type(max)

            if self.showLabel == 1:
                self.printLabel()

            if self.value > self.max:
                self.set(self.max)

            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.maxInput.set(self.labelFormat%self.max) 

                self.opPanel.toggleMax.set(1)
                self.opPanel.max_entry.configure(state='normal', fg='gray0')
            self.maxOld = self.max
                
        else:
            self.max = None
            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.toggleMax.set(0)
                self.opPanel.max_entry.configure(state='disabled', fg='gray40')


    def setIncrement(self, incr):
        if incr is not None:

            assert type(incr) in [types.IntType, types.FloatType],\
               "Illegal type for increment. Expected type %s or %s, got %s"%(
                   type(0), type(0.0), type(incr) )

            
            self.increment = self.type(incr)
            self.offsetValue = self.value
            self.incrementOld = self.increment

            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.incrInput.set(self.labelFormat%self.increment)
                
                self.opPanel.toggleIncr.set(1)
                self.opPanel.incr_entry.configure(state='normal', fg='gray0')
                
        else:
            self.increment = None
            if hasattr(self.opPanel, 'optionsForm'):
                self.opPanel.toggleIncr.set(0)
                self.opPanel.incr_entry.configure(state='disabled', fg='gray40')


    def setPrecision(self, val):
        assert type(val) in [types.IntType, types.FloatType],\
               "Illegal type for precision. Expected type %s or %s, got %s"%(
                     type(0), type(0.0), type(val) )
        val = int(val)
        
        if val > 10:
            val = 10
        if val < 1:
            val = 1
        self.precision = val
        if self.type == float:
            self.labelFormat = "%."+str(self.precision)+"f"
        else:
            self.labelFormat = "%d"

        if hasattr(self.opPanel, 'optionsForm'):
            w = self.opPanel.idf.entryByName['selPrec']['widget']
            w.setvalue(val)
            
        if self.opPanel:
            self.opPanel.updateDisplay()
         
        # and update the printed label
        if self.canvas and self.showLabel == 1:
            self.printLabel()


    def setContinuous(self, cont):
        """ cont can be None, 0 or 1 """

        assert cont in [True, False, 0, 1],\
             "Illegal value for continuous: expecting a boolean True, False, got %s"%cont

        self.continuous = cont
        if hasattr(self.opPanel, 'optionsForm'):
            w=self.opPanel.idf.entryByName['togCont']['widget']
            if cont:
                w.setvalue('on')
            else:
                w.setvalue('off')


    def setShowLabel(self, val):
        """Show label can be 0, 1 or 2
0: no label
1: show label only when value changes
2: label is always shown"""
        
        assert val in [0,1,2],\
               "Illegal value for showLabel. Expected 0, 1 or 2, got %s"%val
        
        if val != 0 and val != 1 and val != 2:
            print "Illegal value. Must be 0, 1 or 2"
            return
        self.showLabel = val
        self.toggleWidgetLabel(val)

        if hasattr(self.opPanel, 'optionsForm'):
            w = self.opPanel.idf.entryByName['togLabel']['widget']
            if self.showLabel == 0:
                label = 'never'
            elif self.showLabel == 1:
                label = 'always'
            elif self.showLabel == 2:
                label = 'move'
            w.setvalue(label)
 
        if self.opPanel:
            self.opPanel.updateDisplay()


    def setOneTurn(self, oneTurn):
        assert type(oneTurn) in [types.IntType, types.FloatType],\
               "Illegal type for oneTurn. Expected %s or %s, got %s"%(
                   type(0), type(0.0), type(oneTurn) )

        self.oneTurn = oneTurn
        self.threeSixtyOver1turn = 360./oneTurn
        self.piOver1turn = math.pi/oneTurn
        self.oneTurnOver2pi = oneTurn / (2*math.pi)
        if self.opPanel:
            self.opPanel.updateDisplay()
        

    def setOrient(self, orient):
        if orient is None:
            if self.width > self.height:
                orient='horizontal'
            else:
                orient = 'vertical'
        assert orient in ['horizontal', 'vertical'],\
               "Expected 'vertical' or 'horizontal', got '%s'"%orient
        self.orient = orient
        

    def setReportDelta(self, rD):
        assert rD in [None, 0, 1],\
               "Expected None, 0 or 1, got %s"%rD
        if rD is None or rD == 0:
            self.reportDelta = 0
        else:
            self.reportDelta = 1


 #####################################################################
 # the 'lock' methods:
 #####################################################################


    def lockTypeCB(self, mode):
        if mode != 0: mode = 1
        self.lockType = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()
        

    def lockMinCB(self, mode): #min entry field
        if mode != 0: mode = 1
        self.lockMin = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()
        

    def lockBMinCB(self, mode): # min checkbutton
        if mode != 0: mode = 1
        self.lockBMin = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()


    def lockMaxCB(self, mode): # max entry field
        if mode != 0: mode = 1
        self.lockMax = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()


    def lockBMaxCB(self, mode): # max checkbutton
        if mode != 0: mode = 1
        self.lockBMax = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()


    def lockIncrementCB(self, mode): # increment entry field
        if mode != 0: mode = 1
        self.lockIncrement = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()


    def lockBIncrementCB(self, mode): # increment checkbutton
        if mode != 0: mode = 1
        self.lockBIncrement = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()


    def lockPrecisionCB(self, mode):
        if mode != 0: mode = 1
        self.lockPrecision = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()
            

    def lockShowLabelCB(self, mode):
        if mode != 0: mode = 1
        self.lockShowLabel = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()


    def lockValueCB(self, mode):
        if mode != 0: mode = 1
        self.lockValue = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()


    def lockContinuousCB(self, mode):
        if mode != 0: mode = 1
        self.lockContinuous = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()


    def lockOneTurnCB(self, mode):
        if mode != 0: mode = 1
        self.lockOneTurn = mode
        if hasattr(self.opPanel, 'optionsForm'):
            self.opPanel.lockUnlockDisplay()