GTK+ Application class



Hi everyone;

as part of the Project Ridley we are trying to get rid of libgnomeui.
One of the missing classes left to mark as deprecated is GnomeApp (and
its sisters: GnomeAppBar and GnomeAppHelper).

GnomeApp is a simple GtkWindow sub-class, which makes its usage too
limited for anything more than a simple, single-window application;
also, with the inclusion of GtkUIManager and the future inclusion of
GtkBuilder in GTK+, having just a boilerplate code for menus, status bar
and tool bar is far less useful than it was before.

Taking example from OS X API and from KDE API, GTK+ should have a base,
abstract class for application developers to implement; this class
should hide away the recent files operations, the desktop-wide events,
the session management and the menu/toolbar building objects - while at
the same time offering the developer a way to override the default
behaviour.  Having a base application class also allows us to offer a
base class for single instance applications.

* API Overview
==============

The base cass is GtkApplication - an abstract G_TYPE_OBJECT with a bunch
of methods to be overridden:

struct _GtkApplicationClass
{
  GObjectClass parent_class;
...

We have a document-based API using these five vfuncs:

... 
  gchar *       (*new_document)      (GtkApplication       *application);
  gboolean      (*save_document)     (GtkApplication       *application,
                                      const gchar          *document_name,
                                      const gchar          *document_uri,
                                      gboolean              save_backup,
                                      gboolean              overwrite,
                                      GError              **error);
  gboolean      (*open_document)     (GtkApplication       *application,
                                      const gchar          *document_uri,
                                      gboolean              read_only,
                                      GError              **error);
  gboolean      (*close_document)    (GtkApplication       *application,
                                      const gchar          *document_name,
                                      GError              **error);
  GSList *      (*list_documents)    (GtkApplication       *application);
...

The vfunc signatures should be self-explanatory.

Each document is addressed by a unique id, using a string; newly created
documents might have a "document-<timestamp>" id string, or a
"document-<monotonic_counter>" id string; opened documents might use the
MD5 hash of the document URI.  Documents have an unique id because there
can be multiple views of the same document and we need to keep a list of
documents as well as windows (and windows for documents) of the
GtkApplication.

Subclasses of GtkApplication *must* override the new_document,
save_document and open_document vfuncs; close_document and list_document
can offer a default implementation (close_document calls save_document
and if successful will remove the document id from the list of known
documents, and list_documents returns the list of known document ids).

The new_document, save_document and open_document vfuncs may chain up to
the parent's class; for instance, open_document default closure will
store the document's URI inside the recently used documents list using
the default settings - unless you want to override that yourself and
*not* chain up to the parent method in your subclass.

+++

Each GtkApplication should declare which MIME types it supports:

  void          (*set_mime_types)    (GtkApplication       *application,
                                      const gchar * const   mime_types[],
                                      gint                  n_mime_types);
  GSList       *(*get_mime_types)    (GtkApplication       *application);

Since the supported MIME types might change depending on the state of
the application at run-time (think of plug-ins) the application
implementation might override one or both these vfuncs; instead of using
string literals for MIME types maybe we should use something similar to
GdkTarget, to know whether the MIME type is supported when saving, when
opening or both, like this:

  typedef enum {
    GDK_MIME_CAN_OPEN,
    GDK_MIME_CAN_SAVE
  } GdkSupportedMimeAction;

  struct _GtkSupportedMime
  {
    const gchar *mime_type;
    guint id;
    GdkSupportedMimeAction action;
  };

  ...

  static const GdkSupportedMime types[] = {
    { "text/plain", 0, GDK_MIME_CAN_OPEN | GDK_MIME_CAN_SAVE },
    { "application/pdf", 0, GDK_MIME_CAN_OPEN },
  };

  static const gint n_types = G_N_ELEMENTS (types);

This might be overkill though.

+++

The menus and toolbars are handled by this vfunc:

... 
  GtkUIManager *(*create_ui_manager) (GtkApplication       *application,
                                      GError              **error);
...

GtkUIManager does not support the generation of a copy of the widgets to
allow the usage of a single GtkUIManager instance with multiple windows;
we might as well add this feature, but even then an application might
decide to use different menus/toolbars depending on the state of the
document, so it makes sense to have the ability to override the
GtkUIManager instance creation.  The default implementation tries to do
the right thing anyway: you can set the UI definition XML using the
GtkApplication API and let it create the GtkUIManager instance for you.

+++

Now we have the window-related vfuncs:

... 
  void          (*add_window)        (GtkApplication       *application,
                                      const gchar          *document_name,
                                      GtkWindow            *window);
  void          (*remove_window)     (GtkApplication       *application,
                                      const gchar          *document_name,
                                      GtkWindow            *window);
  GSList *      (*list_windows)      (GtkApplication       *application,
                                      const gchar          *document_name);
...

Each window is bound to the document id; these vfuncs can be overridden
in case you want to control some data depending on the lifetime of the
window, but the default implementation (just add/remove/get the internal
list of windows) should just work for most of the cases.

* Further Improvements
==============

- GtkApplication API can be used to access the GtkSession object, as
soon as one is implemented; also it can be used to access the desktop
abstraction API (screensaver state, network state, power state), once we
have one.

- Single instance application can be implemented using a GtkApplication
subclass called GtkUniqueApplication, which offers a wrapper around a
GdkUniqueApplication class whose implementation depends on the GTK+
target backend (Xlibs/D-Bus on x11, named pipes on win32, Un*x sockets
on quartz and direct-fb).  I've already done a Xlibs backend for the
GtkUnique stand-alone library and merging the code with GTK+ would
remove one library developers need to link to.

- Once GtkBuilder hits HEAD we could do add a vfunc for it similar to
the GtkUIManager vfunc.

* Relevant Material
===================

I started a page on the wiki[1] about the application class; there's
also the Project Ridley page[2], the LibgnomeMustDie[3] page and a bug
open about this issue[4].

I'd like to hear from developers using an "application" abstraction in
their code (like Gedit, Evince and Epiphany) what they think about this
API and what they think an application API should provide for their
needs.  For instance, this API is designed for document-based
applications; non document-based application might have other needs and
I'd like to know whether they can be abstracted into a general-purpose
class or not.

+++

[1] http://live.gnome.org/GTK%2B/ApplicationClass
[2] http://live.gnome.org/ProjectRidley
[3] http://live.gnome.org/LibgnomeMustDie
[3] http://bugzilla.gnome.org/show_bug.cgi?id=127958

Ciao,
Emmanuele.

-- 
Emmanuele Bassi,  E: ebassi gmail com
W: http://www.emmanuelebassi.net
B: http://log.emmanuelebassi.net




[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]