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

  • Be able to get basic information on the recent-items (MIME type, title) without actually trying to access the URI (e.g. without doing stat() or connecting to the network). Rationale: menus get populated when you activate them, and you don't want to make this process slow.

Each object has the following properties:

  • URI - main key to access an item into the list
  • MIME type
  • Display name - derivable from URI if it is not present.
  • Description - derivable from URI if it is not present
  • Timestamp when the item was created
  • Timestamp when the item was last modified
  • Timestamp when the item was last visited
  • List of applications that have accessed this item:
    • Tuples with <app identifier, application command line to launch this>

    • Number of times this has been registered (for "most frequently accessed" queries)
    • Time of last registration
  • List of groups
    • Each group is a category, an application-specific string or a user defined string
    • Think of the groups as meta-classes of applications ("Email", "Graphics", "Text Editors", etc.)
    • Also, they could be like "tags"
  • Private flag - to say whether it is app-specific and then it should not be displayed in the desktop-wide list of recent items.

Open questions:

  • Do we need timestamps for both "last visited" and "last modified"? Isn't one of them enough?
    • Are the timestamps for the recent item itself, or the item they point to? (The time the file on disk was created vs. the time the recent item entry was created.)
      • The recent item itself; an "mtime" attribute would be redundant, since the "last modified" timestamp would be changed each time an application changes the document pointed by the URI; if we want to intercept changes on the items inside the list, we could very well change the "last modified" item ourselves. -- EmmanueleBassi

    • When an item can be modified by others as well, the distinction would enable to answer the question "Has this item changed since I last looked at it?"
  • Is "number of times accessed" a single property, or is it per-application?
  • Do we need the private flag? What uses it currently?
  • Icons. This is somewhat up to the caller, I think, as it can just call gnome_thumbnail_factory_lookup() on an item's URI. If the thumbnail is missing, it can simply use the icon corresponding to the item's MIME type. For GtkFileChooser in particular, the file chooser will certainly call gtk_file_system_render_icon() internally, which uses an abstract GtkFilePath (which is, in effect, a URI).

Requirements for RecentManager

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

  • Get list of items, filtered and sorted.
  • Clear the list.
  • Add/update an item.
  • Remove an item.
  • Get notification when the items change.
    • This should be handled by a daemon higher on the stack; a daemon watching the items inside the list might also update all the meta-data of the changed item and offer a default RecentManager instance for the applications to share. -- EmmanueleBassi

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.

  • MatthiasClasen: xdgmime can determine the mimetype from filename and/or a buffer, so you can handle the reading of non-local files in a higher layer.

    • EmmanueleBassi: using xdgmime would remove the mandatory MIME type when registering a recently used resource. What's left, then, are the application's name (which could be obtained by g_get_application_name()) and the command line needed to launch the item. Federico hinted that we could use the .desktop file instead of the name, exec tuple, but this would mean that there should be a way to bind the desktop file to an application, e.g. we would need a g_set_application_launcher() function. Update (2005-08-24): I've just submitted bug gnomebug:314428 which adds an API for setting the desktop entry file associated to an application; this should enable us to remove the EggRecentData structure, thus reducing the RecentManager's API complexity. Additional meta-data, such as the groups/tags, the private hint, the name and the description could be set using other API (e.g.: egg_recent_manager_add_full(), or directely accessing the storage file, if we want to keep the RecentManager's API straightforward.

  • NickolayShmyrev: The question is - why don't depend on GnomeVFS/Other parts of gnome platform. What is the reason of introducing dozens of hacks around gnome and reimplementation of gnome-vfs in Gtk. Even if there is reason to do so, probably some abstraction like what is done if filechooser to let it support gnome-vfs can be done also in egg_recent. But actually you should not implement this advanced funtionality in graphical library as gtk.

    • EmmanueleBassi: I began working on a separate platform library, just for Gnome; that is why I created a spec in the first place. But having yet another library in the chain is pointless, and we can't merge it inside libgnome like libgnome-desktop, since we are going to get rid of libgnome altoghether. So it was proposed to create the manager (and the UI built upon that) inside Gtk. As for the backend: it could be doable using multiple loadable runtime modules, but the filechooser works by not exposing the GtkFileSystem class; instead, we must expose the RecentManager class.

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).

  • MatthiasClasen: I think it should be possible to have inlined recently used files. When it is integrated with GtkUIManager it should not be more complicated than adding a <recent-files/> entry to the xml.

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:

  • Guidelines for apps which do file management operations: delete items from the recent-items list when a file is deleted, rename an item when a file gets moved. Include folders which the user opens in the recent-files list.
  • Document how to disable the recent file monitoring completely, for those users who want it. (Is it possible?)
    • This is set at an higher level, e.g. any application using a GtkRecentManager instance can unref it and the monitoring goes away; the panel, for instance, can use a GConf key to disable the Recent Documents menu item. -- EmmanueleBassi

User interface mock-ups:

  • Recent-files list which is always present in the desktop.
    • This can be done with an applet. I wrote a simple one using the new API: you can find it here.

  • Stuff for File menus. Windows and Gnome have this directly in the File menu; MacOS has "File/Open Recent/<submenu>"

    • The inline list can be handled by the GtkUIManager using a place-holder tag; or by the developer, using a GtkRecentManager object to get the items; if a developer chooses the sub-menu, he can use the GtkRecentChooserMenu widget and use gtk_menu_item_set_submenu. -- EmmanueleBassi

Goodies:

  • Windows has a "Recent" folder which is maintained automatically by the Explorer shell. The folder contains shortcuts to the recent-files, and it looks like any other folder. EggRecent has a module for gnome-vfs which I think does the same thing. Do we actually need this?

    • This could also be a target for a nautils extension. -- EmmanueleBassi

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.

Attic/RecentFilesAndBookmarks (last edited 2013-11-23 00:47:58 by WilliamJonMcCann)