# This is a component of AXIS, a front-end for emc # Copyright 2007 Anders Wallin # # TJP 12 04 2007 # Rugludallur saw that spinbuttons had no initial value until after thumbs inc'd or de'c # TJP saw that if xml prescribed 1234 the spinbutton locked up after the inc/dec # it seems a new term in the __init__ may fix this # end TJP 12 04 2007 # # Added initval to checkbutton/scale for initial values, Dallur 15 April 2007 (jarl stefansson) (jarl stefansson) # # Multiple additions and amendments as per notations - ArcEye 2013 # # 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 , and end with In the documentation for each widget, optional tags are shown bracketed: [ ] such a tag is not required for pyVCP to work, but may add functionality or modify the behaviour of a widget. Example XML file: 40 "my-led" This will create a VCP with a single LED widget which indicates the value of HAL pin compname.my-led """ from Tkinter import * from hal import * import math import bwidget import time # ------------------------------------------- class pyvcp_dial(Canvas): # Dial widget by tomp """ A dial that outputs a HAL_FLOAT reacts to both mouse-wheel and mouse dragging [ 376 ] [ "grey" ] [ "pink" ] [ "white" ] [ 100 ] number of changes per rev, is # of dial tick marks, beware hi values) [ -33.123456 ] [ 3.3 ] [ "Gallons per Hour" ] (knob label) [ 123 ] (initial value a whole number must end in '.') [ .001 ] (scale value a whole number must end in '.') [ "anaout" ] [ 1] creates param pin if > 0, set to initval, value can then be set externally, ArcEye 2013 key bindings: untested no wheel mouse untested no wheel mouse used internally during drag used internally to record beginning of drag used internally at end of drag divides scale by 10 resets scale to original value multiplies scale by 10 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) def __init__(self,root,pycomp,halpin=None,halparam=None,param_pin=0,size=200,cpr=40,dialcolor="", \ edgecolor="",dotcolor="grey",min_=None,max_=None, \ text=None,initval=0,resolution=0.001, \ **kw): pad=size/10 self.counts = int(round(initval/resolution)) self.out = self.counts * resolution # float output out self.origValue=initval # in case user wants to reset the pot/valve/thingy #self.text3=resolution Canvas.__init__(self,root,width=size,height=size) pad2=pad-size/15 self.circle2=self.create_oval(pad2,pad2,size-pad2,size-pad2,width=3)# edge circle self.itemconfig(self.circle2,fill=edgecolor,activefill=edgecolor) self.circle=self.create_oval(pad,pad,size-pad,size-pad) # dial circle self.itemconfig(self.circle,fill=dialcolor,activefill=dialcolor) 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=dotcolor,activefill="black") self.line = self.create_line( self.mid+(self.r*1)*math.cos(self.alfa), \ self.mid+(self.r*1)*math.sin(self.alfa), \ self.mid+(self.r*1.1)*math.cos(self.alfa), \ self.mid+(self.r*1.1)*math.sin(self.alfa)) self.itemconfig(self.line,arrow="last",arrowshape=(10,10,10)) self.itemconfig(self.line,width=10) #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+self.txtroom], text=str(self.out),font=('Arial',-self.txtroom)) # the scale # self.delta=self.create_text([self.mid,self.mid+self.txtroom], # text='x '+ str(self.funit),font=('Arial',-self.txtroom)) # self.bind('',self.wheel_up) # untested no wheel mouse # self.bind('',self.wheel_down) # untested no wheel mouse # self.bind('',self.motion) #during drag # self.bind('',self.bdown) #begin of drag # self.bind('',self.bup) #end of drag # self.bind('',self.chgScaleDn) # doubleclick scales down # self.bind('',self.resetScale) # doubleclick resets scale # self.bind('',self.chgScaleUp) # doubleclick scales up # self.bind('',self.resetValue) # shift resets value self.draw_ticks(cpr) self.dragstartx=0 self.dragstarty=0 self.dragstart=0 self.dotcolor=dotcolor # create the hal pin if halpin == None: halpin = "dial."+str(pyvcp_dial.n)+".out" self.halpin=halpin if halparam == None: self.param_pin = param_pin if self.param_pin == 1: halparam = "dial." + str(pyvcp_dial.n) + ".param_pin" self.halparam=halparam pycomp.newpin(halparam, HAL_FLOAT, HAL_IN) pyvcp_dial.n += 1 self.pycomp=pycomp pycomp.newpin(halpin, HAL_FLOAT, HAL_OUT) pycomp[self.halparam] = self.origValue self.oldValue = self.origValue self.value = self.origValue def chgScaleDn(self,event): # reduces the scale by 10x self.funit=self.funit/10.0 self.counts *= 10 self.update_scale() self.update_dro() self.update_dot() def chgScaleUp(self,event): # increases the scale by 10x self.funit=self.funit*10.0 self.counts = (self.counts + 5) / 10 self.out = self.counts * self.funit self.update_scale() self.update_dro() self.update_dot() def resetScale(self,event): # reset scale to original value self.funit=self.origFunit self.counts = int(round(self.out / self.funit)) self.out = self.counts * self.funit self.update_scale() def resetValue(self,event): # reset output to orifinal value self.counts = int(round(self.origValue / self.funit)) self.out= self.counts * self.funit self.update_dot() self.update_dro() def dot_coords(self): # calculate the coordinates for the dot DOTR=0.04*self.size DOTPOS=0.85 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="black",activefill="black") def bup(self,event): self.itemconfig(self.dot,fill=self.dotcolor) 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="black",activefill="black") def wheel_up(self,event): self.up() def wheel_down(self,event): self.down() def down(self): self.alfa-=self.d_alfa self.counts -= 1 self.out = self.counts * self.funit #TJP clip down side if self.mymin != None: if self.outself.mymax: self.out=self.mymax self.counts = self.mymax * self.funit self.update_dot() self.update_dro() def update_dot(self): self.coords(self.dot, self.dot_coords() ) self.coords(self.line, self.mid+(self.r*1)*math.cos(self.alfa),self.mid+(self.r*1)*math.sin(self.alfa), \ self.mid+(self.r*1.1)*math.cos(self.alfa), \ self.mid+(self.r*1.1)*math.sin(self.alfa)) 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) def draw_ticks(self,cpr): for n in range(0,cpr,2): for i in range(0,2): startx=self.mid+self.r*math.cos((n+i)*self.d_alfa) starty=self.mid+self.r*math.sin((n+i)*self.d_alfa) if i == 0: length = 1.15 width = 2 else: length = 1.1 width = 1 stopx=self.mid+length*self.r*math.cos((n+i)*self.d_alfa) stopy=self.mid+length*self.r*math.sin((n+i)*self.d_alfa) self.create_line(startx,starty,stopx,stopy,width=width) def update(self,pycomp): self.pycomp[self.halpin] = self.out self.value = pycomp[self.halparam] if self.value != self.oldValue : self.counts = int(round(self.value / self.funit)) self.out= self.counts * self.funit self.alfa = self.value * (6.28 / 360) - 1.57 print self.alfa self.update_dot() self.update_dro() self.oldValue = self.value # -------------------------------------------