linuxcnc/lib/python/gladevcp/speedcontrol.py
luz paz 09a439ab21 lib/python: fix various typos
Found via `codespell -q 3 -S *.po,*.pot,*.ts,./.git/logs,./share,./docs/man/es,./configs/attic,*_fr.*,*_es.*,README_es -L ans,ba,bulle,componentes,doubleclick,dout,dum,fo,halp,ihs,inout,parm,parms,ro,ser,te,ue,wille,wonte`
2022-07-27 00:20:58 -04:00

485 lines
20 KiB
Python
Executable file

#!/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.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 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.table = Gtk.Table(n_rows=2,n_columns=5)
self.table.attach( self.btn_minus, 0, 1, 0, 1, Gtk.AttachOptions.SHRINK, Gtk.AttachOptions.SHRINK )
self.table.attach( self.draw, 1, 4, 0, 1, Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND, Gtk.AttachOptions.EXPAND )
self.table.attach( self.btn_plus, 4, 5, 0, 1, Gtk.AttachOptions.SHRINK, Gtk.AttachOptions.SHRINK )
self.add(self.table)
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()