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`
485 lines
20 KiB
Python
Executable file
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()
|