Site Overlay

Creating custom widgets for PyGTK in Glade

Glade_Custom_Widgets

When developing GUI applications for Python, PyGTK expects the the coder to define the complete layout of the GUI with its objects and the corresponding wiring of events, signals and actions in the code. This can be quite tedious, and fortunately there is Glade which is UI designer that allows to graphically layout a window. It then produces an XML file that can be added to the Python code. One question that remains is: How can I add custom widgets that extend standard Gtk widgets to Glade? This is what I am going to demonstrate in this post.

1. Definition of “custom widget”

There are two cases that are covered by the term custom widget:

  1. A group of two or more standard widgets that are dubbed composite widgets in Glade. The typical show case for composite widgets is a GtkLabel that serves as a description for a GtkEntry next to it (there is also a YouTube video corresponding to the slides).
  2. An extension of an existing standard widget that is obtained by sub-classing and exposes and/or overrides additional properties, methods, signals – the usual stuff you would expect with sub-classing.

We will focus on the second case, i.e. a custom widget that we derive as a subclass from a standard Gtk widget. I will demonstrate this with the AwsomeTextView subclass that has been derived from the standard Gtk.TextView adapted from this askubuntu.com discussion. Although this discussion dates back 10 years, for me it’s still the most useful set of instructions I have been able to find on the net.

2. What we need to achieve the above

We have two challenges to overcome in our custom widget exercise:

  1. Organize the housekeeping inside Glade: Glade needs to know what custom widget it should take to its toolbox, from which standard Gtk widget class it is derived, what placeholder icon is used in the toolbox, what properties and methods the widget exposes, what signals it should feature and connect to potential callback functions.
  2. Import the custom widget into the code of our final application. This includes both the import of the code module belonging to our custom subclass as well as the xml information for the Gtk.Builder.

2.1 Set up Glade to feature a custom widget

In its current version 3.22, Glade needs two things for processing a custom widget: an xml file dubbed catalog file that holds information such as the name of the widget, the widget group it belongs to, the parent widget it extends, the icon that should be used for the widget in the placeholder, etc.

Secondly, we need a Python file called module file that holds the usual subclass code of the custom widget: name of the subclass, the parent class it extends from, additional properties and methods exposed, overrides, etc.

2.1.1 Catalog XML file

Older versions expect this file to be saved in some /usr/.../glade/catalogs directory or similar. In Ubuntu 20.04 LTS with the standard apt (not snap) installation of Glade, the directory would be /usr/share/glade/catalogs. More recent version of Glade allow to specify extra catalog paths that are scanned for catalog xml files.

As the catalog file is also needed for the final application, it is therefore recommendable to save directly in the application directory and add the directory path as an extra catalog path in the Glade Preferences. This way we avoid having to fiddle around with the PYTHONPATH environment variable on our system.

From other samples I found on the internet, I have composed the following catalog file awsome_text_view.xml for my AwesomeTextView widget that I want to add as a custom widget to the Glade toolbox:

A few words on the structure of catalog files:

  • The first part is the catalog header (lines 1-4). Among other attributes, this section includes the catalog name, the library name. Note that the catalog name does not have to be identical to the file name you use for storing the xml catalog file.
  • The init function (line 6) – which as far as I can observe – always seems to be glade_python_init. No idea why it must be there. For this demonstration at least, I found out that the init function line can be commented out.
  • The specification of each Glade widget class in the <glade-widget-classes> section. A lot of catalog examples I have come across omit the parent property. This is important though, as it tells Glade from which existing class the new custom widget is expected to inherit. What is also often omitted is the icon-name property: This determines what little icon is shown when the toolbox is opened in Glade by clicking the button with the 3 vertical dots next to the Display button in the object header of Glade’s center pane.
  • Recent versions of Glade also expect the catalog to include a glade-widget-group section that determines under which widget group the widgets in the catalog are shown when clicking on the button with the 3 vertical dots right of the Display button. The title settings determines under which global category the widget is listed. The glade-widget-class-ref name property has to be in line with the name property in the respective glade-widget-class section above.

Note that the catalog file is sufficient for Glade to show an icon in the toolbox, allow the user to drag the icon into the UI design window, work on properties and signals of the custom widgets (at least as far as properties and signals are concerned that are inherited from the base class of the Gtk standard widget) and to save it as a .glade file that can later be read by the Gtk.Builder of the app using the custom widget.

2.1.2 Module Python file

The module file is a typical Python class file. In our example, it just exposes another set_font() method (lines 11-12), to which we can pass a Pango.FontDescription compliant string like 'Monospace':

On my Ubuntu, Glade has a module directory under /usr/x86_64-linux-gnu/glade/modules where module files traditionally were expected to save. For the sake of simplicity, we will put the module file directly into the application development directory.

Note that prior to subclassing, we have to import Gtk (and in our special case Pango) from the gi.repository (line 1).

The __gtype_name__ assignment (line 6) is related to the type identification in Python.

2.2 The “test driver” app

Now lets test if our AwesomeTextView custom widget behaves as expected when we make use of it in an app. For sake of simplicity we will keep all files in the same development directory. What we need is a .glade file that defines what our GUI looks like and a Python application code.

2.2.1 Glade output file

The first thing we have to do is make Glade aware that there is a custom catalog with information on our AwesomeTextView widget waiting for it in our app development directory. In order to achieve this, we open Glade, pick Preferences from the main menu and enter the path to our app development directory holding the custom catalog xml file under Extra catalog paths.

Remember: The custom catalog file is the file we created in section 2.1.1. Make sure that the module file from section 2.1.2 is also located in this directory.

Saving our GUI in Glade leaves us with a .glade output file which is actually another xml file. In the next step we will have the Gtk.Builder class read all We can pass to the Gtk.Builder of our application. Although this file has a .glade extension, it is in reality an xml file and can be opened with any text editor.

Unlike out catalog file and the module file, this file is an output file by Glade and not something we have to feed into Glade – but as we need inject the trinity of catalog file, module file and Glade output file into our application code, I have presented the file here.

The test.glade file for our application looks as follows:

Note that our glade file in line 5 references our awesome_text_view catalog from before. Further note that we have connected a delete_event to an handler on_win1_delete_event for our application window that we gave the id win1. We will have to provide that handler in the Python module file.

2.2.2 Python application code

Our test.py code looks as follows:

The import instruction in line 1 references our module file from section 2.1.2. This module file must be present in our application development directory under the same name – in this case awsome_text_view.py.

3. Result

Depending on whether we put ‘Monospace‘ or ‘Serif‘ as a string into the set_font method in the application code in line 15, we obtain the following two results for our AwesomeTextView test driver application:

Demonstration of AwesomeTextView class with different font settings