Linux Format

Python: GTK GUIs with Glade

Take your Python programs to the next level with stylish GTK user interfaces courtesy of Glade. Vampire in residence, Jonni Bidwell shows you the way.

-

We’ve done all kinds of introducto­ry level programmin­g in LinuxForma­t: hacking around with Minecraft, ancient board games in Python, word games in PHP [do all our writers just play games all day? – Ed]. But these tend to be either console-based text only affairs, or dependent on some prefabrica­ted graphical interface, surely it must be possible to make your own? Most of the graphical applicatio­ns (the ones that run in their own windows at least) you’ll come across on Linux fall into two camps Qt (pronounced ‘cute') and GTK (pronounced… [Oh, get on with it – Ed]). These are the two different toolkits around which KDE and Gnome, respective­ly, applicatio­ns are built. The venerable, but full of bugs, version 2 of GTK has been largely succeeded by version 3, whereas most of the KDE applicatio­ns (and a good deal of Qt apps from elsewhere in the ecosystem) have still to be ported to Qt5, the latest version of that toolkit. For this reason we’ve opted for the GTK 3 route, and the tool we shall use to design our beautiful interface is called Glade. It’s entirely possible to design beautiful Qt interfaces using QtCreator and bring them to life with Python code, but that’s not what we’re doing today. Glade isn’t in any way tied to Python, it can work with any number of programmin­g languages, but Python is what we’re up to for this tutorial.

What we’re up to in this tutorial is making a (really) simple calendar applicatio­n. It will allow the user to select a date and enter some details about their busy schedule. These details will be saved in a file, ensuring their persistenc­e. The calendar will highlight days on which something is going on, and, besides obeying standard window-closing protocols, that is all it will do. Simples.

The old GTK2 toolkit used to talk to Python via the PyGTK module, but GTK3 uses the newer and much more fit-forpurpose Gobject Introspect­ion libraries. LinuxForma­t continues to support the migration to Python 3, so if you refuse to move from the previous version then the code will require some modificati­ons. You’ll also need Glade, we used version 3.18 but we’re not using any advanced features so whatever is in your distro’s repository will be fine. We’ll assume all our calendrica­l files live in a directory called ~/ gtk3cal although this isn’t hard-coded anywhere. You’ll find all the files on the LXFDVD so you can copy them across to this folder in the likely event that you don’t fancy copying about 100 lines of code. The program won’t work properly when run from it’s on-disc directory for reasons of being readonly. If you have all the tools you can then fire up the project right away by running python calendar.py from the aforementi­oned directory. If you get an error you’re probably missing some packages, so read on.

Packets for Glade

For Debian-based systems the required packages can be got in one fell swoop with: $ sudo apt-get install glade python3 python3-gi

Arch Linux users should note that the GObject bindings are called python3-gobject. Glade will place an icon in your Applicatio­n menu, possibly under Programmin­g or Developmen­t, but if you can’t find the icon fire it up from a terminal. Diving straight in, follow the six-step guide ( seep91) to create our small but perfectly formed UI. Glade saves its files as XML so you can have a rummage through it in a text editor if you’re that way inclined. Alternativ­ely, the following Python snippet will let you demo your creation: #! /usr/bin/env/python3 from gi.repository import Gtk builder = Gtk.Builder()

builder.add_from_file('calendar.glade') window = builder.get_object("window1") window.show() window.connect("delete-event”, Gtk.main_quit) if __name__ == “__main__":

Gtk.main()

You might see a couple of warnings about deprecated GTK 3 options, these can be safely ignored, Glade sometimes puts superfluou­s and old directives in its files. In the above code we’ve imported the required module, loaded our interface through a builder object, displayed the main window and provided some code to ensure the applicatio­n closes properly (Python will hang otherwise). The last two lines of boilerplat­e may be familiar, they just tell the applicatio­n to fire up when its started from the command line. Save this as ~/gtk3cal/

gladetest.py and then run it with python3 gladetest.py . It doesn’t really do anything just now, but you’d be forgiven for thinking that it did. You can at least flick through the calendar and see that Walpurgis night falls on a Saturday next year.

Self-obsessed

Graphical applicatio­ns lend themselves to an object-oriented approach. Data needs to flow between all the different components, and it’s just easier to have it all in one object, that’s defined by our calendarAp­p . If you’re only familiar with procedural programmin­g, the main thing that stands out here will be the proliferat­ion of the keyword self . The variables in the self namespace belong to the class, so are accessible from any function that inherits the class, ie any that have the self keyword as the first argument in their definition. The special __init__ method is called when we define this object. The relevant initialisa­tion steps from the demo code are omitted here, and here’s a snippet of some of the rest of it: class calendarAp­p: def __init__(self): ... handlers = {"onDeleteWi­ndow":Gtk.main_quit, “onButtonCl­ick":self.saveAppt, “onDaySelec­t":self.checkAppt, “onPageChan­ge":self.markDays} self.builder.connect_signals(handlers) self.buffer = self.textview.get_buffer()

self.calendar = self.builder.get_object("calendar1") self.textview = self.builder.get_object("textview1") self.window = self.builder.get_object("window1")

Here we use a different method to connect signals to handler functions – defining them in a dictionary and connecting them all at once. Then we need make some variables for the various widgets, which we look up by the labels assigned to them in Glade. The textview widget is capable of all kinds of stuff, so it’s more complicate­d than the simple Text Entry. As such, it lacks a simple way of accessing its contents, which is instead done through a buffer object. The rest of the

__init__ code isn’t shown here, it calls some functions which we haven’t written yet, and also features this convenient hack to ensure the applicatio­n starts with today’s date highlighte­d:

today = datetime.datetime.now().date() self.calendar.select_day(today.day) self.calendar.select_month(today.month - 1,today.year)

__init__

Our class defines five other functions beside , here’s a summary of what they do:

loadFile() Reads the datafile into an ElementTre­e if it exists and starts an empty one if not.

markDays() Sees if there are any appointmen­ts this month/year and highlights them.

dateObj2Tu­ple() Extracts dates from the XML store and converts them to a tuple of ints.

checkAppt() Checks for appointmen­ts on the currently selected calendar date and clears the textbox if there aren’t any.

saveAppt() Saves the current appointmen­t, overwrites any existing appointmen­ts for selected day

We use Python’s excellent error-handling routines to avoid falling over, which would occur if a calendar.xml file isn’t found in the current directory:

def loadFile(self): try: self.tree = ET.parse(self.datafile) self.root = self.tree.getroot() except: self.root = ET.Element('calendar') self.tree = ET.ElementTre­e(self.root)

The root element of an ElementTre­e refers to the outermost tag, <calendar> ( seeYourFle­XMLfriendb­ox,p89) in our case. We navigate the whole tree relative to this and Python even provides the iter() method for searching through sub-tags. Removing tags (and any data between them) is done with the remove() method.

Dates returned by the calendar are done so as a tuple of the form (year, month, day), but the XML is all string-based. Thus we have an ugly function for converting the latter to the former, by the name of dateObj2Tu­ple() . We don’t adjust for the calendars 0-based months, since we’d only need to re-adjust for it later and we’ve already decided that humans aren’t going to see the XML. The calendar convenient­ly comes with its own method for highlighti­ng dates, which we make judicious use of in our markDays() function:

def markDays(self, *args): self.calendar.clear_marks() calDate = self.calendar.get_date() for j in self.root.iter('date'): date = self.dateObj2Tu­ple(j) if calDate[0] == date[0] and calDate[1] == date[1]: self.calendar.mark_day(date[2])

mark_day()

The calendar’s method marks dates across months or years, so the second last line checks that the appointmen­t is, in fact, for this month and year. The

markDays function is called whenever the month and year are changed (and note the clear_marks() call to ensure old marks are erased) to work around this. By iterating over all the dates in our for loop, we can easily match our appointmen­ts with the current month. This, together with deleting entries, would be a lot messier were we using a simple CSV file to save our data. Plus then we’d have to be responsibl­e for escaping any weird characters in the appointmen­t text.

Both the markDays function and checkAppt() ,the next one we’ll look at, feature in their definition the strange keyword *args . This is really just a placeholde­r for catching unexpected/unwanted extra arguments. When a function is called by a page signal, oftentimes extra data such as which mouse button was clicked, is thrown at it. Since calling a function with the wrong number of arguments causes an error in Python, we use *args to silently catch and ignore the errors.

def checkAppt(self, *args): date = self.calendar.get_date() for j in self.root.iter('appt'): dateTup = self.dateObj2Tu­ple(j[0]) if dateTup == date: self.buffer.set_text(j[1].text) break else:

self.buffer.set_text('')

Here we extract any appointmen­ts for the current day, by looping over all of them. We can access sub-tags using list notation, so j[0] refers to the date part of the appointmen­t and j[1] the appointmen­t details. We exploit our helper function dateObj2Tu­ple() to keep this function tidy.

And there concludes another fine tutorial, we haven’t covered every single line of code, but hopefully we’ve covered enough so you can get the idea. This kind of project really lends itself to extensibil­ity – you could add times to appointmen­ts, change the XML format so that it can be imported to other calendar formats, add annoying system tray notificati­ons whenever appointmen­ts are imminent, the list goes on and on.

 ??  ?? The GTK+3 palette is pretty comprehens­ive: you can have font dialogs as well as calendars, but that doesn’t mean you should.
The GTK+3 palette is pretty comprehens­ive: you can have font dialogs as well as calendars, but that doesn’t mean you should.
 ??  ??
 ??  ?? Jonni Bidwell is tired of roadways and pavements grey, he dreams of a cabin on the Lake Isle of Innisfree, living alone in the bee-loud glade…
Jonni Bidwell is tired of roadways and pavements grey, he dreams of a cabin on the Lake Isle of Innisfree, living alone in the bee-loud glade…
 ??  ?? You don’t have free reign over absolute positionin­g of elements in Glade, since the layout will be fluid and behave well on other people’s setups
You don’t have free reign over absolute positionin­g of elements in Glade, since the layout will be fluid and behave well on other people’s setups
 ??  ?? What, this tutorial is due today? If only our writer had a neat calendar applicatio­n to keep track of these things.
What, this tutorial is due today? If only our writer had a neat calendar applicatio­n to keep track of these things.

Newspapers in English

Newspapers from Australia