This section is an overview of GladeVCP which is in emc2 master since of Dec 14,2010.
See also the related HalWidgets and ActionWidgets pages.
Stock gladevcp can be used pretty much in the style of PyVCP - the major difference being that in the case of gladevcp, the glade user interface editor is used to design a screen layout, whereas PyVCP is driven by manually edited XML files. Also, PyVCP uses the TkInter widgets, whereas gladevcp uses the much richer GTK toolkit. Both support 'HAL widgets' - widgets with one or more associated HAL pin which is the interface to the rest of EMC. Stock gladevcp and PyVCP panels are really just a bunch of virtual switches, dials, leds an so forth, wired to the outside world through the HAL layer, designed to set and read ints, bits and floats values.
Most widget sets, and their associated user interface editors, support the concept of callbacks - functions in user-writen code which are executed when 'something happens' in the UI - events like mouse clicks, characters typed, mouse movement, timer events, window hiding and exposure and so forth. Both PyVCP and stock gladevcp mainly do two things: mapping events on HAL widgets to actions like a value change of the associated HAL pin, and in the other direction - detecting if HAL pin values changed and updating the associated widget, like a LED, a meter, a bar, or just some label displaying a value. However, neither PyVCP nor stock gladevcp provide support for other actions than changing HAL values. Doing something more complex, like executing MDI commands to call a G-code subroutine, is outside scope.
So there are now two ways to add actions to a GladeVCP panel: either use some stock EMC Action Widgets, or do it "by hand" - writing a Python handler and associate it with a widget event like 'pressed' or 'value-changed'.
When using Python event handlers - which are at a lower level than Actions - one needs to take care of dealing with status oneself. For this purpose, there's the EMC Stat widget - it's purpose is to associate event handlers with EMC status changes. The corresponding signal names are are:
We also extended the way the HAL input pins interact with the gladevcp panel. Beyond HAL widgets displaying pin values, there is now a way to attach a 'value-changed' callback to a HAL pin, which fits nicely with the event-driven structure of a typical widget application: every activity, be it mouse click, key, timer expired, or the change of a HAL pin's value, generates a callback and is handled by the same orthogonal mechanism.
Note that the above refers to explictely declared HAL pins. HAL widgets come with a pre-defined signal 'hal-pin-changed', see the HalWidgets page for details.
See the 'Adding HAL pins' section below for details.
A annoying aspect of gladevcp in its earlier form and pyvcp is the fact that you may change values through text entry, sliders, spin boxes, toggle buttons etc, but their settings are not saved and restored at the next run of EMC - they start at the default value as set in the panel or widget definition. Therefore I added an easy-to-use mechanism to save and restore the state of HAL widgets, and program variables (in fact instance attributes) as well. This mechanism uses the popular '.ini' file syntax and has safeguards against the .ini file and the corresponding user interface or program variables getting out of sync - just imagine renaming, adding or deleting widgets in glade: an .ini file lying around from a previous program version, or an entirely different user interface, would be not be able to restore the state properly. This situation is detected through a signature which depends on all object names and types which are saved and to be restored. In the case of signature mismatch, a new .ini file with default settings is generated.
The overall protocol is as follows:
For simple tasks it's sufficient to define functions named after the glade signal handlers. These will be called when the corresponding event happens in the widget tree. Here's a trivial example - it assumes that the 'pressed' signal of a GTK Button or HAL Button is linked to a callback called 'on_button_press':
nhits = 0
def on_button_press(gtkobj,data=None): global nhits nhits += 1 gtkobj.set_label("hits: %d" % nhits)
Add this function to a Python file and run as follows:
Note communication between handlers has to go through global variables, which does not scale well and is positively Unpythonic. This is why we came up with the class-based handler model.
The idea here is: handlers are linked to class methods. The underlying class(es) are instantiated and inspected during gladevcp startup and linked to the widget tree as signal handlers. So the task now is to write:
Here is a minimum user-defined handler example module:
Class MyCallbacks : def on_this_signal(self,obj,data=None): print "this_signal happened, obj=",obj
def get_handlers(halcomp,builder,useropts): return [MyCallbacks ()]
Now, 'on_this_signal' will be available as signal handler to your widget tree.
If during module inspection gladevcp finds a function 'get_handlers', it calls it as follows:
get_handlers(halcomp,builder,useropts)the arguments are:
gladevcp then inspects the list of class instances and retrieves their method names. Qualifying method names are connected to the widget tree as signal handlers. Only method names which do not begin with an '_' (underscore) are considered.
Note that regardless wether you're using the libglade or the new GtkBuilder? format for your glade UI, widgets can always be referred to as builder.get_object(<widgetname>). Also, the complete list of widgets is available as builder.get_objects() regardless of UI format.
It is important to know in which state of affairs your get_handlers() function is called so you know what is safe to do there and what not. First, modules are imported and initialized in command line order. After successful import, get_handlers() is called in the following state:
Once all modules have been imported and method names extracted, the following steps happen:
So when your handler class is initialized, all widgets are existent but not yet realized (displayed on screen). And the HAL component is'nt ready as well, so its unsafe to access pins values in your __init__() method.
If you want to have a callback to execute at program start after it is safe to access HAL pins, then a connect a handler to the realize signal of the top level window1 (which might be its only real purpose). At this point gladevcp is done with all setup tasks, the halfile has been run, and gladevcp is about to enter the gtk main loop.
Within a class, method names must be unique. However, it is ok to have multiple class instances passed to gladevcp by get_handlers() with identically named methods. When the corresponding signal occurs, these methods will be called in definition order - module by module, and within a module, in the order class instances are returned by get_handlers().
Instead of extending gladevcp for any conceivable option which could potentially be useful for a handler class, you may use the -U <useroption> flag (repeatedly if you wish). This flag collects a list of <useroption> strings. This list is passed to the get_handlers() function (useropts argument). Your code is free to interpret these strings as you see fit. An possible usage would be to pass them to the Python exec function in your get_handlers() as follows:
debug = 0 ... def get_handlers(halcomp,builder,useropts): ... global debug # assuming there's a global var for cmd in useropts: exec cmd in globals()
This way you can pass arbitrary Python statements to your module through the gladevcp -U option, for example:
gladevcp -U debug=42 -U "print 'debug=%d' % debug" ...
This should set debug to 2 and confirm that your module actually did it.
def __init__(self, halcomp,builder,useropts): self.halcomp = halcomp self.builder = builder self.useropts = useropts
self.defaults = { # the following names will be saved/restored as method attributes # the save/restore mechanism is strongly typed - the variable's type will be derived from the type of the # initialisation value. Currently supported types are: int, float, bool, string IniFile.vars : { 'nhits' : 0, 'a': 1.67, 'd': True ,'c' : "a string"},
# to save/restore all widget's state which might remotely make sense, add this: IniFile.widgets : widget_defaults(builder.get_objects()) # a sensible alternative might be to retain only all HAL output widgets' state: # IniFile.widgets: widget_defaults(select_widgets(self.builder.get_objects(), hal_only=True,output_only = True)), }
and associate an .ini file with this descriptor:
self.ini_filename = __name__ + '.ini' self.ini = IniFile(self.ini_filename,self.defaults,self.builder) self.ini.restore_state(self)
after restore_state(), self will have attributes set if as running the following:
self.nhits = 0 self.a = 1.67 self.d = True self.c = "a string"
Note that types are saved and preserved on restore. This example assumes that the ini file didnt exist or had the default values from self.defaults.
After this incantation, you can use the following IniFil methods:
To save the widget and/or variable state on exit, connect a signal handler to the window1 (toplevel) destroy event:
def on_destroy(self,obj,data=None): self.ini.save_state(self)
Next time you start the gladevcp application, the widgets should come up in the state when the application was closed.
You can do that, but note that the values in self.defaults override your edits if there is a syntax or type error in your edit. The error is detected, a console message will hint about that happened, and the bad inifile will be renamed to have the .BAD suffix. Subsequent bad ini files overwrite earlier .BAD files.
If you need HAL pins which are not associated with a specific HAL widget, add them as follows:
import hal_glib ... # in your handler class __init__(): self.example_trigger = hal_glib.GPin(halcomp.newpin('example-trigger', hal.HAL_BIT, hal.HAL_IN))
To get a callback when this pin's value changes, associate a value-change callback with this pin, add:
self.example_trigger.connect('value-changed', self._on_example_trigger_change)
and define a callback method (or function, in this case leave out the 'self' parameter):
# note '_' - this method will not be visible to the widget tree def _on_example_trigger_change(self,pin,userdata=None): print "pin value changed to:" % (pin.get())
Since gladevcp uses GTK widgets which rely on the GObject base class, the full glib functionaliy is available. Here is an example for a timer callback:
def _on_timer_tick(self,userdata=None): ... return True # to restart the timer; return False for on-shot
... # demonstrate a slow background timer - granularity is one second # for a faster timer (granularity 1msec), use this: # glib.timeout_add(100, self._on_timer_tick,userdata) # 10Hz glib.timeout_add_seconds(1, self._on_timer_tick)
Axis uses the TkInter? widget set. GladeVCP? applications use Gtk widgets and run in a separate process context. They are hooked into Axis with the Xembed protocol. This allows a child application like gladevcp to properly fit in a parent's window, and - in theory - have integrated event handling.
However, this assumes that both parent and child application properly support the Xembed protocol, which Gtk does, but TkInter? doesnt. A consequence of this is that certain keys would not be forwarded from a gladevcp panel to Axis properly under all circumstances. One of these situations was the case when an Entry, or SpinButton? widget had focus: in this case, for instance an Escape key would not have been forwarded to Axis and cause an abort as it should, with potentially disastrous consequences.
Therefore, key events in gladevcp are explicitly handled, and selectively forwarded to Axis, to assure that such situations cannot arise. For details, see the keyboard_forward() function in lib/python/gladevcp/xembed.py .
Visit emc2/configs/gladevcp for running examples. The templates subdirectory has starters for your own projects.
The probe example is actually cute and useful, at least for me. NB: it requires a running EMC.