Skip to content

Instantly share code, notes, and snippets.

@nkp216
Last active March 24, 2020 10:16
Show Gist options
  • Save nkp216/87a68ece5361f3472159424b7115041b to your computer and use it in GitHub Desktop.
Save nkp216/87a68ece5361f3472159424b7115041b to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
# -*- coding:UTF-8 -*-
# GladeVcp Widget
# SpeedControl is a widget specially made to control an adjustment
# with a touch screen. It is a replacement to the normal scale widget
# witch is difficult to slide on a touch screen.
#
# Copyright (c) 2016 Norbert Schechner
#
#
# 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 gtk
import gobject
from math import pi
import hal
# This is needed to make the hal pin, making them directly with hal, will
# not allow to use them in glade without linuxcnc beeing started
from hal_widgets import _HalSpeedControlBase
class SpeedControl(gtk.VBox, _HalSpeedControlBase):
'''
The SpeedControl Widget serves as a slider with button to increment od decrease
the value and a progress bar showing the value with or without units
It is designed to be used with touch screens
SpeedControl(size, value, min, max, inc_speed, unit, color, template)
height = integer : The height of the widget in pixel
allowed values are 24 to 96
default is 36
value = float : The start value to set
allowed values are in the range from 0.001 to 99999.0
default is 10.0
min = float : The min allowed value
allowed values are 0.0 to 99999.0
default is 0.0
max = float : The max allowed value
allowed values are 0.001, 99999.0
default is 100.0
increment = float : sets the applied increment per mouse click,
-1 means 100 increments fom min to max
inc_speed = integer : Sets the timer delay for the increment speed holding pressed the buttons
allowed values are 20 to 300
default is 100
unit = string : Sets the unit to be shown in the bar after the value
any string is allowed
default is ""
color = Color : Sets the color of the bar
any hex color is allowed
default is "#FF8116"
template = Templ. : Text template to display the value Python formatting is used
Any allowed format
default is "%.1f"
do_hide_button = Bool : Whether to show or hide the increment an decrement button
True or False
Default = False
'''
__gtype_name__ = 'SpeedControl'
__gproperties__ = {
'height' : ( gobject.TYPE_INT, 'The height of the widget in pixel', 'Set the height of the widget',
24, 96, 36, gobject.PARAM_READWRITE|gobject.PARAM_CONSTRUCT),
'value' : (gobject.TYPE_FLOAT, 'Value', 'The value to set',
0.001, 99999.0, 10.0, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
'min' : (gobject.TYPE_FLOAT, 'Min Value', 'The min allowed value to apply',
0.0, 99999.0, 0.0, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
'max' : (gobject.TYPE_FLOAT, 'Max Value', 'The max allowed value to apply',
0.001, 99999.0, 100.0, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
'increment' : (gobject.TYPE_FLOAT, 'Increment Value', 'The increment value to apply, -1 means 100 steps from max to min',
-1.0, 99999.0, -1.0, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
'inc_speed' : ( gobject.TYPE_INT, 'The speed of the increments', 'Set the timer delay for the increment speed',
20, 300, 100, gobject.PARAM_READWRITE|gobject.PARAM_CONSTRUCT),
'unit' : ( gobject.TYPE_STRING, 'unit', 'Sets the unit to be shown in the bar after the value',
"", gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
'color' : (gtk.gdk.Color.__gtype__, 'color', 'Sets the color of the bar',
gobject.PARAM_READWRITE),
'template' : (gobject.TYPE_STRING, 'Text template for bar value',
'Text template to display. Python formatting may be used for one variable',
"%.1f", gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
'do_hide_button' : ( gobject.TYPE_BOOLEAN, 'Hide the button', 'Display the button + and - to alter the values',
False, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
}
__gproperties = __gproperties__
__gsignals__ = {
'value_changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_FLOAT,)),
'scale_changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_FLOAT,)),
'min_reached': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_BOOLEAN,)),
'max_reached': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_BOOLEAN,)),
'exit': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()),
}
def __init__(self, size = 36, value = 0, min = 0, max = 100, inc_speed = 100, unit = "", color = "#FF8116", template = "%.1f"):
super(SpeedControl, self).__init__()
# basic settings
self._size = size
self._value = value
self._min = min
self._max = max
self.color = gtk.gdk.Color(color)
self._unit = unit
self._increment = (self._max - self._min) / 100.0
self._template = template
self._speed = inc_speed
self.event = gtk.EventBox()
self.adjustment = gtk.Adjustment(self._value, self._min, self._max, self._increment, 0)
self.adjustment.connect("value_changed", self._on_value_changed)
self.btn_plus = gtk.Button("+")
self.btn_minus = gtk.Button("-")
self.draw = gtk.DrawingArea()
self.draw.connect("expose-event", self.expose)
self.event.add(self.draw)
self.table = gtk.Table(rows=1,columns=5)
self.table.attach( self.btn_minus, 0, 1, 0, 1, gtk.SHRINK, gtk.SHRINK )
self.table.attach( self.event, 1, 4, 0, 1, gtk.FILL|gtk.EXPAND, gtk.EXPAND )
self.table.attach( self.btn_plus, 4, 5, 0, 1, gtk.SHRINK, gtk.SHRINK )
self.add(self.table)
self.show_all()
self.connect("destroy", gtk.main_quit)
self.event.connect("button_press_event", self.press)
self.event.connect("button_release_event", self.release)
self.percentage = 0
def hide_button(self, state):
if state:
self.btn_minus.hide()
self.btn_plus.hide()
else:
self.btn_minus.show()
self.btn_plus.show()
def press(self,widget, event):
if (event.button == 1):
x = event.x
y = event.y
print "x=",x,"y=",y
w = self.draw.allocation.width
if w:
self.percentage = (x / w) * 100
self._draw_widget()
self._value = (self._max - self._min) / 100 * self.percentage
self.set_value(self._value)
def release(self,widget, event):
if (event.button == 1):
x = event.x
y = event.y
print "x=",x,"y=",y
w = self.draw.allocation.width
if w:
self.percentage = (x / w) * 100
self._draw_widget()
self._value = (self._max - self._min) / 100 * self.percentage
self.set_value(self._value)
# init the hal pin management
def _hal_init(self):
_HalSpeedControlBase._hal_init(self)
# the scale, as the widget may show units per minute, but linuxcnc expects units per second
self.hal_pin_scale = self.hal.newpin(self.hal_name+".scale", hal.HAL_FLOAT, hal.HAL_IN)
self.hal_pin_scale.connect("value-changed", self._on_scale_changed)
self.hal_pin_scale.set(60.0)
# the scaled value to be handled in hal
self.hal_pin_scaled_value = self.hal.newpin(self.hal_name+".scaled-value", hal.HAL_FLOAT, hal.HAL_OUT)
# this draws our widget on the screen
def expose(self, widget, event):
# create the cairo window
# I do not know why this works without importing cairo
self.cr = widget.window.cairo_create()
# call to paint the widget
self._draw_widget()
# draws the frame, meaning the background
def _draw_widget(self):
w = self.draw.allocation.width
# draw a rectangle with rounded edges and a black frame
linewith = self._size / 24
if linewith < 1:
linewith = 1
radius = self._size / 7.5
if radius < 1:
radius = 1
width_to_fill = w * self.percentage / 100
r, g, b = self.get_color_tuple(self.color)
self.cr.set_source_rgb(r, g, b)
# get the middle points of the corner radius
tl = [radius, radius] # Top Left
tr = [width_to_fill - radius, radius] # Top Right
br = [width_to_fill - radius, self._size - radius] # Bottom Left
bl = [radius, self._size - radius] # Bottom Right
# could be written shorter, but this way it is easier to understand
#self.cr.arc(tl[0], tl[1], radius, 2 * (pi/2), 3 * (pi/2))
#self.cr.arc(tr[0], tr[1], radius, 3 * (pi/2), 4 * (pi/2))
#self.cr.arc(br[0], br[1], radius, 0 * (pi/2), 1 * (pi/2))
#self.cr.arc(bl[0], bl[1], radius, 1 * (pi/2), 2 * (pi/2))
self.cr.rectangle(0,0,width_to_fill,self._size)
self.cr.close_path()
self.cr.fill()
self.cr.set_line_width(linewith)
self.cr.set_source_rgb(0, 0, 0)
# get the middle points of the corner radius
tl = [radius, radius] # Top Left
tr = [w - radius, radius] # Top Right
bl = [w - radius, self._size - radius] # Bottom Left
br = [radius, self._size - radius] # Bottom Right
# could be written shorter, but this way it is easier to understand
#self.cr.arc(tl[0], tl[1], radius, 2 * (pi/2), 3 * (pi/2))
#self.cr.arc(tr[0], tr[1], radius, 3 * (pi/2), 4 * (pi/2))
#self.cr.arc(bl[0], bl[1], radius, 0 * (pi/2), 1 * (pi/2))
#self.cr.arc(br[0], br[1], radius, 1 * (pi/2), 2 * (pi/2))
#rectangle(x,y,w,h)
self.cr.rectangle(0,0,w,self._size)
self.cr.close_path()
# draw the label in the bar
self.cr.set_source_rgb(0 ,0 ,0)
self.cr.set_font_size(self._size / 3)
tmpl = lambda s: self._template % s
label = tmpl(self._value)
if self._unit:
label += " " + self._unit
w,h = self.cr.text_extents(label)[2:4]
self.draw.set_size_request(int(w) + int(h), self._size)
left = self.draw.allocation.width /2
top = self._size / 2
self.cr.move_to(left - w / 2 , top + h / 2)
self.cr.show_text(label)
self.cr.stroke()
# This allows to set the value from external, i.e. propertys
def set_value(self, value):
self.adjustment.set_value(value)
self.update_button()
try:
self.hal_pin_scaled_value.set(self._value / self.hal_pin_scale.get())
except:
pass
self.queue_draw()
# Will return the value to external call
# so it will do also to hal_widget_base
def get_value(self):
return self._value
# if the value does change from outside, i.e. changing the adjustment value
# we are not sync, so
def _on_value_changed(self, widget):
value = widget.get_value()
if value != self._value:
self._value = value
self.set_value(self._value)
self.emit("value_changed", value)
# if the value does change from hal side, we have to update the scaled value
def _on_scale_changed(self, pin):
new_scale = pin.get()
self.emit("scale_changed", new_scale)
self.set_value(self._value)
# returns the separate RGB color numbers from the color widget
def _convert_to_rgb(self, spec):
color = spec.to_string()
temp = color.strip("#")
r = temp[0:4]
g = temp[4:8]
b = temp[8:]
return (int(r, 16), int(g, 16), int(b, 16))
# returns separate values for red, green and blue of a gtk_color
def get_color_tuple(gtk_color,c):
return (c.red_float, c.green_float, c.blue_float)
# set the digits of the shown value
def set_digits(self, digits):
if int(digits) > 0:
self._template = "%.{0}f".format(int(digits))
else:
self._template = "%d"
# allow changing the adjustment from outside
# so the widget can be connected to existing adjustments
def set_adjustment(self, adjustment):
self.adjustment = adjustment
self.adjustment.connect("value_changed", self._on_value_changed)
self._min = self.adjustment.get_lower()
self._max = self.adjustment.get_upper()
self._increment = (self._max - self._min) / 100.0
self.adjustment.set_page_size(adjustment.get_page_size())
self._value = self.adjustment.get_value()
self.set_value(self._value)
# Hiding the button, the widget can also be used as pure value bar
def hide_button(self, state):
if state:
self.btn_minus.hide()
self.btn_plus.hide()
else:
self.btn_minus.show()
self.btn_plus.show()
# if the adjustment changes from external command, we need to check
# the button state. I.e. the value is equal max value, and the max value
# has been changed, the plus button will remain unsensitive
def update_button(self):
if self._value <= self._min:
self._value = self._min
self.btn_minus.set_sensitive(False)
else:
self.btn_minus.set_sensitive(True)
if self._value >= self._max:
self._value = self._max
self.btn_plus.set_sensitive(False)
else:
self.btn_plus.set_sensitive(True)
# Get properties
def do_get_property(self, property):
name = property.name.replace('-', '_')
if name in self.__gproperties.keys():
if name == 'color':
col = getattr(self, name)
colorstring = col.to_string()
print("col = ",col)
print("colorstring = ",colorstring)
return getattr(self, name)
return getattr(self, name)
else:
raise AttributeError('unknown property %s' % property.name)
# Set properties
def do_set_property(self, property, value):
try:
name = property.name.replace('-', '_')
if name in self.__gproperties.keys():
setattr(self, name, value)
if name == "height":
self._size = value
if name == "value":
self.set_value(value)
if name == "min":
self._min = value
self.adjustment.lower = value
self._increment = (self._max - self._min) / 100.0
if name == "max":
self._max = value
self.adjustment.upper = value
self._increment = (self._max - self._min) / 100.0
if name == "increment":
if value < 0:
self._increment = (self._max - self._min) / 100.0
else:
self._increment = value
if name == "inc_speed":
self._speed = value
if name == "unit":
self._unit = value
if name == "color":
self.color = value
if name == "template":
self._template = value
self._draw_widget()
else:
raise AttributeError('unknown property %s' % property.name)
except:
pass
# for testing without glade editor:
# to show some behavior and setting options
def main():
window = gtk.Window()
#speedcontrol = SpeedControl(size = 48, value = 10000, min = 0, max = 15000, inc_speed = 100, unit = "mm/min", color = "#FF8116", template = "%.3f")
speedcontrol = SpeedControl()
window.add(speedcontrol)
window.set_title("Button Speed Control")
window.set_position(gtk.WIN_POS_CENTER)
window.show_all()
speedcontrol.set_property("height", 48)
speedcontrol.set_property("unit", "mm/min")
speedcontrol.set_property("color", gtk.gdk.Color("#FF8116"))
speedcontrol.set_property("min", 0)
speedcontrol.set_property("max", 15000)
speedcontrol.set_property("increment", 250.123)
speedcontrol.set_property("inc_speed", 100)
speedcontrol.set_property("value", 10000)
speedcontrol.set_property("template", "%.3f")
#speedcontrol.set_digits(1)
speedcontrol.hide_button(True)
gtk.main()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment