© W. David Ashley and Andrew Krause 2019
W. David Ashley and Andrew KrauseFoundations of PyGTK Developmenthttps://doi.org/10.1007/978-1-4842-4179-0_2

2. The Application and ApplicationWindow Classes

W. David Ashley1  and Andrew Krause2
(1)
AUSTIN, TX, USA
(2)
Leesburg, VA, USA
 

A new set of classes were introduced in GTK+ 3.x: Gtk.Application and Gtk.ApplicationWindow. These classes are designed to be the base instances for your GUI application. They wrap the application and the main window behavior of your application. They have many built-in features and provide containers for the functions in your application. The Gtk.Application and Gtk.ApplicationWindow classes are described in detail in this chapter because they are the basis for all the example programs in this book.

The Gtk.Application Class

Gtk.Application is the base class of a GTK application. Its primary purpose is to separate your program from Python __main__ function, which is a Python implementation detail. The philosophy of Gtk.Application is that applications are interested in being told what needs to happen and when it needs to happen in response to actions from the user. The exact mechanism by which Python starts applications is uninteresting.

Gtk.Application exposes a set of signals (or virtual methods) that an application should respond to.
  • startup: Sets up the application when it first starts. The virtual method name for this signal is do_startup.

  • shutdown: Performs shutdown tasks. The virtual method name for this signal is do_shutdown.

  • activate: Shows the default first window of the application (like a new document). The virtual method name for this signal is do_activate.

  • open: Opens files and shows them in a new window. This corresponds to someone trying to open a document (or documents) using the application from the file browser, or similar. The virtual method name for this signal is do_open.

When your application starts, the startup signal is fired. This gives you a chance to perform initialization tasks that are not directly related to showing a new window. After this, depending on how the application is started, either activate or open signal is called next.

The signal name and the receiving method name should not be the same. The receiving method name should have an on_ prefix. For instance, a signal named paste should have a connect call that looks something like the following.
action = Gio.SimpleAction.new("paste", None)
action.connect("activate", self.on_paste)
self.add_action(action)

Note that you have to specify the new signal name and the corresponding method name. By convention in GTK+ 3.x, signals that are built into an existing class have a do_ prefix for their corresponding method names. Callbacks should have method names with an on_ prefix. Adding a prefix to the method name prevents inadvertently overriding method names that are not a part of the signal mechanism.

Gtk.Application defaults to applications being single-instance. If the user attempts to start a second instance of a single-instance application, then Gtk.Application signals the first instance, and you receive additional activate or open signals. In this case, the second instance exits immediately without calling startup or shutdown signals.

For this reason, you should do essentially no work at all from Python’s __main__ function. All startup initialization should be done in Gtk.Application do_startup. This avoids wasting work in the second-instance case where the program exits immediately.

The application continues to run as long as it needs to. This is usually as long as there are any open windows. You can also force the application to stay alive by using the hold method.

On shutdown, you receive a shutdown signal where you can do any necessary cleanup tasks (such as saving files to disk).

Gtk.Application does not implement __main__ for you; you must do so yourself. Your __main__ function should be as small as possible and do almost nothing except create your Gtk.Application and run it. The “real work” should always be done in response to the signals fired by Gtk.Application.

Primary vs. Local Instance

The primary instance of an application is the first instance that is run. A remote instance is an instance that has started but is not the primary instance. The term local instance is refers to the current process, which may or may not be the primary instance.

Gtk.Application only emits signals in the primary instance. Calls to the Gtk.Application API can be made in primary or remote instances (and are made from the vantage of being the local instance). When the local instance is the primary instance, method calls on Gtk.Application result in signals being emitted locally. When the local instance is a remote instance, method calls result in messages being sent to the primary instance and the signals are emitted there.

For example, calling the do_activate method on the primary instance emits the activate signal. Calling it on a remote instance results in a message being sent to the primary instance, and it emits the activate signal.

