Import LinuxCNC source tree
This commit is contained in:
489
lib/python/gladevcp/speedcontrol.py
Executable file
489
lib/python/gladevcp/speedcontrol.py
Executable file
@@ -0,0 +1,489 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- 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
|
||||
# which 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 gi
|
||||
gi.require_version("Gtk","3.0")
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GObject
|
||||
from gi.repository import GLib
|
||||
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 being started
|
||||
if __name__ == "__main__":
|
||||
from hal_widgets import _HalSpeedControlBase
|
||||
else:
|
||||
from .hal_widgets import _HalSpeedControlBase
|
||||
|
||||
class SpeedControl(Gtk.Box, _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 from 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.ParamFlags.READWRITE|GObject.ParamFlags.CONSTRUCT),
|
||||
'value' : (GObject.TYPE_FLOAT, 'Value', 'The value to set',
|
||||
0.001, 99999.0, 10.0, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
||||
'min' : (GObject.TYPE_FLOAT, 'Min Value', 'The min allowed value to apply',
|
||||
0.0, 99999.0, 0.0, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
||||
'max' : (GObject.TYPE_FLOAT, 'Max Value', 'The max allowed value to apply',
|
||||
0.001, 99999.0, 100.0, GObject.ParamFlags.READWRITE | GObject.ParamFlags.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.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
||||
'inc_speed' : ( GObject.TYPE_INT, 'The speed of the increments', 'Set the timer delay for the increment speed',
|
||||
20, 300, 100, GObject.ParamFlags.READWRITE|GObject.ParamFlags.CONSTRUCT),
|
||||
'unit' : ( GObject.TYPE_STRING, 'unit', 'Sets the unit to be shown in the bar after the value',
|
||||
"", GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
||||
'color' : (Gdk.RGBA, 'color', 'Sets the color of the bar',
|
||||
GObject.ParamFlags.READWRITE),
|
||||
'template' : (GObject.TYPE_STRING, 'Text template for bar value',
|
||||
'Text template to display. Python formatting may be used for one variable',
|
||||
"%.1f", GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
||||
'do_hide_button' : ( GObject.TYPE_BOOLEAN, 'Hide the button', 'Display the button + and - to alter the values',
|
||||
False, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
||||
}
|
||||
__gproperties = __gproperties__
|
||||
|
||||
__gsignals__ = {
|
||||
'value_changed': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_FLOAT,)),
|
||||
'scale_changed': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_FLOAT,)),
|
||||
'min_reached': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_BOOLEAN,)),
|
||||
'max_reached': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_BOOLEAN,)),
|
||||
'exit': (GObject.SignalFlags.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 = Gdk.RGBA()
|
||||
self.color.parse(color)
|
||||
self._unit = unit
|
||||
self._increment = (self._max - self._min) / 100.0
|
||||
self._template = template
|
||||
self._speed = inc_speed
|
||||
|
||||
self.adjustment = Gtk.Adjustment(value = self._value, lower = self._min, upper = self._max, step_increment = self._increment, page_increment = 0)
|
||||
self.adjustment.connect("value_changed", self._on_value_changed)
|
||||
self.adjustment.connect("changed", self._on_value_changed)
|
||||
|
||||
self.btn_plus = Gtk.Button(label = "+")
|
||||
self.btn_plus.connect("pressed", self.on_btn_plus_pressed)
|
||||
self.btn_plus.connect("released", self.on_btn_plus_released)
|
||||
self.btn_minus = Gtk.Button(label = "-")
|
||||
self.btn_minus.connect("pressed", self.on_btn_minus_pressed)
|
||||
self.btn_minus.connect("released", self.on_btn_minus_released)
|
||||
|
||||
self.draw = Gtk.DrawingArea()
|
||||
self.draw.connect("draw", self.expose)
|
||||
|
||||
self.draw.set_valign(Gtk.Align.CENTER)
|
||||
self.btn_plus.set_valign(Gtk.Align.CENTER)
|
||||
self.btn_minus.set_valign(Gtk.Align.CENTER)
|
||||
|
||||
self.table = Gtk.Box()
|
||||
self.table.pack_start(self.btn_minus, expand=False, fill=False, padding=0)
|
||||
self.table.pack_start(self.draw, expand=True, fill=True, padding=0)
|
||||
self.table.pack_start(self.btn_plus, expand=False, fill=False, padding=0)
|
||||
|
||||
self.pack_start(self.table, fill=True, expand=True, padding=0)
|
||||
self.show_all()
|
||||
self.connect("destroy", Gtk.main_quit)
|
||||
|
||||
|
||||
self._update_widget()
|
||||
|
||||
def _update_widget(self):
|
||||
self.btn_plus.set_size_request(self._size,self._size)
|
||||
self.btn_minus.set_size_request(self._size,self._size)
|
||||
|
||||
# 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)
|
||||
|
||||
# pins to allow hardware button to be connected to the software button
|
||||
self.hal_pin_increase = self.hal.newpin(self.hal_name+".increase", hal.HAL_BIT, hal.HAL_IN)
|
||||
self.hal_pin_increase.connect("value-changed", self._on_plus_changed)
|
||||
self.hal_pin_decrease = self.hal.newpin(self.hal_name+".decrease", hal.HAL_BIT, hal.HAL_IN)
|
||||
self.hal_pin_decrease.connect("value-changed", self._on_minus_changed)
|
||||
|
||||
# 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.get_property('window').cairo_create()
|
||||
|
||||
# call to paint the widget
|
||||
self._draw_widget()
|
||||
|
||||
# draws the frame, meaning the background
|
||||
def _draw_widget(self):
|
||||
w = self.draw.get_allocated_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
|
||||
|
||||
# fill the rectangle with selected color
|
||||
# first get the width of the area to fill
|
||||
percentage = (self._value - self._min) * 100 / (self._max - self._min)
|
||||
width_to_fill = w * 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.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))
|
||||
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.get_allocated_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)
|
||||
|
||||
# we create a timer and repeat the increment command as long as the button is pressed
|
||||
def on_btn_plus_pressed(self, widget):
|
||||
self.timer_id = GLib.timeout_add(self._speed, self.increase)
|
||||
|
||||
# destroy the timer to finish increasing the value
|
||||
def on_btn_plus_released(self, widget):
|
||||
# we have to put this in a try, as the hal pin changed signal will be emitted
|
||||
# also on creation of the hal pin, but the default is False, but we do not have
|
||||
# a self.timer_id at this state.
|
||||
try:
|
||||
GLib.source_remove(self.timer_id)
|
||||
except:
|
||||
pass
|
||||
|
||||
# increase the value
|
||||
def increase(self):
|
||||
value = self.adjustment.get_value()
|
||||
value += self._increment
|
||||
if value > self._max:
|
||||
value = self._max
|
||||
self.btn_plus.set_sensitive(False)
|
||||
self.set_value(value)
|
||||
return False
|
||||
elif not self.btn_minus.get_sensitive():
|
||||
self.btn_minus.set_sensitive(True)
|
||||
self.set_value(value)
|
||||
return True
|
||||
|
||||
# we create a timer and repeat the decrease command as long as the button is pressed
|
||||
def on_btn_minus_pressed(self, widget):
|
||||
self.timer_id = GLib.timeout_add(self._speed, self.decrease)
|
||||
|
||||
# destroy the timer to finish increasing the value
|
||||
def on_btn_minus_released(self, widget):
|
||||
# we have to put this in a try, as the hal pin changed signal will be emitted
|
||||
# also on creation of the hal pin, but the default is False, but we do not have
|
||||
# a self.timer_id at this state.
|
||||
try:
|
||||
GLib.source_remove(self.timer_id)
|
||||
except:
|
||||
pass
|
||||
|
||||
# decrease the value
|
||||
def decrease(self):
|
||||
value = self.adjustment.get_value()
|
||||
value -= self._increment
|
||||
if value < self._min:
|
||||
value = self._min
|
||||
self.btn_minus.set_sensitive(False)
|
||||
self.set_value(value)
|
||||
return False
|
||||
elif not self.btn_plus.get_sensitive():
|
||||
self.btn_plus.set_sensitive(True)
|
||||
self.set_value(value)
|
||||
return True
|
||||
|
||||
# if the hal pin changes, we will virtually press the corresponding button
|
||||
def _on_plus_changed(self,pin):
|
||||
if pin.get():
|
||||
self.on_btn_plus_pressed(None)
|
||||
else:
|
||||
self.on_btn_plus_released(None)
|
||||
|
||||
# if the hal pin changes, we will virtually press the corresponding button
|
||||
def _on_minus_changed(self,pin):
|
||||
if pin.get():
|
||||
self.on_btn_minus_pressed(None)
|
||||
else:
|
||||
self.on_btn_minus_released(None)
|
||||
|
||||
# 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, c.green, c.blue)
|
||||
|
||||
# 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()
|
||||
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
|
||||
self._update_widget()
|
||||
if name == "value":
|
||||
self.set_value(value)
|
||||
if name == "min":
|
||||
self._min = value
|
||||
self.adjustment.set_lower(value)
|
||||
self._increment = (self._max - self._min) / 100.0
|
||||
if name == "max":
|
||||
self._max = value
|
||||
self.adjustment.set_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
|
||||
if name == "do_hide_button":
|
||||
self.hide_button(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.WindowPosition.CENTER)
|
||||
window.show_all()
|
||||
speedcontrol.set_property("height", 48)
|
||||
speedcontrol.set_property("unit", "mm/min")
|
||||
color = Gdk.RGBA()
|
||||
color.parse("#FF8116")
|
||||
speedcontrol.set_property("color", color)
|
||||
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()
|
||||
Reference in New Issue
Block a user