/* GTK - The GIMP Toolkit * gtkrecentchoosermenu.c - Recently used items menu widget * Copyright (C) 2005, Emmanuele Bassi * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "config.h" #include #include "gtkrecentmanager.h" #include "gtkrecentfilter.h" #include "gtkrecentchooser.h" #include "gtkrecentchooserutils.h" #include "gtkrecentchooserprivate.h" #include "gtkrecentchoosermenu.h" #include "gtkicontheme.h" #include "gtkintl.h" #include "gtksettings.h" #include "gtkmenushell.h" #include "gtkmenuitem.h" #include "gtkseparatormenuitem.h" #include "gtkmenu.h" #include "gtkimage.h" #include "gtklabel.h" #include "gtkbox.h" #include "gtktooltip.h" #include "gtktypebuiltins.h" #include "gtkprivate.h" /** * SECTION:gtkrecentchoosermenu * @Short_description: Displays recently used files in a menu * @Title: GtkRecentChooserMenu * @See_also:#GtkRecentChooser * * #GtkRecentChooserMenu is a widget suitable for displaying recently used files * inside a menu. It can be used to set a sub-menu of a #GtkMenuItem using * gtk_menu_item_set_submenu(), or as the menu of a #GtkMenuToolButton. * * Note that #GtkRecentChooserMenu does not have any methods of its own. Instead, * you should use the functions that work on a #GtkRecentChooser. * * Note also that #GtkRecentChooserMenu does not support multiple filters, as it * has no way to let the user choose between them as the #GtkRecentChooserWidget * and #GtkRecentChooserDialog widgets do. Thus using gtk_recent_chooser_add_filter() * on a #GtkRecentChooserMenu widget will yield the same effects as using * gtk_recent_chooser_set_filter(), replacing any currently set filter * with the supplied filter; gtk_recent_chooser_remove_filter() will remove * any currently set #GtkRecentFilter object and will unset the current filter; * gtk_recent_chooser_list_filters() will return a list containing a single * #GtkRecentFilter object. * * Recently used files are supported since GTK+ 2.10. */ struct _GtkRecentChooserMenuPrivate { /* the recent manager object */ GtkRecentManager *manager; /* max size of the menu item label */ gint label_width; gint first_recent_item_pos; GtkWidget *placeholder; /* RecentChooser properties */ gint limit; guint show_private : 1; guint show_not_found : 1; guint show_tips : 1; guint show_icons : 1; guint local_only : 1; guint show_numbers : 1; GtkRecentSortType sort_type; GtkRecentSortFunc sort_func; gpointer sort_data; GDestroyNotify sort_data_destroy; GSList *filters; GtkRecentFilter *current_filter; guint local_manager : 1; gulong manager_changed_id; gulong populate_id; }; enum { PROP_0, PROP_SHOW_NUMBERS, }; #define FALLBACK_ITEM_LIMIT 10 #define DEFAULT_LABEL_WIDTH 30 static void gtk_recent_chooser_menu_finalize (GObject *object); static void gtk_recent_chooser_menu_dispose (GObject *object); static void gtk_recent_chooser_menu_constructed (GObject *object); static void gtk_recent_chooser_iface_init (GtkRecentChooserIface *iface); static void gtk_recent_chooser_menu_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void gtk_recent_chooser_menu_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static gboolean gtk_recent_chooser_menu_set_current_uri (GtkRecentChooser *chooser, const gchar *uri, GError **error); static gchar * gtk_recent_chooser_menu_get_current_uri (GtkRecentChooser *chooser); static gboolean gtk_recent_chooser_menu_select_uri (GtkRecentChooser *chooser, const gchar *uri, GError **error); static void gtk_recent_chooser_menu_unselect_uri (GtkRecentChooser *chooser, const gchar *uri); static void gtk_recent_chooser_menu_select_all (GtkRecentChooser *chooser); static void gtk_recent_chooser_menu_unselect_all (GtkRecentChooser *chooser); static GList * gtk_recent_chooser_menu_get_items (GtkRecentChooser *chooser); static GtkRecentManager *gtk_recent_chooser_menu_get_recent_manager (GtkRecentChooser *chooser); static void gtk_recent_chooser_menu_set_sort_func (GtkRecentChooser *chooser, GtkRecentSortFunc sort_func, gpointer sort_data, GDestroyNotify data_destroy); static void gtk_recent_chooser_menu_add_filter (GtkRecentChooser *chooser, GtkRecentFilter *filter); static void gtk_recent_chooser_menu_remove_filter (GtkRecentChooser *chooser, GtkRecentFilter *filter); static GSList * gtk_recent_chooser_menu_list_filters (GtkRecentChooser *chooser); static void gtk_recent_chooser_menu_set_current_filter (GtkRecentChooserMenu *menu, GtkRecentFilter *filter); static void gtk_recent_chooser_menu_populate (GtkRecentChooserMenu *menu); static void gtk_recent_chooser_menu_set_show_tips (GtkRecentChooserMenu *menu, gboolean show_tips); static void set_recent_manager (GtkRecentChooserMenu *menu, GtkRecentManager *manager); static void item_activate_cb (GtkWidget *widget, gpointer user_data); static void manager_changed_cb (GtkRecentManager *manager, gpointer user_data); G_DEFINE_TYPE_WITH_CODE (GtkRecentChooserMenu, gtk_recent_chooser_menu, GTK_TYPE_MENU, G_ADD_PRIVATE (GtkRecentChooserMenu) G_IMPLEMENT_INTERFACE (GTK_TYPE_RECENT_CHOOSER, gtk_recent_chooser_iface_init)) static void gtk_recent_chooser_iface_init (GtkRecentChooserIface *iface) { iface->set_current_uri = gtk_recent_chooser_menu_set_current_uri; iface->get_current_uri = gtk_recent_chooser_menu_get_current_uri; iface->select_uri = gtk_recent_chooser_menu_select_uri; iface->unselect_uri = gtk_recent_chooser_menu_unselect_uri; iface->select_all = gtk_recent_chooser_menu_select_all; iface->unselect_all = gtk_recent_chooser_menu_unselect_all; iface->get_items = gtk_recent_chooser_menu_get_items; iface->get_recent_manager = gtk_recent_chooser_menu_get_recent_manager; iface->set_sort_func = gtk_recent_chooser_menu_set_sort_func; iface->add_filter = gtk_recent_chooser_menu_add_filter; iface->remove_filter = gtk_recent_chooser_menu_remove_filter; iface->list_filters = gtk_recent_chooser_menu_list_filters; } static void gtk_recent_chooser_menu_class_init (GtkRecentChooserMenuClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->constructed = gtk_recent_chooser_menu_constructed; gobject_class->dispose = gtk_recent_chooser_menu_dispose; gobject_class->finalize = gtk_recent_chooser_menu_finalize; gobject_class->set_property = gtk_recent_chooser_menu_set_property; gobject_class->get_property = gtk_recent_chooser_menu_get_property; _gtk_recent_chooser_install_properties (gobject_class); /** * GtkRecentChooserMenu:show-numbers: * * Whether the first ten items in the menu should be prepended by * a number acting as a unique mnemonic. * * Since: 2.10 */ g_object_class_install_property (gobject_class, PROP_SHOW_NUMBERS, g_param_spec_boolean ("show-numbers", P_("Show Numbers"), P_("Whether the items should be displayed with a number"), FALSE, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); } static void gtk_recent_chooser_menu_init (GtkRecentChooserMenu *menu) { GtkRecentChooserMenuPrivate *priv; menu->priv = gtk_recent_chooser_menu_get_instance_private (menu); priv = menu->priv; priv->show_icons= TRUE; priv->show_numbers = FALSE; priv->show_tips = FALSE; priv->show_not_found = TRUE; priv->show_private = FALSE; priv->local_only = TRUE; priv->limit = FALLBACK_ITEM_LIMIT; priv->sort_type = GTK_RECENT_SORT_NONE; priv->label_width = DEFAULT_LABEL_WIDTH; priv->first_recent_item_pos = -1; priv->placeholder = NULL; priv->current_filter = NULL; } static void gtk_recent_chooser_menu_finalize (GObject *object) { GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (object); GtkRecentChooserMenuPrivate *priv = menu->priv; priv->manager = NULL; if (priv->sort_data_destroy) { priv->sort_data_destroy (priv->sort_data); priv->sort_data_destroy = NULL; } priv->sort_data = NULL; priv->sort_func = NULL; G_OBJECT_CLASS (gtk_recent_chooser_menu_parent_class)->finalize (object); } static void gtk_recent_chooser_menu_dispose (GObject *object) { GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (object); GtkRecentChooserMenuPrivate *priv = menu->priv; if (priv->manager_changed_id) { if (priv->manager) g_signal_handler_disconnect (priv->manager, priv->manager_changed_id); priv->manager_changed_id = 0; } if (priv->populate_id) { g_source_remove (priv->populate_id); priv->populate_id = 0; } if (priv->current_filter) { g_object_unref (priv->current_filter); priv->current_filter = NULL; } G_OBJECT_CLASS (gtk_recent_chooser_menu_parent_class)->dispose (object); } static void gtk_recent_chooser_menu_constructed (GObject *object) { GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (object); GtkRecentChooserMenuPrivate *priv = menu->priv; G_OBJECT_CLASS (gtk_recent_chooser_menu_parent_class)->constructed (object); g_assert (priv->manager); /* we create a placeholder menuitem, to be used in case * the menu is empty. this placeholder will stay around * for the entire lifetime of the menu, and we just hide it * when it's not used. we have to do this, and do it here, * because we need a marker for the beginning of the recent * items list, so that we can insert the new items at the * right place when idly populating the menu in case the * user appended or prepended custom menu items to the * recent chooser menu widget. */ priv->placeholder = gtk_menu_item_new_with_label (_("No items found")); gtk_widget_set_sensitive (priv->placeholder, FALSE); g_object_set_data (G_OBJECT (priv->placeholder), "gtk-recent-menu-placeholder", GINT_TO_POINTER (TRUE)); gtk_menu_shell_insert (GTK_MENU_SHELL (menu), priv->placeholder, 0); gtk_widget_show (priv->placeholder); /* (re)populate the menu */ gtk_recent_chooser_menu_populate (menu); } static void gtk_recent_chooser_menu_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (object); GtkRecentChooserMenuPrivate *priv = menu->priv; switch (prop_id) { case PROP_SHOW_NUMBERS: if (priv->show_numbers != g_value_get_boolean (value)) { priv->show_numbers = g_value_get_boolean (value); g_object_notify_by_pspec (object, pspec); } break; case GTK_RECENT_CHOOSER_PROP_RECENT_MANAGER: set_recent_manager (menu, g_value_get_object (value)); break; case GTK_RECENT_CHOOSER_PROP_SHOW_PRIVATE: if (priv->show_private != g_value_get_boolean (value)) { priv->show_private = g_value_get_boolean (value); g_object_notify_by_pspec (object, pspec); } break; case GTK_RECENT_CHOOSER_PROP_SHOW_NOT_FOUND: if (priv->show_not_found != g_value_get_boolean (value)) { priv->show_not_found = g_value_get_boolean (value); g_object_notify_by_pspec (object, pspec); } break; case GTK_RECENT_CHOOSER_PROP_SHOW_TIPS: gtk_recent_chooser_menu_set_show_tips (menu, g_value_get_boolean (value)); break; case GTK_RECENT_CHOOSER_PROP_SHOW_ICONS: if (priv->show_icons != g_value_get_boolean (value)) { priv->show_icons = g_value_get_boolean (value); g_object_notify_by_pspec (object, pspec); } break; case GTK_RECENT_CHOOSER_PROP_SELECT_MULTIPLE: g_warning ("%s: Choosers of type '%s' do not support selecting multiple items.", G_STRFUNC, G_OBJECT_TYPE_NAME (object)); break; case GTK_RECENT_CHOOSER_PROP_LOCAL_ONLY: if (priv->local_only != g_value_get_boolean (value)) { priv->local_only = g_value_get_boolean (value); g_object_notify_by_pspec (object, pspec); } break; case GTK_RECENT_CHOOSER_PROP_LIMIT: if (priv->limit != g_value_get_int (value)) { priv->limit = g_value_get_int (value); g_object_notify_by_pspec (object, pspec); } break; case GTK_RECENT_CHOOSER_PROP_SORT_TYPE: if (priv->sort_type != g_value_get_enum (value)) { priv->sort_type = g_value_get_enum (value); g_object_notify_by_pspec (object, pspec); } break; case GTK_RECENT_CHOOSER_PROP_FILTER: gtk_recent_chooser_menu_set_current_filter (menu, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_recent_chooser_menu_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (object); GtkRecentChooserMenuPrivate *priv = menu->priv; switch (prop_id) { case PROP_SHOW_NUMBERS: g_value_set_boolean (value, priv->show_numbers); break; case GTK_RECENT_CHOOSER_PROP_SHOW_TIPS: g_value_set_boolean (value, priv->show_tips); break; case GTK_RECENT_CHOOSER_PROP_LIMIT: g_value_set_int (value, priv->limit); break; case GTK_RECENT_CHOOSER_PROP_LOCAL_ONLY: g_value_set_boolean (value, priv->local_only); break; case GTK_RECENT_CHOOSER_PROP_SORT_TYPE: g_value_set_enum (value, priv->sort_type); break; case GTK_RECENT_CHOOSER_PROP_SHOW_PRIVATE: g_value_set_boolean (value, priv->show_private); break; case GTK_RECENT_CHOOSER_PROP_SHOW_NOT_FOUND: g_value_set_boolean (value, priv->show_not_found); break; case GTK_RECENT_CHOOSER_PROP_SHOW_ICONS: g_value_set_boolean (value, priv->show_icons); break; case GTK_RECENT_CHOOSER_PROP_SELECT_MULTIPLE: g_value_set_boolean (value, FALSE); break; case GTK_RECENT_CHOOSER_PROP_FILTER: g_value_set_object (value, priv->current_filter); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static gboolean gtk_recent_chooser_menu_set_current_uri (GtkRecentChooser *chooser, const gchar *uri, GError **error) { GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser); GList *children, *l; GtkWidget *menu_item = NULL; gboolean found = FALSE; gint i = 0; children = gtk_container_get_children (GTK_CONTAINER (menu)); for (l = children; l != NULL; l = l->next, i++) { GtkRecentInfo *info; menu_item = GTK_WIDGET (l->data); info = g_object_get_data (G_OBJECT (menu_item), "gtk-recent-info"); if (!info) continue; if (strcmp (uri, gtk_recent_info_get_uri (info)) == 0) { gtk_menu_set_active (GTK_MENU (menu), i); found = TRUE; break; } } g_list_free (children); if (!found) { g_set_error (error, GTK_RECENT_CHOOSER_ERROR, GTK_RECENT_CHOOSER_ERROR_NOT_FOUND, _("No recently used resource found with URI “%s”"), uri); } return found; } static gchar * gtk_recent_chooser_menu_get_current_uri (GtkRecentChooser *chooser) { GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser); GtkWidget *menu_item; GtkRecentInfo *info; menu_item = gtk_menu_get_active (GTK_MENU (menu)); if (!menu_item) return NULL; info = g_object_get_data (G_OBJECT (menu_item), "gtk-recent-info"); if (!info) return NULL; return g_strdup (gtk_recent_info_get_uri (info)); } static gboolean gtk_recent_chooser_menu_select_uri (GtkRecentChooser *chooser, const gchar *uri, GError **error) { GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser); GList *children, *l; GtkWidget *menu_item = NULL; gboolean found = FALSE; children = gtk_container_get_children (GTK_CONTAINER (menu)); for (l = children; l != NULL; l = l->next) { GtkRecentInfo *info; menu_item = GTK_WIDGET (l->data); info = g_object_get_data (G_OBJECT (menu_item), "gtk-recent-info"); if (!info) continue; if (0 == strcmp (uri, gtk_recent_info_get_uri (info))) found = TRUE; } g_list_free (children); if (!found) { g_set_error (error, GTK_RECENT_CHOOSER_ERROR, GTK_RECENT_CHOOSER_ERROR_NOT_FOUND, _("No recently used resource found with URI “%s”"), uri); return FALSE; } else { gtk_menu_shell_select_item (GTK_MENU_SHELL (menu), menu_item); return TRUE; } } static void gtk_recent_chooser_menu_unselect_uri (GtkRecentChooser *chooser, const gchar *uri) { GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser); gtk_menu_shell_deselect (GTK_MENU_SHELL (menu)); } static void gtk_recent_chooser_menu_select_all (GtkRecentChooser *chooser) { g_warning ("This function is not implemented for " "widgets of class '%s'", g_type_name (G_OBJECT_TYPE (chooser))); } static void gtk_recent_chooser_menu_unselect_all (GtkRecentChooser *chooser) { g_warning ("This function is not implemented for " "widgets of class '%s'", g_type_name (G_OBJECT_TYPE (chooser))); } static void gtk_recent_chooser_menu_set_sort_func (GtkRecentChooser *chooser, GtkRecentSortFunc sort_func, gpointer sort_data, GDestroyNotify data_destroy) { GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser); GtkRecentChooserMenuPrivate *priv = menu->priv; if (priv->sort_data_destroy) { priv->sort_data_destroy (priv->sort_data); priv->sort_data_destroy = NULL; } priv->sort_func = NULL; priv->sort_data = NULL; priv->sort_data_destroy = NULL; if (sort_func) { priv->sort_func = sort_func; priv->sort_data = sort_data; priv->sort_data_destroy = data_destroy; } } static GList * gtk_recent_chooser_menu_get_items (GtkRecentChooser *chooser) { GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser); GtkRecentChooserMenuPrivate *priv = menu->priv; return _gtk_recent_chooser_get_items (chooser, priv->current_filter, priv->sort_func, priv->sort_data); } static GtkRecentManager * gtk_recent_chooser_menu_get_recent_manager (GtkRecentChooser *chooser) { GtkRecentChooserMenuPrivate *priv; priv = GTK_RECENT_CHOOSER_MENU (chooser)->priv; return priv->manager; } static void gtk_recent_chooser_menu_add_filter (GtkRecentChooser *chooser, GtkRecentFilter *filter) { GtkRecentChooserMenu *menu; menu = GTK_RECENT_CHOOSER_MENU (chooser); gtk_recent_chooser_menu_set_current_filter (menu, filter); } static void gtk_recent_chooser_menu_remove_filter (GtkRecentChooser *chooser, GtkRecentFilter *filter) { GtkRecentChooserMenu *menu; menu = GTK_RECENT_CHOOSER_MENU (chooser); if (filter == menu->priv->current_filter) { g_object_unref (menu->priv->current_filter); menu->priv->current_filter = NULL; g_object_notify (G_OBJECT (menu), "filter"); } } static GSList * gtk_recent_chooser_menu_list_filters (GtkRecentChooser *chooser) { GtkRecentChooserMenu *menu; GSList *retval = NULL; menu = GTK_RECENT_CHOOSER_MENU (chooser); if (menu->priv->current_filter) retval = g_slist_prepend (retval, menu->priv->current_filter); return retval; } static void gtk_recent_chooser_menu_set_current_filter (GtkRecentChooserMenu *menu, GtkRecentFilter *filter) { GtkRecentChooserMenuPrivate *priv; priv = menu->priv; if (priv->current_filter) g_object_unref (G_OBJECT (priv->current_filter)); priv->current_filter = filter; if (priv->current_filter) g_object_ref_sink (priv->current_filter); gtk_recent_chooser_menu_populate (menu); g_object_notify (G_OBJECT (menu), "filter"); } /* taken from libeel/eel-strings.c */ static gchar * escape_underscores (const gchar *string) { gint underscores; const gchar *p; gchar *q; gchar *escaped; if (!string) return NULL; underscores = 0; for (p = string; *p != '\0'; p++) underscores += (*p == '_'); if (underscores == 0) return g_strdup (string); escaped = g_new (char, strlen (string) + underscores + 1); for (p = string, q = escaped; *p != '\0'; p++, q++) { /* Add an extra underscore. */ if (*p == '_') *q++ = '_'; *q = *p; } *q = '\0'; return escaped; } static void gtk_recent_chooser_menu_add_tip (GtkRecentChooserMenu *menu, GtkRecentInfo *info, GtkWidget *item) { GtkRecentChooserMenuPrivate *priv; gchar *path; g_assert (info != NULL); g_assert (item != NULL); priv = menu->priv; path = gtk_recent_info_get_uri_display (info); if (path) { gchar *tip_text = g_strdup_printf (_("Open “%s”"), path); gtk_widget_set_tooltip_text (item, tip_text); gtk_widget_set_has_tooltip (item, priv->show_tips); g_free (path); g_free (tip_text); } } static GtkWidget * gtk_recent_chooser_menu_create_item (GtkRecentChooserMenu *menu, GtkRecentInfo *info, gint count) { GtkRecentChooserMenuPrivate *priv; gchar *text; GtkWidget *item, *image, *label; GIcon *icon; GtkWidget *box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); g_assert (info != NULL); priv = menu->priv; if (priv->show_numbers) { gchar *name, *escaped; name = g_strdup (gtk_recent_info_get_display_name (info)); if (!name) name = g_strdup (_("Unknown item")); escaped = escape_underscores (name); /* avoid clashing mnemonics */ if (count <= 10) /* This is the label format that is used for the first 10 items * in a recent files menu. The %d is the number of the item, * the %s is the name of the item. Please keep the _ in front * of the number to give these menu items a mnemonic. */ text = g_strdup_printf (C_("recent menu label", "_%d. %s"), count, escaped); else /* This is the format that is used for items in a recent files menu. * The %d is the number of the item, the %s is the name of the item. */ text = g_strdup_printf (C_("recent menu label", "%d. %s"), count, escaped); label = gtk_label_new_with_mnemonic (text); g_free (escaped); g_free (name); } else { text = g_strdup (gtk_recent_info_get_display_name (info)); label = gtk_label_new (text); } g_free (text); /* ellipsize the menu item label, in case the recent document * display name is huge. */ gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); gtk_label_set_max_width_chars (GTK_LABEL (label), priv->label_width); if (priv->show_icons) { icon = gtk_recent_info_get_gicon (info); image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU); if (icon) g_object_unref (icon); gtk_container_add (GTK_CONTAINER (box), image); } item = gtk_menu_item_new (); gtk_container_add (GTK_CONTAINER (box), label); gtk_container_add (GTK_CONTAINER (item), box); g_signal_connect (item, "activate", G_CALLBACK (item_activate_cb), menu); return item; } static void gtk_recent_chooser_menu_insert_item (GtkRecentChooserMenu *menu, GtkWidget *menuitem, gint position) { GtkRecentChooserMenuPrivate *priv = menu->priv; gint real_position; if (priv->first_recent_item_pos == -1) { GList *children, *l; children = gtk_container_get_children (GTK_CONTAINER (menu)); for (real_position = 0, l = children; l != NULL; real_position += 1, l = l->next) { GObject *child = l->data; gboolean is_placeholder = FALSE; is_placeholder = GPOINTER_TO_INT (g_object_get_data (child, "gtk-recent-menu-placeholder")); if (is_placeholder) break; } g_list_free (children); priv->first_recent_item_pos = real_position; } else real_position = priv->first_recent_item_pos; gtk_menu_shell_insert (GTK_MENU_SHELL (menu), menuitem, real_position + position); gtk_widget_show (menuitem); } /* removes the items we own from the menu */ static void gtk_recent_chooser_menu_dispose_items (GtkRecentChooserMenu *menu) { GList *children, *l; children = gtk_container_get_children (GTK_CONTAINER (menu)); for (l = children; l != NULL; l = l->next) { GtkWidget *menu_item = GTK_WIDGET (l->data); gboolean has_mark = FALSE; /* check for our mark, in order to remove just the items we own */ has_mark = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), "gtk-recent-menu-mark")); if (has_mark) { GtkRecentInfo *info; /* destroy the attached RecentInfo struct, if found */ info = g_object_get_data (G_OBJECT (menu_item), "gtk-recent-info"); if (info) g_object_set_data_full (G_OBJECT (menu_item), "gtk-recent-info", NULL, NULL); /* and finally remove the item from the menu */ gtk_container_remove (GTK_CONTAINER (menu), menu_item); } } /* recalculate the position of the first recent item */ menu->priv->first_recent_item_pos = -1; g_list_free (children); } typedef struct { GList *items; gint n_items; gint loaded_items; gint displayed_items; GtkRecentChooserMenu *menu; GtkWidget *placeholder; } MenuPopulateData; static MenuPopulateData * create_menu_populate_data (GtkRecentChooserMenu *menu) { MenuPopulateData *pdata; pdata = g_slice_new (MenuPopulateData); pdata->items = NULL; pdata->n_items = 0; pdata->loaded_items = 0; pdata->displayed_items = 0; pdata->menu = menu; pdata->placeholder = g_object_ref (menu->priv->placeholder); return pdata; } static void free_menu_populate_data (MenuPopulateData *pdata) { if (pdata->placeholder) g_object_unref (pdata->placeholder); g_slice_free (MenuPopulateData, pdata); } static gboolean idle_populate_func (gpointer data) { MenuPopulateData *pdata; GtkRecentChooserMenuPrivate *priv; GtkRecentInfo *info; gboolean retval; GtkWidget *item; pdata = (MenuPopulateData *) data; priv = pdata->menu->priv; if (!pdata->items) { pdata->items = gtk_recent_chooser_get_items (GTK_RECENT_CHOOSER (pdata->menu)); if (!pdata->items) { /* show the placeholder here */ gtk_widget_show (pdata->placeholder); pdata->displayed_items = 1; priv->populate_id = 0; return FALSE; } else gtk_widget_hide (pdata->placeholder); pdata->n_items = g_list_length (pdata->items); pdata->loaded_items = 0; } info = g_list_nth_data (pdata->items, pdata->loaded_items); item = gtk_recent_chooser_menu_create_item (pdata->menu, info, pdata->displayed_items); if (!item) goto check_and_return; gtk_recent_chooser_menu_add_tip (pdata->menu, info, item); gtk_recent_chooser_menu_insert_item (pdata->menu, item, pdata->displayed_items); pdata->displayed_items += 1; /* mark the menu item as one of our own */ g_object_set_data (G_OBJECT (item), "gtk-recent-menu-mark", GINT_TO_POINTER (TRUE)); /* attach the RecentInfo object to the menu item, and own a reference * to it, so that it will be destroyed with the menu item when it's * not needed anymore. */ g_object_set_data_full (G_OBJECT (item), "gtk-recent-info", gtk_recent_info_ref (info), (GDestroyNotify) gtk_recent_info_unref); check_and_return: pdata->loaded_items += 1; if (pdata->loaded_items == pdata->n_items) { g_list_free_full (pdata->items, (GDestroyNotify) gtk_recent_info_unref); priv->populate_id = 0; retval = FALSE; } else retval = TRUE; return retval; } static void idle_populate_clean_up (gpointer data) { MenuPopulateData *pdata = data; if (pdata->menu->priv->populate_id == 0) { /* show the placeholder in case no item survived * the filtering process in the idle loop */ if (!pdata->displayed_items) gtk_widget_show (pdata->placeholder); } free_menu_populate_data (pdata); } static void gtk_recent_chooser_menu_populate (GtkRecentChooserMenu *menu) { MenuPopulateData *pdata; GtkRecentChooserMenuPrivate *priv = menu->priv; if (priv->populate_id) return; pdata = create_menu_populate_data (menu); /* remove our menu items first */ gtk_recent_chooser_menu_dispose_items (menu); priv->populate_id = gdk_threads_add_idle_full (G_PRIORITY_HIGH_IDLE + 30, idle_populate_func, pdata, idle_populate_clean_up); g_source_set_name_by_id (priv->populate_id, "[gtk+] idle_populate_func"); } /* bounce activate signal from the recent menu item widget * to the recent menu widget */ static void item_activate_cb (GtkWidget *widget, gpointer user_data) { GtkRecentChooser *chooser = GTK_RECENT_CHOOSER (user_data); GtkRecentInfo *info = g_object_get_data (G_OBJECT (widget), "gtk-recent-info"); gtk_recent_chooser_menu_set_current_uri (chooser, gtk_recent_info_get_uri (info), NULL); _gtk_recent_chooser_item_activated (chooser); } /* we force a redraw if the manager changes when we are showing */ static void manager_changed_cb (GtkRecentManager *manager, gpointer user_data) { GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (user_data); gtk_recent_chooser_menu_populate (menu); } static void set_recent_manager (GtkRecentChooserMenu *menu, GtkRecentManager *manager) { GtkRecentChooserMenuPrivate *priv = menu->priv; if (priv->manager) { if (priv->manager_changed_id) { g_signal_handler_disconnect (priv->manager, priv->manager_changed_id); priv->manager_changed_id = 0; } if (priv->populate_id) { g_source_remove (priv->populate_id); priv->populate_id = 0; } priv->manager = NULL; } if (manager) priv->manager = manager; else priv->manager = gtk_recent_manager_get_default (); if (priv->manager) priv->manager_changed_id = g_signal_connect (priv->manager, "changed", G_CALLBACK (manager_changed_cb), menu); } static void foreach_set_shot_tips (GtkWidget *widget, gpointer user_data) { GtkRecentChooserMenu *menu = user_data; GtkRecentChooserMenuPrivate *priv = menu->priv; gboolean has_mark; /* toggle the tooltip only on the items we create */ has_mark = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "gtk-recent-menu-mark")); if (has_mark) gtk_widget_set_has_tooltip (widget, priv->show_tips); } static void gtk_recent_chooser_menu_set_show_tips (GtkRecentChooserMenu *menu, gboolean show_tips) { GtkRecentChooserMenuPrivate *priv = menu->priv; if (priv->show_tips == show_tips) return; priv->show_tips = show_tips; gtk_container_foreach (GTK_CONTAINER (menu), foreach_set_shot_tips, menu); g_object_notify (G_OBJECT (menu), "show-tips"); } /* * Public API */ /** * gtk_recent_chooser_menu_new: * * Creates a new #GtkRecentChooserMenu widget. * * This kind of widget shows the list of recently used resources as * a menu, each item as a menu item. Each item inside the menu might * have an icon, representing its MIME type, and a number, for mnemonic * access. * * This widget implements the #GtkRecentChooser interface. * * This widget creates its own #GtkRecentManager object. See the * gtk_recent_chooser_menu_new_for_manager() function to know how to create * a #GtkRecentChooserMenu widget bound to another #GtkRecentManager object. * * Returns: a new #GtkRecentChooserMenu * * Since: 2.10 */ GtkWidget * gtk_recent_chooser_menu_new (void) { return g_object_new (GTK_TYPE_RECENT_CHOOSER_MENU, "recent-manager", NULL, NULL); } /** * gtk_recent_chooser_menu_new_for_manager: * @manager: a #GtkRecentManager * * Creates a new #GtkRecentChooserMenu widget using @manager as * the underlying recently used resources manager. * * This is useful if you have implemented your own recent manager, * or if you have a customized instance of a #GtkRecentManager * object or if you wish to share a common #GtkRecentManager object * among multiple #GtkRecentChooser widgets. * * Returns: a new #GtkRecentChooserMenu, bound to @manager. * * Since: 2.10 */ GtkWidget * gtk_recent_chooser_menu_new_for_manager (GtkRecentManager *manager) { g_return_val_if_fail (manager == NULL || GTK_IS_RECENT_MANAGER (manager), NULL); return g_object_new (GTK_TYPE_RECENT_CHOOSER_MENU, "recent-manager", manager, NULL); } /** * gtk_recent_chooser_menu_get_show_numbers: * @menu: a #GtkRecentChooserMenu * * Returns the value set by gtk_recent_chooser_menu_set_show_numbers(). * * Returns: %TRUE if numbers should be shown. * * Since: 2.10 */ gboolean gtk_recent_chooser_menu_get_show_numbers (GtkRecentChooserMenu *menu) { g_return_val_if_fail (GTK_IS_RECENT_CHOOSER_MENU (menu), FALSE); return menu->priv->show_numbers; } /** * gtk_recent_chooser_menu_set_show_numbers: * @menu: a #GtkRecentChooserMenu * @show_numbers: whether to show numbers * * Sets whether a number should be added to the items of @menu. The * numbers are shown to provide a unique character for a mnemonic to * be used inside ten menu item’s label. Only the first the items * get a number to avoid clashes. * * Since: 2.10 */ void gtk_recent_chooser_menu_set_show_numbers (GtkRecentChooserMenu *menu, gboolean show_numbers) { g_return_if_fail (GTK_IS_RECENT_CHOOSER_MENU (menu)); if (menu->priv->show_numbers == show_numbers) return; menu->priv->show_numbers = show_numbers; g_object_notify (G_OBJECT (menu), "show-numbers"); }