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: