This site has been retired. For up to date information, see handbook.gnome.org or gitlab.gnome.org.


[Home] [TitleIndex] [WordIndex

Index

Notice: the old text in this page has been moved to /OldDiscussion

API Overview

Basic Objects

The Recently Used Resources List is handled by instances of the RecentManager class. This object watches the list and should notifies the user's for changes in the list.

Each item inside the list is represented by an instance of the RecentInfo class.

Requirements for RecentInfo

Each object has the following properties:

Open questions:

Requirements for RecentManager

Items are identified by URI. It doesn't make sense to have a URI twice in the list.

Storage Format

The recently used resources list would be stored on disk using a subclass of the XBEL format. Since recent files are bookmarks to actual resources, using a standard bookmark storage format should increase the portability and interoperability of the recently used resources architecture.

XBEL is a freedesktop.org endorsed standard, and can be parsed by any common XML parser, like GMarkup, expat and libxml2.

The draft for the storage format is available here.

Considerations

since this is a work in progress, I'll add here some consciousness-stream-like considerations; feel free to LART me if needed -- EmmanueleBassi

Other platforms API is very straightforward: they just require the URI/path of the file to add. This is due to the fact that Win32 places the recently used files API into the shell, where they have access to all sort of goodies, like getting the MIME type and the icon directly out of the filesystem API. I'll not comment on how messy this solution seems to me, for I don't want to turn this into a windows-bashing page. Cocoa, on the other hand, has the very interesting NsDocument class, which defines - you wouldn't believe - a document (I said you wouldn't believe it); using this class is possible to get the MIME type of the document, its location, and everything else.

Back to our side of the pond, now.

Gtk doesn't have the slightest idea of documents (even though it would be quite nice to have a GtkDocument abstract class, with virtual methods to save-to-uri, open-from-uri, close, whatever); also, the MIME type stuff at Gtk level is contained in the xdgmime library (see Gtk's XDG-MIME code), but xdgmime knows only how to retrieve the MIME type from local files. For non-local files, we should use Gnome VFS, but we can't depend on it.

Another problem is the metadata about the application. FedericoMenaQuintero suggested that we could use the name of the application's .desktop file (as the KDE people do). This could solve us some issues about the application's name and command line, sparing us from having to store both inside our storage format/file; but the API should be modified to reflect this, and the implementation could be tricky: we should have the desktop item parser inside Gtk just for the parameters of the Exec line.

In order to minimize API bloat, I made the RecentItem an access-only boxed type (and renamed it to RecentInfo), built when looking up an item inside the recently used resources list; this because the RecentItem object contains more data than what the user would pass to the RecentManager when adding a new item to the list - that is: every application that has registered the item, the timestamps of the recent item, the state of the resource pointed by the URI, etc.

So, I reached a cleaned up version of the new RecentManager API:

gboolean       gtk_recent_manager_add_item    (GtkRecentManager  *recent_manager,
                                               const gchar       *uri,
                                               GError           **error);
gboolean       gtk_recent_manager_add_full    (GtkRecentManager  *recent_manager,
                                               const gchar       *uri,
                                               GtkRecentData     *item_data,
                                               GError           **error);
gboolean       gtk_recent_manager_remove_item (GtkRecentManager  *recent_manager,
                                               const gchar       *uri,
                                               GError           **error);
GtkRecentInfo *gtk_recent_manager_lookup_item (GtkRecentManager  *recent_manager,
                                               const gchar       *uri,
                                               GError           **error);

RecentData is just a commodity structure holding the item's metadata needed when adding/updating an item in the list:

struct _GtkRecentData
{
  gchar *display_name;
  gchar *description;

  gchar *mime_type; /* mandatory */

  guint is_private : 1;

  gchar **groups;

  gchar *app_name; /* mandatory */
  gchar *app_exec; /* mandatory */
};

