# This is a component of AXIS, a front-end for emc# Copyright 2007 Anders Wallin <anders.wallin@helsinki.fi>## This program is free software; you can redistribute it and/or modify# it under the terms of the GNU General Public License as published by# the Free Software Foundation; either version 2 of the License, or# (at your option) any later version.## This program is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the# GNU General Public License for more details.## You should have received a copy of the GNU General Public License# along with this program; if not, write to the Free Software# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA""" A widget library for pyVCP The layout and composition of a Python Virtual Control Panel is specified with an XML file. The file must begin with <pyvcp>, and end with </pyvcp> In the documentation for each widget, optional tags are shown bracketed: [ <option>Something</option> ] such a tag is not required for pyVCP to work, but may add functionality or modify the behaviour of a widget. Example XML file: <pyvcp> <led> <size>40</size> <halpin>"my-led"</halpin> </led> </pyvcp> This will create a VCP with a single LED widget which indicates the value of HAL pin compname.my-led """#TJP 2007 01 25 some checkbtns with IOpins will wnat to set when pressed, others will reset,# so a param was added 'downVal' to specify what the down value meant, allowing True or False#TJP 20070125 widgets work visually, now...# clean up checkbutton btnType "momentary" real bad name, it's original checkbutton# add a cb for external stimulus to checkbutton btnType 'toggle'# an external signal must be able to reset the btn thru the halpin, the halpin is type IO##TJP 20070124 commented calls to tooltip, rt not closing well, errs when restarted #TJP 20070122 added tooltios yesterday, seemed ok, #TJP now i see that there's a bug, the 1ts time i use it... ok, the 2nd time ... i get# tomp@cncbox:~/emc2.1-alpha0$ bin/pyvcp -c fred jnk5.xml# HAL: ERROR: duplicate component name 'fred'# ULAPI: WARNING: module 'HAL_fred' failed to delete shmem 01# Error: Multiple components with the same name.# and i 'fix' by 'scripts/realtime stop; scripts/realtime start'# i commented the tooltip code out & it's fine for multiple reps# why?# the bug in the tooltip code, cuz if i use xml w/o tooltip, then mux reps are ok# and err occurs when xml has tooltip, but tooltips not used, so bug in creation, not use of tooltip# confirmed... just commented the creat_tooltip & is ok mux timesfrom Tkinter import *from hal import *import math# this makes only functions named here be included in the pydoc__all__=["pyvcp_label"]# FIXME: this is ugly, a list of valid widgets in this module should be # created somehow automagically# or another mechanism for vcpparse.py to know which elements are valid should# be constructedelements =["pyvcp","led","vbox","hbox","vbox" \ ,"button","scale","checkbutton","bar" \ ,"label","number","spinbox","radiobutton","jogwheel"\ ,"meter","dial"]# FIXME: this is ugly, each widget should know what parameters are valid# for itself, and vcpparse.py should check validity for each widget individually# TJP most parms listed at tkinter's site dont work in pyvcp# and not becasue they're not in the list below, i tried addingthem, # and then the name-value pirs appeared on the vcpars's w_command list, but still didnt work# so, if it's crippled, i dont want extra bloat in here... # i removed the 'should be' and left the 'live with it' elements##TJP 20070122 added tooltips parm ttiptxt# can they be active ? like be the dro or the scale, something that changes?##TJP 20070123 added btnType token for Button widget# if btnType = Mom, (momentary) then btn halpin is O(utput) (needs no info about halpin's prev state)# if btnType = Togl (toggle) then halpin is IO(in and output) (btn needs to know pin state so btn can change it)# seems like it should just ask the pin, not tell the pin !! the pin should be an object with it's own data, and methods#TJP added compass param for labelsparameters = ["size","text","orient","halpin","format" \ ,"font","min_","max_","resolution" \ ,"choices","cpr","fillcolor","bgcolor" \ ,"bd","relief","init", \ "ttiptxt", "btnType","compass"]from Tkinter import *import math# -------------------------------------------class pyvcp_dial(Canvas): # Dial widget by tomp """ A dial that outputs a HAL_FLOAT reacts to both mouse-wheel and mouse dragging <dial> [ <size>376</size> ] [ <cpr>100</cpr> ] number of changes per rev, is # of dial tick marks, beware hi values) [ <min_>-33.123456</min_> ] [ <max_>3.3</max_> ] [ <text>"Gallons per Hour"</text> ] (knob label) [ <init>123</init> ] (initial value a whole number must end in '.') [ <resolution>.001</resolution> ] (scale value a whole number must end in '.') [ <halpin>"anaout"</halpin> ] [ <ttiptxt>"dont touch that dial!"</ttiptxt> ] (tooltip that occurs when cursor is over widget) </dial> key bindings: <Button-4> untested no wheel mouse <Button-5> untested no wheel mouse <Button1-Motion> used internally during drag <ButtonPress> used internally to record beginning of drag <ButtonRelease> used internally at end of drag <Double-1> divides scale by 10 <Double-2> resets scale to original value <Double-3> multiplies scale by 10 <Shift-1> shift-click resets original analog value features: text autoscales """ # FIXME: # -jogging should be enabled only when the circle has focus # TJP nocando: only widgets have events, not thier 'items', the circle is an item # -circle should maintain focus when mouse over dot # TJP nocando: ditto, the circle is an item, so focus & event are not aligned to it # -jogging by dragging with the mouse could work better # -add a scaled output, scale changes when alt/ctrl/shift is held down # TJP dblLeftClick divides scale by 10 , dblRightClcik muxs by 10 n=0 #TJP TODO: let some artists look at it, butt ugly! #TJP cpr is overloaded, now it means "chgs per rev" not "counts per rev" #TJP the tik marks could get very fine, avoid high cpr to size ratios (easily seen) #TJP awallin chgd text text2 text3 to text init resolution, better names :) #TJP 20070122 add var for tooltip text def __init__(self,root,pycomp,halpin=None,size=200,cpr=40, \ min_=None,max_=None, \ text=None,init=0,resolution=0.1, \ ttiptxt=None, \ **kw): pad=10 self.out=init # float output out self.origValue=init # in case user wants to reset the pot/valve/thingy Canvas.__init__(self,root,width=size,height=size) self.circle=self.create_oval(pad,pad,size-pad,size-pad) self.itemconfig(self.circle) self.mid=size/2 self.r=(size-2*pad)/2 self.alfa=0 self.d_alfa=2*math.pi/cpr self.size=size self.funit=resolution self.origFunit=self.funit # allow restoration self.mymin=min_ self.mymax=max_ self.dot = self.create_oval(self.dot_coords()) self.itemconfig(self.dot,fill="yellow",activefill="green") #TJP items get rendered in order of creation, so the knob will be behind these texts #TJP the font can be described with pixel size by using negative value self.txtroom=size/6 # a title, if the user has supplied one if text!=None: self.title=self.create_text([self.mid,self.mid-self.txtroom], text=text,font=('Arial',-self.txtroom)) # the output self.dro=self.create_text([self.mid,self.mid], text=str(self.out),font=('Arial',-self.txtroom)) # the scale self.delta=self.create_text([self.mid,self.mid+self.txtroom], text=str(self.funit),font=('Arial',-self.txtroom)) self.bind('<Button-4>',self.wheel_up) # untested no wheel mouse self.bind('<Button-5>',self.wheel_down) # untested no wheel mouse self.bind('<Button1-Motion>',self.motion) #during drag self.bind('<ButtonPress>',self.bdown) #begin of drag self.bind('<ButtonRelease>',self.bup) #end of drag self.bind('<Double-1>',self.chgScaleDn) # doubleclick scales down self.bind('<Double-2>',self.resetScale) # doubleclick resets scale self.bind('<Double-3>',self.chgScaleUp) # doubleclick scales up self.bind('<Shift-1>',self.resetValue) # shift resets value self.draw_ticks(cpr) self.dragstartx=0 self.dragstarty=0 self.dragstart=0 # create the hal pin if halpin == None: halpin = "dial."+str(pyvcp_dial.n)+".out" pyvcp_dial.n += 1 pycomp.newpin(halpin, HAL_FLOAT, HAL_OUT) self.halpin=halpin #TJP 20070122 tooltip if the user passes a ttiptext #TJP if i want to chg the text, i need a handle... self.ttip=createToolTip(self,ttiptxt) #TJP 20070124 struck till i find out cause of odd rt errs, not fully shutdown after scripts/realtime stop #self.ttt=ttiptxt #if self.ttt != None: # #self.ttip=createToolTip(self, self.ttt) # print "no ttip 2dA" self.pycomp=pycomp def chgScaleDn(self,event): # reduces the scale by 10x self.funit=self.funit/10.0 self.update_scale() def chgScaleUp(self,event): # increases the scale by 10x self.funit=self.funit*10.0 self.update_scale() def resetScale(self,event): # reset scale to original value self.funit=self.origFunit self.update_scale() def resetValue(self,event): # reset output to orifinal value self.out=self.origValue self.update_dro() def dot_coords(self): # calculate the coordinates for the dot DOTR=0.08*self.size DOTPOS=0.75 midx = self.mid+DOTPOS*self.r*math.cos(self.alfa) midy = self.mid+DOTPOS*self.r*math.sin(self.alfa) return midx-DOTR, midy-DOTR,midx+DOTR,midy+DOTR def bdown(self,event): self.dragstartx=event.x self.dragstarty=event.y self.dragstart=math.atan2((event.y-self.mid),(event.x-self.mid)) self.itemconfig(self.dot,fill="green",activefill="green") def bup(self,event): self.itemconfig(self.dot,fill="yellow") def motion(self,event): dragstop = math.atan2((event.y-self.mid),(event.x-self.mid)) delta = dragstop - self.dragstart if delta>=self.d_alfa: self.up() self.dragstart=math.atan2((event.y-self.mid),(event.x-self.mid)) elif delta<=-self.d_alfa: self.down() self.dragstart=math.atan2((event.y-self.mid),(event.x-self.mid)) self.itemconfig(self.dot,fill="green",activefill="green") def wheel_up(self,event): self.up() def wheel_down(self,event): self.down() def down(self): self.alfa-=self.d_alfa self.out-=self.funit #TJP clip down side if self.mymin != None: if self.out<self.mymin: self.out=self.mymin self.update_dot() self.update_dro() def up(self): self.alfa+=self.d_alfa self.out+=self.funit #TJP clip up side if self.mymax != None: if self.out>self.mymax: self.out=self.mymax self.update_dot() self.update_dro() def update_dot(self): self.coords(self.dot, self.dot_coords() ) def update_dro(self): valtext = str(self.out) self.itemconfig(self.dro,text=valtext) def update_scale(self): valtext = str(self.funit) valtext = 'x ' + valtext self.itemconfig(self.delta,text=valtext) ##TJP 20070122 BEGIN scratch code used in effort to get changable tootips, doesnt work yet 20070122 #TJP 20070124 no ref, no calls to tooltip till rt errs fixed #self.ttip.text=valtext #self.itemconfig(self.ttip, text=valtext) ##works, is right value , doesnt affect tooltip tho print "<>", self.ttip.text, "<>" ##works, is hex jibberish, of no use print ">",self.ttip,"<" ##TJP 20070122 END scratch code used in effort to get changable tootips, doesnt work yet 20070122 def draw_ticks(self,cpr): for n in range(0,cpr): startx=self.mid+self.r*math.cos(n*self.d_alfa) starty=self.mid+self.r*math.sin(n*self.d_alfa) stopx=self.mid+1.15*self.r*math.cos(n*self.d_alfa) stopy=self.mid+1.15*self.r*math.sin(n*self.d_alfa) self.create_line([startx,starty,stopx,stopy]) def update(self,pycomp): self.pycomp[self.halpin] = self.out# -------------------------------------------class pyvcp_meter(Canvas): """ Meter - shows the value of a FLOAT with an analog meter <meter> [ <size>300</size> ] [ <halpin>"mymeter"</halpin> ] [ <text>"My Voltage"</text> ] [ <min_>-22</min_> ] [ <max_>123</max_> ] </meter> """ # FIXME: logarithmic scale option n=0 def __init__(self,root,pycomp,halpin=None, size=200,text=None,min_=0,max_=100,**kw): pad=10 Canvas.__init__(self,root,width=size,height=size) self.halpin=halpin self.min_=min_ self.max_=max_ range_=2.5 self.min_alfa=-math.pi/2-range_ self.max_alfa=-math.pi/2+range_ self.circle=self.create_oval(pad,pad,size-pad,size-pad) self.itemconfig(self.circle,fill="white") self.mid=size/2 self.r=(size-2*pad)/2 self.alfa=0 self.line = self.create_line([self.mid,self.mid, \ self.mid+0.8*self.r*math.cos(self.alfa), \ self.mid+0.8*self.r*math.sin(self.alfa)],fill="red") self.itemconfig(self.line,width=3) if text!=None: t=self.create_text([self.mid,self.mid-20]) self.itemconfig(t,text=text) self.itemconfig(t,font=('Arial',20)) self.draw_ticks() # create the hal pin if halpin == None: self.halpin = "meter."+str(pyvcp_meter.n)+".value" pyvcp_meter.n += 1 pycomp.newpin(self.halpin, HAL_FLOAT, HAL_IN) self.value = pycomp[self.halpin] def update(self,pycomp): self.value = pycomp[self.halpin] scale=(self.max_-self.min_)/(self.max_alfa-self.min_alfa) self.alfa=self.min_alfa + (self.value-self.min_)/scale if self.alfa > self.max_alfa: self.alfa = self.max_alfa elif self.alfa < self.min_alfa: self.alfa = self.min_alfa self.coords(self.line, self.mid,self.mid, \ self.mid+0.8*self.r*math.cos(self.alfa), \ self.mid+0.8*self.r*math.sin(self.alfa)) def draw_ticks(self): d_alfa = float((self.max_alfa-self.min_alfa))/10 d_value = float((self.max_-self.min_))/10 for n in range(0,11): startx=self.mid+self.r*math.cos(self.min_alfa + n*d_alfa) starty=self.mid+self.r*math.sin(self.min_alfa + n*d_alfa) stopx=self.mid+0.85*self.r*math.cos(self.min_alfa + n*d_alfa) stopy=self.mid+0.85*self.r*math.sin(self.min_alfa + n*d_alfa) textx=stopx - 0.1*self.r*math.cos(self.min_alfa + n*d_alfa) texty=stopy - 0.1*self.r*math.sin(self.min_alfa + n*d_alfa) self.create_line([startx,starty,stopx,stopy]) t=self.create_text([textx,texty]) self.itemconfig(t,text=str(self.min_+d_value*n))# -------------------------------------------class pyvcp_jogwheel(Canvas): """" A jogwheel that outputs a HAL_FLOAT count reacts to both mouse-wheel and mouse dragging <jogwheel> [ <cpr>33</cpr> ] (counts per revolution) [ <halpin>"myjogwheel"</halpin> ] [ <size>300</size> ] </jogwheel> """ # FIXME: # -jogging should be enabled only when the circle has focus # -circle should maintain focus when mouse over dot # -jogging by dragging with the mouse could work better # -add a scaled output, scale changes when alt/ctrl/shift is held down n=0 def __init__(self,root,pycomp,halpin=None,size=200,cpr=40,**kw): pad=10 self.count=0 Canvas.__init__(self,root,width=size,height=size) self.circle=self.create_oval(pad,pad,size-pad,size-pad) self.itemconfig(self.circle,fill="lightgrey",activefill="darkgrey") self.mid=size/2 self.r=(size-2*pad)/2 self.alfa=0 self.d_alfa=2*math.pi/cpr self.size=size self.dot = self.create_oval(self.dot_coords()) self.itemconfig(self.dot,fill="black") #self.itemconfig(self.line,arrow="last") #self.itemconfig(self.line,width=3) self.bind('<Button-4>',self.wheel_up) self.bind('<Button-5>',self.wheel_down) self.bind('<Button1-Motion>',self.motion) self.bind('<ButtonPress>',self.bdown) self.draw_ticks(cpr) self.dragstartx=0 self.dragstarty=0 self.dragstart=0 # create the hal pin if halpin == None: halpin = "jogwheel."+str(pyvcp_jogwheel.n)+".count" pyvcp_jogwheel.n += 1 pycomp.newpin(halpin, HAL_FLOAT, HAL_OUT) self.halpin=halpin pycomp[self.halpin] = self.count self.pycomp=pycomp def dot_coords(self): DOTR=0.08*self.size DOTPOS=0.75 midx = self.mid+DOTPOS*self.r*math.cos(self.alfa) midy = self.mid+DOTPOS*self.r*math.sin(self.alfa) return midx-DOTR, midy-DOTR,midx+DOTR,midy+DOTR def bdown(self,event): self.dragstartx=event.x self.dragstarty=event.y self.dragstart=math.atan2((event.y-self.mid),(event.x-self.mid)) def motion(self,event): dragstop = math.atan2((event.y-self.mid),(event.x-self.mid)) delta = dragstop - self.dragstart if delta>=self.d_alfa: self.up() self.dragstart=math.atan2((event.y-self.mid),(event.x-self.mid)) elif delta<=-self.d_alfa: self.down() self.dragstart=math.atan2((event.y-self.mid),(event.x-self.mid)) def wheel_up(self,event): self.up() def wheel_down(self,event): self.down() def down(self): self.alfa-=self.d_alfa self.count-=1 self.pycomp[self.halpin] = self.count self.update_dot() def up(self): self.alfa+=self.d_alfa self.count+=1 self.pycomp[self.halpin] = self.count self.update_dot() def update_dot(self): self.coords(self.dot, self.dot_coords() ) def draw_ticks(self,cpr): for n in range(0,cpr): startx=self.mid+self.r*math.cos(n*self.d_alfa) starty=self.mid+self.r*math.sin(n*self.d_alfa) stopx=self.mid+1.15*self.r*math.cos(n*self.d_alfa) stopy=self.mid+1.15*self.r*math.sin(n*self.d_alfa) self.create_line([startx,starty,stopx,stopy]) def update(self,pycomp): # this is stupid, but required for updating pin # when first connected to a signal self.pycomp[self.halpin] = self.count# -------------------------------------------class pyvcp_radiobutton(Frame): n=0 def __init__(self,master,pycomp,halpin=None,choices=[],**kw): f=Frame.__init__(self,master,bd=2,relief=GROOVE) self.v = IntVar() self.v.set(1) self.choices=choices if halpin == None: halpin = "radiobutton."+str(pyvcp_radiobutton.n) pyvcp_radiobutton.n += 1 self.halpins=[] n=0 for c in choices: b=Radiobutton(self,f, text=str(c) ,variable=self.v, value=pow(2,n)) b.pack() if n==0: b.select() c_halpin=halpin+"."+str(c) pycomp.newpin(c_halpin, HAL_BIT, HAL_OUT) self.halpins.append(c_halpin) n+=1 # FIXME # this is a fairly stupid way of updating the pins # since the calculation is done every 100ms wether a change # has happened or not. see below. def update(self,pycomp): index=math.log(self.v.get(),2) index=int(index) for pin in self.halpins: pycomp[pin]=0; pycomp[self.halpins[index]]=1; # FIXME # this would be a much better way of updating the # pins, but at the moment I can't get it to work # this is never called even if I set command=self.update() # in the call to Radiobutton above def changed(self): index=math.log(self.v.get(),2) index=int(index) print "active:",self.halpins[index]# -------------------------------------------class pyvcp_label(Label): """ Static text label <label> <text>"My Label:"</text> <compass>"ne"</compass> [ where text goes into label ] </label> """ #TJP default location is center def __init__(self,master,pycomp,compass=None,**kw): if compass==None: ankr=CENTER else: #print "compass dir passed ",compass ankr=compass Label.__init__(self,master,**kw) #TJP you have to fill the space for anchors like center to work # else what appears to be 'left' is really centered in a tiny box smooshed up to left wall ! self.pack(fill=X, expand=1, anchor=ankr) def update(self,pycomp): pass# -------------------------------------------class pyvcp_vbox(Frame): """ Box in which widgets are packed vertically <vbox> <relief>GROOVE</relief> (FLAT, SUNKEN, RAISED, GROOVE, RIDGE) <bd>3</bd> (border width) place widgets here </vbox> """ def __init__(self,master,pycomp,bd=0,relief=FLAT): Frame.__init__(self,master,bd=bd,relief=relief) self.pack(expand=1,fill=BOTH) def update(self,pycomp): pass def packtype(self): return "top"# -------------------------------------------class pyvcp_hbox(Frame): """ Box in which widgets are packed horizontally <vbox> <relief>GROOVE</relief> (FLAT, SUNKEN, RAISED, GROOVE, RIDGE) <bd>3</bd> (border width) place widgets here </vbox> """ def __init__(self,master,pycomp,bd=0,relief=FLAT): Frame.__init__(self,master,bd=bd,relief=relief) self.pack(expand=1,fill=BOTH) def update(self,pycomp): pass def packtype(self): return "left"# -------------------------------------------class pyvcp_spinbox(Spinbox): """ (control) controls a float, also shown as text reacts to the mouse wheel <spinbox> [ <halpin>"my-spinbox"</halpin> ] [ <min_>55</min_> ] sets the minimum value to 55 [ <max_>123</max_> ] sets the maximum value to 123 </spinbox> """ # FIXME: scale resolution when shift/ctrl/alt is held down? #TJP spinbox has a default width of 20 chars.... a bit much for 2^32, try 12 ( i allow 5.4f format n=0 def __init__(self,master,pycomp,halpin=None, min_=0,max_=100,resolution=1,format="2.1f",**kw): self.v = DoubleVar() if 'increment' not in kw: kw['increment'] = resolution if 'from' not in kw: kw['from'] = min_ if 'to' not in kw: kw['to'] = max_ if 'format' not in kw: kw['format'] = "%" + format kw['command'] = self.command Spinbox.__init__(self,master,textvariable=self.v,width=12,borderwidth=5,**kw) if halpin == None: halpin = "spinbox."+str(pyvcp_spinbox.n) pyvcp_spinbox.n += 1 self.halpin=halpin self.value=min_ self.oldvalue=min_ self.format = "%(b)"+format self.max_=max_ self.min_=min_ self.resolution=resolution self.v.set( str( self.format % {'b':self.value} ) ) pycomp.newpin(halpin, HAL_FLOAT, HAL_OUT) self.bind('<Button-4>',self.wheel_up) self.bind('<Button-5>',self.wheel_down) def command(self): self.value = self.v.get() def update(self,pycomp): pycomp[self.halpin] = self.value if self.value != self.oldvalue: self.v.set( str( self.format % {'b':self.value} ) ) self.oldvalue=self.value def wheel_up(self,event): self.value += self.resolution if self.value > self.max_: self.value = self.max_ def wheel_down(self,event): self.value -= self.resolution if self.value < self.min_: self.value = self.min_# -------------------------------------------class pyvcp_number(Label): """ (indicator) shows a float as text """ n=0 def __init__(self,master,pycomp,halpin=None,format="2.1f",**kw): self.v = StringVar() self.format=format Label.__init__(self,master,textvariable=self.v,padx=4,pady=4,**kw) if halpin == None: halpin = "number."+str(pyvcp_number.n) pyvcp_number.n += 1 self.halpin=halpin self.value=0.0 dummy = "%(b)"+self.format self.v.set( str( dummy % {'b':self.value} ) ) pycomp.newpin(halpin, HAL_FLOAT, HAL_IN) def update(self,pycomp): newvalue = pycomp[self.halpin] if newvalue != self.value: self.value=newvalue dummy = "%(b)"+self.format self.v.set( str( dummy % {'b':newvalue} ) )# -------------------------------------------class pyvcp_bar(Canvas): """ (indicator) a bar-indicator for a float""" n=0 # FIXME logarithmic scale? # FIXME specifying negative startval or endval does not work def __init__(self,master,pycomp, fillcolor="green",bgcolor="grey", halpin=None,min_=0.0,max_=100.0,**kw): self.cw=200 # canvas width self.ch=50 # canvas height self.bh=30 # bar height self.bw=150 # bar width self.pad=((self.cw-self.bw)/2) Canvas.__init__(self,master,width=self.cw,height=self.ch) if halpin == None: halpin = "bar."+str(pyvcp_bar.n) pyvcp_bar.n += 1 self.halpin=halpin self.endval=max_ self.startval=min_ pycomp.newpin(halpin, HAL_FLOAT, HAL_IN) # the border border=self.create_rectangle(self.pad,1,self.pad+self.bw,self.bh) self.itemconfig(border,fill=bgcolor) # the bar self.bar=self.create_rectangle(self.pad+1,2,self.pad+1,self.bh-1) self.itemconfig(self.bar,fill=fillcolor) self.value=0.0 # some dummy value to start with # start text start_text=self.create_text(self.pad,self.bh+10,text=str(self.startval) ) #end text end_text=self.create_text(self.pad+self.bw,self.bh+10,text=str(self.endval) ) # value text self.val_text=self.create_text(self.pad+self.bw/2, self.bh/2,text=str(self.value) ) def update(self,pycomp): # update value newvalue=pycomp[self.halpin] if newvalue != self.value: self.value = newvalue percent = self.value/(self.endval-self.startval) if percent < 0.0: percent = 0 elif percent > 1.0: percent = 1.0 # set value text valtext = str( "%(b)3.1f" % {'b':self.value} ) self.itemconfig(self.val_text,text=valtext) # set bar size self.coords(self.bar, self.pad+1, 2, self.pad+self.bw*percent, self.bh-1)# -------------------------------------------class pyvcp_led(Canvas): """ (indicator) a LED color is on_color when halpin is 1, off_color when halpin is 0 <led> <halpin> "MyOptionalLedName" (opt, default is 'led' ) </halpin> <on_color> "barney-purple" (opt, default green) </on_color> <off_color> "puce" (opt, default red) </off_color> <size> 30 (opt, default is 20 pxls) </size> </led> """ n=0 def __init__(self,master,pycomp, halpin=None, off_color="red",on_color="green",size=20,**kw): Canvas.__init__(self,master,width=size,height=size,bd=0) self.off_color=off_color self.on_color=on_color self.oh=self.create_oval(1,1,size,size) self.state=0 self.itemconfig(self.oh,fill=off_color) if halpin == None: halpin = "led."+str(pyvcp_led.n) self.halpin=halpin pycomp.newpin(halpin, HAL_BIT, HAL_IN) pyvcp_led.n+=1 def update(self,pycomp): newstate = pycomp[self.halpin] if newstate != self.state: if newstate == 1: self.itemconfig(self.oh,fill=self.on_color) self.state=1 else: self.itemconfig(self.oh,fill=self.off_color) self.state=0# -------------------------------------------class pyvcp_checkbutton(Checkbutton): """ (control) a checkbutton halpin is 1 when checkbutton-momentary pressed, 0 otherwise halpin is O (the btn sees it as write only) halpin changes state when checkbutton-toggle pressed, unchanged otherwise halpin is IO (the btn can read it and write it [set it the opp state] ) 20070123 added parameter 'btnType' to allowed token list allow values "Opin" & "IOpin" <checkbutton> <halpin>"mybtn"</halpin> <btnType>"O"</btnType> halpin is ouput only </checkbutton> <checkbutton> <halpin>"mybtn"</halpin> optional default is created from "checkbutton" & a counter <btnType>"IO"</btnType> optional default it O <text>"Flood"</init> optional default is from a counter <init>True</init> optional default is True </checkbutton> """ #TJP 20070125 only chg the halpin when pressed, not when release, so: dont cnx halpin to self.v!!! n=0 def __init__(self,master,pycomp,halpin=None,btnType=None,text=None,init=None, **kw): if btnType==None: btnType="Opin" if ((btnType!="Opin")&(btnType!="IOpin")): return if init==None: init=True if ((init!=True)&(init!=False)): return if text==None: btntxt=str(pyvcp_checkbutton.n) else: btntxt = text self.v = BooleanVar(master) #for original 'Opin' type checkbutton self.downVal=init # the logic value to emit when pressed self.btntype=btnType # "Opin" or "IOpin" #i dont see why this is necc, else update gives # AttributeError: pyvcp_checkbutton instance has no attribute 'newstate' self.newstate=self.oldstate=None if btnType=="Opin": Checkbutton.__init__(self,master, \ variable=self.v, \ text=None, indicatoron=0, width=14, \ onvalue=1, offvalue=0, \ **kw) else: Checkbutton.__init__(self,master, \ text=None, indicatoron=0, width=14, \ **kw) if halpin == None: halpin = "checkbutton."+str(pyvcp_checkbutton.n) self.halpin=halpin if btnType=="Opin": pycomp.newpin(halpin, HAL_BIT, HAL_OUT) self.configure(text=btntxt) else: pycomp.newpin(halpin, HAL_BIT, HAL_IO) self.configure(text=btntxt) #show the color according to <init>, set the pin & history to opposite of presed state ( becuz not pressed yet ) if self.downVal==True: colr="red" self.configure(activebackground=colr) self.configure(bg=colr) pycomp[self.halpin]=self.oldstate=self.newstate=False # if init was True, then begin UP, Red, and oldstate = False else: colr="green" self.configure(activebackground=colr) self.configure(selectcolor=colr) self.configure(bg=colr) pycomp[self.halpin]=self.oldstate=self.newstate=True # if init was False, then begin UP, Green & oldstate = True self.bind('<ButtonPress>',self.bdown) # only bind the IOpin vrsn of cbtn chg output state when pressed pyvcp_checkbutton.n += 1 self.mirror=None # this func requests a change in state def bdown(self,event): if self.btntype=="IOpin": if self.downVal==True: self.newstate=True else: self.newstate=False self.configure(state=DISABLED) #def bup(self) # there is no bup, only external srcs can release the btn, not this widget def update(self,pycomp): if self.btntype=="Opin": pycomp[self.halpin]=self.v.get() else: # btnType = "IO" dirty = False # no need to change appearance till we know theres been a change if self.newstate!=self.oldstate: # detect a change caused by btn press ( an internal change ) pycomp[self.halpin]=self.newstate dirty=True # need to chg appearance self.oldstate=self.newstate elif pycomp[self.halpin]!=self.oldstate: # detect a change caused by by external src dirty=True # need to chg appearance self.oldstate=self.newstate=pycomp[self.halpin] if dirty==True: tmp=pycomp[self.halpin] if self.downVal==True: # downVal is True if tmp==True: # halpin is True self.select() # btn looks down looks decided by downVal & halpin colr="green" # colr is green color decided by halpin else: # halpin is False self.deselect() # btn looks up colr="red" # colr is red else: # downVal is False if tmp==True: # halpin is True self.deselect() # btn looks up colr="green" # colr is green else: # halpin is False self.select() # btn looks down colr="red" # colr is red self.configure(selectcolor=colr) self.configure(activebackground=colr) self.configure(state=NORMAL) dirty=False# -------------------------------------------class pyvcp_button(Button): """ (control) a button halpin is 1 when button pressed, 0 otherwise """ n=0 def __init__(self,master,pycomp,halpin=None,**kw): Button.__init__(self,master,**kw) if halpin == None: halpin = "button."+str(pyvcp_button.n) self.halpin=halpin pycomp.newpin(halpin, HAL_BIT, HAL_OUT) self.state=0; self.bind("<ButtonPress>", self.pressed) self.bind("<ButtonRelease>", self.released) self.configure(height=1) pyvcp_button.n += 1 def pressed(self,event): # "the button was pressed" self.state=1 def released(self,event): # the button was released self.state=0 def update(self,pycomp): pycomp[self.halpin]=self.state# -------------------------------------------class pyvcp_scale(Scale): """ (control) a slider halpin-i is integer output halpin-f is float output """ # FIXME scale resolution when ctrl/alt/shift is held down? # FIXME allow user to specify size n=0 def __init__(self,master,pycomp, resolution=1,halpin=None,min_=0,max_=10,**kw): self.resolution=resolution Scale.__init__(self,master,resolution=self.resolution, from_=min_,to=max_,**kw) if halpin == None: halpin = "scale."+str(pyvcp_scale.n) self.halpin=halpin pycomp.newpin(halpin+"-i", HAL_S32, HAL_OUT) pycomp.newpin(halpin+"-f", HAL_FLOAT, HAL_OUT) self.bind('<Button-4>',self.wheel_up) self.bind('<Button-5>',self.wheel_down) pyvcp_scale.n += 1 def update(self,pycomp): pycomp[self.halpin+"-f"]=self.get() pycomp[self.halpin+"-i"]=int(self.get()) def wheel_up(self,event): self.set(self.get()+self.resolution) def wheel_down(self,event): self.set(self.get()-self.resolution)#----------------------------------------begin tooltips -------------------------------------#TJP 20070122 tooltip adopted from code in IDLE see CallTipWindow.pyclass ToolTip(object): def __init__(self, somewdgt): self.somewdgt = somewdgt self.tipwindow = None self.id = None self.x = self.y = 0 def showtip(self, text): "Display text in tooltip window" self.txt = text if self.tipwindow or not self.txt: return #orig x, y, cx, cy = self.somewdgt.bbox("insert") #orig x = x + self.somewdgt.winfo_rootx() + 27 #orig y = y + cy + self.somewdgt.winfo_rooty() +27 ##TJP dont bother, alwys rtns 4 0's mytupl=self.somewdgt.bbox(self.text) #orig x = x + self.somewdgt.winfo_rootx() + 27 #TJP the posn is set to the widget's topleft corner , but right by 5 and down by 5 pxls x = self.somewdgt.winfo_rootx() + 5 #orig y = y + cy + self.somewdgt.winfo_rooty() +27 y = self.somewdgt.winfo_rooty() + 5 #TJP the tooltip is a TopLevel type window ( on the top level, so its visible ... a hidden tooltip is useless :) self.tipwindow = tw = Toplevel(self.somewdgt) tw.wm_overrideredirect(1) tw.wm_geometry("+%d+%d" % (x, y)) #TJP chg from label=Label(... to self.lbl=Label(... self.lbl = Label(tw, text=self.txt, justify=LEFT, background="#ffffe0", relief=SOLID, borderwidth=1, font=("arial", "8", "normal")) self.lbl.pack(ipadx=1) #the rendering doesnt really happen till this func is exited, # eg: a sleep inside here delays the window&label from appearing # >not< a delay before hidingitself #time.sleep(1) #print "hello" def hidetip(self): tw = self.tipwindow self.tipwindow = None if tw: tw.destroy() #TJP will the std vpcparse update handle this? no, vcparse.updater only updates a comp, so maybe use a comp to store the data? #works, called by every widget created with a tool tip #def dummy(self): # print "derfhello"#TJP 20070122 this indentation and sequence is neccesarydef createToolTip(somewdgt, text): #somewdgt is the widget the tooltip belongs to #toolTip is the tooltip object created by nxt line... a toolTip is NOT a widget toolTip = ToolTip(somewdgt) toolTip.text=text #call funcs internal to tooltip like this... toolTip.dummy() #somewdgt.after is allowed, but is after widget created, not aftet ttip seen def enter(event): toolTip.showtip(text) def leave(event): toolTip.hidetip() somewdgt.bind('<Enter>', enter) somewdgt.bind('<Leave>', leave) return toolTip#----------------------------------------end tooltips-----------------------------------------if __name__ == '__main__': print "You can't run pyvcp_widgets.py by itself..."