Import LinuxCNC source tree
This commit is contained in:
383
lib/python/gladevcp/hal_dial.py
Normal file
383
lib/python/gladevcp/hal_dial.py
Normal file
@@ -0,0 +1,383 @@
|
||||
#!/usr/bin/env python3
|
||||
# hal_dial.py
|
||||
|
||||
# a pygtk widget that implements a dial with HAL pins
|
||||
# Copyright 2014 Chris Morley
|
||||
|
||||
# based on
|
||||
# http://www.pygtk.org/articles/cairo-pygtk-widgets/cairo-pygtk-widgets.htm
|
||||
# author: Lawrence Oluyede <l.oluyede@gmail.com>
|
||||
# date: 16 February 2005
|
||||
#
|
||||
# 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.
|
||||
|
||||
import gi
|
||||
gi.require_version("Gtk","3.0")
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GObject
|
||||
|
||||
import math
|
||||
|
||||
if __name__ == "__main__":
|
||||
from hal_widgets import _HalJogWheelBase
|
||||
else:
|
||||
from .hal_widgets import _HalJogWheelBase
|
||||
|
||||
class Hal_Dial(Gtk.DrawingArea, _HalJogWheelBase):
|
||||
__gsignals__ = dict(count_changed=(GObject.SignalFlags.RUN_FIRST,
|
||||
GObject.TYPE_NONE,
|
||||
(GObject.TYPE_INT, GObject.TYPE_FLOAT, GObject.TYPE_FLOAT)),
|
||||
scale_changed=(GObject.SignalFlags.RUN_FIRST,
|
||||
GObject.TYPE_NONE,
|
||||
(GObject.TYPE_INT, GObject.TYPE_FLOAT))
|
||||
)
|
||||
__gtype_name__ = 'Hal_Dial'
|
||||
__gproperties__ = {
|
||||
'show_counts' : ( GObject.TYPE_BOOLEAN, 'Display the counts in the widget', 'Display or not the counts value',
|
||||
True, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
||||
'count_type_shown' : ( GObject.TYPE_INT, 'What to display in center', '0: counts 1:scaled counts 2:delta scaled counts',
|
||||
0, 2, 0, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
||||
'cpr' : ( GObject.TYPE_INT, 'Counts per revolution', 'Set the value of counts per revolution',
|
||||
25, 360, 100, GObject.ParamFlags.READWRITE|GObject.ParamFlags.CONSTRUCT),
|
||||
'label' : ( GObject.TYPE_STRING, 'label', 'Sets the string to be shown in the upper part of the widget',
|
||||
"Dial", GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
||||
'center_color' : ( Gdk.Color.__gtype__, 'Color of the center', "",
|
||||
GObject.ParamFlags.READWRITE),
|
||||
'scale_adjustable' : ( GObject.TYPE_BOOLEAN, 'Allow adjustable scaling', 'Clicking can adjust the scaling.',
|
||||
True, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
||||
'scale' : ( GObject.TYPE_FLOAT, 'Scale', 'Sets the scale. Scale is multiplied to current counts.',
|
||||
.0001,1000,1, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
||||
}
|
||||
__gproperties = __gproperties__
|
||||
|
||||
def __init__(self):
|
||||
super(Hal_Dial, self).__init__()
|
||||
|
||||
# Gtk.Widget signals
|
||||
self.connect("draw", self.expose)
|
||||
self.connect("button_press_event", self.button_press)
|
||||
self.connect("button_release_event", self.button_release)
|
||||
self.connect("motion_notify_event", self.motion_notify)
|
||||
self.connect("scroll_event", self._scroll)
|
||||
# public
|
||||
self.size = 100
|
||||
self.cpr = 100
|
||||
self.label = "Dial"
|
||||
self.scale = 1.0
|
||||
self.scale_adjustable = True
|
||||
self.count_type_shown=1
|
||||
self.center_color = Gdk.Color.parse('#bdefbdefbdef')[1] # gray
|
||||
# private
|
||||
self._minute_offset = 0 # the offset of the pointer hand
|
||||
self._last_offset = 0
|
||||
self._dragging = False # true if the interface is being dragged
|
||||
self._count = self._delta_scaled = 0.0
|
||||
self._show_counts = True
|
||||
|
||||
# unmask events
|
||||
self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK |
|
||||
Gdk.EventMask.BUTTON_RELEASE_MASK |
|
||||
Gdk.EventMask.POINTER_MOTION_MASK |
|
||||
Gdk.EventMask.SCROLL_MASK)
|
||||
|
||||
# init the hal pin management
|
||||
def _hal_init(self):
|
||||
_HalJogWheelBase._hal_init(self)
|
||||
|
||||
# This function is called from hal_widgets.py
|
||||
def get_value(self):
|
||||
return self._count
|
||||
|
||||
# This function is called from hal_widgets.py
|
||||
def get_scaled_value(self):
|
||||
return (self._count*self.scale)
|
||||
|
||||
# This function is called from hal_widgets.py
|
||||
def get_delta_scaled_value(self):
|
||||
return (self._delta_scaled)
|
||||
|
||||
def set_label(self, labelcontent):
|
||||
self.set_property("label",labelcontent)
|
||||
|
||||
def expose(self, widget, event):
|
||||
# check for sensitivity flags so color can be changed
|
||||
if self.is_sensitive():
|
||||
self.alpha = 1
|
||||
else:
|
||||
self.alpha = 0.3
|
||||
|
||||
context = widget.get_property('window').cairo_create()
|
||||
self.set_size_request(100, 100)
|
||||
# set a clip region for the expose event
|
||||
#TODO
|
||||
#context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
|
||||
#context.clip()
|
||||
self.draw(context)
|
||||
return False
|
||||
|
||||
def button_press(self, widget, event):
|
||||
button1 = event.button == 1
|
||||
button2 = event.button == 2
|
||||
button3 = event.button == 3
|
||||
if button1:
|
||||
self.start_drag(widget,event)
|
||||
if not self.scale_adjustable:
|
||||
return False
|
||||
if button1 and (event.type == Gdk.EventType._2BUTTON_PRESS):
|
||||
self.scale=self.scale*.10
|
||||
self.redraw_canvas()
|
||||
self.emit("scale_changed", self._count,self.scale)
|
||||
if button3 and (event.type == Gdk.EventType._2BUTTON_PRESS):
|
||||
self.scale=self.scale*10
|
||||
self.redraw_canvas()
|
||||
self.emit("scale_changed", self._count,self.scale)
|
||||
|
||||
def button_release(self, widget, event):
|
||||
button1 = event.button == 1
|
||||
button2 = event.button == 2
|
||||
button3 = event.button == 3
|
||||
shift = event.state & Gdk.ModifierType.SHIFT_MASK
|
||||
if not button1:return
|
||||
if self._dragging:
|
||||
self._dragging = False
|
||||
self.emit_count_changed_signal(event.x, event.y)
|
||||
return False
|
||||
|
||||
# handle the scroll wheel of the mouse
|
||||
def _scroll(self, widget, event):
|
||||
if event.direction == Gdk.ScrollDirection.UP:
|
||||
self._count += 1
|
||||
self._minute_offset +=1
|
||||
self._delta_scaled += self.scale
|
||||
if event.direction == Gdk.ScrollDirection.DOWN:
|
||||
self._count -= 1
|
||||
self._minute_offset -=1
|
||||
self._delta_scaled -= self.scale
|
||||
self._last_offset = self._minute_offset
|
||||
self.redraw_canvas()
|
||||
self.emit("count_changed", self._count,self.scale,self._delta_scaled)
|
||||
|
||||
def start_drag(self,widget,event):
|
||||
minutes = self._minute_offset
|
||||
# from
|
||||
# http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html
|
||||
px = event.x - widget.get_allocation().width / 2
|
||||
py = widget.get_allocation().height / 2 - event.y
|
||||
lx = math.sin(math.pi / (self.cpr/2.0) * minutes)
|
||||
ly = math.cos(math.pi / (self.cpr/2.0) * minutes)
|
||||
u = lx * px + ly * py
|
||||
|
||||
# on opposite side of origin
|
||||
if u < 0:
|
||||
return False
|
||||
|
||||
d2 = math.pow(px - u * lx, 2) + math.pow(py - u * ly, 2)
|
||||
if d2 < 50: # pixels away from the line
|
||||
self._dragging = True
|
||||
return False
|
||||
|
||||
def motion_notify(self, widget, event):
|
||||
if self._dragging:
|
||||
self.emit_count_changed_signal(event.x, event.y)
|
||||
|
||||
def emit_count_changed_signal(self, x, y):
|
||||
# decode the minute hand
|
||||
# normalize the coordinates around the origin
|
||||
x -= self.get_allocation().width / 2
|
||||
y -= self.get_allocation().height / 2
|
||||
|
||||
# phi is a bearing from north clockwise, use the same geometry as we
|
||||
# did to position the minute hand originally
|
||||
phi = math.atan2(x, -y)
|
||||
#print phi
|
||||
if phi < 0:
|
||||
phi += math.pi * 2
|
||||
|
||||
minute = int(phi * (self.cpr/2.0) / math.pi)
|
||||
self._minute_offset = minute
|
||||
last = int(self._last_offset)
|
||||
delta = last - int(minute)
|
||||
#print last,int(minute),'delta',delta,'count:',self._count
|
||||
if not delta == 0:
|
||||
# epi is a range to check for cross over of zero.
|
||||
# if you move the pointer too fast gtk/python does not update
|
||||
# the position fast enough to detect the actually cross over.
|
||||
epi= self.cpr*.25
|
||||
if last > (self.cpr-epi) and minute < epi :
|
||||
change = self.cpr-last + minute
|
||||
self._count += change
|
||||
self._delta_scaled += change*self.scale
|
||||
elif minute > (self.cpr-epi) and last < epi :
|
||||
change = self.cpr-minute + last
|
||||
self._count -= change
|
||||
self._delta_scaled -= change*self.scale
|
||||
else:
|
||||
self._count -= delta
|
||||
self._delta_scaled -= delta*self.scale
|
||||
self._last_offset = minute
|
||||
self.redraw_canvas()
|
||||
self.emit("count_changed", self._count,self.scale,self._delta_scaled)
|
||||
|
||||
def draw(self, context):
|
||||
rect = self.get_allocation()
|
||||
x = self.get_allocated_width()/2
|
||||
y = self.get_allocated_height()/2
|
||||
|
||||
radius = min(rect.width / 2.0, rect.height / 2.0) - 5
|
||||
# black rim
|
||||
context.arc(x, y, radius, 0, 2.0 * math.pi)
|
||||
context.set_source_rgba(0, 0, 0,self.alpha)
|
||||
context.fill_preserve()
|
||||
# black inner circle outline
|
||||
context.arc(x, y, 0.8 * radius, 0, 2.0 * math.pi)
|
||||
context.stroke()
|
||||
# changeable filled inner circle
|
||||
context.set_source_rgb(0.8, .8, .8,)
|
||||
color = self.center_color
|
||||
context.set_source_rgba(color.red/65535., color.green/65535., color.blue/65535., self.alpha)
|
||||
context.arc(x, y, 0.8 * radius-context.get_line_width()/2, 0, 2.0 * math.pi)
|
||||
context.fill()
|
||||
context.stroke()
|
||||
|
||||
# rim ticks
|
||||
# we color the zero tick red - which is actually the tick 3/4 the way around
|
||||
context.set_source_rgb(1, 1, 1)
|
||||
for i in range(self.cpr):
|
||||
context.save()
|
||||
if i == self.cpr*.75:
|
||||
context.set_source_rgb(0.9, 0.1, 0.1)
|
||||
elif i == self.cpr*.75+1:
|
||||
context.set_source_rgb(1, 1, 1)
|
||||
if i % 5 == 0:
|
||||
inset = 0.2 * radius
|
||||
else:
|
||||
inset = 0.1 * radius
|
||||
context.set_line_width(0.5 * context.get_line_width())
|
||||
|
||||
context.move_to(x + (radius - inset) * math.cos(i * math.pi / (self.cpr/2.0)),
|
||||
y + (radius - inset) * math.sin(i * math.pi / (self.cpr/2.0)))
|
||||
context.line_to(x + radius * math.cos(i * math.pi / (self.cpr/2.0)),
|
||||
y + radius * math.sin(i * math.pi / (self.cpr/2.0)))
|
||||
context.stroke()
|
||||
context.restore()
|
||||
|
||||
# pointer
|
||||
# the line is rotated n degrees (pi/(ticks/2))
|
||||
context.set_source_rgba(0, 0, 0,self.alpha)
|
||||
minutes = self._minute_offset
|
||||
# calculate starting and stopping position of pointer line
|
||||
x_calc = radius * math.sin(math.pi / (self.cpr/2.0)* minutes)
|
||||
y_calc = radius * -math.cos(math.pi / (self.cpr/2.0) * minutes)
|
||||
context.move_to(x + 0.50 * x_calc, y + 0.50 * y_calc)
|
||||
context.line_to(x + 0.75 * x_calc, y + 0.75 * y_calc)
|
||||
context.stroke()
|
||||
|
||||
if self.label:
|
||||
context.set_font_size(int(radius/6))
|
||||
w,h = context.text_extents(str(self.label))[2:4]
|
||||
context.move_to(x - w / 2 , y + h / 2- 2*h)
|
||||
context.show_text(str(self.label))
|
||||
|
||||
# Do we want to see the counts value? If so draw it
|
||||
if self._show_counts:
|
||||
if self.count_type_shown == 0:
|
||||
text=str(self._count)
|
||||
elif self.count_type_shown == 1:
|
||||
text=str(self._count*self.scale)
|
||||
elif self.count_type_shown == 2:
|
||||
text=str(self._delta_scaled)
|
||||
context.set_font_size(int(radius/4))
|
||||
w,h = context.text_extents(text)[2:4]
|
||||
context.move_to(x - w / 2 , y + h / 2)
|
||||
context.show_text(text)
|
||||
# show scale value, if required
|
||||
if self.scale_adjustable:
|
||||
context.set_font_size(int(radius/8))
|
||||
w2,h2 = context.text_extents(str(self.scale))[2:4]
|
||||
context.move_to(x - w2 / 2 , y + h2 / 2+2*h)
|
||||
context.show_text(str(self.scale))
|
||||
|
||||
def redraw_canvas(self):
|
||||
if self.get_property('window'):
|
||||
alloc = self.get_allocation()
|
||||
self.queue_draw_area(alloc.x, alloc.y, alloc.width, alloc.height)
|
||||
#https://developer.gnome.org/gtkmm/stable/classGdk_1_1Window.html#a6ec5f7e788470159672511c964771285
|
||||
#self.get_property('window').process_updates(True)
|
||||
self.queue_draw()
|
||||
|
||||
# Get propertys
|
||||
def do_get_property(self, property):
|
||||
name = property.name.replace('-', '_')
|
||||
if name in list(self.__gproperties.keys()):
|
||||
return getattr(self, name)
|
||||
else:
|
||||
raise AttributeError('unknown property %s' % property.name)
|
||||
|
||||
# Set propertys
|
||||
def do_set_property(self, property, value):
|
||||
try:
|
||||
name = property.name.replace('-', '_')
|
||||
if name in list(self.__gproperties.keys()):
|
||||
setattr(self, name, value)
|
||||
if name == 'show_counts':
|
||||
self._show_counts = value
|
||||
elif name == "size":
|
||||
self.size = value
|
||||
self.set_size_request(self.size, self.size)
|
||||
elif name == "cpr":
|
||||
self.cpr = value
|
||||
elif name == "scale":
|
||||
self.scale = value
|
||||
elif name == "center_color":
|
||||
self.center_color = value
|
||||
elif name == "scale_adjustable":
|
||||
self.scale_adjustable = value
|
||||
elif name == "label":
|
||||
if len(str(value)) > 15:
|
||||
value = str(value)[:15]
|
||||
self.label = str(value)
|
||||
self.queue_draw()
|
||||
else:
|
||||
raise AttributeError('unknown property %s' % property.name)
|
||||
except:
|
||||
pass
|
||||
|
||||
# For testing directly
|
||||
def count_changed(widget, count,scale,delta_scale):
|
||||
pass
|
||||
#print('delta scale =',delta_scale)
|
||||
#print("Count changed - count %02i scale %f = %f" % (count,scale,count*scale))
|
||||
def scale_changed(widget, count,scale):
|
||||
pass
|
||||
#print("Scaled changed - count %02i scale %f = %f" % (count,scale,count*scale))
|
||||
|
||||
def main():
|
||||
window = Gtk.Window()
|
||||
wheel = Hal_Dial()
|
||||
wheel.set_property('cpr', 200)
|
||||
wheel.set_property('count_type_shown', 2)
|
||||
wheel.set_property('label', 'Test Dial 12345')
|
||||
wheel.set_property('scale', 10.5)
|
||||
wheel.set_property('scale_adjustable', True)
|
||||
wheel.set_property('center_color', Gdk.Color.parse('#bdefbdefbdef')[1])
|
||||
window.add(wheel)
|
||||
window.connect("destroy", Gtk.main_quit)
|
||||
window.set_title("Hal_Dial")
|
||||
wheel.connect("count_changed", count_changed)
|
||||
wheel.connect("scale_changed", scale_changed)
|
||||
window.show_all()
|
||||
|
||||
Gtk.main()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user