You rarely need to know if the local instance is primary or remote. In almost all cases, you should call the Gtk.Application method that you are interested in and have it forwarded or handled locally, as appropriate.

Actions

An application can register a set of actions that it supports in addition to the default activate and open actions. Actions are added to the application with the GActionMap interface, and invoked or queried with the GActionGroup interface.

As with the activate and open signals, calling activate_action on the primary instance activates the named action in the current process. Calling activate_action on a remote instance sends a message to the primary instance, causing the action to be activated there.

Dealing with the Command Line

Normally, Gtk.Application assumes that arguments passed on the command line are files to be opened. If no arguments are passed, then it assumes that an application is being launched to show its main window or an empty document. When files are given, you receive these files (in the form of GFile) from the open signal; otherwise, you receive an activate signal. It is recommended that new applications make use of this default handling of command-line arguments.

If you want to deal with command-line arguments in more advanced ways, there are several (complementary) mechanisms by which you can do this.

First, the handle-local-options signal is emitted, and the signal handler gets a dictionary with the parsed options. To make use of this, you need to register your options with the add_main_option_entries method. The signal handler can return a non-negative value to end the process with this exit code, or a negative value to continue with the regular handling of command-line options. A popular use of this signal is to implement a --version argument that works without communicating with a remote instance.

If handle-local-options is not flexible enough for your needs, you can override the local_command_line virtual function to entirely take over the handling of command-line arguments in the local instance. If you do so, you are responsible for registering the application and for handling a --help argument (the default local_command_line function does this for you).

It is also possible to invoke actions from handle-local-options or local_command_line in response to command-line arguments. For example, a mail client may choose to map the --compose command-line argument to an invocation of its compose action. This is done by calling activate_action from the local_command_line implementation. If the command line being processed is in the primary instance, then the compose action is invoked locally. If it is a remote instance, the action invocation is forwarded to the primary instance.

Note in particular that it is possible to use action activations instead of activate or open. It is perfectly reasonable that an application could start without an activate signal ever being emitted. activate is only supposed to be the default “started with no options” signal. Actions are meant to be used for anything else.

Some applications may wish to perform even more advanced handling of command lines, including controlling the life cycle of the remote instance and its exit status once it quits, as well as forwarding the entire contents of the command-line arguments, the environment, and forwarding stdin/stdout/ stderr. This can be accomplished using the HANDLES_COMMAND_LINE option and the command-line signal.

Example

Listing 2-1 provides a very simple example of an instance derived from the Gtk.Application class.
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                        flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE, **kwargs)
        self.window = None
        self.add_main_option("test", ord("t"), GLib.OptionFlags.NONE, GLib.OptionArg.NONE, "Command line test", None)
        def do_startup(self):
            Gtk.Application.do_startup(self)
            action = Gio.SimpleAction.new("quit", None)
            action.connect("activate", self.on_quit)
            self.add_action(action)
        def do_activate(self):
            # We only allow a single window and raise any existing ones if not self.window:
                # Windows are associated with the application
                # when the last one is closed the application shuts down self.window = AppWindow(application=self, title="Main Window")
            self.window.present()
        def do_command_line(self, command_line):
            options = command_line.get_options_dict()
            if options.contains("test"):
                # This is printed on the main instance
                print("Test argument received")
            self.activate()
            return 0
Listing 2-1

An Example of the Gtk.Application Class

This example is a very simple instance of the Gtk.Application class. This example will be enhanced throughout this book as you gain knowledge of GTK+ 3.x.

Line 23 of the example shows how to create an instance of the Gtk.ApplicationWindow class.

The next section outlines the Gtk.ApplicationWindow class.

The Gtk.ApplicationWindow Class

The Gtk.ApplicationWindow class is the main visible window for your application. Under default conditions, this is the one and only main window visible to the user, unless the application has been set to “multi-instance” (the default is “single-instance”).

