By now, you have learned a great deal about GTK+ and its supporting libraries. You have enough knowledge to use the widgets provided by PyGTK to create complex applications of your own.
However, one thing that you have not yet learned is how to create your own widgets. Therefore, this chapter is dedicated to deriving new classes from existing GTK+ classes. You are guided through some examples to show you how easy this is done using PyGTK.
In this chapter, you learn how to derive new classes and widgets from GTK+ widgets. We provide several examples of how to do this and discuss some of the problems you might encounter along the way.
An Image/Label Button
Since GTK+ 3.1, all stock items have been deprecated. While I agree with this decision, I was disappointed that the Gtk.Button was not extended to include an option for a button to display both an image and text. After eliminating the use-stock property, a Gtk.Button can only display text or an image, but not both at the same time.
The workaround for this is easily implemented but is extremely repetitive, and it is not object-oriented at all. You can see an example of how the workaround is implemented in the “Using Push Buttons” section. You can easily see that this solution would be very repetitive if you have a lot of buttons to code, and you are not making good use of code reuse with this implementation.
Another point of contention is that the programmer is forced to look up the real image they want from a string. What if the new implementation did that work for you and all you needed to supply to the new widget was the lookup string? After all, you probably want to use an image from the user’s default theme, so just let the new widget do all that work.
ImageLabelButton Class Implementation
The first point to understand is that when a Gtk.Button is created, the style of the button is set when you assign either the image or label property. Once assigned, the style of the button can never be changed. That is also the case for the new ImageLabelButton.
To start our discussion, let’s take a closer look at the initialization of the widget. We allow two new properties and override one Gtk.Button existing property. The property label overrides the parent property but is used in the same way as the text for the label widget. The properties orientation and image are new. They are used, respectively, to specify the orientation of the label/image (horizontal or vertical) and the string name to look up the corresponding default theme icon.
The rest of the initialization code is straightforward. Create a Gtk.Box with either the default orientation or the one specified by the keyword argument. Next, if the image keyword is specified, look up the name in the default user theme, fetch the icon, and add the image to Gtk.Box. Next, if the label is specified, create a Gtk.Label and add that to Gtk.Box. Lastly, add the box to the button.
We changed the Gtk.ImageLabelButton class by adjusting the alignment of the image and the label text so that they remain centered together no matter how the button is sized. We used the set_halign() method and turned off the fill and expand properties used in the pack_start() method.
Note that we do not override any other methods or properties of the underlying Gtk.Button . In this case, there is no need to modify the button in any other way. ImageLabelButton behaves as a normal Gtk.Button would. Therefore, we have accomplished our mission of creating a new class of button.
Most importantly, there is some error detection code in the new class to catch invalid data types and values. It cannot be stressed enough that you provide this kind argument checking. The lack of proper error messages and proper error detection can ruin all the work you put into a new class because it does not provide enough debug information to correct even minor mistakes or problems, which will cause your class to fall into disuse.
Custom Message Dialogs
Another reason to subclass GTK+ widgets is to save work by integrating more behavior into the widget. For instance, a standard GTK+ dialog requires a lot of initialization before you ever display the dialog. You can solve a repeated amount of work by integrating a standard look-and-feel to all of your message dialogs.
A Customized Question Dialog Implementation
There are separate classes for each type of message dialog.
The dialog always contains an icon. The icon displayed is dependent on the type of dialog being displayed (message, information, error, etc.).
The dialog always displays a primary message.
The number and type of buttons displayed have a logical default that can be overridden by the user.
All dialogs default to modal.
An additional message can also be displayed in the dialog. It is enclosed in an expander that can be used any time the dialog is displayed.
There are two additional methods supplied with the class. The first method, set_message(), sets both the primary dialog message and an optional additional message. The second method, run(), shows the dialog, runs the dialog, destroys the dialog, and returns the response_id. The run() method is optional if you want a non-modal dialog displayed. Of course, you have to supply additional functionality in the run() dialog to make that happen.
It is obvious that loading the custom design into the dialog has both advantages and disadvantages. The main disadvantage is combining the design and the functionality together. The big advantage is that should you wish to change the design, there is only one place to modify it.
From this example, it should be an easy exercise for the user to create similar subclasses for error, message, information, and warning dialogs. Just remember that consistency is the key to this task.
Multithreaded Applications
Multithreaded applications are at the core of any high-end GTK+ application, which is any application that utilizes databases, network communication, client-server activities, interprocess communications, and any other process that uses long running transactions. All of these applications require either multiple processes or threads to manage the communications to and from the separate entities to supply and receive information from each other.
GTK+ is a single thread library. It is not thread safe to access its API from multiple threads. All API calls must come from the main thread of the application. This means that long-running transactions can make the user interface seem to freeze, sometimes for very long periods of time.
The key to solving this problem is to move all long-running transactions to other threads. But, this is not easy because it involves setting up threads and supplying some type of thread safe communications for two or more threads or processes to utilize.
Most books on the topic of GUIs usually ignore this problem and concentrate on the GUI itself. This is a great disservice to the reader because just about any GUI application that the reader encounters in their professional life is multithreaded, but the reader has no experience in this type of application.
This book supplies an example to give you a better idea of what a multithreaded application looks like and the basics on how to organize it. The example is not the only way to architect a multithreaded application, but it does supply all the basics for such an application. The details and methods might be different for your project, but you are following the same basic outline supplied by our example.
Multithreaded Application
Before we examine the listing in detail, let’s describe the application requirements and see how we satisfied those requirements.
Our application is a simulation of a database client and a server—all in a single multithreaded program. The main window requests data from the threaded server and waits for a response. The server waits for a request and then supplies the data back to the client. The client side of the application is a simple GTK+ application that displays the data fetched from the server. The server is a single Python function running in a thread. It waits for a request, provides the data, and then waits for the next request.
The key to all of this is that the GTK+ client does not freeze, no matter how long the server takes to provide the data back to the client. This allows the application (and all other applications) to continue processing desktop events.
Let’s start our examination of the listing right at the top—the dbsim server function, which stands for database simulator. We kept this function as simple as possible to reveal the basic functionality. The code is an endless loop that waits for a transaction to appear on a queue. q1.get() tries to read a transaction off the queue and waits to return when a transaction becomes available. dbsim does nothing with the transaction data; instead, it just builds a Python dictionary. It then puts the dictionary on a return queue with the q2.put(items). Finally, processing returns to the top of the forever loop and waits for the next transaction.
The solution shown here works fine for a single client, but breaks down when multiple clients try to access the server because there is no way to synchronize the client requests with the returned data. We would need to enhance the application to provide that level of synchronization.
If you want to experiment with longer transaction times from the server, insert a time.sleep() statement between the q1.get() and the q2.put(items) statements. This provides the proof that the client does not freeze during a long-running transaction.
Now let’s see how the client works. The client is a standard GTK+ application, except for the on_load_button_clicked() method. This method accesses the database simulator thread to obtain the information to fill out the entry fields displayed on the main window. The first task is to send the request to the database simulator. It does this by placing a request on a queue that is read by the simulator.
The while statement starts the loop by checking to see if there are pending GTK+ events to process and whether data has been placed in the target variable. If either condition is True, the tight loop is entered. Next, we process a single GTK+ event (if one is ready). Next, we try to fetch data from the server. self.q2.get(block=False) is a non-blocking request. If the queue is empty, then an exception is raised and then ignored because we need to continue the loop until the data is available.
Once the data is successfully fetched, the on_load_button_clicked() method continues by filling out the displayed entry fields with the supplied information.
The key part of this statement is the daemon=True argument, which allows the thread to watch for the main thread to finish, and when it does, it kills the server thread so that the application ends gracefully.
This application example has all the basic for communication between two threads. We have two queues for requests and returned data. We have a thread that performs all the long-running transactions needed by the client. And finally, we have a client that does not freeze while waiting for information from the server. This is the basic architecture for a multithreaded GUI application.
The Proper Way to Align Widgets
Prior to GTK+ 3.0, the proper way to align widgets was through the Gtk.Alignment class. This class was deprecated starting with GTK+ 3.0, thus seeming to eliminate an easy way to align widgets. But in truth, there are two methods in the Gtk.Widget class that can align widgets in any container: the halign() and the valign() methods.
Aligning Widgets
As you can see, aligning a widget is really simple, and the overhead is reduced because we are not invoking a new class for each aligned widget. This method of aligning widgets should be sufficient for most of your application needs.
Summary
This chapter presented three widget customization examples, which should provide enough information for you to create your own custom widgets. There are many more possibilities to increase the usability and quality of your applications.