= Writing Extensions for Gaupol =

<<TableOfContents(3)>>

== Introduction ==

Extensions, as the name implies, extend the features of Gaupol. Extensions are suitable for rarely used or obscure features and customizations of the user interface or behaviour.

Gaupol is written in [[http://www.python.org/|Python]] and so are its extensions as well. Python is an expressive, dynamic programming language well suitable for rapid extension development. Gaupol's user interface is implemented with the [[http://www.gtk.org/|GTK+]] toolkit using the [[http://www.pygtk.org/|PyGTK]] bindings. The reader is expected to be familiar with both Python and PyGTK and able to read documentation on both. Relevant parts of Gaupol's API will be explained; for details, consult the [[http://download.gna.org/gaupol/doc/|full API documentation]].

Extensions consist of two parts: a metadata file (with the extension `.gaupol-extension`) and Python code. Gaupol looks for both files under both the global data directory and the user's home directory, which is `~/.local/share/gaupol/extensions` on Unix or `C:\Documents and Settings\USERNAME\Application Data\Gaupol\extensions` on Windows XP or `C:\Users\USERNAME\AppData\Roaming\Gaupol\extensions` on Windows Vista and 7. The metadata file contains information about the extension and tells Gaupol where to look for the corresponding Python code. The Python code contains the extension implementation as a subclass of `gaupol.Extension`.

== The Hello World Extension ==

As customary, let us start with the simplest possible extension -- one that prints "Hello, World!" to standard output. Place the following metadata in a file named `hello.gaupol-extension` in the extension directory under a `hello` subdirectory. This is a minimal metadata file, see the ''Extension Metadata'' section for a description of all available fields.

{{{
[Gaupol Extension]
GaupolVersion=0.16
Module=hello
Name=Hello, World
Description=Print greetings to standard output
}}}

Under the same directory, place the following code in a file named `hello.py`.

{{{#!python
import gaupol

class HelloWorldExtension(gaupol.Extension):

    def setup(self, application):
        print "Hello, World!"

    def teardown(self, application):
        print "Goodbye, World!"

    def update(self, application, page):
        print "Still here!"
}}}

The "Hello, World" extension should now be visible in the preferences dialog. If you started Gaupol from the terminal, you will see when the three methods are called.

The first line in the code imports the `gaupol` package. In addition to that, you may often need the `aeidon` package, which contains the user-interface-independent code used by `gaupol`. The defined extension class, in this case `HelloWorldExtension`, must be implemented as a subclass of `gaupol.Extension`.

The `setup` method is called every time the extension is activated, either manually in the preferences dialog or upon application startup. The method can be used to set the state required by the extension to function; for example, you can add menu- and toolbar entries, read configurations from file, connect to signals, etc. For many of these tasks, you'll need the application instance given as an argument. See the ''Common Tasks'' section for details on various things you may want to do in the `setup` method.

The `teardown` method is called every time the extension is deactivated, either manually in the preferences dialog or upon application exit. The method should be used to unset the state previously set by the extension; for example, to remove menu- and toolbar entries and to disconnect signal handlers. The `teardown` method should immediately and completely reverse everything done earlier in the `setup` method.

The `update` method is called every time the application GUI is being updated (which is very often). Most of these updates are checking if an action should in the current state of the application be sensitive or insensitive, but you can do whatever other updates needed too. The page argument is the page (i.e. tab or project) currently active in the application. It should be the same as what you'd get by calling `application.get_current_page`.

== Extension Metadata ==

The extension metadata file defines information about the extension, which need to be available without importing the actual extension code. Let us go over all the metadata fields using the bookmarks extension as an example.

{{{
[Gaupol Extension]
GaupolVersion=0.16
Module=bookmarks
Requires=side-pane;
Name=Bookmarks
Description=Marking subtitles for easy navigation
Version=0.16
Authors=Osmo Salomaa <otsaloma@iki.fi>;
Copyright=Copyright (C) 2008-2010 Osmo Salomaa;
Website=http://home.gna.org/gaupol
}}}

 * '''`GaupolVersion`''' defines the version of Gaupol that the extension has been verified to function properly with. You don't need to check compatibility with each new version of Gaupol or keep bumping this number; Gaupol currently does nothing with this information. It will, however, be used in the future to refuse to load old extensions if there have been some major API changes.

 * '''`Module`''' defines the name (sans extension) of the file that the code can be found in. It is passed to the `__import__` function in Python. This means that it can actually be a package instead of a module if you have a lot of code. In the case of a package, you need to define or import the extension class in your package's `__init__` file.

 * '''`Requires`''' defines a semicolon-separated list of other extension modules that the extension depends on. In this example, the bookmarks extension adds a page to the side pane widget provided by the side-pane extension.

 * '''`Name`''' defines the name of the extension as it is shown to the user in the list of available extensions in the preferences dialog.

 * '''`Description`''' defines a short, one-line description of the extension as it is shown to the user in the list of available extensions in the preferences dialog.

 * '''`Version`''' is the version number of the extension and is shown to the user in the extension's about dialog. If developing a large extension with frequent releases, using version numbers may help the users keep track of the changes.

 * '''`Authors`''' defines a semicolon-separated list of authors' names and email addresses in the common format ''NAME <EMAIL>'', which are shown to the user in the extension's about dialog.

 * '''`Copyright`''' defines a semicolon-separated list of copyright strings, which are shown to the user in the extension's about dialog.

 * '''`Website`''' defines a URL pointing to where the extension can be downloaded from. It is shown as a clickable link in the extension's about dialog.

 * '''`Hidden`''' defines whether or not the extension should be visible in the extension list in the preferences dialog or not. The implicit default value is `False`. You can use `True` to hide the extension. This is useful if the extension by itself does nothing, but is required by other extensions that actually do something. For example, the side pane extension is hidden, because nobody needs a blank side pane. The bookmarks extension uses the side pane and when it is activated, the side pane extension is automatically activated as well.

== Important Gaupol API ==

The `setup`, `teardown` and `update` methods give you access to the application instance, which contains the user interface common to all projects. The `get_current_page` method of application returns the page instance corresponding to the currently active tab in the user interface. Page gives you access to the data (project) and a subclass of gtk.!TreeView to display that data (view). The actual subtitles are accessible via a project instance variable, but most of the time, you'll rather want to use methods in the Project class to modify the subtitles, because that way the modifications will be registered with the undo/redo-system. These are the main classes of gaupol and their relations are presented in the below UML-diagram.

{{attachment:classes.png}}

== Common Tasks ==

=== Reading and Writing Configuration Files ===

The preferred way to store configurations across sessions is to use Gaupol's ini-style JSON-processed configuration file. All options in that file are read every time Gaupol starts, so you don't need to separately read configurations, but rather provide the `gaupol.conf` module with default values for all options.

{{{#!python
    def setup(self, application):
        gaupol.conf.register_extension("hello", {"display_greetings": True})
}}}

Given the above `register_extension` call, the `display_greetings` option will be made available as a global variable under `gaupol.conf` and will be accessible as `gaupol.conf.extensions.hello.display_greetings`. You may well assign the section `gaupol.gtk.conf.extensions.hello` to `self.conf` or something for shorter names.

To assign a value to a configuration option, all you need to do is simply assign the value to the attribute (in this case `gaupol.conf.extensions.hello.display_greetings`) as the correct Python data type. Writing configurations to file (including type conversion to strings) is done automatically upon exit.

=== Showing a Preferences Dialog ===

The ''Extensions'' tab of Gaupol's preferences dialog allows the user to edit preferences of extensions as well. To enable that, all you need to do is implement the `show_preferences_dialog` method.

{{{#!python
    def show_preferences_dialog(self, parent):
        dialog = HelloPreferencesDialog(parent)
        dialog.show()
}}}

=== Showing Help ===

The ''Extensions'' tab of Gaupol's preferences dialog allows the user to view documentation of extensions. Also, if your extension implements dialogs you can add a help button to those dialogs. There is no specific help-system used in Gaupol and thus the recommended way to display documentation is to write (or generate) HTML-files and launch a web browser using `gaupol.util.show_uri` to view that documentation.

=== Adding Menu- and Toolbar Items ===

`gtk.UIManager` is used to add menu- and toolbar items. In the `setup` method, the UI manager instance is available as `application.uim`. Consult the documentation on [[http://library.gnome.org/devel/pygtk/stable/class-gtkuimanager.html|gtk.UIManager]] or see the extensions shipped with Gaupol for an example on how to use UI manager.

=== Adding a Column to the Subtitle View ===

Adding a column to the subtitle view is fairly easy if you're familiar with `gtk.TreeView`. Below is an adapted piece of code from the bookmarks extension that adds one pixbuf column to `page.view`. Apart from the usual tree view routines, there is an `identifier` attribute set on the column, which is used to update the `page.view.columns` enumeration.

{{{#!python
        renderer = gtk.CellRendererPixbuf()
        column = gtk.TreeViewColumn("Bm.", renderer)
        column.set_clickable(True)
        column.set_reorderable(True)
        label = page.view.get_header_label("Bm.")
        label.set_tooltip_text("Bookmark")
        column.set_widget(label)
        column.set_data("identifier", "bookmark")
        column.set_cell_data_func(renderer, func)
        page.view.insert_column(column, 0)
}}}

The bookmarks extension uses `set_cell_data_func` to display data in the column. `set_cell_data_func` is known to be very inefficient, it results in a very large amount of calls to the defined function. This is fine for bookmarks, where most rows are empty, and in the rest, the same preloaded icon is shown. But, if adding columns with more complicated data, you may wish to explore more efficient ways of loading that data and if such ways require any changes to gaupol, they will be welcomed.

=== Adding a Page to the Side Pane ===

To add a page to the side pane, all you need to do is add a dependency on the side-pane extension in the metadata file and in your extension's `setup` method, call `application.side_pane.add_page` with three arguments: the widget to be displayed in the side pane, the name of the page (use the module name of your extension) and the title to be shown in the side pane page selector.

{{{
[Gaupol Extension]
Requires=side-pane;
}}}

{{{#!python
application.side_pane.add_page(widget, "foo", "Foo")
}}}

=== Adding a Text View or an Entry to the Main Window ===

Gaupol contains a lot of keybindings that do not require a modifier (e.g. the Control key). If adding some text input widget in the main window, it is needed to disable an action group to avoid triggering UI manager actions when typing text. You need to keep track of where the focus is in the main window and depending on that, enable or disable the `main-unsafe` action group. Below is how it is done in the side pane extension.

{{{#!python
    def setup(self, application):
        callback = self._on_application_window_set_focus
        handler_id = application.window.connect("set-focus", callback)

    def _on_application_window_set_focus(self, window, widget):
        """Disable unsafe UI manager actions.

        Disabling unsafe UI manager actions allows the side pane to contain any
        widgets that can have input focus and can have their own keybindings,
        without being in conflict with the keybindings of UI manager actions.
        """
        if widget is None: return
        action_group = self.application.get_action_group("main-unsafe")
        in_side_pane = widget.is_ancestor(self._paned.get_child1())
        if self._has_focus or in_side_pane:
            action_group.set_sensitive(not in_side_pane)
        self._has_focus = in_side_pane
}}}

=== Adding a Text Correction Task ===

Application contains a `text-assistant-request-pages` signal, which is fired when the text correction assistant is being initialized. To add a text correction task, you need to listen to that signal and in the callback, call `assistant.add_page` or `assistant.add_pages`. Pages given as arguments to those functions must be subclasses of `gaupol.TextAssistantPage`. See the existing pages (in file `gaupol/assistants.py`) for an example on how to implement a page that provides a text correction task.