Gtk.ApplicationWindow is a Gtk.Window subclass that offers extra functionality for better integration with Gtk.Application features. Notably, it can handle both the application menu as well as the menu bar (see Gtk.Application.set_app_menu() and Gtk.Application.set_menubar()).

When the Gtk.ApplicationWindow is used in coordination with the Gtk.Application class, there is a close relationship between the two classes. Both classes create new actions (signals) that may be acted upon by either class. But the Gtk.ApplicationWindow class is responsible for the full functionality of the widgets contained in the window. It should be noted that the Gtk.ApplicationWindow class also creates a connection for the delete-event that activates the do_quit method of the associated Gtk.Application class.

Actions

The Gtk.ApplicationWindow class implements the Gio.ActionGroup and Gio.ActionMap interfaces to let you add window-specific actions exported by the associated Gtk.Application with its application-wide actions. Window-specific actions are prefixed with win. Prefix and application-wide actions are prefixed with the app. prefix. Actions must be addressed with the prefixed name when referring to them from a Gio.MenuModel.

Note that widgets placed inside the Gtk.ApplicationWindow class can also activate these actions if they implement the Gtk.Actionable interface.

Locking

As with Gtk.Application, the GDK lock is acquired when processing actions arrive from other processes, and should therefore be held when activating actions locally (if GDK threads are enabled).

Example

Listing 2-2 is a very simple version of the integration between the Gtk.Application class and the Gtk.ApplicationWindow class. This example becomes the building block for all subsequent examples in this book.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import GLib, Gio, Gtk
# This would typically be its own
file MENU_XML="""
<?xml version="1.0" encoding="UTF-8"?> <interface>
    <menu id="app-menu">
        <section>
            <attribute name="label" translatable="yes">Change label</attribute> <item>
                <attribute name="action">win.change_label</attribute>
                <attribute name="target">String 1</attribute>
                <attribute name="label" translatable="yes">String 1</attribute> </item>
            <item>
                <attribute name="action">win.change_label</attribute>
                <attribute name="target">String 2</attribute>
                <attribute name="label" translatable="yes">String 2</attribute> </item>
            <item>
                <attribute name="action">win.change_label</attribute>
                <attribute name="target">String 3</attribute>
                <attribute name="label" translatable="yes">String 3</attribute> </item>
            </section>
            <section>
                <item>
                    <attribute name="action">win.maximize</attribute>
                    <attribute name="label" translatable="yes">Maximize</attribute> </item>
            </section>
        <section>
        <item>
            <attribute name="action">app.about</attribute>
            <attribute name="label" translatable="yes">_About</attribute>
        </item>
        <item>
            <attribute name="action">app.quit</attribute>
            <attribute name="label" translatable="yes">_Quit</attribute> <attribute name="accel"><Primary>q</attribute>
        </item>
        </section>
    </menu>
</interface>
"""
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # This will be in the windows group and have the "win" prefix
        max_action = Gio.SimpleAction.new_stateful("maximize", None,
                                        GLib.Variant.new_boolean(False))
        max_action.connect("change-state", self.on_maximize_toggle)
        self.add_action(max_action)
        # Keep it in sync with the actual state
        self.connect("notify::is-maximized",
                    lambda obj, pspec: max_action.set_state(
                    GLib.Variant.new_boolean(obj.props.is_maximized)))
        lbl_variant = GLib.Variant.new_string("String 1")
        lbl_action = Gio.SimpleAction.new_stateful("change_label",
                                                lbl_variant.get_type(),
                                                lbl_variant)
        lbl_action.connect("change-state", self.on_change_label_state)
        self.add_action(lbl_action)
        self.label = Gtk.Label(label=lbl_variant.get_string(),
                                margin=30)
        self.add(self.label)
    def on_change_label_state(self, action, value):
        action.set_state(value)
        self.label.set_text(value.get_string())
    def on_maximize_toggle(self, action, value):
        action.set_state(value)
        if value.get_boolean():
            self.maximize()
        else:
            self.unmaximize()
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                        flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE,
                        **kwargs)
        self.window = None
        self.add_main_option("test", ord("t"),
                            GLib.OptionFlags.NONE, GLib.OptionArg.NONE, "Command line test", None)
    def do_startup(self):
        Gtk.Application.do_startup(self)
        action = Gio.SimpleAction.new("about", None)
        action.connect("activate", self.on_about)
        self.add_action(action)
        action = Gio.SimpleAction.new("quit", None)
        action.connect("activate", self.on_quit)
        self.add_action(action)
        builder = Gtk.Builder.new_from_string(MENU_XML, -1)
        self.set_app_menu(builder.get_object("app-menu"))
    def do_activate(self):
        # We only allow a single window and raise any existing ones if not self.window:
            # Windows are associated with the application
            # When the last one is closed the application shuts down self.window = AppWindow(application=self, title="Main Window")
        self.window.present()
    def do_command_line(self, command_line):
        options = command_line.get_options_dict()
        if options.contains("test"):
            # This is printed on the main instance
            print("Test argument received")
        self.activate()
        return 0
    def on_about(self, action, param):
        about_dialog = Gtk.AboutDialog(transient_for=self.window, modal=True)
        about_dialog.present()
    def on_quit(self, action, param):
        self.quit()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 2-2

