initval field setting the correct widget to active at initialisation, but changes to prevent unnecessary updates supressing initial setting of associated pin to match. Explicitly setting pin associated with active button selection via initval at initialisation corrects this. Signed-off-by: Mick Grant <arceye@mgware.co.uk>
1669 lines
57 KiB
Python
1669 lines
57 KiB
Python
# This is a component of AXIS, a front-end for emc
|
|
# Copyright 2007 Anders Wallin <anders.wallin@helsinki.fi>
|
|
#
|
|
# 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 <value>1234</value> 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., 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
|
|
"""
|
|
|
|
|
|
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
|
|
<dial>
|
|
[ <size>376</size> ]
|
|
[ <dialcolor>"grey"</dialcolor> ]
|
|
[ <edgecolor>"pink"</edgecolor> ]
|
|
[ <dotcolor>"white"</dotcolor> ]
|
|
[ <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)
|
|
[ <initval>123</initval> ] (initial value a whole number must end in '.')
|
|
[ <resolution>.001</resolution> ] (scale value a whole number must end in '.')
|
|
[ <halpin>"anaout"</halpin> ]
|
|
[ <param_pin>1</param_pin>] creates param pin if > 0, set to initval, value can then be set externally, ArcEye 2013
|
|
</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)
|
|
|
|
|
|
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.1, \
|
|
**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],
|
|
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('<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
|
|
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.out<self.mymin:
|
|
self.out=self.mymin
|
|
self.counts = self.mymin * self.funit
|
|
self.update_dot()
|
|
self.update_dro()
|
|
|
|
def up(self):
|
|
self.alfa+=self.d_alfa
|
|
self.counts += 1
|
|
self.out = self.counts * self.funit
|
|
#TJP clip up side
|
|
if self.mymax != None:
|
|
if self.out>self.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.update_dot()
|
|
self.update_dro()
|
|
self.oldValue = self.value
|
|
|
|
# -------------------------------------------
|
|
|
|
|
|
|
|
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> ]
|
|
[ <subtext>"Volts"</subtext>
|
|
[ <min_>-22</min_> ]
|
|
[ <max_>123</max_> ]
|
|
[ <majorscale>10</majorscale> ]
|
|
[ <minorscale>5</minorscale> ]
|
|
[ <region1>(70,80,"green")</region1> ]
|
|
[ <region2>(80,100,"orange")</region2> ]
|
|
[ <region3>(100,123,"red")</region3> ]
|
|
</meter>
|
|
"""
|
|
# FIXME: logarithmic scale option
|
|
n=0
|
|
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):
|
|
self.size = size
|
|
self.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(self.pad,self.pad,size-self.pad,size-self.pad, width=2)
|
|
self.itemconfig(self.circle,fill="white")
|
|
self.mid=size/2
|
|
self.r=(size-2*self.pad)/2
|
|
self.alfa=0
|
|
if minorscale==None:
|
|
self.minorscale=0
|
|
else:
|
|
self.minorscale=minorscale
|
|
if majorscale==None:
|
|
self.majorscale=float((self.max_-self.min_)/10)
|
|
else:
|
|
self.majorscale=majorscale
|
|
if text!=None: t=self.create_text([self.mid,self.mid-size/12],font="Arial %d bold" % (size/10),text=text)
|
|
if subtext!=None: t=self.create_text([self.mid,self.mid+size/12],font="Arial %d" % (size/30+5),text=subtext)
|
|
if region1!=None: self.draw_region(region1)
|
|
if region2!=None: self.draw_region(region2)
|
|
if region3!=None: self.draw_region(region3)
|
|
self.draw_ticks()
|
|
|
|
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))
|
|
self.itemconfig(self.line,width=3)
|
|
|
|
# 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 rad2deg(self, rad): return rad*180/math.pi
|
|
|
|
def value2angle(self, value):
|
|
#returns angle for a given value
|
|
scale = (self.max_-self.min_)/(self.max_alfa-self.min_alfa)
|
|
alfa = self.min_alfa + (value-self.min_)/scale
|
|
if alfa > self.max_alfa:
|
|
alfa = self.max_alfa
|
|
elif alfa < self.min_alfa:
|
|
alfa = self.min_alfa
|
|
return alfa
|
|
|
|
def p2c(self, radius, angle):
|
|
#returns the cathesian coordinates (x,y) for given polar coordinates
|
|
#radius in percent of self.r; angle in radians
|
|
return self.mid+radius*self.r*math.cos(angle), self.mid+radius*self.r*math.sin(angle)
|
|
|
|
def update(self,pycomp):
|
|
self.value = pycomp[self.halpin]
|
|
self.alfa = self.value2angle(self.value)
|
|
x,y = self.p2c(0.8, self.alfa)
|
|
self.coords(self.line,self.mid,self.mid,x,y)
|
|
|
|
def draw_region(self, (start, end, color)):
|
|
#Draws a colored region on the canvas between start and end
|
|
start = self.value2angle(start)
|
|
start = -self.rad2deg(start)
|
|
end = self.value2angle(end)
|
|
end = -self.rad2deg(end)
|
|
extent = end-start
|
|
halfwidth = math.floor(0.1*self.r/2)+1
|
|
xy = self.pad+halfwidth, self.pad+halfwidth, self.size-self.pad-halfwidth, self.size-self.pad-halfwidth
|
|
self.create_arc(xy, start=start, extent=extent, outline=color, width=(halfwidth-1)*2, style="arc")
|
|
|
|
def draw_ticks(self):
|
|
value = self.min_
|
|
while value <= self.max_:
|
|
alfa = self.value2angle(value)
|
|
xy1 = self.p2c(1,alfa)
|
|
xy2 = self.p2c(0.85,alfa)
|
|
xytext = self.p2c(0.75,alfa)
|
|
self.create_text(xytext,font="Arial %d" % (self.size/30+5), text="%g" % value)
|
|
self.create_line(xy1, xy2, width=2)
|
|
value = value + self.majorscale
|
|
#minor ticks
|
|
value = self.min_
|
|
if self.minorscale > 0:
|
|
while value <= self.max_:
|
|
if (value % self.majorscale) != 0:
|
|
alfa = self.value2angle(value)
|
|
xy1 = self.p2c(1,alfa)
|
|
xy2 = self.p2c(0.9,alfa)
|
|
self.create_line(xy1, xy2)
|
|
value = value + self.minorscale
|
|
|
|
|
|
# -------------------------------------------
|
|
|
|
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=size/10
|
|
self.count=0
|
|
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.circle=self.create_oval(pad,pad,size-pad,size-pad)
|
|
self.itemconfig(self.circle,fill="lightgrey",activefill="lightgrey")
|
|
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.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=8)
|
|
|
|
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.06*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))
|
|
|
|
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() )
|
|
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 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
|
|
|
|
# -------------------------------------------
|
|
## ArcEye - added - no example given and the one in docs misses out initval ##
|
|
|
|
"""" A radiobutton will set one of the halpins true. The other pins are set false.
|
|
<radiobutton>
|
|
<choices>["one","two","three"]</choices> labels next to each button
|
|
<halpin>"radio"</halpin> pin giving index of active button
|
|
<initval>0</initval> index of button pin to set true at start
|
|
</radiobutton>
|
|
"""
|
|
################################################################################
|
|
|
|
class pyvcp_radiobutton(Frame):
|
|
n=0
|
|
def __init__(self,master,pycomp,halpin=None,initval=0,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==initval:
|
|
b.select()
|
|
c_halpin=halpin+"."+str(c)
|
|
pycomp.newpin(c_halpin, HAL_BIT, HAL_OUT)
|
|
self.halpins.append(c_halpin)
|
|
n+=1
|
|
|
|
self.selected = initval
|
|
pycomp[self.halpins[initval]]=1
|
|
|
|
## ArcEye - FIXED - only update the pins if changed ##
|
|
def update(self,pycomp):
|
|
index=int(math.log(self.v.get(),2))
|
|
if index != self.selected:
|
|
for pin in self.halpins:
|
|
pycomp[pin]=0;
|
|
pycomp[self.halpins[index]]=1;
|
|
self.selected = index
|
|
|
|
|
|
# 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>
|
|
<halpin>"name"</halpin>
|
|
<disable_pin>True</disable_pin>
|
|
</label>
|
|
"""
|
|
n=0
|
|
def __init__(self,master,pycomp,halpin=None,disable_pin=False,**kw):
|
|
Label.__init__(self,master,**kw)
|
|
self.disable_pin=disable_pin
|
|
if disable_pin:
|
|
if halpin == None:
|
|
halpin = "label."+str(pyvcp_label.n)
|
|
pyvcp_label.n += 1
|
|
halpin_disable = halpin+".disable"
|
|
self.halpin_disable = halpin_disable
|
|
pycomp.newpin(halpin_disable, HAL_BIT, HAL_IN)
|
|
|
|
def update(self,pycomp):
|
|
if self.disable_pin:
|
|
is_disabled = pycomp[self.halpin_disable]
|
|
if is_disabled == 1: Label.config(self,state=DISABLED)
|
|
else: Label.config(self,state=NORMAL)
|
|
else:pass
|
|
|
|
|
|
|
|
# -------------------------------------------
|
|
|
|
### ArcEye 01052013 - added new widget #############################################
|
|
|
|
class pyvcp_multilabel(Label):
|
|
""" Selectable text label, can display up to 6 label legends
|
|
when associated bit pin is activated
|
|
<multilabel>
|
|
<legends>["Label1" "Label2" "Label3" "Label4" "Label5" "Label6"]</legends>
|
|
<font>("Helvetica", 20)</font>
|
|
<disable_pin>True</disable_pin>
|
|
<initval>0</initval> Which legend to display by default (make sure it matches initval for linked widget if any)
|
|
<halpin>None</halpin> Optional alternative name base for pins
|
|
</multilabel>
|
|
"""
|
|
|
|
n=0
|
|
def __init__(self,master,pycomp,halpin=None,disable_pin=False,legends=[],initval=0,**kw):
|
|
Label.__init__(self,master,**kw)
|
|
self.disable_pin=disable_pin
|
|
|
|
if halpin == None:
|
|
halpin = "multilabel."+str(pyvcp_multilabel.n)
|
|
pyvcp_multilabel.n += 1
|
|
|
|
self.halpins=[]
|
|
n=0
|
|
for c in legends:
|
|
c_halpin=halpin+".legend"+str(n)
|
|
pycomp.newpin(c_halpin, HAL_BIT, HAL_IN)
|
|
self.halpins.append(c_halpin)
|
|
n+=1
|
|
#limit to 6 legends
|
|
if n >= 6:
|
|
break
|
|
self.legends = legends
|
|
self.num_pins = n
|
|
|
|
if disable_pin:
|
|
halpin_disable = halpin+".disable"
|
|
self.halpin_disable = halpin_disable
|
|
pycomp.newpin(halpin_disable, HAL_BIT, HAL_IN)
|
|
|
|
# test for out of range initval number
|
|
if initval >= 0 and initval <= self.num_pins :
|
|
val = initval
|
|
else :
|
|
val = 0
|
|
|
|
Label.config(self,text= legends[val])
|
|
pycomp[self.halpins[val]] = 1
|
|
self.pin_index = val
|
|
|
|
def update(self,pycomp):
|
|
if self.disable_pin:
|
|
is_disabled = pycomp[self.halpin_disable]
|
|
if is_disabled == 1:
|
|
Label.config(self,state=DISABLED)
|
|
else:
|
|
Label.config(self,state=NORMAL)
|
|
|
|
# no telling how people will use this, so just break at first
|
|
# set pin that is not the existing set one
|
|
# if several pins are set one after another, the legend for the
|
|
# last one set will end up being displayed
|
|
index = -1
|
|
for x in xrange(0, self.num_pins):
|
|
state = pycomp[self.halpins[x]]
|
|
if state == 1 :
|
|
index = x
|
|
if index != self.pin_index :
|
|
break
|
|
|
|
if index > -1 and index != self.pin_index:
|
|
for x in xrange(0, self.num_pins):
|
|
pycomp[self.halpins[x]] = 0
|
|
|
|
pycomp[self.halpins[index]] = 1
|
|
Label.config(self,text= self.legends[index])
|
|
self.pin_index = index
|
|
|
|
|
|
|
|
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.fill = 'x'
|
|
self.side = 'top'
|
|
self.anchor = 'center'
|
|
self.expand = 'yes'
|
|
|
|
def update(self,pycomp):
|
|
pass
|
|
|
|
def add(self, container, widget):
|
|
if isinstance(widget, pyvcp_boxexpand):
|
|
self.expand = widget.expand
|
|
return
|
|
if isinstance(widget, pyvcp_boxfill):
|
|
self.fill = widget.fill
|
|
return
|
|
if isinstance(widget, pyvcp_boxanchor):
|
|
self.anchor = widget.anchor
|
|
return
|
|
widget.pack(side=self.side, anchor=self.anchor, fill=self.fill, expand=self.expand)
|
|
|
|
class pyvcp_boxfill:
|
|
def __init__(self, master, pycomp, fill):
|
|
self.fill = fill
|
|
def update(self, pycomp): pass
|
|
|
|
class pyvcp_boxanchor:
|
|
def __init__(self, master, pycomp, anchor):
|
|
self.anchor = anchor
|
|
def update(self, pycomp): pass
|
|
|
|
class pyvcp_boxexpand:
|
|
def __init__(self, master, pycomp, expand):
|
|
self.expand = expand
|
|
def update(self, pycomp): pass
|
|
|
|
# -------------------------------------------
|
|
|
|
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.fill = 'y'
|
|
self.side = 'left'
|
|
self.anchor = 'center'
|
|
self.expand = 'yes'
|
|
|
|
def update(self,pycomp):
|
|
pass
|
|
|
|
def add(self, container, widget):
|
|
if isinstance(widget, pyvcp_boxexpand):
|
|
self.expand = widget.expand
|
|
return
|
|
if isinstance(widget, pyvcp_boxfill):
|
|
self.fill = widget.fill
|
|
return
|
|
if isinstance(widget, pyvcp_boxanchor):
|
|
self.anchor = widget.anchor
|
|
return
|
|
widget.pack(side=self.side, anchor=self.anchor, fill=self.fill)
|
|
|
|
class pyvcp_labelframe(LabelFrame):
|
|
"""
|
|
frame with a title
|
|
"""
|
|
def __init__(self,master,pycomp,**kw):
|
|
LabelFrame.__init__(self,master,**kw)
|
|
self.pack(expand=1,fill=BOTH)
|
|
def update(self,pycomp):
|
|
pass
|
|
def add(self, container, widget):
|
|
widget.pack(side="top", fill="both", expand="yes")
|
|
|
|
class pyvcp_tabs(bwidget.NoteBook):
|
|
def __init__(self, master, pycomp, cnf={}, **kw):
|
|
self.names = kw.pop("names", [])
|
|
self.idx = 0
|
|
self._require(master)
|
|
Widget.__init__(self, master, "NoteBook", cnf, kw)
|
|
|
|
def update(self, pycomp): pass
|
|
|
|
def add(self, container, child):
|
|
child.pack(side="top", fill="both", anchor="ne")
|
|
if self.idx == 1:
|
|
self.raise_page(self.names[0])
|
|
|
|
def getcontainer(self):
|
|
if len(self.names) < self.idx:
|
|
self.names.append("Tab-%d" % self.idx)
|
|
name = self.names[self.idx]
|
|
self.idx += 1
|
|
return self.insert("end", name, text=name)
|
|
|
|
# -------------------------------------------
|
|
|
|
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
|
|
[ <initval>100</initval> ] sets intial value to 100 TJP 12 04 2007
|
|
[ <param_pin>1</param_pin>] creates param pin if > 0, set to initval, value can then be set externally, ArcEye 2013
|
|
</spinbox>
|
|
"""
|
|
# FIXME: scale resolution when shift/ctrl/alt is held down?
|
|
|
|
n=0
|
|
def __init__(self,master,pycomp,halpin=None, halparam=None,param_pin=0,
|
|
min_=0,max_=100,initval=0,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,**kw)
|
|
|
|
if halpin == None:
|
|
halpin = "spinbox."+str(pyvcp_spinbox.n)
|
|
|
|
self.halpin=halpin
|
|
|
|
if halparam == None:
|
|
self.param_pin = param_pin
|
|
if self.param_pin == 1:
|
|
halparam = "spinbox." + str(pyvcp_spinbox.n) + ".param_pin"
|
|
self.halparam=halparam
|
|
pycomp.newpin(halparam, HAL_FLOAT, HAL_IN)
|
|
|
|
pyvcp_spinbox.n += 1
|
|
|
|
if initval < min_:
|
|
self.value=min_
|
|
elif initval > max_:
|
|
self.value=max_
|
|
else:
|
|
self.value=initval
|
|
self.oldvalue=min_
|
|
|
|
if self.param_pin == 1:
|
|
self.init=self.value
|
|
self.oldinit=self.init
|
|
pycomp[self.halparam] = self.init
|
|
|
|
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)
|
|
self.bind('<Return>',self.return_pressed)
|
|
|
|
def return_pressed(self, event):
|
|
self.value = self.v.get()
|
|
if self.value < self.min_:
|
|
self.value = self.min_
|
|
if self.value > self.max_:
|
|
self.value = self.max_
|
|
|
|
|
|
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
|
|
|
|
if self.param_pin == 1:
|
|
self.init = pycomp[self.halparam]
|
|
if self.init != self.oldinit:
|
|
self.v.set( str( self.format % {'b':self.init} ) )
|
|
self.oldinit=self.init
|
|
self.value=self.init
|
|
|
|
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,**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_u32(Label):
|
|
""" (indicator) shows a u32 as text """
|
|
n=0
|
|
def __init__(self,master,pycomp,halpin=None,format="d",**kw):
|
|
self.v = StringVar()
|
|
self.format=format
|
|
Label.__init__(self,master,textvariable=self.v,**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_U32, 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_s32(Label):
|
|
""" (indicator) shows a s32 as text """
|
|
n=0
|
|
def __init__(self,master,pycomp,halpin=None,format="d",**kw):
|
|
self.v = StringVar()
|
|
self.format=format
|
|
Label.__init__(self,master,textvariable=self.v,**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_S32, 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_timer(Label):
|
|
""" (indicator) shows elapsed time as HH:MM:SS
|
|
two pins - run and reset
|
|
time advances whenever run is true
|
|
time holds whenever run is false
|
|
time resets to zero on a rising edge of reset
|
|
"""
|
|
n=0
|
|
def __init__(self,master,pycomp,halpin=None,**kw):
|
|
self.v = StringVar()
|
|
Label.__init__(self,master,textvariable=self.v,**kw)
|
|
if halpin == None:
|
|
halpin = "timer."+str(pyvcp_timer.n)
|
|
pyvcp_timer.n += 1
|
|
self.halpins=[]
|
|
c_halpin=halpin+".reset"
|
|
pycomp.newpin(c_halpin, HAL_BIT, HAL_IN)
|
|
self.halpins.append(c_halpin)
|
|
c_halpin=halpin+".run"
|
|
pycomp.newpin(c_halpin, HAL_BIT, HAL_IN)
|
|
self.halpins.append(c_halpin)
|
|
|
|
self.resetvalue=0
|
|
self.runvalue=0
|
|
# starttime is the time of the last rising edge of 'run'
|
|
self.starttime=0
|
|
# basetime is the sum of all prior 'run=1' periods
|
|
self.basetime=0
|
|
self.currtime=0
|
|
self.v.set( "00:00:00")
|
|
|
|
|
|
def update(self,pycomp):
|
|
resetvalue = pycomp[self.halpins[0]]
|
|
runvalue = pycomp[self.halpins[1]]
|
|
if resetvalue != self.resetvalue:
|
|
self.resetvalue=resetvalue
|
|
if resetvalue == 1:
|
|
self.basetime=0
|
|
self.starttime=time.time()
|
|
if runvalue != self.runvalue:
|
|
self.runvalue=runvalue
|
|
if runvalue == 1:
|
|
# rising edge
|
|
self.starttime = time.time()
|
|
else:
|
|
# falling edge
|
|
self.basetime += time.time() - self.starttime
|
|
if runvalue == 1:
|
|
total=self.basetime + time.time() - self.starttime
|
|
else:
|
|
total=self.basetime
|
|
hr = int(total / 3600)
|
|
remainder = total - hr*3600
|
|
mn = int(remainder / 60)
|
|
sec = int(remainder - mn*60)
|
|
self.v.set( str( "%02d:%02d:%02d" % (hr,mn,sec) ) )
|
|
|
|
|
|
# -------------------------------------------
|
|
|
|
class pyvcp_bar(Canvas):
|
|
|
|
## reworked by ArcEye 10022014
|
|
## allow value ranges in different colours
|
|
|
|
""" (indicator) a bar-indicator for a float
|
|
<bar>
|
|
<halpin>"my-bar"</halpin>
|
|
<min_>0</min_>
|
|
<max_>150</max_>
|
|
<bgcolor>"grey"</bgcolor>
|
|
<range1>(0,100,"green")</range1>
|
|
<range2>(101,129,"orange")</range2>
|
|
<range3>(130,150,"red")</range3>
|
|
<fillcolor>"green"</fillcolor>
|
|
</bar>
|
|
"""
|
|
n=0
|
|
|
|
def __init__(self,master,pycomp,fillcolor="green",bgcolor="grey",
|
|
halpin=None,min_=0.0,max_=100.0,range1=None,range2=None,range3=None,**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)
|
|
|
|
self.value=0.0 # some dummy value to start with
|
|
|
|
pycomp[self.halpin] = self.value
|
|
|
|
# the border
|
|
border=self.create_rectangle(self.pad,1,self.pad+self.bw,self.bh)
|
|
self.itemconfig(border,fill=bgcolor)
|
|
|
|
# the bar
|
|
tmp=self.bar_coords()
|
|
start=tmp[0]
|
|
end=tmp[1]
|
|
self.bar=self.create_rectangle(start,2,end,self.bh-1)
|
|
# default fill unless overriden
|
|
self.itemconfig(self.bar,fill=fillcolor)
|
|
|
|
# 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) )
|
|
|
|
if range1!=None and range2!=None and range3!=None:
|
|
self.range1 = range1
|
|
self.range2 = range2
|
|
self.range3 = range3
|
|
self.ranges = True
|
|
else:
|
|
self.ranges = False
|
|
|
|
def set_fill(self, (start1, end1, color1),(start2, end2, color2), (start3, end3, color3)):
|
|
if self.value:
|
|
if (self.value > start1) and (self.value <= end1):
|
|
self.itemconfig(self.bar,fill=color1)
|
|
else:
|
|
if (self.value > start2) and (self.value <= end2):
|
|
self.itemconfig(self.bar,fill=color2)
|
|
else:
|
|
if (self.value > start3) and (self.value <= end3):
|
|
self.itemconfig(self.bar,fill=color3)
|
|
|
|
|
|
def bar_coords(self):
|
|
""" calculates the coordinates in pixels for the bar """
|
|
# the bar should start at value = zero
|
|
# and extend to value = self.value
|
|
# it should not extend beyond the initial box reserved for the bar
|
|
min_pixels=self.pad
|
|
max_pixels=self.pad+self.bw
|
|
bar_end = min_pixels + ((float)(max_pixels-min_pixels)/(float)(self.endval-self.startval)) * (self.value-self.startval)
|
|
if bar_end>max_pixels:
|
|
bar_end = max_pixels
|
|
elif bar_end < min_pixels:
|
|
bar_end = min_pixels
|
|
bar_start = min_pixels + ((float)(max_pixels-min_pixels)/(float)(self.endval-self.startval)) * (0-self.startval)
|
|
if bar_start < min_pixels: # don't know if this is really needed
|
|
bar_start = min_pixels
|
|
|
|
return [bar_start, bar_end]
|
|
|
|
|
|
def update(self,pycomp):
|
|
# update value
|
|
newvalue=pycomp[self.halpin]
|
|
if newvalue != self.value:
|
|
self.value = newvalue
|
|
valtext = str( "%(b)3.1f" % {'b':self.value} )
|
|
self.itemconfig(self.val_text,text=valtext)
|
|
# set bar colour
|
|
if self.ranges:
|
|
self.set_fill(self.range1, self.range2, self.range3)
|
|
# set bar size
|
|
tmp=self.bar_coords()
|
|
start=tmp[0]
|
|
end=tmp[1]
|
|
self.coords(self.bar, start, 2,
|
|
end, self.bh-1)
|
|
|
|
|
|
|
|
# -------------------------------------------
|
|
|
|
|
|
|
|
|
|
class pyvcp_led(Canvas):
|
|
""" (indicator) a LED
|
|
<led>
|
|
<on_color>"colorname"</on_color> Default color red
|
|
<off_color>"colorname"</off_color> Default color green
|
|
<disable_pin>True</disable_pin> Optional halpin sets led to disable_color
|
|
<disable_color>"colorname"</disable_color> Default color gray80
|
|
</led>"""
|
|
n=0
|
|
def __init__(self,master,pycomp, halpin=None,disable_pin=False,
|
|
off_color="red",on_color="green",disabled_color="gray80",size=20,**kw):
|
|
Canvas.__init__(self,master,width=size,height=size,bd=0)
|
|
self.off_color=off_color
|
|
self.on_color=on_color
|
|
self.disabled_color=disabled_color
|
|
self.disable_pin = disable_pin
|
|
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)
|
|
pyvcp_led.n+=1
|
|
self.halpin=halpin
|
|
pycomp.newpin(halpin, HAL_BIT, HAL_IN)
|
|
if disable_pin:
|
|
halpin_disable = halpin+".disable"
|
|
self.halpin_disable = halpin_disable
|
|
pycomp.newpin(halpin_disable, HAL_BIT, HAL_IN)
|
|
|
|
|
|
def update(self,pycomp):
|
|
newstate = pycomp[self.halpin]
|
|
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
|
|
if self.disable_pin:
|
|
is_disabled = pycomp[self.halpin_disable]
|
|
if is_disabled == 1:
|
|
self.itemconfig(self.oh,fill=self.disabled_color)
|
|
|
|
|
|
|
|
|
|
# -------------------------------------------
|
|
|
|
class pyvcp_rectled(Canvas):
|
|
|
|
""" (indicator) a LED
|
|
<rectled>
|
|
<on_color>"colorname"</on_color> Default color red
|
|
<off_color>"colorname"</off_color> Default color green
|
|
<disable_pin>True</disable_pin> Optional halpin sets led to disable_color
|
|
<disable_color>"somecolor"</disable_color> Default color light gray
|
|
</rectled>"""
|
|
|
|
n=0
|
|
def __init__(self,master,pycomp, halpin=None,disable_pin=False,
|
|
off_color="red",on_color="green",disabled_color="gray80",height=10,width=30,**kw):
|
|
Canvas.__init__(self,master,width=width,height=height,bd=2)
|
|
self.off_color=off_color
|
|
self.on_color=on_color
|
|
self.disabled_color=disabled_color
|
|
self.disable_pin = disable_pin
|
|
self.oh=self.create_rectangle(1,1,width,height)
|
|
self.state=0
|
|
self.itemconfig(self.oh,fill=off_color)
|
|
if halpin == None:
|
|
halpin = "led."+str(pyvcp_led.n)
|
|
pyvcp_led.n+=1
|
|
self.halpin=halpin
|
|
pycomp.newpin(halpin, HAL_BIT, HAL_IN)
|
|
if disable_pin:
|
|
halpin_disable = halpin+".disable"
|
|
self.halpin_disable = halpin_disable
|
|
pycomp.newpin(halpin_disable, HAL_BIT, HAL_IN)
|
|
|
|
|
|
def update(self,pycomp):
|
|
newstate = pycomp[self.halpin]
|
|
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
|
|
if self.disable_pin:
|
|
is_disabled = pycomp[self.halpin_disable]
|
|
if is_disabled == 1:
|
|
self.itemconfig(self.oh,fill=self.disabled_color)
|
|
|
|
|
|
|
|
|
|
# -------------------------------------------
|
|
|
|
## ArcEye - the initval field is missing from the docs, so few people aware it can be preselected
|
|
## 12022014 added changepin which allows toggling of value from HAL without GUI action
|
|
|
|
class pyvcp_checkbutton(Checkbutton):
|
|
|
|
""" (control) a check button
|
|
halpin is 1 when button checked, 0 otherwise
|
|
<checkbutton>
|
|
[ <halpin>"my-checkbutton"</halpin> ]
|
|
[ <text>"Name of Button"</text>] text set in widget
|
|
[ <initval>1</initval> ] sets intial value to 1, all values >=0.5 are assumed to be 1
|
|
</checkbutton>
|
|
"""
|
|
n=0
|
|
def __init__(self,master,pycomp,halpin=None,initval=0,**kw):
|
|
self.v = BooleanVar(master)
|
|
Checkbutton.__init__(self,master,variable=self.v,onvalue=1, offvalue=0,**kw)
|
|
if halpin == None:
|
|
halpin = "checkbutton."+str(pyvcp_checkbutton.n)
|
|
self.halpin=halpin
|
|
pycomp.newpin(halpin, HAL_BIT, HAL_OUT)
|
|
changepin = halpin + ".changepin"
|
|
self.changepin=changepin
|
|
pycomp.newpin(changepin, HAL_BIT, HAL_IN)
|
|
pycomp[self.changepin] = 0
|
|
|
|
pyvcp_checkbutton.n += 1
|
|
|
|
if initval >= 0.5:
|
|
self.value=1
|
|
else:
|
|
self.value=0
|
|
self.v.set(self.value)
|
|
self.reset = 0
|
|
|
|
def update(self,pycomp):
|
|
# prevent race condition if connected to permanently on pin
|
|
if pycomp[self.changepin] and not(self.reset):
|
|
self.v.set(not(self.v.get()))
|
|
self.reset = 1
|
|
pycomp[self.changepin] = 0 # try to reset, but may not work
|
|
|
|
if not(pycomp[self.changepin]) and(self.reset):
|
|
self.reset = 0
|
|
pycomp[self.changepin] = 0 # make sure is reset now
|
|
|
|
pycomp[self.halpin]=self.v.get()
|
|
|
|
|
|
|
|
|
|
# -------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
class pyvcp_button(Button):
|
|
""" (control) a button
|
|
halpin is 1 when button pressed, 0 otherwise
|
|
optional halpin.disable disables the button
|
|
<button>
|
|
<halpin>"name"</halpin>
|
|
<disablepin>True</disablepin>
|
|
</button>"""
|
|
n=0
|
|
def __init__(self,master,pycomp,halpin=None,disable_pin=False,**kw):
|
|
Button.__init__(self,master,**kw)
|
|
if halpin == None:
|
|
halpin = "button."+str(pyvcp_button.n)
|
|
pyvcp_button.n += 1
|
|
self.halpin=halpin
|
|
pycomp.newpin(halpin, HAL_BIT, HAL_OUT)
|
|
self.disable_pin = disable_pin
|
|
if not disable_pin == False:
|
|
halpin_disable = halpin + ".disable"
|
|
pycomp.newpin(halpin_disable, HAL_BIT, HAL_IN)
|
|
self.halpin_disable=halpin_disable
|
|
self.state=0;
|
|
self.bind("<ButtonPress>", self.pressed)
|
|
self.bind("<ButtonRelease>", self.released)
|
|
self.pycomp = pycomp
|
|
|
|
def pressed(self,event):
|
|
if self.disable_pin:
|
|
is_disabled = self.pycomp[self.halpin_disable]
|
|
if is_disabled == 1: return
|
|
self.pycomp[self.halpin]=1
|
|
|
|
def released(self,event):
|
|
if self.disable_pin:
|
|
is_disabled = self.pycomp[self.halpin_disable]
|
|
if is_disabled == 1: return
|
|
self.pycomp[self.halpin]=0
|
|
|
|
def update(self,pycomp):
|
|
if self.disable_pin:
|
|
is_disabled = pycomp[self.halpin_disable]
|
|
if is_disabled == 1: Button.config(self,state=DISABLED)
|
|
else: Button.config(self,state=NORMAL)
|
|
else:pass
|
|
|
|
|
|
|
|
|
|
|
|
# -------------------------------------------
|
|
|
|
class pyvcp_scale(Scale):
|
|
""" (control) a slider
|
|
halpin-i is integer output
|
|
halpin-f is float output
|
|
|
|
<scale>
|
|
[ <halpin>"my-scale"</halpin> ]
|
|
[ <resolution>0.1</resolution> ] scale value a whole number must end in '.'
|
|
[ <orient>HORIZONTAL</orient> ] aligns the scale horizontal
|
|
[ <min_>-33</min_> ] sets the minimum value to -33
|
|
[ <max_>26</max_> ] sets the maximum value to 26
|
|
[ <initval>10</initval> ] sets intial value to 10
|
|
[ <param_pin>1</param_pin>] creates param pin if > 0, set to initval, value can then be set externally, ArcEye 2013
|
|
</scale>
|
|
|
|
"""
|
|
# 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,halparam=None,min_=0,max_=10,initval=0,param_pin=0,**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
|
|
|
|
if halparam == None:
|
|
self.param_pin = param_pin
|
|
if self.param_pin == 1:
|
|
halparam = "scale."+str(pyvcp_scale.n)+".param_pin"
|
|
self.halparam=halparam
|
|
pycomp.newpin(halparam, HAL_FLOAT, HAL_IN)
|
|
|
|
pyvcp_scale.n += 1
|
|
|
|
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)
|
|
|
|
|
|
if initval < min_:
|
|
self.value=min_
|
|
elif initval > max_:
|
|
self.value=max_
|
|
else:
|
|
self.value=initval
|
|
|
|
self.set(self.value)
|
|
|
|
if self.param_pin == 1:
|
|
self.oldInit=self.value
|
|
self.init=self.value
|
|
pycomp[self.halparam] = self.value
|
|
|
|
def update(self,pycomp):
|
|
pycomp[self.halpin+"-f"]=self.get()
|
|
pycomp[self.halpin+"-i"]=int(self.get())
|
|
|
|
if self.param_pin == 1:
|
|
self.init = pycomp[self.halparam]
|
|
if self.init != self.oldInit :
|
|
self.set(self.init)
|
|
self.value=self.init
|
|
self.oldInit=self.init
|
|
|
|
def wheel_up(self,event):
|
|
self.set(self.get()+self.resolution)
|
|
|
|
def wheel_down(self,event):
|
|
self.set(self.get()-self.resolution)
|
|
|
|
|
|
class pyvcp_table(Frame):
|
|
def __init__(self, master, pycomp, flexible_rows=[], flexible_columns=[], uniform_columns="", uniform_rows=""):
|
|
Frame.__init__(self, master)
|
|
for r in flexible_rows:
|
|
self.grid_rowconfigure(r, weight=1)
|
|
for c in flexible_columns:
|
|
self.grid_columnconfigure(c, weight=1)
|
|
for i, r in enumerate(uniform_rows):
|
|
self.grid_rowconfigure(i+1, uniform=r)
|
|
for i, c in enumerate(uniform_columns):
|
|
self.grid_columnconfigure(i+1, uniform=c)
|
|
|
|
self._r = self._c = 0
|
|
self.occupied = {}
|
|
self.span = (1,1)
|
|
self.sticky = "ne"
|
|
|
|
def add(self, container, child):
|
|
if isinstance(child, pyvcp_tablerow):
|
|
self._r += 1
|
|
self._c = 1
|
|
return
|
|
elif isinstance(child, pyvcp_tablespan):
|
|
self.span = child.span
|
|
return
|
|
elif isinstance(child, pyvcp_tablesticky):
|
|
self.sticky = child.sticky
|
|
return
|
|
r, c = self._r, self._c
|
|
while self.occupied.has_key((r, c)):
|
|
c = c + 1
|
|
rs, cs = self.span
|
|
child.grid(row=r, column=c, rowspan=rs, columnspan=cs,
|
|
sticky=self.sticky)
|
|
for ri in range(r, r+rs):
|
|
for ci in range(c, c+cs):
|
|
self.occupied[ri,ci] = True
|
|
|
|
self.span = 1,1
|
|
self._c = c+cs
|
|
|
|
def update(self, pycomp): pass
|
|
|
|
class pyvcp_tablerow:
|
|
def __init__(self, master, pycomp): pass
|
|
def update(self, pycomp): pass
|
|
|
|
class pyvcp_tablespan:
|
|
def __init__(self, master, pycomp, rows=1, columns=1):
|
|
self.span = rows, columns
|
|
def update(self, pycomp): pass
|
|
|
|
class pyvcp_tablesticky:
|
|
def __init__(self, master, pycomp, sticky):
|
|
self.sticky = sticky
|
|
def update(self, pycomp): pass
|
|
|
|
class pyvcp_include(Frame):
|
|
def __init__(self, master, pycomp, src, expand="yes", fill="both", anchor="center", prefix=None, **kw):
|
|
Frame.__init__(self,master,**kw)
|
|
|
|
self.master = master
|
|
self.fill = fill
|
|
self.anchor = anchor
|
|
self.expand = expand
|
|
|
|
if prefix is not None:
|
|
oldprefix = pycomp.getprefix()
|
|
pycomp.setprefix(prefix)
|
|
import vcpparse, xml.dom.minidom, xml.parsers.expat
|
|
|
|
try:
|
|
doc = xml.dom.minidom.parse(src)
|
|
except xml.parsers.expat.ExpatError, detail:
|
|
print "Error: could not open",src,"!"
|
|
print detail
|
|
sys.exit(1)
|
|
|
|
# find the pydoc element
|
|
for e in doc.childNodes:
|
|
if e.nodeType == e.ELEMENT_NODE and e.localName == "pyvcp":
|
|
break
|
|
|
|
if e.localName != "pyvcp":
|
|
print "Error: no pyvcp element in file!"
|
|
sys.exit()
|
|
pyvcproot=e
|
|
vcpparse.nodeiterator(pyvcproot,self)
|
|
|
|
if prefix is not None:
|
|
pycomp.setprefix(oldprefix)
|
|
|
|
def update(self, pycomp): pass
|
|
|
|
def add(self, container, widget):
|
|
widget.pack(fill=self.fill, anchor=self.anchor, expand=self.expand)
|
|
|
|
class _pyvcp_dummy:
|
|
def add(self, container, widget): pass
|
|
def update(self, pycomp): pass
|
|
def pack(self, *args, **kw): pass
|
|
|
|
class pyvcp_title(_pyvcp_dummy):
|
|
def __init__(self, master, pycomp, title, iconname=None):
|
|
master.wm_title(title)
|
|
if iconname: master.wm_iconname(iconname)
|
|
|
|
class pyvcp_axisoptions(_pyvcp_dummy):
|
|
def __init__(self, master, pycomp):
|
|
import rs274.options
|
|
rs274.options.install(master)
|
|
|
|
class pyvcp_option(_pyvcp_dummy):
|
|
def __init__(self, master, pycomp, pattern, value, priority=None):
|
|
master.option_add(pattern, value, priority)
|
|
|
|
class pyvcp_image(_pyvcp_dummy):
|
|
all_images = {}
|
|
def __init__(self, master, pycomp, name, **kw):
|
|
self.all_images[name] = PhotoImage(name, kw, master)
|
|
|
|
class _pyvcp_image(Label):
|
|
def __init__(self, master, pycomp, images, halpin=None, **kw):
|
|
Label.__init__(self, master, **kw)
|
|
if isinstance(images, basestring): images = images.split()
|
|
self.images = images
|
|
if halpin == None:
|
|
halpin = "number."+str(pyvcp_number.n)
|
|
pyvcp_number.n += 1
|
|
self.halpin = halpin
|
|
self.value = 0
|
|
self.last = None
|
|
pycomp.newpin(halpin, self.pintype, HAL_IN)
|
|
|
|
def update(self, pycomp):
|
|
l = pycomp[self.halpin]
|
|
if l != self.last:
|
|
try:
|
|
self.configure(image=self.images[l])
|
|
except (IndexError, KeyError):
|
|
print >>sys.stderr, "Unknown image #%d on %s" % (l, self.halpin)
|
|
self.last = l
|
|
|
|
class pyvcp_image_bit(_pyvcp_image):
|
|
pintype = HAL_BIT
|
|
class pyvcp_image_u32(_pyvcp_image):
|
|
pintype = HAL_U32
|
|
|
|
# This must come after all the pyvcp_xxx classes
|
|
elements = []
|
|
__all__ = []
|
|
for _key in globals().keys():
|
|
if _key.startswith("pyvcp_"):
|
|
elements.append(_key[6:])
|
|
__all__.append(_key)
|
|
|
|
if __name__ == '__main__':
|
|
print "You can't run pyvcp_widgets.py by itself..."
|
|
# vim:sts=4:sw=4:et:
|