/ lib / python / pyvcp_widgets.py
pyvcp_widgets.py
   1  #    This is a component of AXIS, a front-end for emc
   2  #    Copyright 2007 Anders Wallin <anders.wallin@helsinki.fi>
   3  #
   4  #   TJP 12 04 2007
   5  #   Rugludallur saw that spinbuttons had no initial value until after thumbs inc'd or de'c
   6  #   TJP saw that if xml prescribed <value>1234</value> the spinbutton locked up after the inc/dec
   7  #   it seems a new term in the __init__ may fix this
   8  #   end TJP 12 04 2007
   9  #
  10  #   Added initval to checkbutton/scale for initial values,  Dallur 15 April 2007 (jarl stefansson) (jarl stefansson)
  11  #
  12  #   Multiple additions and amendments as per notations - ArcEye 2013
  13  #
  14  #    This program is free software; you can redistribute it and/or modify
  15  #    it under the terms of the GNU General Public License as published by
  16  #    the Free Software Foundation; either version 2 of the License, or
  17  #    (at your option) any later version.
  18  #
  19  #    This program is distributed in the hope that it will be useful,
  20  #    but WITHOUT ANY WARRANTY; without even the implied warranty of
  21  #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  22  #    GNU General Public License for more details.
  23  #
  24  #    You should have received a copy of the GNU General Public License
  25  #    along with this program; if not, write to the Free Software
  26  #    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  27  
  28  """ A widget library for pyVCP 
  29      
  30      The layout and composition of a Python Virtual Control Panel is specified
  31      with an XML file. The file must begin with <pyvcp>, and end with </pyvcp>
  32  
  33      In the documentation for each widget, optional tags are shown bracketed:
  34      [ <option>Something</option> ]
  35      such a tag is not required for pyVCP to work, but may add functionality or
  36      modify the behaviour of a widget.
  37  
  38      Example XML file:
  39      <pyvcp>
  40          <led>
  41              <size>40</size>
  42              <halpin>"my-led"</halpin>
  43          </led>
  44      </pyvcp>
  45      This will create a VCP with a single LED widget which indicates the value 
  46      of HAL pin compname.my-led 
  47  """
  48  
  49  
  50  from Tkinter import *
  51  from hal import *
  52  import math
  53  import bwidget
  54  import time
  55  
  56  # -------------------------------------------
  57  
  58  
  59  class pyvcp_dial(Canvas):
  60      # Dial widget by tomp
  61      """ A dial that outputs a HAL_FLOAT 
  62          reacts to both mouse-wheel and mouse dragging
  63          <dial>
  64              [ <size>376</size> ]
  65              [ <dialcolor>"grey"</dialcolor> ]
  66              [ <edgecolor>"pink"</edgecolor> ] 
  67              [ <dotcolor>"white"</dotcolor> ]
  68              [ <cpr>100</cpr> ]    number of changes per rev, is # of dial tick marks, beware hi values)            
  69              [ <min_>-33.123456</min_> ]
  70              [ <max_>3.3</max_> ]
  71              [ <text>"Gallons per Hour"</text> ]            (knob label)
  72              [ <initval>123</initval> ]           (initial value a whole number must end in '.')
  73              [ <resolution>.001</resolution> ]          (scale value a whole number must end in '.')
  74              [ <halpin>"anaout"</halpin> ]
  75              [ <param_pin>1</param_pin>] creates param pin if > 0, set to initval, value can then be set externally, ArcEye 2013
  76          </dial>
  77                  
  78                  key bindings:
  79                      <Button-4>              untested no wheel mouse
  80                      <Button-5>              untested no wheel mouse
  81  
  82                      <Button1-Motion>      used internally during drag
  83                      <ButtonPress>          used internally to record beginning of drag
  84                      <ButtonRelease>          used internally at end of drag
  85  
  86                      <Double-1> divides scale by 10
  87                      <Double-2> resets scale to original value
  88                      <Double-3> multiplies scale by 10
  89          
  90                      <Shift-1>   shift-click resets original analog value 
  91  
  92                  features:
  93                      text autoscales
  94  
  95      """
  96      # FIXME:
  97      # -jogging should be enabled only when the circle has focus
  98      #   TJP nocando:   only widgets have events, not thier 'items', the circle is an item
  99      
 100      # -circle should maintain focus when mouse over dot
 101      #   TJP nocando:   ditto, the circle is an item, so focus & event are not aligned to it
 102      
 103      # -jogging by dragging with the mouse could work better
 104      
 105      # -add a scaled output, scale changes when alt/ctrl/shift is held down
 106      #   TJP dblLeftClick divides scale by 10 , dblRightClcik muxs by 10
 107      
 108      
 109      n=0
 110      #TJP TODO: let some artists look at it, butt ugly!
 111      #TJP cpr is overloaded, now it means "chgs per rev" not "counts per rev"
 112      #TJP the tik marks could get very fine, avoid high cpr to size ratios (easily seen)
 113      
 114     
 115      def __init__(self,root,pycomp,halpin=None,halparam=None,param_pin=0,size=200,cpr=40,dialcolor="", \
 116              edgecolor="",dotcolor="grey",min_=None,max_=None, \
 117              text=None,initval=0,resolution=0.1, \
 118              **kw):
 119          
 120          pad=size/10
 121  
 122          self.counts = int(round(initval/resolution))
 123          self.out = self.counts * resolution #  float output   out
 124          self.origValue=initval       # in case user wants to reset the pot/valve/thingy
 125  
 126          #self.text3=resolution
 127  
 128          Canvas.__init__(self,root,width=size,height=size)
 129          pad2=pad-size/15
 130          self.circle2=self.create_oval(pad2,pad2,size-pad2,size-pad2,width=3)# edge circle
 131          self.itemconfig(self.circle2,fill=edgecolor,activefill=edgecolor)
 132  
 133          self.circle=self.create_oval(pad,pad,size-pad,size-pad)             # dial circle
 134          self.itemconfig(self.circle,fill=dialcolor,activefill=dialcolor)
 135          
 136          self.itemconfig(self.circle)
 137          self.mid=size/2
 138          self.r=(size-2*pad)/2
 139          self.alfa=0
 140          self.d_alfa=2*math.pi/cpr
 141          self.size=size
 142  
 143          self.funit=resolution          
 144          self.origFunit=self.funit        # allow restoration
 145          
 146          self.mymin=min_            
 147          self.mymax=max_
 148  
 149          self.dot = self.create_oval(self.dot_coords())
 150          self.itemconfig(self.dot,fill=dotcolor,activefill="black")
 151          self.line = self.create_line( self.mid+(self.r*1)*math.cos(self.alfa), \
 152                              self.mid+(self.r*1)*math.sin(self.alfa), \
 153                              self.mid+(self.r*1.1)*math.cos(self.alfa), \
 154                              self.mid+(self.r*1.1)*math.sin(self.alfa))
 155          self.itemconfig(self.line,arrow="last",arrowshape=(10,10,10))
 156          self.itemconfig(self.line,width=10)
 157  
 158          #TJP items get rendered in order of creation, so the knob will be behind these texts
 159          #TJP the font can be described with pixel size by using negative value
 160          self.txtroom=size/6
 161  
 162          # a title, if the user has supplied one
 163          if text!=None:
 164              self.title=self.create_text([self.mid,self.mid-self.txtroom],
 165                          text=text,font=('Arial',-self.txtroom))
 166          # the output
 167          self.dro=self.create_text([self.mid,self.mid],
 168                          text=str(self.out),font=('Arial',-self.txtroom))
 169          # the scale
 170          self.delta=self.create_text([self.mid,self.mid+self.txtroom], 
 171                          text='x '+ str(self.funit),font=('Arial',-self.txtroom))
 172  
 173          
 174          self.bind('<Button-4>',self.wheel_up)            # untested no wheel mouse
 175          self.bind('<Button-5>',self.wheel_down)          # untested no wheel mouse
 176          
 177          self.bind('<Button1-Motion>',self.motion)        #during drag
 178          self.bind('<ButtonPress>',self.bdown)                #begin of drag
 179          self.bind('<ButtonRelease>',self.bup)                #end of drag 
 180  
 181          self.bind('<Double-1>',self.chgScaleDn)            # doubleclick scales down
 182          self.bind('<Double-2>',self.resetScale)         # doubleclick resets scale
 183          self.bind('<Double-3>',self.chgScaleUp)         # doubleclick scales up
 184  
 185          self.bind('<Shift-1>',self.resetValue)          # shift resets value
 186          
 187          self.draw_ticks(cpr)
 188  
 189          self.dragstartx=0
 190          self.dragstarty=0
 191  
 192          self.dragstart=0
 193          self.dotcolor=dotcolor
 194  
 195          # create the hal pin
 196          if halpin == None:
 197              halpin = "dial."+str(pyvcp_dial.n)+".out"
 198          self.halpin=halpin            
 199  
 200          if halparam == None:
 201              self.param_pin = param_pin
 202              if self.param_pin == 1:
 203                  halparam = "dial." + str(pyvcp_dial.n) + ".param_pin"
 204                  self.halparam=halparam        
 205                  pycomp.newpin(halparam, HAL_FLOAT, HAL_IN)
 206  
 207          pyvcp_dial.n += 1
 208          self.pycomp=pycomp
 209          pycomp.newpin(halpin, HAL_FLOAT, HAL_OUT)
 210  
 211          pycomp[self.halparam] = self.origValue
 212          self.oldValue = self.origValue
 213          self.value = self.origValue
 214  
 215      def chgScaleDn(self,event):
 216          # reduces the scale by 10x
 217          self.funit=self.funit/10.0
 218          self.counts *= 10
 219          self.update_scale()
 220          self.update_dro()
 221          self.update_dot()
 222      
 223      def chgScaleUp(self,event):
 224          # increases the scale by 10x
 225          self.funit=self.funit*10.0
 226          self.counts = (self.counts + 5) / 10
 227          self.out = self.counts * self.funit
 228          self.update_scale()
 229          self.update_dro()
 230          self.update_dot()
 231      
 232      def resetScale(self,event):
 233          # reset scale to original value
 234          self.funit=self.origFunit
 235          self.counts = int(round(self.out / self.funit))
 236          self.out = self.counts * self.funit
 237          self.update_scale()
 238      
 239      def resetValue(self,event):
 240          # reset output to orifinal value
 241          self.counts = int(round(self.origValue / self.funit))
 242          self.out= self.counts * self.funit
 243          self.update_dot()
 244          self.update_dro()
 245  
 246      def dot_coords(self):
 247          # calculate the coordinates for the dot
 248          DOTR=0.04*self.size
 249          DOTPOS=0.85
 250          midx = self.mid+DOTPOS*self.r*math.cos(self.alfa)
 251          midy = self.mid+DOTPOS*self.r*math.sin(self.alfa)
 252          return midx-DOTR, midy-DOTR,midx+DOTR,midy+DOTR
 253  
 254      def bdown(self,event):
 255          self.dragstartx=event.x
 256          self.dragstarty=event.y
 257          self.dragstart=math.atan2((event.y-self.mid),(event.x-self.mid))
 258          self.itemconfig(self.dot,fill="black",activefill="black")
 259  
 260      def bup(self,event):
 261          self.itemconfig(self.dot,fill=self.dotcolor)
 262  
 263      def motion(self,event):
 264          dragstop = math.atan2((event.y-self.mid),(event.x-self.mid))
 265          delta = dragstop - self.dragstart
 266          if delta>=self.d_alfa:
 267              self.up()
 268              self.dragstart=math.atan2((event.y-self.mid),(event.x-self.mid))
 269          elif delta<=-self.d_alfa:
 270              self.down()
 271              self.dragstart=math.atan2((event.y-self.mid),(event.x-self.mid))
 272          self.itemconfig(self.dot,fill="black",activefill="black")
 273  
 274      def wheel_up(self,event):
 275          self.up()
 276  
 277      def wheel_down(self,event):
 278          self.down()
 279  
 280      def down(self):
 281          self.alfa-=self.d_alfa
 282          self.counts -= 1
 283          self.out = self.counts * self.funit
 284          #TJP clip down side
 285          if self.mymin != None:
 286              if self.out<self.mymin:
 287                  self.out=self.mymin
 288                  self.counts = self.mymin * self.funit
 289          self.update_dot()
 290          self.update_dro()
 291  
 292      def up(self):
 293          self.alfa+=self.d_alfa
 294          self.counts += 1
 295          self.out = self.counts * self.funit
 296          #TJP clip up side
 297          if self.mymax != None:
 298              if self.out>self.mymax:
 299                  self.out=self.mymax
 300                  self.counts = self.mymax * self.funit
 301          self.update_dot()
 302          self.update_dro()
 303  
 304      def update_dot(self):
 305          self.coords(self.dot, self.dot_coords() )
 306          self.coords(self.line, self.mid+(self.r*1)*math.cos(self.alfa),self.mid+(self.r*1)*math.sin(self.alfa), \
 307                              self.mid+(self.r*1.1)*math.cos(self.alfa), \
 308                              self.mid+(self.r*1.1)*math.sin(self.alfa))  
 309  
 310      def update_dro(self):
 311          valtext = str(self.out)
 312          self.itemconfig(self.dro,text=valtext)
 313  
 314      def update_scale(self):
 315          valtext = str(self.funit)
 316          valtext = 'x ' + valtext
 317          self.itemconfig(self.delta,text=valtext)
 318  
 319      def draw_ticks(self,cpr):
 320          for n in range(0,cpr,2):
 321             for i in range(0,2):
 322              startx=self.mid+self.r*math.cos((n+i)*self.d_alfa)
 323              starty=self.mid+self.r*math.sin((n+i)*self.d_alfa)
 324              if i == 0:
 325                 length = 1.15
 326                 width = 2
 327              else:
 328                 length = 1.1
 329                 width = 1
 330              stopx=self.mid+length*self.r*math.cos((n+i)*self.d_alfa)
 331              stopy=self.mid+length*self.r*math.sin((n+i)*self.d_alfa)
 332              self.create_line(startx,starty,stopx,stopy,width=width)
 333  
 334      def update(self,pycomp):
 335          self.pycomp[self.halpin] = self.out
 336  
 337          self.value = pycomp[self.halparam]
 338          if self.value != self.oldValue :
 339              self.counts = int(round(self.value / self.funit))
 340              self.out= self.counts * self.funit
 341              self.update_dot()
 342              self.update_dro()
 343              self.oldValue = self.value
 344  
 345  # -------------------------------------------
 346  
 347  
 348  
 349  class pyvcp_meter(Canvas):
 350      """ Meter - shows the value of a FLOAT with an analog meter
 351          <meter>
 352              [ <size>300</size> ]
 353              [ <halpin>"mymeter"</halpin> ]
 354              [ <text>"My Voltage"</text> ]
 355              [ <subtext>"Volts"</subtext>
 356              [ <min_>-22</min_> ]
 357              [ <max_>123</max_> ]
 358              [ <majorscale>10</majorscale> ]
 359              [ <minorscale>5</minorscale> ]
 360              [ <region1>(70,80,"green")</region1> ]
 361              [ <region2>(80,100,"orange")</region2> ]
 362              [ <region3>(100,123,"red")</region3> ]
 363          </meter>
 364      """
 365      # FIXME: logarithmic scale option
 366      n=0
 367      def __init__(self,root,pycomp,halpin=None, size=200,text=None,subtext=None,min_=0,max_=100,majorscale=None, minorscale=None,region1=None,region2=None,region3=None,**kw):
 368          self.size = size
 369          self.pad=10
 370          Canvas.__init__(self,root,width=size,height=size)
 371          self.halpin=halpin
 372          self.min_=min_
 373          self.max_=max_
 374          range_=2.5
 375          self.min_alfa=-math.pi/2-range_
 376          self.max_alfa=-math.pi/2+range_
 377          self.circle=self.create_oval(self.pad,self.pad,size-self.pad,size-self.pad, width=2)
 378          self.itemconfig(self.circle,fill="white")
 379          self.mid=size/2
 380          self.r=(size-2*self.pad)/2
 381          self.alfa=0
 382          if minorscale==None: 
 383              self.minorscale=0
 384          else:
 385              self.minorscale=minorscale
 386          if majorscale==None: 
 387              self.majorscale=float((self.max_-self.min_)/10)
 388          else: 
 389              self.majorscale=majorscale
 390          if text!=None: t=self.create_text([self.mid,self.mid-size/12],font="Arial %d bold" % (size/10),text=text)
 391          if subtext!=None: t=self.create_text([self.mid,self.mid+size/12],font="Arial %d" % (size/30+5),text=subtext)
 392          if region1!=None: self.draw_region(region1)
 393          if region2!=None: self.draw_region(region2)
 394          if region3!=None: self.draw_region(region3)
 395          self.draw_ticks()
 396  
 397          self.line = self.create_line([self.mid,self.mid, self.mid+self.r*math.cos(self.alfa), self.mid+self.r*math.sin(self.alfa)],fill="red", arrow="last", arrowshape=(0.9*self.r,self.r,self.r/20))
 398          self.itemconfig(self.line,width=3)
 399  
 400          # create the hal pin
 401          if halpin == None:
 402              self.halpin = "meter."+str(pyvcp_meter.n)+".value"
 403              pyvcp_meter.n += 1
 404          pycomp.newpin(self.halpin, HAL_FLOAT, HAL_IN)
 405          self.value = pycomp[self.halpin]
 406      
 407      def rad2deg(self, rad): return rad*180/math.pi
 408  
 409      def value2angle(self, value):
 410              #returns angle for a given value
 411              scale = (self.max_-self.min_)/(self.max_alfa-self.min_alfa)
 412              alfa = self.min_alfa + (value-self.min_)/scale
 413              if alfa > self.max_alfa:
 414                  alfa = self.max_alfa
 415              elif alfa < self.min_alfa:
 416                  alfa = self.min_alfa            
 417              return alfa
 418      
 419      def p2c(self, radius, angle): 
 420          #returns the cathesian coordinates (x,y) for given polar coordinates 
 421          #radius in percent of self.r; angle in radians
 422          return self.mid+radius*self.r*math.cos(angle), self.mid+radius*self.r*math.sin(angle)
 423  
 424      def update(self,pycomp):
 425          self.value = pycomp[self.halpin]
 426          self.alfa = self.value2angle(self.value)
 427          x,y = self.p2c(0.8, self.alfa)
 428          self.coords(self.line,self.mid,self.mid,x,y)
 429  
 430      def draw_region(self, (start, end, color)):
 431              #Draws a colored region on the canvas between start and end
 432              start = self.value2angle(start)
 433              start = -self.rad2deg(start)
 434              end = self.value2angle(end)
 435              end = -self.rad2deg(end)
 436              extent = end-start
 437              halfwidth = math.floor(0.1*self.r/2)+1
 438              xy = self.pad+halfwidth, self.pad+halfwidth, self.size-self.pad-halfwidth, self.size-self.pad-halfwidth
 439              self.create_arc(xy, start=start, extent=extent, outline=color, width=(halfwidth-1)*2, style="arc")
 440  
 441      def draw_ticks(self):
 442          value = self.min_
 443          while value <= self.max_:
 444              alfa = self.value2angle(value)
 445              xy1 = self.p2c(1,alfa)
 446              xy2 = self.p2c(0.85,alfa)
 447              xytext = self.p2c(0.75,alfa)
 448              self.create_text(xytext,font="Arial %d" % (self.size/30+5), text="%g" % value)
 449              self.create_line(xy1, xy2, width=2)
 450              value = value + self.majorscale
 451          #minor ticks
 452          value = self.min_
 453          if self.minorscale > 0:
 454              while value <= self.max_:
 455                  if (value % self.majorscale) != 0:
 456                      alfa = self.value2angle(value)
 457                      xy1 = self.p2c(1,alfa)
 458                      xy2 = self.p2c(0.9,alfa)
 459                      self.create_line(xy1, xy2)
 460                  value = value + self.minorscale
 461  
 462               
 463  # -------------------------------------------
 464  
 465  class pyvcp_jogwheel(Canvas):
 466      """" A jogwheel that outputs a HAL_FLOAT count
 467          reacts to both mouse-wheel and mouse dragging
 468          <jogwheel>
 469              [ <cpr>33</cpr> ]                       (counts per revolution)
 470              [ <halpin>"myjogwheel"</halpin> ]
 471              [ <size>300</size> ]
 472           </jogwheel>
 473      """
 474      # FIXME:
 475      # -jogging should be enabled only when the circle has focus
 476      # -circle should maintain focus when mouse over dot
 477      # -jogging by dragging with the mouse could work better
 478      # -add a scaled output, scale changes when alt/ctrl/shift is held down
 479      n=0
 480      def __init__(self,root,pycomp,halpin=None,size=200,cpr=40,**kw):
 481          pad=size/10
 482          self.count=0
 483          Canvas.__init__(self,root,width=size,height=size)
 484          pad2=pad-size/15
 485          self.circle2=self.create_oval(pad2,pad2,size-pad2,size-pad2,width=3)# edge circle
 486          self.circle=self.create_oval(pad,pad,size-pad,size-pad)
 487          self.itemconfig(self.circle,fill="lightgrey",activefill="lightgrey")
 488          self.mid=size/2
 489          self.r=(size-2*pad)/2
 490          self.alfa=0
 491          self.d_alfa=2*math.pi/cpr
 492          self.size=size
 493          
 494          
 495          self.dot = self.create_oval(self.dot_coords())
 496          self.itemconfig(self.dot,fill="black")
 497          self.line = self.create_line( self.mid+(self.r*1)*math.cos(self.alfa), \
 498                              self.mid+(self.r*1)*math.sin(self.alfa), \
 499                              self.mid+(self.r*1.1)*math.cos(self.alfa), \
 500                              self.mid+(self.r*1.1)*math.sin(self.alfa))
 501          self.itemconfig(self.line,arrow="last",arrowshape=(10,10,10))
 502          self.itemconfig(self.line,width=8)
 503  
 504          self.bind('<Button-4>',self.wheel_up)
 505          self.bind('<Button-5>',self.wheel_down)
 506          self.bind('<Button1-Motion>',self.motion)
 507          self.bind('<ButtonPress>',self.bdown)
 508          self.draw_ticks(cpr)
 509          self.dragstartx=0
 510          self.dragstarty=0
 511          self.dragstart=0
 512  
 513          # create the hal pin
 514          if halpin == None:
 515              halpin = "jogwheel."+str(pyvcp_jogwheel.n)+".count"
 516              pyvcp_jogwheel.n += 1
 517          pycomp.newpin(halpin, HAL_FLOAT, HAL_OUT)
 518          self.halpin=halpin
 519          pycomp[self.halpin] = self.count
 520          self.pycomp=pycomp
 521                   
 522      def dot_coords(self):
 523          DOTR=0.06*self.size
 524          DOTPOS=0.85
 525          midx = self.mid+DOTPOS*self.r*math.cos(self.alfa)
 526          midy = self.mid+DOTPOS*self.r*math.sin(self.alfa)
 527          return midx-DOTR, midy-DOTR,midx+DOTR,midy+DOTR
 528      
 529      def bdown(self,event):
 530          self.dragstartx=event.x
 531          self.dragstarty=event.y
 532          self.dragstart=math.atan2((event.y-self.mid),(event.x-self.mid))
 533  
 534      def motion(self,event):
 535          dragstop = math.atan2((event.y-self.mid),(event.x-self.mid))
 536          delta = dragstop - self.dragstart
 537          if delta>=self.d_alfa:
 538              self.up()
 539              self.dragstart=math.atan2((event.y-self.mid),(event.x-self.mid))
 540          elif delta<=-self.d_alfa:
 541              self.down()
 542              self.dragstart=math.atan2((event.y-self.mid),(event.x-self.mid))
 543      
 544      def wheel_up(self,event):
 545          self.up()
 546          
 547      def wheel_down(self,event):
 548          self.down()
 549  
 550      def down(self):
 551          self.alfa-=self.d_alfa
 552          self.count-=1
 553          self.pycomp[self.halpin] = self.count
 554          self.update_dot()       
 555      
 556      def up(self):
 557          self.alfa+=self.d_alfa
 558          self.count+=1
 559          self.pycomp[self.halpin] = self.count
 560          self.update_dot()  
 561  
 562      def update_dot(self):
 563          self.coords(self.dot, self.dot_coords() )  
 564          self.coords(self.line, self.mid+(self.r*1)*math.cos(self.alfa),self.mid+(self.r*1)*math.sin(self.alfa), \
 565                              self.mid+(self.r*1.1)*math.cos(self.alfa), \
 566                              self.mid+(self.r*1.1)*math.sin(self.alfa))         
 567  
 568      def draw_ticks(self,cpr):
 569          for n in range(0,cpr):
 570              startx=self.mid+self.r*math.cos(n*self.d_alfa)
 571              starty=self.mid+self.r*math.sin(n*self.d_alfa)
 572              stopx=self.mid+1.15*self.r*math.cos(n*self.d_alfa)
 573              stopy=self.mid+1.15*self.r*math.sin(n*self.d_alfa)
 574              self.create_line([startx,starty,stopx,stopy])
 575  
 576      def update(self,pycomp):
 577          # this is stupid, but required for updating pin
 578          # when first connected to a signal
 579          self.pycomp[self.halpin] = self.count
 580          
 581  # -------------------------------------------
 582  ## ArcEye - added - no example given and the one in docs misses out initval  ##
 583  
 584  """" A radiobutton will set one of the halpins true. The other pins are set false.
 585      <radiobutton>
 586          <choices>["one","two","three"]</choices>    labels next to each button
 587          <halpin>"radio"</halpin>                    pin giving index of active button
 588          <initval>0</initval>                        index of button pin to set true at start
 589          <orient>HORIZONTAL</orient>                 add horizontal tag default is vertical
 590      </radiobutton>
 591      """
 592  ################################################################################
 593  
 594  class pyvcp_radiobutton(Frame):
 595      n=0
 596      def __init__(self,master,pycomp,halpin=None,initval=0,orient=None,choices=[],**kw):
 597          f=Frame.__init__(self,master,bd=2,relief=GROOVE)
 598          self.v = IntVar()
 599          self.v.set(1)
 600          self.choices=choices
 601          self.side = 'top'
 602          if orient != None:
 603              self.side = 'left'
 604          if halpin == None:
 605              halpin = "radiobutton."+str(pyvcp_radiobutton.n)
 606              pyvcp_radiobutton.n += 1
 607          
 608          self.halpins=[]
 609          n=0
 610          for c in choices:
 611              b=Radiobutton(self,f, text=str(c),variable=self.v, value=pow(2,n))
 612              b.pack(side=self.side)
 613              if n==initval: 
 614                  b.select()
 615              c_halpin=halpin+"."+str(c)
 616              pycomp.newpin(c_halpin, HAL_BIT, HAL_OUT)
 617              self.halpins.append(c_halpin)
 618              n+=1
 619  
 620          self.selected = initval
 621          pycomp[self.halpins[initval]]=1 
 622  
 623  ## ArcEye - FIXED - only update the pins if changed  ##
 624      def update(self,pycomp):
 625          index=int(math.log(self.v.get(),2))
 626          if index != self.selected: 
 627              for pin in self.halpins:
 628                  pycomp[pin]=0;
 629              pycomp[self.halpins[index]]=1;
 630              self.selected = index
 631  
 632  
 633      # FIXME
 634      # this would be a much better way of updating the
 635      # pins, but at the moment I can't get it to work
 636      # this is never called even if I set command=self.update()
 637      # in the call to Radiobutton above
 638      def changed(self):
 639          index=math.log(self.v.get(),2)
 640          index=int(index)
 641          print "active:",self.halpins[index]
 642  
 643  
 644  
 645  # -------------------------------------------
 646  
 647  class pyvcp_label(Label):
 648      """ Static text label 
 649          <label>
 650              <text>"My Label:"</text>
 651              <halpin>"name"</halpin>
 652              <disable_pin>True</disable_pin>
 653          </label>
 654      """
 655      n=0
 656      def __init__(self,master,pycomp,halpin=None,disable_pin=False,**kw):
 657          Label.__init__(self,master,**kw)
 658          self.disable_pin=disable_pin
 659          if disable_pin:
 660              if halpin == None:
 661                  halpin = "label."+str(pyvcp_label.n) 
 662                  pyvcp_label.n += 1
 663              halpin_disable = halpin+".disable"
 664              self.halpin_disable = halpin_disable
 665              pycomp.newpin(halpin_disable, HAL_BIT, HAL_IN)   
 666          
 667      def update(self,pycomp):
 668          if self.disable_pin: 
 669              is_disabled = pycomp[self.halpin_disable]     
 670              if is_disabled == 1: Label.config(self,state=DISABLED)
 671              else: Label.config(self,state=NORMAL)
 672          else:pass
 673         
 674  
 675  
 676  # -------------------------------------------
 677  
 678  ### ArcEye 01052013 - added new widget #############################################
 679  
 680  class pyvcp_multilabel(Label):
 681      """ Selectable text label, can display up to 6 label legends
 682          when associated bit pin is activated
 683          <multilabel>
 684              <legends>["Label1" "Label2" "Label3" "Label4" "Label5" "Label6"]</legends>
 685              <font>("Helvetica", 20)</font>
 686              <disable_pin>True</disable_pin>
 687              <initval>0</initval>    Which legend to display by default (make sure it matches initval for linked widget if any) 
 688              <halpin>None</halpin>   Optional alternative name base for pins
 689          </multilabel>
 690      """
 691      
 692      n=0
 693      def __init__(self,master,pycomp,halpin=None,disable_pin=False,legends=[],initval=0,**kw):
 694          Label.__init__(self,master,**kw)
 695          self.disable_pin=disable_pin
 696          
 697          if halpin == None:
 698              halpin = "multilabel."+str(pyvcp_multilabel.n)
 699              pyvcp_multilabel.n += 1
 700          
 701          self.halpins=[]
 702          n=0
 703          for c in legends:
 704              c_halpin=halpin+".legend"+str(n)
 705              pycomp.newpin(c_halpin, HAL_BIT, HAL_IN)
 706              self.halpins.append(c_halpin)
 707              n+=1
 708              #limit to 6 legends
 709              if n >= 6:
 710                  break
 711          self.legends = legends
 712          self.num_pins = n
 713          
 714          if disable_pin:
 715              halpin_disable = halpin+".disable"
 716              self.halpin_disable = halpin_disable
 717              pycomp.newpin(halpin_disable, HAL_BIT, HAL_IN)
 718  
 719          # test for out of range initval number
 720          if initval >= 0 and initval <= self.num_pins :
 721              val = initval
 722          else :
 723              val = 0
 724              
 725          Label.config(self,text= legends[val])
 726          pycomp[self.halpins[val]] = 1
 727          self.pin_index = val
 728  
 729      def update(self,pycomp):
 730          if self.disable_pin: 
 731              is_disabled = pycomp[self.halpin_disable]     
 732              if is_disabled == 1: 
 733                  Label.config(self,state=DISABLED)
 734              else: 
 735                  Label.config(self,state=NORMAL)
 736                  
 737          # no telling how people will use this, so just break at first
 738          # set pin that is not the existing set one
 739          # if several pins are set one after another, the legend for the
 740          # last one set will end up being displayed
 741          index = -1
 742          for x in xrange(0, self.num_pins):
 743              state = pycomp[self.halpins[x]]
 744              if state == 1 :
 745                  index = x
 746                  if index != self.pin_index :
 747                      break
 748                  
 749          if index > -1 and index != self.pin_index:
 750              for x in xrange(0, self.num_pins):
 751                  pycomp[self.halpins[x]] = 0
 752  
 753              pycomp[self.halpins[index]] = 1
 754              Label.config(self,text= self.legends[index])
 755              self.pin_index = index
 756  
 757  
 758  
 759  class pyvcp_vbox(Frame):
 760      """ Box in which widgets are packed vertically
 761          <vbox>
 762              <relief>GROOVE</relief>         (FLAT, SUNKEN, RAISED, GROOVE, RIDGE)
 763              <bd>3</bd>                      (border width)
 764              place widgets here
 765          </vbox>
 766      """
 767      def __init__(self,master,pycomp,bd=0,relief=FLAT):
 768          Frame.__init__(self,master,bd=bd,relief=relief)
 769          self.fill = 'x'
 770          self.side = 'top'
 771          self.anchor = 'center'
 772          self.expand = 'yes'
 773  
 774      def update(self,pycomp): 
 775          pass
 776  
 777      def add(self, container, widget):
 778          if isinstance(widget, pyvcp_boxexpand):
 779              self.expand = widget.expand
 780              return
 781          if isinstance(widget, pyvcp_boxfill):
 782              self.fill = widget.fill
 783              return
 784          if isinstance(widget, pyvcp_boxanchor):
 785              self.anchor = widget.anchor
 786              return
 787          widget.pack(side=self.side, anchor=self.anchor, fill=self.fill, expand=self.expand)
 788  
 789  class pyvcp_boxfill:
 790      def __init__(self, master, pycomp, fill):
 791          self.fill = fill
 792      def update(self, pycomp): pass
 793  
 794  class pyvcp_boxanchor:
 795      def __init__(self, master, pycomp, anchor):
 796          self.anchor = anchor
 797      def update(self, pycomp): pass
 798  
 799  class pyvcp_boxexpand:
 800      def __init__(self, master, pycomp, expand):
 801          self.expand = expand
 802      def update(self, pycomp): pass
 803  
 804  # -------------------------------------------
 805  
 806  class pyvcp_hbox(Frame):
 807      """ Box in which widgets are packed horizontally
 808          <vbox>
 809              <relief>GROOVE</relief>         (FLAT, SUNKEN, RAISED, GROOVE, RIDGE)
 810              <bd>3</bd>                      (border width)
 811              place widgets here
 812          </vbox>        
 813      """
 814      def __init__(self,master,pycomp,bd=0,relief=FLAT):
 815          Frame.__init__(self,master,bd=bd,relief=relief)
 816          self.fill = 'y'
 817          self.side = 'left'
 818          self.anchor = 'center'
 819          self.expand = 'yes'
 820  
 821      def update(self,pycomp): 
 822          pass
 823  
 824      def add(self, container, widget):
 825          if isinstance(widget, pyvcp_boxexpand):
 826              self.expand = widget.expand
 827              return
 828          if isinstance(widget, pyvcp_boxfill):
 829              self.fill = widget.fill
 830              return
 831          if isinstance(widget, pyvcp_boxanchor):
 832              self.anchor = widget.anchor
 833              return
 834          widget.pack(side=self.side, anchor=self.anchor, fill=self.fill)
 835  
 836  class pyvcp_labelframe(LabelFrame):
 837      """
 838       frame with a title
 839      """
 840      def __init__(self,master,pycomp,**kw):
 841          LabelFrame.__init__(self,master,**kw)
 842          self.pack(expand=1,fill=BOTH)
 843      def update(self,pycomp):
 844          pass
 845      def add(self, container, widget):
 846          widget.pack(side="top", fill="both", expand="yes")
 847  
 848  class pyvcp_tabs(bwidget.NoteBook):
 849      def __init__(self, master, pycomp, cnf={}, **kw):
 850          self.names = kw.pop("names", [])
 851          self.idx = 0
 852          self._require(master)
 853          Widget.__init__(self, master, "NoteBook", cnf, kw)
 854  
 855      def update(self, pycomp): pass
 856  
 857      def add(self, container, child):
 858          child.pack(side="top", fill="both", anchor="ne")
 859          if self.idx == 1:
 860              self.raise_page(self.names[0])
 861  
 862      def getcontainer(self):
 863          if len(self.names) < self.idx:
 864              self.names.append("Tab-%d" % self.idx)
 865          name = self.names[self.idx]
 866          self.idx += 1
 867          return self.insert("end", name, text=name)
 868  
 869  # -------------------------------------------
 870  
 871  class pyvcp_spinbox(Spinbox):
 872      """ (control) controls a float, also shown as text 
 873          reacts to the mouse wheel 
 874          <spinbox>
 875              [ <halpin>"my-spinbox"</halpin> ]
 876              [ <min_>55</min_> ]   sets the minimum value to 55
 877              [ <max_>123</max_> ]  sets the maximum value to 123
 878              [ <initval>100</initval> ]  sets initial value to 100  TJP 12 04 2007
 879              [ <param_pin>1</param_pin>] creates param pin if > 0, set to initval, value can then be set externally, ArcEye 2013            
 880          </spinbox>
 881      """
 882      # FIXME: scale resolution when shift/ctrl/alt is held down?
 883   
 884      n=0
 885      def __init__(self,master,pycomp,halpin=None, halparam=None,param_pin=0,
 886                      min_=0,max_=100,initval=0,resolution=1,format="2.1f",**kw):
 887          self.v = DoubleVar()
 888          if 'increment' not in kw: kw['increment'] = resolution
 889          if 'from' not in kw: kw['from'] = min_
 890          if 'to' not in kw: kw['to'] = max_
 891          if 'format' not in kw: kw['format'] = "%" + format
 892          kw['command'] = self.command
 893          Spinbox.__init__(self,master,textvariable=self.v,**kw)
 894          
 895          if halpin == None:
 896              halpin = "spinbox."+str(pyvcp_spinbox.n)
 897              
 898          self.halpin=halpin
 899  
 900          if halparam == None:
 901              self.param_pin = param_pin
 902              if self.param_pin == 1:
 903                  halparam = "spinbox." + str(pyvcp_spinbox.n) + ".param_pin"
 904                  self.halparam=halparam
 905                  pycomp.newpin(halparam, HAL_FLOAT, HAL_IN)
 906  
 907          pyvcp_spinbox.n += 1
 908          
 909          if initval < min_:
 910              self.value=min_
 911          elif initval > max_:
 912              self.value=max_
 913          else:
 914              self.value=initval
 915          self.oldvalue=min_
 916  
 917          if self.param_pin == 1:
 918              self.init=self.value
 919              self.oldinit=self.init
 920              pycomp[self.halparam] = self.init
 921              
 922          self.format = "%(b)"+format
 923          self.max_=max_
 924          self.min_=min_
 925          self.resolution=resolution
 926          self.v.set( str( self.format  % {'b':self.value} ) )
 927          pycomp.newpin(halpin, HAL_FLOAT, HAL_OUT)
 928          
 929          self.bind('<Button-4>',self.wheel_up)
 930          self.bind('<Button-5>',self.wheel_down)
 931          self.bind('<Return>',self.return_pressed)
 932  
 933      def return_pressed(self, event):
 934          self.value = self.v.get()
 935          if self.value < self.min_:
 936              self.value = self.min_
 937          if self.value > self.max_:
 938              self.value = self.max_
 939  
 940  
 941      def command(self):
 942          self.value = self.v.get()
 943  
 944      def update(self,pycomp):
 945          pycomp[self.halpin] = self.value
 946          if self.value != self.oldvalue:
 947              self.v.set( str( self.format  % {'b':self.value} ) )
 948              self.oldvalue=self.value
 949  
 950          if self.param_pin == 1:            
 951              self.init = pycomp[self.halparam]
 952              if self.init != self.oldinit:
 953                  self.v.set( str( self.format  % {'b':self.init} ) )
 954                  self.oldinit=self.init    
 955                  self.value=self.init
 956            
 957      def wheel_up(self,event):
 958          self.value += self.resolution
 959          if self.value > self.max_:
 960              self.value = self.max_
 961            
 962       
 963      def wheel_down(self,event):
 964          self.value -= self.resolution
 965          if self.value < self.min_:
 966              self.value = self.min_
 967            
 968  
 969  
 970  # -------------------------------------------
 971  
 972  class pyvcp_number(Label):
 973      """ (indicator) shows a float as text """
 974      n=0
 975      def __init__(self,master,pycomp,halpin=None,format="2.1f",**kw):
 976          self.v = StringVar()
 977          self.format=format
 978          Label.__init__(self,master,textvariable=self.v,**kw)
 979          if halpin == None:
 980              halpin = "number."+str(pyvcp_number.n)
 981              pyvcp_number.n += 1
 982          self.halpin=halpin
 983          self.value=0.0
 984          dummy = "%(b)"+self.format
 985          self.v.set( str( dummy  % {'b':self.value} ) )
 986          pycomp.newpin(halpin, HAL_FLOAT, HAL_IN)
 987  
 988      def update(self,pycomp):    
 989          newvalue = pycomp[self.halpin]
 990          if newvalue != self.value:
 991              self.value=newvalue
 992              dummy = "%(b)"+self.format
 993              self.v.set( str( dummy  % {'b':newvalue} ) )
 994  
 995  
 996  class pyvcp_u32(Label):
 997      """ (indicator) shows a u32 as text """
 998      n=0
 999      def __init__(self,master,pycomp,halpin=None,format="d",**kw):
