This chapter introduces some simple GTK+ applications and a few GTK+ widgets. We cover topics that are utilized in the upcoming chapters and example applications.
The basic function and method calls used by all GTK+ Python applications
The object-oriented nature of the GTK+ widget system
The role that signals, callbacks, and events play in your application
How to alter textual styles with the Pango Text Markup Language
Some useful functions and methods for widgets
How to make a clickable label
How to get and set properties (attributes) using the widget methods
It is important that you grasp the concepts presented so that you have a proper foundation.
Hello World
Practically every programming language book in the world starts with a Hello World example. While this book is no different, the example it uses is more complicated than most other language examples. This is because we base our example around the Gtk.Application and Gtk.ApplicationWindow classes. This makes the example program somewhat longer, and, at first glance, overblown for such a simple GTK+ window. But it also allows good explanations for how GTK+ works and how the Python bindings wrap the APIs into a very good object-oriented system.
HelloWorld.py
If you have previous experience with GTK+, you may notice some GTK+ 2.x common elements are missing. We explicitly make this a Python 3 program at line 1. This is necessary because the GTK+ 3.x modules are only available in Python version 3.x. This declaration allows lines 4–6 to properly establish the GTK+ 3.x environment.
Lines 8–11 support the visible GTK+ window. The only activity we need to support for this application is calling the super class to initialize it. But there seems to be some missing activities! All of those missing elements are either contained in the Gtk.ApplicationWindow superclass or they are supported in the Gtk.Application class. One of the default supporting actions connects the delete-event to a default method to quit the application.
Lines 13–23 support the application logic. One of the four default methods for the Gtk.Application class are defined in our subclass. The do_activate method performs the activation activities needed.
do_activate is called when the application is activated (after startup). In this case, two functions are needed. First, we check to see if this is the initial call to the method, and if it is, we create the Application GTK+ window instance. Second, we activate and show (present) the main application window.
Lines 25–27 are the only Python statements needed to start our application. No other statements are necessary, and in fact, none should be added. All the application work should take place in the Gtk.Application class or the Gtk.ApplicationWindow class or their subclasses. This prevents any unnecessary work taking place for a “single instance” application that has attempted to start up another application instance.
GTK+ Widget Hierarchy
The GTK+ application programming interface is actually a C language API. However, it is organized in such a way that an object-oriented language like Python can wrap the C API so that the entire set of APIs are turned into a set of classes organized in a hierarchy.
The transition from GTK+ 2.x to 3.x made changes that have helped other languages create object-oriented bindings that are easier to maintain and easier to implement. For instance, while Python 2.x supported abstract classes, they were buried in the collection classes and were hard to implement in your own code. Python 3.3 supplies the collections.abc module, which makes it easy for you to subclass classes in the module to create your own abstract classes. Also, the GTK+ 3.x API drastically reduces the number of abstract classes. In the future, all of them will probably be eliminated.
The GTK+ 3.x object hierarchy is documented by the PyGObject API Reference ( http://lazka.github.io / pgi-docs/#Gtk-3.0) document. This is the Python GTK+ 3.x reference document. It covers everything you need to know about the Python object bindings to GTK+, including the object hierarchy, supported classes, interfaces, functions, methods, and properties. While the document is mostly comprehensive, it lacks information concerning some new classes. We hope that this book provides that information, as well excellent examples on how to use all the widgets and classes.
While it is important to have an understanding of the GTK+ hierarchy, it is still possible to create good GUI applications with only a superficial understanding. But the more you understand the hierarchy, the better control you have over your application.
Extending HelloWorld.py
HelloWorld with Label
We now have an application that displays data, and thus is a little more useful. Let’s take a look at the changes we made to the program to achieve this result.
Lines 12–15 are where most of the changes have been made. On line 12, we create Gtk.Label with text “Hello World!” contained within it. On line 13, we make that text selectable. This allows the user to select the text and copy it to the clipboard. On line 14, we add the label to the Gtk.ApplicationWindow default container. All main windows in GTK+ derive from Gtk.Container, so it is possible to add widgets to that container. Line 15 resizes Gtk.ApplicationWindow.
Line 27 shows all the widgets contained by Gtk.ApplicationWindow. We need this method call because the present method does not perform that function. It only shows the main window.
These are the only changes made to Listing 3-1. As you can see, it does not take a lot of effort to add new functionality to a Python GTK+ application.
The GTK.Label Widget
This call creates a new label with the specified text included. The text may include Python escape sequences (such as " "), which GTK+ uses to format your text on the screen.
set_selectable: This method turns on/off the text’s selectability. The default is off. This is very useful for things like error messages, where the user may wish to copy the text to the clipboard.
set_text: This method replaces the current label text with the specified new text.
set_text_with_mnemonic: This method replaces the current label text with the specified new text. The new text may or may not have a mnemonic contained within it. If characters in the text are preceded by an underscore, they are underlined, which indicates that they represent a keyboard accelerator called a mnemonic. The mnemonic key can be used to activate another widget, chosen automatically, or explicitly using Gtk.Label.set_mnemonic_widget.
get_text: This method retrieves the current label text.
Layout Containers
The Gtk.ApplicationWindow and Gtk.Window classes both indirectly derive from the Gtk.Container widget. This means that all the methods in the Gtk.Container are available to the derived windows.
By using the add method, widgets or other container types can be added to a main window. That is how GTK.Label is added to the main window. It follows when you add a widget to a container that a parent/child relationship is formed; the container becomes the parent and the label becomes a child of the container.
The parent/child relationship between widgets is very important in GTK+ for many reasons. For example, when a parent widget is destroyed, GTK+ recursively destroys all the child widgets, no matter how deeply nested they are.
Containers also have a default sizing algorithm. This can be both good and bad news. In many cases, the default sizing is just what you want; but in many cases, it is not. You can override the default sizing by resizing the main window.
Another sizing helper for the container is the set_border_width method. It allows you to create a border around the text so that when the user shrinks the window manually, the window has a minimum size determined by the size of text and the border width.
There is more information on containers and layouts in Chapter 4.
Signals and Callbacks
GTK+ is a system that relies on signals and callback methods. A signal is a notification to your application that the user has performed some action. You can tell GTK+ to run a method or function when the signal is emitted. These are called callback methods/functions.
Caution
GTK+ signals are not the same as POSIX signals! Signals in GTK+ are propagated by events from the X Window System. Each provides separate methods. These two signal types should not be used interchangeably.
After you initialize your user interface, control is given to the gtk_main() function through the Gtk.Application class instance, which sleeps until a signal is emitted. At this point, control is passed to other methods/functions.
As the programmer, you connect signals to their methods/callback functions. The callback method/function is called when the action has occurred and the signal is emitted, or when you have explicitly emitted the signal. You also have the capability of stopping signals from being emitted at all.
Note
It is possible to connect signals at any point within your applications. For example, new signals can be connected within callback methods/functions. However, you should try to initialize mission-critical callbacks before calling gtk_main() or the present() method in the Gtk.Application instance.
There are many types of signals, and just like functions, they are inherited from parent structures. Many signals are generic to all widgets, such as "hide" and "grab-focus" or specific to the widget such as the Gtk.RadioButton signal "group-changed". In any case, widgets derived from a class can use all the signals available to all of its ancestors.
Connecting the Signal
GTK+ emits the "destroy" signal when widget.destroy() is called on the widget or when False is returned from a delete_event() callback method/function. If you reference the API documentation, you see that the destroy signal belongs to the Gtk.Object class. This means that every class in GTK+ inherits the signal. You can be notified of the destruction of any GTK+ structure/instance.
When typing the signal name, the underscore and dash characters are interchangeable. They are parsed as the same character, so it does not make any difference which one you choose. I use the underscore character in all the examples in this book.
The second parameter in the connect() method is the callback method/function which is called when the signal is emitted. The format of the callback method/function depends on the function prototype requirements of each specific signal. An example callback method is shown in the next section.
The last parameter in the connect() method allows you to send extra parameters to the callback method/function. Unlike the C version of the g_signal_connect() function, the Python version of the connect() method call allows you to pass as many extra parameters as you need for the callback method/function. This is very useful because it prevents the artificial creation of a single variable container that wraps a number of variables/classes that you wish to pass to a callback/method.
The return value for connect() is the handler identifier of the signal. You can use this with GObject.signal_handler_block(), GObject.signal_handler_unblock(), and GObject.signal_handler_disconnect(). These functions stop a callback method/function from being called, re-enable the callback function, and remove the signal handler from a widget’s handler list, respectively. More information is in the API documentation.
Callback Methods/Functions
You can find an example format of a callback method/function for each signal in the API documentation, but this is the generic format. The widget parameter is the object that performed the connect() call.
There are other possible required arguments that may appear in the middle as well, although this is not always the case. For these parameters, you need to reference the documentation of the signal you are utilizing.
The last parameter of your callback method/function corresponds to the last parameter of connect(). Remember that there can be as many of these optional arguments as you need, but the number of extra parameters from the connect() call and the number of extra arguments in the callback method/ function definition must be the same.
You should also note that the first argument to the method version of the callback is the self argument required by Python in method definitions; otherwise, the function and method definitions are the same.
Events
Events are special types of signals that are emitted by the X Window System. They are initially emitted by the X Window System and then sent from the window manager to your application to be interpreted by the signal system provided by GLib. For example, the "destroy" signal is emitted on the widget, but the "delete-event" event is first recognized by the underlying Gdk.Window of the widget, and then emitted as a signal of the widget.
The first instance of an event you encountered was the "delete-event". The "delete-event" signal is emitted when the user tries to close the window. The window can be exited by clicking the Close button on the title bar, using the Close pop-up menu item in the taskbar, or by any other means provided by the window manager.
The first difference in the callback method/function is the boolean return value. If True is returned from an event callback, GTK+ assumes the event has already been handled and it does not continue. By returning False, you are telling GTK+ to continue handling the event. False is the default return value for the function, so you do not need to use the "delete-event" signal in most cases. This is only useful if you want to override the default signal handler.
By returning False from the "delete-event" callback function, widget.destroy() is automatically called on the widget. This signal automatically continues with the action, so there is no need to connect to it unless you want to override the default.
In addition, the callback function includes the Gdk.Event parameter. Gdk.Event is a union of the Gdk.EventType enumeration and all the available event structures. Let’s first look at the Gdk.EventType enumeration.
Event Types
The Gdk.EventType enumeration provides a list of available event types. These can be used to determine the type of event that has occurred, since you may not always know what has happened.
For example, if you connect the "button-press-event" signal to a widget, there are three different types of events that can cause the signal’s callback function to be run: Gdk.EventType.BUTTON_PRESS, Gdk.EventType.2BUTTON_PRESS, and Gdk.EventType.3BUTTON_PRESS. Double-clicks and triple-clicks emit the Gdk.EventType.BUTTON_PRESS as a second event as well, so being able to distinguish between different types of events is necessary.
Appendix B provides see a complete list of the events available to you. It shows the signal name that is passed to connect(), the Gdk.EventType enumeration value, and a description of the event.
In this example, if the event type is Gdk.EventType.DELETE, False is returned, and widget.destroy() is called on the widget; otherwise, True is returned, and no further action is taken.
Using Specific Event Structures
There are many useful properties in the Gdk.EventKey structure that we use throughout the book. At some point, it would be useful for you to browse some of the Gdk.Event structures in the API documentation. We cover a few of the most important structures in this book, including Gdk.EventKey and Gdk.EventButton.
The only variable that is available in all the event structures is the event type, which defines the type of event that has occurred. It is a good idea to always check the event type to avoid handling it in the wrong way.
Further GTK+ Methods
Before continuing on to further examples, I would like to draw your attention to a few functions that will come in handy in later chapters and when you create your own GTK+ applications.
Gtk.Widget Methods
The Gtk.Widget structure contains many useful functions that you can use with any widget. This section outlines a few that you need in a lot of your applications.
Generally, this is only called on top-level widgets. It is usually only used to destroy dialog windows and to implement menu items that quit the application. It is used in the next example to quit the application when a button is clicked.
By passing –1 to either parameter, you are telling GTK+ to use its natural size, or the size that the widget would normally be allocated to if you do not define a custom size. This is used if you want to specify only the height or only the width parameter. It also allows you to reset the widget to its original size.
There is no way to set a widget with a width or height of less than 1 pixel, but by passing 0 to either parameter, GTK+ makes the widget as small as possible. Again, it is not resized so small that it’s non-functional or unable to draw itself.
Because of internationalization, there is a danger in setting the size of any widget. The text may look great on your computer, but on a computer using a German translation of your application, the widget may be too small or large for the text. Themes also present issues with widget sizing, because widgets are defaulted to different sizes, depending on the theme. Therefore, it is best to allow GTK+ to choose the size of widgets and windows in most cases.
If you want to re-enable a widget and its children, you need only to call this method on the same widget. Children are affected by the sensitivity of their parents, but they only reflect the parents’ settings, instead of changing their properties.
Gtk.Window Methods
You have now seen two examples using the Gtk.Window class. You learned how to set the title of a window and add a child widget. Now, let’s explore a few more functions that allow you to further customize windows.
Caution
You should note that the ability to resize is controlled by the window manager, so this setting may not be honored in all cases!
The preceding caution brings up an important point. Much of what GTK+ does interacts with the functionality provided by the window manager. Because of this, not all of your window settings may be followed on all window managers. This is because your settings are merely hints that are either used or ignored. You should keep in mind that your requests may or may not be honored when designing applications with GTK+.
Unlike widget.set_size_request(), window.set_default_size() only sets the initial size of the window; it does not prevent the user from resizing it to a larger or smaller size. If you set a height or width parameter to 0 , the window’s height or width is set to the minimum possible size. If you pass –1 to either parameter, the window is set to its natural size.
This function defines the gravity of the widget, which is the point that layout calculations consider (0, 0). Possible values for the Gdk.Gravity enumeration include Gdk.Gravity.NORTH_WEST, Gdk.Gravity.NORTH, Gdk.Gravity.GRAVITY_NORTH_EAST, Gdk.Gravity.WEST, Gdk.Gravity.CENTER, Gdk.Gravity.EAST, Gdk.Gravity.SOUTH_WEST, Gdk.Gravity.SOUTH, Gdk.Gravity.SOUTH_EAST, and Gdk.Gravity.STATIC.
North, south, east, and west refer to the top, bottom, right, and left edges of the screen. They are used to construct multiple gravity types. Gdk.Gravity.STATIC refers to the top-left corner of the window itself, ignoring window decorations.
True is returned if the icon was successfully loaded and set.
Caution
Icons are a complex topic and have many behavioral complexities, including icon sets, scaling, and interactions with themes. See the GTK+ documentation for more information.
Process Pending Events
At times, you may want to process all pending events in an application. This is extremely useful when you are running a piece of code that takes a long time to process. This causes your application to appear frozen, because widgets are not redrawn if the CPU is taken up by another process. For example, in an integrated development environment that I created called OpenLDev, I have to update the user interface while a build command is being processed; otherwise, the window would lock up and no build output would be shown until the build was complete.
The loop calls Gtk.main_iteration(), which processes the first pending event for your application. This continues while Gtk.events_pending() returns True, which tells you whether there are events waiting to be processed.
Using this loop is an easy solution to the freezing problem, but a better solution is to use coding strategies that avoid the problem altogether. For example, you can use idle functions found in GLib to call a function only when there are no actions of greater importance to process.
Buttons
Gtk.Button is a special kind of container that can only contain a single child. However, that child can be a container itself, thus allowing a button to contain multiple widgets. The Gtk.Button class is a clickable entity. It can be connected to a defined method of the owning container or window.
HelloWorld with Button
Those of you who are experienced GTK+ 2.x developers may wonder why we did not use a stock button instead. Stock buttons have been deprecated since GTK+ 3.1 and should not be used in new code. This may come as a huge surprise because this causes a lot of work when upgrading a 2.x application to a 3.x application. But all is not as bad as it first seems. By converting to non-stock buttons, your application becomes more portable for all supported platforms.
Let's take a detailed look at the button code. There is some interesting code.
Line 12 sets the border width around the button to be created later. Lines 13–16 create the button and connect it to a method in the Gtk.Application instance. Line 13 creates a button with the mnemonic label "_Close". The underline indicates that the letter C is the mnemonic. When the user presses Alt+C, the "clicked" signal is emitted by the button.
Line 14 connects the "clicked" signal produced by the button to the on_button_clicked method in the Gtk.ApplicationWindow instance. It does this by obtaining the instance from the kwargs argument. The dictionary name application was assigned a value on line 28, and that value was fetched on line 14 to point to the correct Gtk.Application instance method.
You may be wondering why we did not connect the button signal to a method local to the Gtk.ApplicationWindow class. This is because the signal to quit the application rightly belongs to the Gtk.Application class, not the Gtk.ApplicationWindow class. This is one of those “gotchas” that can be very hard to understand and apply properly. You need to think carefully when connecting signals to methods to make sure that the correct class gets the signal. This is a roundabout method to process the "clicked" signal. The normal way is to create your own method, like on_button_clicked, in the Gtk.ApplicationWindow class and connect the signal to that method. We are only showing this example to make the point that you can send signals to either the Gtk.ApplicationWindow instance or the Gtk.Application instance.
Line 14 sets the relief style for the button. You should always use the Gtk.ReliefStyle.NORMAL style unless you have good reasons for doing otherwise.
Line 16 adds the button to the Gtk.ApplicationWindow container. This works just like adding a label, as shown in Listing 3-2.
Lines 19–20 process the "clicked" signal emitted from our button. Our only action is to destroy the Gtk.ApplicationWindow instance.
We should note that when the last Gtk.ApplicationWindow instance is destroyed, the Gtk.Application causes the application to exit.
Test Your Understanding
In this chapter, you learned about the window, button, and label widgets. It is time to put that knowledge into practice. In the following exercise, you employ your knowledge of the structure of GTK+ applications, signals, and the GObject property system.
Exercise 1: Using Events and Properties
This exercise expands on the first two examples in this chapter by creating a Gtk.ApplicationWindow class that has the ability to destroy itself. You should set your first name as the title of the window. A selectable Gtk.Label with your last name as the default text string should be added as the child of the window.
Let’s consider other properties of this window: it should not be resizable and the minimum size should be 300×100 pixels. The methods to perform these tasks were discussed in this chapter.
Next, by looking at the API documentation, connect the key-press-event signal to the window. In the "key-press-event" callback function, switch the window title and the label text. For example, the first time the callback method is called, the window title should be set to your last name and the label text to your first name.
Once you have completed exercise 1, you can find a description of the solution in Appendix D. The solution’s complete source code can be downloaded from www.gtkbook.com .
Once you have completed this exercise, you are ready to move on to the next chapter, which covers container widgets. These widgets allow your main window to contain more than just a single widget, which was the case in all the examples in this chapter.
However, before you continue, you should know about www.gtkbook.com , which can supplement Foundations of PyGTK Development. This web site is filled with downloads, links to further GTK+ information, C and Python refresher tutorials, API documentation, and more. You can use it as you go through this book to aid in your quest to learn GTK+.
Summary
The Gtk.Label class was introduced with an example program.
The Gtk.Button class was introduced with an example program.
The signals and methods to catch the signals were introduced. This concept is covered in more depth in a later chapter.
The concept of containers was introduced. This concept is covered in more depth in Chapter 4.
In Chapter 4, we cover the Gtk.Container class and the vast array of container types available.