Site Overlay

Creating a custom Entry widget in PyGtk for use with Glade

The GtkEntry widget is pretty dumb: Whatever input is sent to the entry field (whether by user input or by the set_text method) is treated as a String. Time to beef up the GtkEntry with an extension that we call SmartEntryFloat. This post is a sequel to my introductory post on creating custom widgets in PyGtk and integrating them into the Glade UI designer.

1. What we would like to have

Our SmartEntryFloat should be an entry field that is particularly geared towards numeric input. We would like to expect the following features:

1.1 Logical separation between actual numeric float value and String representation displayed or entered

  • Entry of a String representing a valid float is directly converted and stored into a value instance variable (of type float). Use focus_out to trigger update of instance variable.
  • Expose a get_value method that returns the content of the float instance variable value. No more need for clumsy cast operations every time we want to process a numeric content from a GtkEntry.
  • Maybe a set_value method that programmatically sets the value property of our SmartEntryFloat object and the String in the entry field.
  • Assure two way synchronization between the float number held in the value instance variable and the String displayed in the field. Programmatically changing the value variable should trigger an update in the String displayed. Changing the String representation of the entry should change the float value.

1.2 Consistency checking of user input

  • Check if entered String can be cast to a reasonable float at all – if not warn user by setting the background color of the entry field to red.
  • Add option to specify allowed numerical range of input. E.g. a smart entry field for the height of a person in meters should only contain values higher than 0 and less than 3 meters. Use 2 float instance variables min_val and max_val and 2 boolean variables min_included and max_included to define the numerical intervals of values allowed.

1.3 Formatted String representation

  • Specify decimal seperators and thousand seperators as class variables.
  • Specify number of decimal places displayed. In case the actual value has more decimal places than displayed according to the formatting, a hint to the user should be given (e.g. yellow background when focus out, on focus in, the entry should display the number with full accuracy so that the user can check the non-rounded value by tabbing into the field).
  • Consistency checks for correctly formatted strings that represent input numbers should be interpreted as valid input and internally cast to a float and stored into the value instance variable.
  • Unformatted strings representing a valid floating value should be converted into formatted strings using the specified decimal and thousand separators. The value instance variable should be updated accordingly.

1.4 Further improvements

In order to make sure that the value instance variable is updated even when the SmartEntry has not lost focus, we could also use the changed signal to set a Timer object (instance variable) in motion. While editing still occurs and new changed signals are fired. The timer is cancelled and restarted again and again. Once the entry object stops firing changed events and the timer expires, consistency checks are performed, the value instance variable is updated and formatting is done.

2. What we need

As already described in the previous post on custom widgets in pyGTK and Glade, we need:

  • a custom catalog xml file that tells Glade all necessary info about our custom widget: what’s its name, what widget group it should belong to, from what base class it derives its properties and signals that we can set in Glade, the icon that should be used for the widget in the toolbox, etc.
  • a custom module file that holds the programmatical logic of the icon: how it should respond to signals, what additional properties and methods it should feature, what override methods there should be. This is a Python class file.
  • While it is not strictly speaking a component of the custom widget itself, we need a test driver GUI application: this is a .glade file (essentially an xml file containing all GUI layout information) and a .py file containing the application code and reading the information of the .glade file with the GtkBuilder.

2.1 Custom catalog

Our custom catalog looks as follows:

Even though we don’t have added the code module yet, Glade will already feature an icon for our SmartWidgetFloat in its toolbox:

When we launch Glade from the console by typing glade, though, we can see that Glade produces some warnings related to our tool. These warnings are issued because there is no module file yet. So the next thing we come up with is our Python file that determines the logic of the class.

2.2 Module file

The module file for the AwesomeTextView custom widget in my previous demonstration lacked one essential feature: There were no overrides for signal handlers (such as how to respond to events like clicked or changed). I would not have known how to write callback functions in pyGTK if it had not been for a post on stackoverflow.com. Also helpful on how to connect the callback was another discussion, again on stackoverflow.com. Although both posts relate to the now deprecated Gtk2 world, they were still helpful in guessing my way to a comparable solution in Gtk3.

2.2.1 Handling signals emitted by our custom widgets

Whenever we want to handle a signal emitted by our SmartEntryFloat, we have to perform 3 steps:

  1. Find out the exact signal name – this is where Glade helps a lot because the Signal tab in the right pane lists all standard signals for a widget. E.g., the signal that responds to entry field losing the focus is focus-out-event.
  2. Connect the signal to a standard handler function (‘callback’) in the module code. This can normally happen in the __init__ function of the class as it is part of the usual housekeeping that happens when an instance of our SmartEntryFloat class is created. We have to issue a command like self.connect('focus-out-event', self.on_focus_out_event). Notice that the first argument is the exact signal id string we found out in 1. The second argument is a reference to the callback function that we want to be carried out once the signal is triggered. The usual naming convention is on_ followed by the signal name with dashes converted to undescores (i.e. on_focus_out_event)
  3. Write the callback function, which tells the SmartWidget what to do when the particular event connected to the function has occured.

So to make a long story short, this is what my module file looks like:

2.3 The test driver program

As a testing application we need to set up:

  1. A Glade layout which features two of our SmartEntryFloat widgets. Basically one widget would be enough to test – but in order to make sure that CSS-driven coloring of the background colors does only affect the entry object where an erroneous input took place and not all widgets, we prefer to run a test in a layout with more than one SmartEntryFloat widget.
  2. A Python code that picks up the .glade layout file and sets up our custom widget (e.g. font, decimal places displayed, numerical range allowed if any)

So here’s what the .glade file looks like:

And here’s our Python test driver code. Notice that it imports our SmartEntry module file in line 4. The previous .glade file is imported by the builder instruction in line 12.

3. Where to put which file

Apart from the custom catalog SmartWidgets.xml for Glade which resides in /usr/share/glade/catalogs/ on my I have so far placed the remaining files (module file, test driver program and .glade definition) of the GUI layout into a single development directory.

But where should the Python module file go if we want to have it in place to be imported for any other development project we are having in place on our machine? I followed the advice of this discussion on stackoverflow.com:

So my custom modules go to the directory shown in line 2 of the output above.