Yorick GUI Tutorial
This page is still in construction. It contains step-to-step instructions to build a GUI for yorick using pygtk (python gtk bindings) and glade.
Go to:
Overview
Building GUIs (Graphical User Interface) for yorick applications is now easy, as we hope we will demonstrate here. The learning curve is not that steep, although a little bit of python syntax will have to be learned on the way. The new method is based on gtk (the gnome tool kit), and as such will work better on linux. Gtk is certainly available for OsX (although we have had trouble building up dependencies) and I have heard it is available on windows too (untested). You will need the gnome libraries, gtk, python and pygtk, and glade and its libraries libglade. You will also need the yorick-yutils package (for pyk.i).
The architecture we propose can be simply described as follow:
- YORICK stays at the core of the application. It carries out all the heavy mathematical processing, input/outputs, graphics. We will have to add a thin layer to communicate with the python process that includes the communication mechanism and wrapper functions.
- GLADE is used to design the graphical interface itself. Glade is an intuitive tool to design GUIs with drag and drop widgets. You just define the structure of your GUI, laying out buttons, sliders, entry or other widgets, and define callback functions corresponding to selected events. Once designed, the GUI definition is saved in a XML format that can be read by libglade/gtk to realize the actual GUI with a single call. Glade is available on linux, and is specific to gnome and gtk.
- PYTHON is used as a glue between yorick and the glade-defined GUI. The event callback functions are defined within this python script. From these functions, one can send messages to yorick to execute tasks. Yorick can also, through python, refresh widgets values, and even display images in the GUI.
spawn()
call from yorick, so is basically made through
pipes (fast). spawn()
imposes uses a slightly different
syntax than the regular yorick syntax: the command's arguments are
separated by spaces instead of comas, and there can be no ommited
arguments, nor keywords (see help page for
yorick's spawn()
and funcdef()
). This often
imposes the need for yorick wrapper functions, that will in turn call
more elaborate yorick (often pre-existing) functions.
Now let's get to work and build up our first yorick gtk GUI.
Hello world
Create the GUI with Glade
Open up a terminal, and type,
$ glade-3
Some distro still have glade-2, you'll have to slightly adapt the instructions below.
You should be presented with this interface:
Follow the instructions below step-by-step:
- Create the top level window:
left menu bar -> toplevels -> window (upper left icon) click - By default, top-level widgets are not visible. Let's make it visible:
go to the right set of panes:
Common -> visible click - Add a button:
left menu bar -> control-and-display -> button (upper left icon) click
and place the button (click) on the recently created window widget - Define the properties of the button: on the right set of panes:
- Go to General
- Enter Hello World! as a label
- Enter helloworld as the widget name (type return to make it register)
- Let's put some space around our button:
Common -> Border width (toward the end): 40 type return - Then we have to define the callback function when this button is pressed:
Signals -> clicked double click on the "handler" column of the "clicked" line
Enter "on_helloworld_clicked"
Note that when you type the first "o", it should complete the field with the name of the widget. Just type return. - Save it:
File -> Save as helloworld.glade - Quit
The python file
This is going to look complicated/overwhelming, but in fact is rather simple. What you want to do is basically copy this file over and adapt it for future use. Below is the python file. I have added ample comments, so read through the code. Most of this will stays identical from one application to another. In fact, I have color coded (in red) what need to be changed if you decide to re-use this to code another GUI.
So copy and paste this file (or get it from here) and save it as helloworld.py alongside your newly created helloworld.glade. One more thing: the python file has to be executable! Just do:
$ chmod 755 helloworld.py
helloworld.py
#!/usr/bin/env python # authors: Matthieu Bec, Francois Rigaut # helloworld.py # import necessary modules import gtk import gtk.glade import sys import gobject import os, fcntl, errno # define the class class helloworld: def destroy(self, wdg, data=None): self.py2yo('quit') # gtk.main_quit() # we'll call this from yorick before it quits. def __init__(self,helloworldtop): self.helloworldtop = helloworldtop # read GUI definition self.glade = gtk.glade.XML(os.path.join(self.helloworldtop,'helloworld.glade')) # handle destroy event (so that we can kill the window through window bar) self.window = self.glade.get_widget('window1') if (self.window): self.window.connect('destroy', self.destroy) # autoconnect to callback functions # this will automatically connect the event handler "on_something_event" # to the here-defined function of the same name. This avoid defining a # long dictionary that serves the same purpose self.glade.signal_autoconnect(self) # set stdin non blocking, this will prevent readline to block # stdin is coming from yorick (yorick spawned this python process) fd = sys.stdin.fileno() flags = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) # ... and add stdin to the event loop (yorick input pipe by spawn) gobject.io_add_watch(sys.stdin,gobject.IO_IN|gobject.IO_HUP,self.yo2py,None) # run: realize the interface, start event management gtk.main() # callback functions # This is the important, user-defined heart of this file. # here, you define the function that will be called when an event is raised # (read: happens): a button is pressed, a slider is moved, you enter a # drawingarea or move the cursor, etc... Just define a handler (callback) of # the same name in the glade UI definition (see signals) and you're done. def on_helloworld_clicked(self,wdg): #string/command to send to yorick #function parameters (separated with spaces) self.py2yo('write "Hello World!"') # minimal wrapper for yorick/python communication # This is really internal to the yorick/python communication and you should # not have to fiddle with it def py2yo(self,msg): # sends string command to yorick sys.stdout.write(msg+'\n') sys.stdout.flush() def yo2py(self,cb_condition,*args): if cb_condition == gobject.IO_HUP: raise SystemExit, "lost pipe to yorick" # handles string command from yorick # note: inidividual message needs to end with \n for proper ungarbling while 1: try: msg = sys.stdin.readline() msg = "self."+msg exec(msg) except IOError, e: if e.errno == errno.EAGAIN: # the pipe's empty, good break # else bomb out raise SystemExit, "yo2py unexpected IOError:" + str(e) except Exception, ee: raise SystemExit, "yo2py unexpected Exception:" + str(ee) return True # check calling syntax (should not be a problem as it is always called # from yorick # if len(sys.argv) != 2: print 'Usage: helloworld.py path_to_glade' raise SystemExit # get path to glade file helloworldtop = str(sys.argv[1]) # execute it top = helloworld(helloworldtop)
Yorick
We're almost done. Now we will create the yorick side, which will
spawn python.
Let's call it (unsurprisingly) helloworld.i, with the
simple content:
/* helloworld.i main function to call the pygtk GUI to helloworld. syntax: yorick -i helloworld.i Authors: F.Rigaut, Dec 2007 */ require,"pyk.i"; helloworldtop = get_cwd(); // build command to spawn: python_exec = helloworldtop+"/helloworld.py"; pyk_cmd=[python_exec,helloworldtop]; // spawn it and attach to _pyk_callback (see pyk.i): // that starts python and pass it the path to the glade file _pyk_proc = spawn(pyk_cmd, _pyk_callback);
Run it!
cd to the directory where you should now have helloworld.i, helloworld.py and helloworld.glade. Make sure helloworld.py is executable. Run the example with
$ yorick -i helloworld.i
The following window should pop up:
Click on it, and you should see "Hello world!" in the yorick terminal.
That's it. You have done it. This might appear anticlimatic, but you have to realize that you have now the keys to building GUIs for yorick. The whole structure is here, and adding functionalities is just a question of adding widgets in glade, python callbacks in the python routine, and yorick wrappers (or not), to get GUIs like the ones below (which are, you'll have to admit, very clean, thanks to gtk2.0+).
As a simple exercize, you could upgrade your GUI to have the button create a graphical window (easy), and add second button to make it plot or display something (beware of the funcdef syntax).
We will see below how to include graphics in the GUI.
Adding graphics
A yorick GUI would probably not be complete without a graphic window. Fortunately, since 2.1.04x (about 08/2007), yorick has the capability to display its graphic window within another window id. The trick here is to reserve space within the gtk window and display the yorick graphics window in there. We will modify the three helloworld files from the previous section. You can cheat and get the 3 modified files in that tarball.- Modify the GUI in glade to include somewhere a drawingarea.
- For instance, you can edit helloworld.glade and add a vertical box parent by right clicking in the existing button (glade-3 only), then add a drawingarea widget (colorful icon bottom of the "control and display" in the left menu bar). Once done, you have to set the size of this drawingarea widget. You can do that within the "Common" tab at the right of the interface. Set for instance the width to 450 and the height to 471.
- Alternatively, you can start from scratch: start with a vertical box, and add drawingarea and a button. Don't forget to set the visibility of the window, and to set the name, label and callback function for the button.
- If yorick tries to create its window within a wid (window id) that does not exist yet, it will create an error. We have therefore to trigger the window creation after the main gtk window is created. To do that, add a callback to the drawingarea: select the drawingarea either from the main gui display or from the right pane widget selector, go to "signals", scroll down and add a handler at "map-event". Call it the default name, "on_drawingarea_map_event".
- Save the file.
- Add the following in the python script, within the "callback
functions" definition:
def on_drawingarea_map_event(self,wdg,*args): mwid = wdg.window.xid; self.py2yo('ywindow_init %d' % mwid) # optionaly (but this is a good place to # do it), update GUI parameters from yorick: # self.py2yo('gui_update')
This is a function that will be called when the drawingarea is *realized* (mapped). What this does is sending a command to yorick to initialize the graphic windows (ywindow_init, see below). As it says, this is also the place to update the various widgets that may display numbers, etc...(not in this gui yet, but e.g. in the complex GUIs that you have seen above). - Add the following lines in helloworld.i:
func ywindow_init(parent_id) { window,0,wait=1,parent=parent_id; }
Go ahead now and try it: yorick -i helloworld.i
. You
should be rewarded with a display like this one:
Now we can readily link the button to display something:
- Change the py2yo call within the python script "on_helloworld_clicked" to, for instance 'pli_random':
- In helloworld.i, add a the corresponding function:
func pli_random(void) { fma; pli,random([2,128,128]); }
Start it, and click on the helloworld button at will.
You should have gotten the drill by now. You can add all kinds of widgets, and connect them to yorick functions to do all kinds of things.
Tips and tricks
- You can get pyk.i in yorick-yutils through pkg_mngr, or here, or through a debian package manager (soon to come: rpms).
- Don't forget to make the python file executable
- Don't forget to make the main window visible in glade