The RecentManager::add_item function will fill up the mandatory bits of the RecentData structure for you, trying to retrieve the MIME type using the xdgmime library from fd.org (already inside GTK). This works just for the "local" files (e.g. the file:// URIs); non-local files will need gnome-vfs (which you should already be using, if you're dealing with non-local files anyway).

Then we have functions to get the items inside the list:

GtkRecentInfo *gtk_recent_manager_lookup_item (GtkRecentManager  *recent_manager,
                                               const gchar       *uri,
                                               GError           **error);
GList *        gtk_recent_manager_get_items   (GtkRecentManager  *recent_manager);

Other functions would manage the sorting method and the filtering functions, but the functions above would basically be the way to access the recently used resources list.

As of July 20th 2005, the new RecentManager code is present in libegg, under the recentchooser module (here).

User Interface

Proposed Widgets

Here I will list some widgetry for viewing the recently used resources; feel free to add other widgets, but please try to come up with a reasonable use case for each widget you propose -- EmmanueleBassi

Each viewer widget should implement the RecentChooser interface, which is just a boilerplate for common operations on the recently used resources list (set/get the current URI, select/unselect a URI, get a RecentInfo object from the current URI, get the list of items inside the list, add/remove/list the filters for the list.

RecentChooserMenu, a menu to be set as a sub-menu in a GtkMenuItem. Recently used items should live in a separate sub-menu, and not as inline menu items. This widget could also be used as a drop-down menu for a GtkMenuToolButton (see Gedit).

Rationale: Inlined recently used files is a pain to implement, makes the File menu grow downwards, it must be kept compact reducing its usefulness, and makes the opening time of the File menu bound to the building of the recently used resources list (a screenshot of the recent menu widget).

List widget

RecentChooserWidget, a complex widget containing a GtkTreeView filled with the recently used items, complete with icon, some metadata about each entry; plus a combo box for filters. This widget should be embeddable inside containers. A GtkDialog widget containing it should also be available as RecentChooserDialog.

Rationale: see the side pane used in Word/Excel (from Office XP, at least); users coming from the win32 world have come to expect this, and I have to admit is pretty fast; it could be easily implemented using this widget (a screen shot of the recent chooser widget).

Platform Portability

win32

For reference, see this thread (continued) on the gtk-devel mailing list

Applications in win32 land usually register a new recently used file inside two lists. The first one is the global Recent Documents list, which is handled only by the Shell; items are only added using the SHAddToRecentDocuments() function. The second list is a per-application MRUList object, which maps a circular queue with 24 available slots; the list is stored as keys inside the Registry, into a per-application space. This two lists exist as separate entities, and an operation affecting one does not reflect on the other (just see it for yourself, by opening a new file inside an application, and then clearing the Recent Documents list).

When a Gtk application adds a new item to the recently used resources list using RecentManager::add_item, the item is stored inside the RecentManager list (and its storage file), and inside the Recent Documents special folder. The effects are basically the same (there are two separate lists), but - as a plus - Gtk developers might programmatically access to items registered by other applications (this is actually doable also by native win32 apps, but it's tricky).

Porting from EggRecent to the GtkRecentManager

Managing the list

Porting is quite easy; before, you had to create a EggRecentModel object and use it to monitor the recently used documents list:

  EggRecentModel *model = egg_recent_model_new (EGG_RECENT_MODEL_SORT_MRU);

  ...

  g_object_unref (model);

And now, you have to create a GtkRecentManager object:

  GtkRecentManager *manager = gtk_recent_manager_new ();

  ...

  g_object_unref (manager);

As you can see, the GtkRecentManager does not handle the sorting of the list, nor the filtering; those pertain the visualization of the list, and thus are handled by the GtkRecentChooser widgets. Since the GtkRecentManager::get_items function returns the recently used documents using a newly allocated GList, you can sort and filter data using the g_list_* functions.

Adding a new item to the recently used documents list is similar, but the gtk_recent_manager_add_item function will set a GError in case of error:

  GError *error = NULL;

  gtk_recent_manager_add_item (manager, document_uri, &error);
  if (error)
    {
      g_warning ("Unable to add '%s' to the list of recently used documents: %s\n",
                 document_uri,
                 error->message);
      
      g_error_free (error);
    }

  g_object_unref (manager);

The gtk_recent_manager_add_item function will try and guess some of the meta-data associated to a URI. If you know some of meta-data about the document you should set the desired fields of a GtkRecentData structure and pass it to the gtk_recent_manager_add_full function instead:

  GtkRecentData *recent_data;
  GError *error = NULL;

  recent_data = g_new0 (GtkRecentData, 1);

  /* the user visible name of the document (maybe its title); should be preferred
   * when displaying the item into the list
   */
  recent_data->display_name = document_name;

  /* the MIME type is mandatory */
  recent_data->mime_type = document_mime_type;

  /* the name of the application that is registering the document (also mandatory) */
  recent_data->app_name = APP_NAME;
  
  /* the command to open a file; the %u string will be automagically expanded
   * to the document's URI when getting the application's command line from the
   * GtkRecentInfo object with gtk_recent_info_get_application_info()
   */
  recent_data->app_exec = g_strjoin (" ", g_get_prgname (), "--open-file", "%u", NULL);

  gtk_recent_manager_add_full (manager, document_uri, recent_data, &error);
  if (error)
    {
      ...
    }
  
  g_free (recent_data->app_exec);
  g_free (recent_data);

Getting the list of items is also similar to EggRecentModel; the GtkRecentInfo data is allocated at look up time in order not to waste memory keeping it around, so you must remember to free the data inside the list and then the list itself when you are done using it:

  GList *recent_items, *l;

  recent_items = gtk_recent_manager_get_items (manager);
  for (l = recent_items; l != NULL; l = l->next)
    {
      GtkRecentInfo *recent_info = (GtkRecentInfo *) l->data;

      do_something_with_the_item (recent_info);
    }

  /* free everything and the list */
  g_list_foreach (recent_items,
                  (GFunc) gtk_recent_info_unref,
                  NULL);
  g_list_free (recent_items);

You can also look up a single item:

  GtkRecentInfo *recent_info;
  GError *error = NULL;

  recent_info = gtk_recent_manager_lookup_item (manager, document_uri, &error);
  if (error)
    {
      display_error (error);

      g_error_free (error);
    }
  else
    {
      do_something_with_the_item (recent_info);

      gtk_recent_info_unref (recent_info);
    }

The GtkRecentInfo is a reference counted boxed type, and it holds all the meta-data of a recently used document, like its display name, its description, the list of each application that has registered the document or the list of groups to which the document belong.

Displaying the list

The old EggRecentViewGtk, EggRecentViewUIManager and EggRecentViewBonobo widgets have been removed: displaying the recently used documents list is handled by any widget implementing the GtkRecentChooser interface. These widgets also handle the sorting and filtering of the list; these widgets will create their own GtkRecentManager objects by default, but you can keep an instance around and let them use it, in order to save memory:

  GtkWidget *chooser;
  gint response;

  /* create a new dialog with the recently used documents list shown using
   * a GtkTreeView widget, using a previously created GtkRecentManager
   * instance
   */
  chooser = gtk_recent_chooser_dialog_new_for_manager ("Recent Documents",
                                                       parent_window,
                                                       manager,
                                                       GTK_STOCK_OPEN, GTK_RESPONSE_OK,
                                                       GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
                                                       NULL);

  /* set the sorting order to "most recently used first" */
  gtk_recent_chooser_set_sort_type (GTK_RECENT_CHOOSER (chooser),
                                    GTK_RECENT_SORT_MRU);

  response = gtk_dialog_run (GTK_DIALOG (chooser));
  if (response == GTK_RESPONSE_OK)
    {
      gchar *uri;
      GtkRecentInfo *info;
      
      /* get the URI of the currently selected item */
      uri = gtk_recent_chooser_get_current_uri (GTK_RECENT_CHOOSER (chooser));
      do_something_with_the_uri (manager, uri);

      /* get the GtkRecentInfo object bound to the currently selected item; it
       * is operationally equivalent to getting the URI from the chooser widget
       * and then getting the RecentInfo object for that URI from the
       * RecentManager instance
       */
      info = gtk_recent_chooser_get_current_item (GTK_RECENT_CHOOSER (chooser));
      do_something_with_the_info (info);

      gtk_recent_info_unref (info);
      g_free (uri);
    }

  gtk_widget_destroy (chooser);

The GtkRecentChooser widgets might display items sorted and filtered, both with alrewady supplied or custom sorting/filtering functions. The biggest difference from the EggRecentView widgets is that the GtkRecentChooser widgets will use their own copy of the list held by a GtkRecentManager instance and will apply the sorting and filtering functions without affecting the list itself, but only the copy; this allows the creation of many viewers with a single controller, like using many GtkTreeView with a single GtkTreeModel instance.

Available sortings are:

  /* most recently used first */
  gtk_recent_chooser_set_sort_type (GTK_RECENT_CHOOSER (chooser),
                                    GTK_RECENT_SORT_MRU);

  /* most recently used last */
  gtk_recent_chooser_set_sort_type (GTK_RECENT_CHOOSER (chooser),
                                    GTK_RECENT_SORT_LRU);

  /* custom sorting function, based on the registration count (most used first) */
  static void
  sort_by_usage_count (GtkRecentInfo *a,
                       GtkRecentInfo *b,
                       gpointer       data)
  {
    gint count_a, count_b;

    count_a = count_b = 0;

    if (gtk_recent_info_has_application (a, APP_NAME))
      gtk_recent_info_get_application_info (a, APP_NAME, NULL, &count_a, NULL);

    if (gtk_recent_info_has_application (b, APP_NAME))
      gtk_recent_info_get_application_info (b, APP_NAME, NULL, &count_b, NULL);

    return count_a < count_b;
  }

  ...
    
  /* set custom sorting and set the custom sorting function */
  gtk_recent_chooser_set_sort_type (GTK_RECENT_CHOOSER (chooser),
                                    GTK_RECENT_SORT_CUSTOM);
  gtk_recent_chooser_set_sort_func (GTK_RECENT_CHOOSER,
                                    sort_by_usage_count,
                                    NULL, /* sort function data */
                                    NULL  /* destroy notify for the data */);

Filtering is done using the GtkRecentFilter object, similar to the GtkFileFilter object used by the GtkFileChooser widgets. The RecentFilter object has a set of pre-defined options based on the meta-data exposed by the GtkRecentInfo object. It also allows custom filtering function:

  GtkRecentFilter *filter;

  filter = gtk_recent_filter_new ();
  
  /* set the user visible name of the filter */
  gtk_recent_filter_set_name (filter, "Since Last Month");

  /* set the maximum age of a recently used document */
  gtk_recent_filter_set_age (filter, 31);

  /* the chooser takes the ownership of the object */
  gtk_recent_chooser_add_filter (GTK_RECENT_CHOOSER (chooser), filter);

  /* set the currently used filter */
  gtk_recent_chooser_set_filter (GTK_RECENT_CHOOSER (chooser), filter);

  filter = gtk_recent_filter_new ();
  gtk_recent_filter_set_name (filter, "Every text file");
  gtk_recent_filter_set_mime_type (filter, "text/plain");
  
  gtk_recent_chooser_add_filter (GTK_RECENT_CHOOSER (chooser), filter);

The GtkRecentChooserWidget and GtkRecentChooserDialog widgets allow multiple filters and the selection of an appropriate one; the GtkRecentChooserMenu widget allows just a single filter object.

To-do list for this page

Documentation:

User interface mock-ups:

Goodies:

References

Freedesktop Recent File Storage Specification FIXME: how to create interwiki link?

EggRecent code

RecentChooser code

GtkFileChooser implementation of bookmarks

Draft for the bookmark storage spec

XML Bookmark Exchange Language (XBEL)

Win32 Shell Functions for adding an item to the MRU list

Cocoa NSDocumentController class The NSDocument class automagically handles the recently used documents using this class.

DesktopPlaces - standard locations accessible throughout the desktop, plus bookmarks.


2024-10-23 10:59