An Example of the Gtk.Application and the Gtk.ApplicationWindow Classes

This example is a full-blown program that should be run from the command line. It modifies the command-line window and adds a menu to it for controlling the application. Most of the menu options are non-functional examples but prove useful for explaining how menu actions act and which class performs the actions specified by the menu XML file.

The top three lines specify the environment for the Python program. Line 5 establishes the Python environment as version 3.x. This is required for all Python programs running GTK 3.x. The next lines establish the Python and GTK imports. It specifically imports the GTK 3.x import libraries. Make sure that you import the modules using the gi interface so that you have the latest modules, because there may be more than one set of modules installed on your system.

The next lines describe the menu XML interface. Each menu item is described by one of three XML attributes. The first is the action attribute. It names an action and the name prefix specifies which class receives the action signal. An app prefix means that Gtk.Application processes the action signal. A win prefix means that Gtk.ApplicationWindow processes the signal. The second attribute is target, which specifies the string that displays in the menu item. The third attribute is label, which specifies whether or not the target attribute string should be translated.

Normally, this XML information is stored in its own file and read at runtime, but to simplify the example, we have included it inline.

The next lines describe the Gtk.ApplicationWindow subclass AppWindow, which encapsulates the main window behavior and all the main window widgets. In this example, there are no widgets contained in the main window. It only intercepts action signals from the menu and acts on those signals.

The main thing to note about the menu signal methods is that they have the same name as specified in the menu XML but with an on_ prefix. The next lines turn two of the four window actions into automatic toggles. The next lines catch the other two signals as method calls.

The Gtk.Application subclass Application encapsulates the application behavior. It provides the application startup and command-line processing, and processes two menu XML signals. As with the methods processed by Gtk.ApplicationWindow, the Gtk.Application method names have an on_ prefix.

First, the initialization for the Gtk.Application subclass calls the superclass to initialize it and then sets up a new command-line option.

The next lines perform the activation activities for the class, and create the Gtk.ApplicationWindow subclass.

Next, two signal methods are defined in the menu XML that are destined for the Gtk.Application subclass.

At the bottom is the actual start of the Python program. The only work that should be done here is to create the class or subclass of Gtk.Application.

Summary

This chapter covered the Gtk.Application and the Gtk.ApplicationWindow classes and the integration of the two classes. We covered how these classes can improve your application and make it more object oriented. The classes can also improve the readability and maintenance of your application.

In subsequent chapters, we cover most of the other GTK+ widgets while using the classes covered in this chapter as the basis for integrating the widgets into sample programs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.135.175