Boogie Woogie Bugle Boy

Lately there has been some activity in projects moving from EggRecent to GtkRecent. Obviously, this led to questions and bugs opened about the API.

One of the questions is: if I had a EggRecentModel singleton what should I use now?. Since EggRecent needed a EggRecentModel around for the entire lifetime of the recently used files list, you had to keep at least a singleton around; you also had to pass it to the objects (not widgets) displaying the list, because each istance of the objects would modify the list itself. GtkRecentManager does not need that, since the widgets usually use their own internal instance of the manager and they don’t change the list in any way. So you can create a new GtkRecentManager and keep it around as a singleton (remember to call g_object_unref() on it when finished, otherwise you’ll leak it):

GtkRecentManager *manager_singleton = gtk_recent_manager_new ();

and then pass it to the widgets:

GtkWidget *dialog =
  gtk_recent_chooser_dialog_new_with_manager ("Recent Documents"
                                              parent,
                                              manager_singleton,
                                              GTK_STOCK_CLOSE,  GTK_RESPONSE_CLOSE,
                                              GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                              NULL);

Nevertheless, you should really use gtk_recent_manager_get_default() which will do the right thing, and pass you a per-process instance and you won’t have to care about its memory management (no g_object_unref() when quitting). Also, using gtk_recent_manager_get_default() means that you don’t have to use the _with_manager variant of the widgets constructor, as they will use the per-process GtkRecentManager by default. Using your own GtkRecentManager makes sense only if you want to use the “filename” constructor-only property to specify your own storage file (and you should do that only if you know what you are doing):

GtkRecentManager *manager = gtk_recent_manager_get_default ();

  ...

GtkWidget *dialog =
  gtk_recent_chooser_dialog_new ("Recent Documents"
                                 parent,
                                 GTK_STOCK_CLOSE,  GTK_RESPONSE_CLOSE,
                                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                 NULL);

Another question is the dreaded I want an inline list of recent files in the File menu; I’ll redirect you to the bug report linked for the rationales about why I think the inline list sucks, and should be removed from the UIs of GNOME applications in favour of the sub-menu. Anyhow, for those Unbelievers still using this Windows-cloned relic of the past, here’s a simple function for adding an inline list to an existing GtkUIManager instance; it’s part of a example of integration between GtkRecent and the UI manager I sent to the gtk-devel mailing list before GUADEC.

static void
add_inline_recent_items (guint         merge_id,
                         GtkUIManager *ui_manager)
{
  GtkActionGroup *action_group;
  GtkRecentManager *manager;
  GList *items, *l;
  guint i;

  action_group = gtk_action_group_new ("recent-info");
  gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);

  manager = gtk_recent_manager_get_default ();

  /* we don't care about sorting and filtering, but it can
   * be done using g_list_sort_with_data() and clamping the
   * resulting list to the desired length
   */
  gtk_recent_manager_set_limit (manager, 4);
  items = gtk_recent_manager_get_items (manager);

  /* no items: attach an insensitive place holder */
  if (!items)
    {
      GtkAction *action;

      action = g_object_new (GTK_TYPE_ACTION,
      			     "name", "recent-info-empty",
			     "label", "No recently used files",
			     "sensitive", FALSE,
			     NULL);
      gtk_action_group_add_action (action_group, action);
      g_object_unref (action);

      gtk_ui_manager_add_ui (ui_manager, merge_id,
  			     "/MenuBar/FileMenu/RecentFiles",
			     "recent-info-empty",
			     "recent-info-empty",
			     GTK_UI_MANAGER_MENUITEM,
			     FALSE);

      return;
    }

  for (i = 0, l = items;
       l != NULL;
       i += 1, l = l->next)
    {
      GtkRecentInfo *info = l->data;
      gchar *name = g_strdup_printf ("recent-info-%d-%lu",
      				     i,
				     (gulong) time (NULL));
      gchar *action_name = g_strdup (name);
      GtkAction *action;

      action = g_object_new (GTK_TYPE_ACTION,
      			     "name", action_name,
			     "label", gtk_recent_info_get_display_name (info),
			     "stock_id", NULL,
			     NULL);
      g_object_set_data_full (G_OBJECT (action), "gtk-recent-info",
                              gtk_recent_info_ref (info),
			      (GDestroyNotify) gtk_recent_info_unref);
      g_signal_connect (action, "activate",
                        G_CALLBACK (recent_activate_cb), NULL);
      gtk_action_group_add_action (action_group, action);
      g_object_unref (action);

      /* all you need is a UI definition with a "placeholder" element called
       * "RecentFiles" under a "menu" element called "FileMenu".
       */
      gtk_ui_manager_add_ui (ui_manager, merge_id,
  			     "/MenuBar/FileMenu/RecentFiles",
			     name,
			     action_name,
			     GTK_UI_MANAGER_MENUITEM,
			     FALSE);

      g_print ("* adding action `%s'\n", action_name);

      g_free (action_name);
      g_free (name);
    }

  /* unref the info objects so that they will be destroyed when
   * the actions to which they are bound are destroyed with the
   * UIManager instance
   */
  g_list_foreach (items, (GFunc) gtk_recent_info_unref, NULL);
  g_list_free (items);
}

This code should give you an idea on how to implement your own version. Making the menu reloading the inline list means removing the UI definitions using the merge_id integer and calling this function again inside a callback attached to the “changed” signal of a GtkRecentManager instance, and it is left as an exercise for the reader. ;-)

GtkRecent and GtkUIManager

I’m really sorry that the UIManager integration did not happen in time for 2.10; it means that some applications will have to implement it by themselves until GTK+ 2.12 is out with a common implementation. In the meantime, continue asking me or opening bugs in Bugzilla.

Update@2006-08-02T09:51+0100: the complete code now has the “reload-when-list-change” feature. Remember: you may cut and paste this code for a basic support of recent files inside an inline menu; there are at least another couple of ways to implement the same level of support, and mostly it depends on how you handle your own documents. I’d like for GTK+ or libgnome or GLib to have a “document” class abstracting most of this and other stuff, like Cocoa on OSX has the NSDocument class.

Update@2006-08-02T16:05+0100: another update – I’ve fixed a couple of leaks (a GtkAction is a GObject and not a GtkObject) and added the sorting function for the inlined list.