1000          self.v = StringVar()
1001          self.format=format
1002          Label.__init__(self,master,textvariable=self.v,**kw)
1003          if halpin == None:
1004              halpin = "number."+str(pyvcp_number.n)
1005              pyvcp_number.n += 1
1006          self.halpin=halpin
1007          self.value=0.0
1008          dummy = "%(b)"+self.format
1009          self.v.set( str( dummy  % {'b':self.value} ) )
1010          pycomp.newpin(halpin, HAL_U32, HAL_IN)
1011  
1012      def update(self,pycomp):    
1013          newvalue = pycomp[self.halpin]
1014          if newvalue != self.value:
1015              self.value=newvalue
1016              dummy = "%(b)"+self.format
1017              self.v.set( str( dummy  % {'b':newvalue} ) )
1018  
1019   
1020  class pyvcp_s32(Label):
1021      """ (indicator) shows a s32 as text """
1022      n=0
1023      def __init__(self,master,pycomp,halpin=None,format="d",**kw):
1024          self.v = StringVar()
1025          self.format=format
1026          Label.__init__(self,master,textvariable=self.v,**kw)
1027          if halpin == None:
1028              halpin = "number."+str(pyvcp_number.n)
1029              pyvcp_number.n += 1
1030          self.halpin=halpin
1031          self.value=0.0
1032          dummy = "%(b)"+self.format
1033          self.v.set( str( dummy  % {'b':self.value} ) )
1034          pycomp.newpin(halpin, HAL_S32, HAL_IN)
1035  
1036      def update(self,pycomp):    
1037          newvalue = pycomp[self.halpin]
1038          if newvalue != self.value:
1039              self.value=newvalue
1040              dummy = "%(b)"+self.format
1041              self.v.set( str( dummy  % {'b':newvalue} ) )
1042  
1043  class pyvcp_timer(Label):
1044      """ (indicator) shows elapsed time as HH:MM:SS
1045      two pins - run and reset
1046      time advances whenever run is true
1047      time holds whenever run is false
1048      time resets to zero on a rising edge of reset
1049      """
1050      n=0
1051      def __init__(self,master,pycomp,halpin=None,**kw):
1052          self.v = StringVar()
1053          Label.__init__(self,master,textvariable=self.v,**kw)
1054          if halpin == None:
1055              halpin = "timer."+str(pyvcp_timer.n)
1056              pyvcp_timer.n += 1
1057          self.halpins=[]
1058          c_halpin=halpin+".reset"
1059          pycomp.newpin(c_halpin, HAL_BIT, HAL_IN)
1060          self.halpins.append(c_halpin)
1061          c_halpin=halpin+".run"
1062          pycomp.newpin(c_halpin, HAL_BIT, HAL_IN)
1063          self.halpins.append(c_halpin)
1064  
1065          self.resetvalue=0
1066          self.runvalue=0
1067          # starttime is the time of the last rising edge of 'run'
1068          self.starttime=0
1069          # basetime is the sum of all prior 'run=1' periods
1070          self.basetime=0
1071          self.currtime=0
1072          self.v.set( "00:00:00")
1073  
1074  
1075      def update(self,pycomp):    
1076          resetvalue = pycomp[self.halpins[0]]
1077          runvalue = pycomp[self.halpins[1]]
1078          if resetvalue != self.resetvalue:
1079              self.resetvalue=resetvalue
1080              if resetvalue == 1:
1081                  self.basetime=0
1082                  self.starttime=time.time()
1083          if runvalue != self.runvalue:
1084              self.runvalue=runvalue
1085              if runvalue == 1:
1086                  # rising edge
1087                  self.starttime = time.time()
1088              else:
1089                  # falling edge
1090                  self.basetime += time.time() - self.starttime
1091          if runvalue == 1:
1092              total=self.basetime + time.time() - self.starttime
1093          else:
1094              total=self.basetime
1095          hr = int(total / 3600)
1096          remainder = total - hr*3600
1097          mn = int(remainder / 60)
1098          sec = int(remainder - mn*60)
1099          self.v.set( str( "%02d:%02d:%02d" % (hr,mn,sec) ) )
1100  
1101  
1102  # -------------------------------------------
1103  
1104  class pyvcp_bar(Canvas):
1105  
1106  ## reworked by ArcEye 10022014
1107  ## allow value ranges in different colours
1108  
1109      """ (indicator) a bar-indicator for a float
1110      <bar>
1111          <halpin>"my-bar"</halpin>
1112          <min_>0</min_>
1113          <max_>150</max_>
1114          <bgcolor>"grey"</bgcolor>
1115          <range1>(0,100,"green")</range1>
1116          <range2>(101,129,"orange")</range2>
1117          <range3>(130,150,"red")</range3>
1118          <fillcolor>"green"</fillcolor>
1119          <canvas_width>200</canvas_width>
1120          <canvas_height>50</canvas_height>
1121          <bar_height>30</bar_height>
1122          <bar_width>150</bar_width>
1123          <format>"3.1f"</format>
1124      </bar>
1125      """
1126      n=0
1127  
1128      def __init__(self,master,pycomp,fillcolor="green",bgcolor="grey",
1129                 halpin=None,min_=0.0,max_=100.0,range1=None,range2=None,
1130                 range3=None,format='3.1f', canvas_width=200,
1131                 canvas_height=50, bar_height=30, bar_width=150,**kw):
1132  
1133          self.cw=canvas_width
1134          self.ch=canvas_height
1135          self.bh=bar_height
1136          self.bw=bar_width
1137          #self.cw=200    # canvas width
1138          #self.ch=50     # canvas height
1139          #self.bh=30     # bar height
1140          #self.bw=150    # bar width
1141          self.pad=((self.cw-self.bw)/2)
1142  
1143          Canvas.__init__(self,master,width=self.cw,height=self.ch)
1144  
1145          if halpin == None:
1146              halpin = "bar."+str(pyvcp_bar.n)
1147              pyvcp_bar.n += 1
1148          self.halpin=halpin
1149          self.endval=max_
1150          self.startval=min_
1151          self.format = "%" + format
1152  
1153          pycomp.newpin(halpin, HAL_FLOAT, HAL_IN)
1154          
1155          self.value=0.0 # some dummy value to start with  
1156               
1157          pycomp[self.halpin] = self.value
1158          
1159          # the border
1160          border=self.create_rectangle(self.pad,1,self.pad+self.bw,self.bh)
1161          self.itemconfig(border,fill=bgcolor)
1162          
1163          # the bar
1164          tmp=self.bar_coords()
1165          start=tmp[0]
1166          end=tmp[1]
1167          self.bar=self.create_rectangle(start,2,end,self.bh-1)
1168  	    # default fill unless overriden
1169          self.itemconfig(self.bar,fill=fillcolor)
1170  
1171          # start text
1172          start_text=self.create_text(self.pad,self.bh+10,text=str(self.startval) )
1173          #end text
1174          end_text=self.create_text(self.pad+self.bw,self.bh+10,text=str(self.endval) )
1175          # value text
1176          self.val_text=self.create_text(self.pad+self.bw/2,
1177                                     self.bh/2,text=str(self.value) )
1178  
1179          if range1!=None and range2!=None and range3!=None:
1180              self.range1 = range1
1181              self.range2 = range2
1182              self.range3 = range3
1183              self.ranges = True
1184          else:
1185              self.ranges = False
1186          
1187      def set_fill(self, (start1, end1, color1),(start2, end2, color2), (start3, end3, color3)):
1188          if self.value:
1189      	    if (self.value > start1) and (self.value <= end1):
1190      		self.itemconfig(self.bar,fill=color1)	
1191      	    else:
1192      		if (self.value > start2) and (self.value <= end2):
1193      		    self.itemconfig(self.bar,fill=color2)	
1194  		else:
1195  		    if (self.value > start3) and (self.value <= end3):
1196      			self.itemconfig(self.bar,fill=color3)	
1197  	
1198  	
1199      def bar_coords(self):
1200          """ calculates the coordinates in pixels for the bar """
1201          # the bar should start at value = zero 
1202          # and extend to value = self.value
1203          # it should not extend beyond the initial box reserved for the bar
1204          min_pixels=self.pad
1205          max_pixels=self.pad+self.bw
1206          bar_end = min_pixels + ((float)(max_pixels-min_pixels)/(float)(self.endval-self.startval)) * (self.value-self.startval)
1207          if bar_end>max_pixels:
1208              bar_end = max_pixels
1209          elif bar_end < min_pixels:
1210              bar_end = min_pixels
1211          bar_start = min_pixels + ((float)(max_pixels-min_pixels)/(float)(self.endval-self.startval)) * (0-self.startval)
1212          if bar_start < min_pixels:  # don't know if this is really needed
1213              bar_start = min_pixels
1214  
1215          return [bar_start, bar_end]
1216      
1217  			
1218      def update(self,pycomp):
1219          # update value
1220          newvalue=pycomp[self.halpin]
1221          if newvalue != self.value:
1222              self.value = newvalue
1223              valtext = str(self.format % self.value)
1224              self.itemconfig(self.val_text,text=valtext)
1225              # set bar colour
1226              if self.ranges:
1227                  self.set_fill(self.range1, self.range2, self.range3)
1228              # set bar size
1229              tmp=self.bar_coords()
1230              start=tmp[0]
1231              end=tmp[1]
1232              self.coords(self.bar, start, 2, 
1233                          end, self.bh-1)
1234  
1235  
1236  
1237  # -------------------------------------------
1238  
1239  
1240  
1241  
1242  class pyvcp_led(Canvas):
1243      """ (indicator) a LED 
1244          <led>
1245              <on_color>"colorname"</on_color>             Default color red
1246              <off_color>"colorname"</off_color>           Default color green
1247              <disable_pin>True</disable_pin>               Optional halpin sets led to disable_color
1248              <disable_color>"colorname"</disable_color>   Default color gray80
1249          </led>"""
1250      n=0
1251      def __init__(self,master,pycomp, halpin=None,disable_pin=False,     
1252                      off_color="red",on_color="green",disabled_color="gray80",size=20,**kw):
1253          Canvas.__init__(self,master,width=size,height=size,bd=0)
1254          self.off_color=off_color
1255          self.on_color=on_color
1256          self.disabled_color=disabled_color
1257          self.disable_pin = disable_pin
1258          self.oh=self.create_oval(1,1,size,size)
1259          self.state = 0
1260          self.itemconfig(self.oh,fill=off_color)
1261          if halpin == None:
1262              halpin = "led."+str(pyvcp_led.n) 
1263              pyvcp_led.n+=1
1264          self.halpin=halpin
1265          pycomp.newpin(halpin, HAL_BIT, HAL_IN)
1266          if disable_pin:
1267              halpin_disable = halpin+".disable"
1268              self.halpin_disable = halpin_disable
1269              pycomp.newpin(halpin_disable, HAL_BIT, HAL_IN)       
1270          
1271  
1272      def update(self,pycomp):
1273          newstate = pycomp[self.halpin]
1274          if newstate == 1:
1275              self.itemconfig(self.oh,fill=self.on_color)
1276              self.state=1
1277          else:
1278              self.itemconfig(self.oh,fill=self.off_color) 
1279              self.state=0
1280          if self.disable_pin:
1281              is_disabled = pycomp[self.halpin_disable]
1282              if is_disabled == 1:
1283                  self.itemconfig(self.oh,fill=self.disabled_color)
1284  
1285  
1286  
1287  
1288  # -------------------------------------------
1289  
1290  class pyvcp_rectled(Canvas):
1291  
1292      """ (indicator) a LED 
1293          <rectled>
1294              <on_color>"colorname"</on_color>             Default color red
1295              <off_color>"colorname"</off_color>           Default color green
1296              <disable_pin>True</disable_pin>               Optional halpin sets led to disable_color
1297              <disable_color>"somecolor"</disable_color>   Default color light gray
1298          </rectled>"""
1299      
1300      n=0
1301      def __init__(self,master,pycomp, halpin=None,disable_pin=False,     
1302                      off_color="red",on_color="green",disabled_color="gray80",height=10,width=30,**kw):
1303          Canvas.__init__(self,master,width=width,height=height,bd=2)
1304          self.off_color=off_color
1305          self.on_color=on_color
1306          self.disabled_color=disabled_color
1307          self.disable_pin = disable_pin
1308          self.oh=self.create_rectangle(1,1,width,height)
1309          self.state=0
1310          self.itemconfig(self.oh,fill=off_color)
1311          if halpin == None:
1312              halpin = "led."+str(pyvcp_led.n)  
1313              pyvcp_led.n+=1     
1314          self.halpin=halpin
1315          pycomp.newpin(halpin, HAL_BIT, HAL_IN)
1316          if disable_pin:
1317              halpin_disable = halpin+".disable"
1318              self.halpin_disable = halpin_disable
1319              pycomp.newpin(halpin_disable, HAL_BIT, HAL_IN)   
1320          
1321  
1322      def update(self,pycomp):
1323          newstate = pycomp[self.halpin]
1324          if newstate == 1:
1325              self.itemconfig(self.oh,fill=self.on_color)
1326              self.state=1
1327          else:
1328              self.itemconfig(self.oh,fill=self.off_color) 
1329              self.state=0
1330          if self.disable_pin:
1331              is_disabled = pycomp[self.halpin_disable]
1332              if is_disabled == 1:
1333                  self.itemconfig(self.oh,fill=self.disabled_color)
1334  
1335  
1336  
1337  
1338  # -------------------------------------------
1339  
1340  ## ArcEye - the initval field is missing from the docs, so few people aware it can be preselected
1341  ## 12022014 added changepin which allows toggling of value from HAL without GUI action
1342  
1343  class pyvcp_checkbutton(Checkbutton):
1344  
1345      """ (control) a check button 
1346          halpin is 1 when button checked, 0 otherwise 
1347          <checkbutton>
1348              [ <halpin>"my-checkbutton"</halpin> ]
1349              [ <text>"Name of Button"</text>]  text set in widget
1350              [ <initval>1</initval> ]  sets initial value to 1, all values >=0.5 are assumed to be 1
1351          </checkbutton>
1352      """
1353      n=0
1354      def __init__(self,master,pycomp,halpin=None,initval=0,**kw):
1355          self.v = BooleanVar(master)
1356          Checkbutton.__init__(self,master,variable=self.v,onvalue=1, offvalue=0,**kw)
1357          if halpin == None:
1358              halpin = "checkbutton."+str(pyvcp_checkbutton.n)
1359  	self.halpin=halpin
1360  	pycomp.newpin(halpin, HAL_BIT, HAL_OUT)
1361  	changepin = halpin + ".changepin"
1362  	self.changepin=changepin
1363  	pycomp.newpin(changepin, HAL_BIT, HAL_IN)
1364          pycomp[self.changepin] = 0
1365  
1366  	pyvcp_checkbutton.n += 1
1367  		
1368          if initval >= 0.5:
1369              self.value=1
1370          else:
1371              self.value=0
1372          self.v.set(self.value)
1373          self.reset = 0
1374  		
1375      def update(self,pycomp):
1376          # prevent race condition if connected to permanently on pin
1377  	if pycomp[self.changepin] and not(self.reset):
1378  	    self.v.set(not(self.v.get()))
1379  	    self.reset = 1
1380      	    pycomp[self.changepin] = 0 # try to reset, but may not work
1381  	    
1382  	if not(pycomp[self.changepin]) and(self.reset):
1383      	    self.reset = 0 
1384      	    pycomp[self.changepin] = 0   # make sure is reset now
1385  	
1386  	pycomp[self.halpin]=self.v.get()
1387  
1388  
1389  
1390  
1391  # -------------------------------------------
1392  
1393  
1394  
1395  
1396  
1397  class pyvcp_button(Button):
1398      """ (control) a button 
1399          halpin is 1 when button pressed, 0 otherwise 
1400          optional halpin.disable disables the button
1401          <button>
1402              <halpin>"name"</halpin>
1403              <disablepin>True</disablepin>
1404          </button>"""
1405      n=0
1406      def __init__(self,master,pycomp,halpin=None,disable_pin=False,**kw):
1407          Button.__init__(self,master,**kw)
1408          if halpin == None:
1409              halpin = "button."+str(pyvcp_button.n)
1410              pyvcp_button.n += 1  
1411          self.halpin=halpin
1412          pycomp.newpin(halpin, HAL_BIT, HAL_OUT)
1413          self.disable_pin = disable_pin
1414          if not disable_pin == False:
1415              halpin_disable = halpin + ".disable" 
1416              pycomp.newpin(halpin_disable, HAL_BIT, HAL_IN)
1417              self.halpin_disable=halpin_disable      
1418          self.state=0;
1419          self.bind("<ButtonPress>", self.pressed)
1420          self.bind("<ButtonRelease>", self.released)    
1421          self.pycomp = pycomp
1422  
1423      def pressed(self,event):
1424          if self.disable_pin: 
1425              is_disabled = self.pycomp[self.halpin_disable] 
1426              if is_disabled == 1: return
1427          self.pycomp[self.halpin]=1
1428  
1429      def released(self,event):
1430          if self.disable_pin: 
1431              is_disabled = self.pycomp[self.halpin_disable] 
1432              if is_disabled == 1: return
1433          self.pycomp[self.halpin]=0
1434  
1435      def update(self,pycomp):
1436          if self.disable_pin: 
1437              is_disabled = pycomp[self.halpin_disable]     
1438              if is_disabled == 1: Button.config(self,state=DISABLED)
1439              else: Button.config(self,state=NORMAL)
1440          else:pass
1441  
1442  
1443  
1444  
1445  
1446  # -------------------------------------------
1447  
1448  class pyvcp_scale(Scale):
1449      """ (control) a slider 
1450          halpin-i is integer output 
1451          halpin-f is float output
1452  
1453          <scale>
1454              [ <halpin>"my-scale"</halpin> ]
1455              [ <resolution>0.1</resolution> ] scale value a whole number must end in '.'
1456              [ <orient>HORIZONTAL</orient>  ] aligns the scale horizontal
1457              [ <min_>-33</min_> ] sets the minimum value to -33
1458              [ <max_>26</max_> ] sets the maximum value to 26
1459              [ <initval>10</initval> ]  sets initial value to 10
1460              [ <param_pin>1</param_pin>] creates param pin if > 0, set to initval, value can then be set externally, ArcEye 2013
1461          </scale>
1462  
1463      """
1464      # FIXME scale resolution when ctrl/alt/shift is held down?
1465      # FIXME allow user to specify size
1466      n=0
1467  
1468      def __init__(self,master,pycomp,
1469                      resolution=1,halpin=None,halparam=None,min_=0,max_=10,initval=0,param_pin=0,**kw):
1470          self.resolution=resolution
1471          Scale.__init__(self,master,resolution=self.resolution,
1472                           from_=min_,to=max_,**kw)
1473  
1474          if halpin == None:
1475              halpin = "scale."+str(pyvcp_scale.n)
1476          self.halpin=halpin
1477  
1478          if halparam == None:
1479              self.param_pin = param_pin
1480              if self.param_pin == 1:
1481                  halparam = "scale."+str(pyvcp_scale.n)+".param_pin"
1482                  self.halparam=halparam        
1483                  pycomp.newpin(halparam, HAL_FLOAT, HAL_IN)
1484  
1485          pyvcp_scale.n += 1       
1486          
1487          pycomp.newpin(halpin+"-i", HAL_S32, HAL_OUT)
1488          pycomp.newpin(halpin+"-f", HAL_FLOAT, HAL_OUT)
1489          
1490          self.bind('<Button-4>',self.wheel_up)
1491          self.bind('<Button-5>',self.wheel_down)
1492          
1493  
1494          if initval < min_:
1495              self.value=min_
1496          elif initval > max_:
1497              self.value=max_
1498          else:
1499              self.value=initval
1500  
1501          self.set(self.value)
1502          
1503          if self.param_pin == 1:       
1504              self.oldInit=self.value
1505              self.init=self.value
1506              pycomp[self.halparam] = self.value
1507  
1508      def update(self,pycomp):
1509          pycomp[self.halpin+"-f"]=self.get()
1510          pycomp[self.halpin+"-i"]=int(self.get())
1511          
1512          if self.param_pin == 1:
1513              self.init = pycomp[self.halparam]
1514              if self.init != self.oldInit :
1515                  self.set(self.init)
1516                  self.value=self.init
1517                  self.oldInit=self.init
1518  
1519      def wheel_up(self,event):
1520          self.set(self.get()+self.resolution)
1521  
1522      def wheel_down(self,event):
1523          self.set(self.get()-self.resolution)
1524  
1525  
1526  class pyvcp_table(Frame):
1527      """ Grid layout widget with rows and columns.
1528          * flexible_columns - list of column indexes that should be flexible width
1529          * flexible_rows - list of row indexes that should be flexible width
1530          * uniform_columns - string of characters for each column, in order. Columns with the same character will be the same width.
1531          * uniform_rows - string of characters for each row, in order. Rows with the same character will be the same height.
1532          * (also accepts Tk options for Frame)
1533  
1534          <table flexible_rows="[3]" flexible_columns="[3]" uniform_columns="aab" uniform_rows="aab" highlightthickness="10" highlightbackground="#CCCCCC">
1535              <tablesticky sticky="nwes"/>
1536              <!-- row 1 -->
1537              <tablerow/>
1538              <label text="AAAAAAAAA" />
1539              <label text="BBBBBBBBB" />
1540              <label text="CCCCCCCCC" />
1541              <!-- row 2 -->
1542              <tablerow/>
1543              <label text="A" />
1544              <label text="B" />
1545              <label text="C" />
1546              <!-- row 3 -->
1547              <tablerow/>
1548              <tablespan columns="3"/>
1549              <label text="Merged columns" />
1550          </table>
1551      """
1552      def __init__(self, master, pycomp, flexible_rows=[], flexible_columns=[], uniform_columns="", uniform_rows="", **kw):
1553          Frame.__init__(self, master, **kw)
1554          for r in flexible_rows:
1555              self.grid_rowconfigure(r, weight=1)
1556          for c in flexible_columns:
1557              self.grid_columnconfigure(c, weight=1)
1558          for i, r in enumerate(uniform_rows):
1559              self.grid_rowconfigure(i+1, uniform=r)
1560          for i, c in enumerate(uniform_columns):
1561              self.grid_columnconfigure(i+1, uniform=c)
1562  
1563          self._r = self._c = 0
1564          self.occupied = {}
1565          self.span = (1,1)
1566          self.sticky = "ne"
1567  
1568      def add(self, container, child):
1569          if isinstance(child, pyvcp_tablerow):
1570              self._r += 1
1571              self._c = 1
1572              return
1573          elif isinstance(child, pyvcp_tablespan):
1574              self.span = child.span
1575              return
1576          elif isinstance(child, pyvcp_tablesticky):
1577              self.sticky = child.sticky
1578              return
1579          r, c = self._r, self._c
1580          while self.occupied.has_key((r, c)):
1581              c = c + 1
1582          rs, cs = self.span
1583          child.grid(row=r, column=c, rowspan=rs, columnspan=cs,
1584                          sticky=self.sticky)
1585          for ri in range(r, r+rs):
1586              for ci in range(c, c+cs):
1587                  self.occupied[ri,ci] = True
1588  
1589          self.span = 1,1
1590          self._c = c+cs
1591  
1592      def update(self, pycomp): pass
1593  
1594  class pyvcp_tablerow:
1595      def __init__(self, master, pycomp): pass
1596      def update(self, pycomp): pass
1597  
1598  class pyvcp_tablespan:
1599      def __init__(self, master, pycomp, rows=1, columns=1):
1600          self.span = rows, columns
1601      def update(self, pycomp): pass
1602  
1603  class pyvcp_tablesticky:
1604      def __init__(self, master, pycomp, sticky):
1605          self.sticky = sticky
1606      def update(self, pycomp): pass
1607      
1608  class pyvcp_include(Frame):
1609      def __init__(self, master, pycomp, src, expand="yes", fill="both", anchor="center", prefix=None, **kw):
1610          Frame.__init__(self,master,**kw)
1611  
1612          self.master = master
1613          self.fill = fill
1614          self.anchor = anchor
1615          self.expand = expand
1616  
1617          if prefix is not None:
1618              oldprefix = pycomp.getprefix()
1619              pycomp.setprefix(prefix)
1620          import vcpparse, xml.dom.minidom, xml.parsers.expat
1621  
1622          try:
1623              doc = xml.dom.minidom.parse(src) 
1624          except xml.parsers.expat.ExpatError, detail:
1625              print "Error: could not open",src,"!"
1626              print detail
1627              sys.exit(1)
1628  
1629          # find the pydoc element
1630          for e in doc.childNodes:
1631              if e.nodeType == e.ELEMENT_NODE and e.localName == "pyvcp":
1632                  break
1633  
1634          if e.localName != "pyvcp":
1635              print "Error: no pyvcp element in file!"
1636              sys.exit()
1637          pyvcproot=e
1638          vcpparse.nodeiterator(pyvcproot,self)
1639  
1640          if prefix is not None:
1641              pycomp.setprefix(oldprefix)
1642  
1643      def update(self, pycomp): pass
1644  
1645      def add(self, container, widget):
1646          widget.pack(fill=self.fill, anchor=self.anchor, expand=self.expand)
1647  
1648  class _pyvcp_dummy:
1649      def add(self, container, widget): pass
1650      def update(self, pycomp): pass
1651      def pack(self, *args, **kw): pass
1652  
1653  class pyvcp_title(_pyvcp_dummy):
1654      def __init__(self, master, pycomp, title, iconname=None):
1655          master.wm_title(title)
1656          if iconname: master.wm_iconname(iconname)
1657  
1658  class pyvcp_axisoptions(_pyvcp_dummy):
1659      def __init__(self, master, pycomp):
1660          import rs274.options
1661          rs274.options.install(master)
1662  
1663  class pyvcp_option(_pyvcp_dummy):
1664      def __init__(self, master, pycomp, pattern, value, priority=None):
1665          master.option_add(pattern, value, priority)
1666  
1667  class pyvcp_image(_pyvcp_dummy):
1668      all_images = {}
1669      def __init__(self, master, pycomp, name, **kw):
1670          self.all_images[name] = PhotoImage(name, kw, master)
1671  
1672  class _pyvcp_image(Label):
1673      def __init__(self, master, pycomp, images, halpin=None, **kw):
1674          Label.__init__(self, master, **kw)
1675          if isinstance(images, basestring): images = images.split()
1676          self.images = images
1677          if halpin == None:
1678              halpin = "number."+str(pyvcp_number.n)
1679              pyvcp_number.n += 1
1680          self.halpin = halpin
1681          self.value = 0
1682          self.last = None
1683          pycomp.newpin(halpin, self.pintype, HAL_IN)
1684  
1685      def update(self, pycomp):
1686          l = pycomp[self.halpin]
1687          if l != self.last:
1688              try:
1689                  self.configure(image=self.images[l])
1690              except (IndexError, KeyError):
1691                  print >>sys.stderr, "Unknown image #%d on %s" % (l, self.halpin)
1692          self.last = l
1693  
1694  class pyvcp_image_bit(_pyvcp_image):
1695      pintype = HAL_BIT
1696  class pyvcp_image_u32(_pyvcp_image):
1697      pintype = HAL_U32
1698  
1699  # This must come after all the pyvcp_xxx classes
1700  elements = []
1701  __all__ = []
1702  for _key in globals().keys():
1703      if _key.startswith("pyvcp_"):
1704          elements.append(_key[6:])
1705          __all__.append(_key)
1706  
1707  if __name__ == '__main__':
1708      print "You can't run pyvcp_widgets.py by itself..."
1709  # vim:sts=4:sw=4:et: