X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkrecentchoosermenu.c;h=25dc025be4451ff44ba7f83f733c847e53b2dd60;hb=HEAD;hp=1c423316dc5c11b6367ec1e40ff1eedeaa330e8a;hpb=0ef457c73040333f114d90a145d0740dc37ebdaa;p=~andy%2Fgtk diff --git a/gtk/gtkrecentchoosermenu.c b/gtk/gtkrecentchoosermenu.c index 1c423316d..25dc025be 100644 --- a/gtk/gtkrecentchoosermenu.c +++ b/gtk/gtkrecentchoosermenu.c @@ -13,17 +13,13 @@ * 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, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library. If not, see . */ #include "config.h" #include -#include - #include "gtkrecentmanager.h" #include "gtkrecentfilter.h" #include "gtkrecentchooser.h" @@ -42,25 +38,49 @@ #include "gtkseparatormenuitem.h" #include "gtkmenu.h" #include "gtkimage.h" -#include "gtkobject.h" -#include "gtktooltips.h" +#include "gtklabel.h" +#include "gtktooltip.h" +#include "gtkactivatable.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. + */ -#include "gtkrecentmanager.h" -#include "gtkrecentfilter.h" -#include "gtkrecentchooser.h" -#include "gtkrecentchooserutils.h" -#include "gtkrecentchooserprivate.h" -#include "gtkrecentchoosermenu.h" -#include "gtkalias.h" struct _GtkRecentChooserMenuPrivate { /* the recent manager object */ GtkRecentManager *manager; - /* size of the icons of the menu items */ - gint icon_size; + /* max size of the menu item label */ + gint label_width; + + gint first_recent_item_pos; + GtkWidget *placeholder; /* RecentChooser properties */ gint limit; @@ -83,22 +103,25 @@ struct _GtkRecentChooserMenuPrivate guint local_manager : 1; gulong manager_changed_id; - /* tooltips for our bookmark items*/ - GtkTooltips *tooltips; + gulong populate_id; }; enum { PROP_0, + PROP_SHOW_NUMBERS, - PROP_SHOW_NUMBERS + /* activatable properties */ + PROP_ACTIVATABLE_RELATED_ACTION, + PROP_ACTIVATABLE_USE_ACTION_APPEARANCE }; -#define FALLBACK_ICON_SIZE 32 + #define FALLBACK_ITEM_LIMIT 10 +#define DEFAULT_LABEL_WIDTH 30 -#define GTK_RECENT_CHOOSER_MENU_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_RECENT_CHOOSER_MENU, GtkRecentChooserMenuPrivate)) static void gtk_recent_chooser_menu_finalize (GObject *object); +static void gtk_recent_chooser_menu_dispose (GObject *object); static GObject *gtk_recent_chooser_menu_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_params); @@ -139,7 +162,7 @@ static GSList * gtk_recent_chooser_menu_list_filters (GtkRecentCh static void gtk_recent_chooser_menu_set_current_filter (GtkRecentChooserMenu *menu, GtkRecentFilter *filter); -static void gtk_recent_chooser_menu_map (GtkWidget *widget); +static void gtk_recent_chooser_menu_populate (GtkRecentChooserMenu *menu); static void gtk_recent_chooser_menu_set_show_tips (GtkRecentChooserMenu *menu, gboolean show_tips); @@ -149,18 +172,25 @@ static void set_recent_manager (GtkRecentChooserMenu *menu, static void chooser_set_sort_type (GtkRecentChooserMenu *menu, GtkRecentSortType sort_type); -static gint get_icon_size_for_widget (GtkWidget *widget); - static void item_activate_cb (GtkWidget *widget, gpointer user_data); static void manager_changed_cb (GtkRecentManager *manager, gpointer user_data); +static void gtk_recent_chooser_activatable_iface_init (GtkActivatableIface *iface); +static void gtk_recent_chooser_update (GtkActivatable *activatable, + GtkAction *action, + const gchar *property_name); +static void gtk_recent_chooser_sync_action_properties (GtkActivatable *activatable, + GtkAction *action); + G_DEFINE_TYPE_WITH_CODE (GtkRecentChooserMenu, gtk_recent_chooser_menu, GTK_TYPE_MENU, G_IMPLEMENT_INTERFACE (GTK_TYPE_RECENT_CHOOSER, - gtk_recent_chooser_iface_init)); + gtk_recent_chooser_iface_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIVATABLE, + gtk_recent_chooser_activatable_iface_init)) static void @@ -180,23 +210,28 @@ gtk_recent_chooser_iface_init (GtkRecentChooserIface *iface) iface->list_filters = gtk_recent_chooser_menu_list_filters; } +static void +gtk_recent_chooser_activatable_iface_init (GtkActivatableIface *iface) +{ + iface->update = gtk_recent_chooser_update; + iface->sync_action_properties = gtk_recent_chooser_sync_action_properties; +} + static void gtk_recent_chooser_menu_class_init (GtkRecentChooserMenuClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - + gobject_class->constructor = gtk_recent_chooser_menu_constructor; + 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; - widget_class->map = gtk_recent_chooser_menu_map; - _gtk_recent_chooser_install_properties (gobject_class); /** - * GtkRecentChooserMenu:show-numbers + * GtkRecentChooserMenu:show-numbers: * * Whether the first ten items in the menu should be prepended by * a number acting as a unique mnemonic. @@ -209,8 +244,12 @@ gtk_recent_chooser_menu_class_init (GtkRecentChooserMenuClass *klass) P_("Show Numbers"), P_("Whether the items should be displayed with a number"), FALSE, - G_PARAM_READWRITE)); + GTK_PARAM_READWRITE)); + + g_object_class_override_property (gobject_class, PROP_ACTIVATABLE_RELATED_ACTION, "related-action"); + g_object_class_override_property (gobject_class, PROP_ACTIVATABLE_USE_ACTION_APPEARANCE, "use-action-appearance"); + g_type_class_add_private (klass, sizeof (GtkRecentChooserMenuPrivate)); } @@ -218,28 +257,28 @@ static void gtk_recent_chooser_menu_init (GtkRecentChooserMenu *menu) { GtkRecentChooserMenuPrivate *priv; - - priv = GTK_RECENT_CHOOSER_MENU_GET_PRIVATE (menu); - - menu->priv = priv; - + + menu->priv = G_TYPE_INSTANCE_GET_PRIVATE (menu, + GTK_TYPE_RECENT_CHOOSER_MENU, + GtkRecentChooserMenuPrivate); + + priv = menu->priv; + priv->show_icons= TRUE; priv->show_numbers = FALSE; priv->show_tips = FALSE; - priv->show_not_found = 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->icon_size = FALLBACK_ICON_SIZE; - + priv->first_recent_item_pos = -1; + priv->placeholder = NULL; + priv->current_filter = NULL; - - priv->tooltips = gtk_tooltips_new (); - g_object_ref_sink (priv->tooltips); } static void @@ -248,44 +287,89 @@ gtk_recent_chooser_menu_finalize (GObject *object) GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (object); GtkRecentChooserMenuPrivate *priv = menu->priv; - g_signal_handler_disconnect (priv->manager, priv->manager_changed_id); - priv->manager_changed_id = 0; - 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; } + + priv->sort_data = NULL; + priv->sort_func = NULL; - if (priv->tooltips) - g_object_unref (priv->tooltips); + 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); + { + g_object_unref (priv->current_filter); + priv->current_filter = NULL; + } - G_OBJECT_CLASS (gtk_recent_chooser_menu_parent_class)->finalize; + G_OBJECT_CLASS (gtk_recent_chooser_menu_parent_class)->dispose (object); } static GObject * gtk_recent_chooser_menu_constructor (GType type, - guint n_construct_properties, - GObjectConstructParam *construct_params) + guint n_params, + GObjectConstructParam *params) { GtkRecentChooserMenu *menu; + GtkRecentChooserMenuPrivate *priv; + GObjectClass *parent_class; GObject *object; - object = G_OBJECT_CLASS (gtk_recent_chooser_menu_parent_class)->constructor (type, - n_construct_properties, - construct_params); + parent_class = G_OBJECT_CLASS (gtk_recent_chooser_menu_parent_class); + object = parent_class->constructor (type, n_params, params); menu = GTK_RECENT_CHOOSER_MENU (object); + priv = menu->priv; - g_assert (menu->priv->manager); - + 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_set_no_show_all (priv->placeholder, TRUE); + gtk_widget_show (priv->placeholder); + + /* (re)populate the menu */ + gtk_recent_chooser_menu_populate (menu); + return object; } @@ -296,38 +380,38 @@ gtk_recent_chooser_menu_set_property (GObject *object, GParamSpec *pspec) { GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (object); + GtkRecentChooserMenuPrivate *priv = menu->priv; switch (prop_id) { case PROP_SHOW_NUMBERS: - menu->priv->show_numbers = g_value_get_boolean (value); + priv->show_numbers = g_value_get_boolean (value); 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: - menu->priv->show_private = g_value_get_boolean (value); + priv->show_private = g_value_get_boolean (value); break; case GTK_RECENT_CHOOSER_PROP_SHOW_NOT_FOUND: - menu->priv->show_not_found = g_value_get_boolean (value); + priv->show_not_found = g_value_get_boolean (value); 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: - menu->priv->show_icons = g_value_get_boolean (value); + priv->show_icons = g_value_get_boolean (value); break; case GTK_RECENT_CHOOSER_PROP_SELECT_MULTIPLE: - g_warning ("%s: RecentChoosers of type `%s' do not support " - "selecting multiple items.", + 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: - menu->priv->local_only = g_value_get_boolean (value); + priv->local_only = g_value_get_boolean (value); break; case GTK_RECENT_CHOOSER_PROP_LIMIT: - menu->priv->limit = g_value_get_int (value); + priv->limit = g_value_get_int (value); break; case GTK_RECENT_CHOOSER_PROP_SORT_TYPE: chooser_set_sort_type (menu, g_value_get_enum (value)); @@ -335,6 +419,12 @@ gtk_recent_chooser_menu_set_property (GObject *object, case GTK_RECENT_CHOOSER_PROP_FILTER: gtk_recent_chooser_menu_set_current_filter (menu, g_value_get_object (value)); break; + case PROP_ACTIVATABLE_RELATED_ACTION: + _gtk_recent_chooser_set_related_action (GTK_RECENT_CHOOSER (menu), g_value_get_object (value)); + break; + case PROP_ACTIVATABLE_USE_ACTION_APPEARANCE: + _gtk_recent_chooser_set_use_action_appearance (GTK_RECENT_CHOOSER (menu), g_value_get_boolean (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -348,41 +438,45 @@ gtk_recent_chooser_menu_get_property (GObject *object, 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, menu->priv->show_numbers); + g_value_set_boolean (value, priv->show_numbers); break; case GTK_RECENT_CHOOSER_PROP_SHOW_TIPS: - g_value_set_boolean (value, menu->priv->show_tips); + g_value_set_boolean (value, priv->show_tips); break; case GTK_RECENT_CHOOSER_PROP_LIMIT: - g_value_set_int (value, menu->priv->limit); + g_value_set_int (value, priv->limit); break; case GTK_RECENT_CHOOSER_PROP_LOCAL_ONLY: - g_value_set_boolean (value, menu->priv->local_only); + g_value_set_boolean (value, priv->local_only); break; case GTK_RECENT_CHOOSER_PROP_SORT_TYPE: - g_value_set_enum (value, menu->priv->sort_type); + g_value_set_enum (value, priv->sort_type); break; case GTK_RECENT_CHOOSER_PROP_SHOW_PRIVATE: - g_value_set_boolean (value, menu->priv->show_private); + g_value_set_boolean (value, priv->show_private); break; case GTK_RECENT_CHOOSER_PROP_SHOW_NOT_FOUND: - g_value_set_boolean (value, menu->priv->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, menu->priv->show_icons); + g_value_set_boolean (value, priv->show_icons); break; case GTK_RECENT_CHOOSER_PROP_SELECT_MULTIPLE: - g_warning ("%s: Recent Choosers of type `%s' do not support " - "selecting multiple items.", - G_STRFUNC, - G_OBJECT_TYPE_NAME (object)); + g_value_set_boolean (value, FALSE); break; case GTK_RECENT_CHOOSER_PROP_FILTER: - g_value_set_object (value, menu->priv->current_filter); + g_value_set_object (value, priv->current_filter); + break; + case PROP_ACTIVATABLE_RELATED_ACTION: + g_value_set_object (value, _gtk_recent_chooser_get_related_action (GTK_RECENT_CHOOSER (menu))); + break; + case PROP_ACTIVATABLE_USE_ACTION_APPEARANCE: + g_value_set_boolean (value, _gtk_recent_chooser_get_use_action_appearance (GTK_RECENT_CHOOSER (menu))); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -401,6 +495,7 @@ gtk_recent_chooser_menu_set_current_uri (GtkRecentChooser *chooser, gboolean found = FALSE; children = gtk_container_get_children (GTK_CONTAINER (menu)); + for (l = children; l != NULL; l = l->next) { GtkRecentInfo *info; @@ -411,8 +506,15 @@ gtk_recent_chooser_menu_set_current_uri (GtkRecentChooser *chooser, if (!info) continue; - if (0 == strcmp (uri, gtk_recent_info_get_uri (info))) - found = TRUE; + if (strcmp (uri, gtk_recent_info_get_uri (info)) == 0) + { + gtk_menu_shell_activate_item (GTK_MENU_SHELL (menu), + menu_item, + TRUE); + found = TRUE; + + break; + } } g_list_free (children); @@ -423,14 +525,9 @@ gtk_recent_chooser_menu_set_current_uri (GtkRecentChooser *chooser, GTK_RECENT_CHOOSER_ERROR_NOT_FOUND, _("No recently used resource found with URI `%s'"), uri); - return FALSE; - } - else - { - gtk_menu_shell_activate_item (GTK_MENU_SHELL (menu), menu_item, TRUE); - - return TRUE; } + + return found; } static gchar * @@ -506,16 +603,16 @@ gtk_recent_chooser_menu_unselect_uri (GtkRecentChooser *chooser, static void gtk_recent_chooser_menu_select_all (GtkRecentChooser *chooser) { - g_warning (_("This function is not implemented for " - "widgets of class '%s'"), + 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_warning ("This function is not implemented for " + "widgets of class '%s'", g_type_name (G_OBJECT_TYPE (chooser))); } @@ -531,11 +628,12 @@ gtk_recent_chooser_menu_set_sort_func (GtkRecentChooser *chooser, if (priv->sort_data_destroy) { priv->sort_data_destroy (priv->sort_data); - - priv->sort_func = NULL; - priv->sort_data = NULL; priv->sort_data_destroy = NULL; } + + priv->sort_func = NULL; + priv->sort_data = NULL; + priv->sort_data_destroy = NULL; if (sort_func) { @@ -545,45 +643,6 @@ gtk_recent_chooser_menu_set_sort_func (GtkRecentChooser *chooser, } } -static gint -sort_recent_items_mru (GtkRecentInfo *a, - GtkRecentInfo *b, - gpointer unused) -{ - g_assert (a != NULL && b != NULL); - - return (gtk_recent_info_get_modified (a) < gtk_recent_info_get_modified (b)); -} - -static gint -sort_recent_items_lru (GtkRecentInfo *a, - GtkRecentInfo *b, - gpointer unused) -{ - g_assert (a != NULL && b != NULL); - - return (gtk_recent_info_get_modified (a) > gtk_recent_info_get_modified (b)); -} - -/* our proxy sorting function */ -static gint -sort_recent_items_proxy (gpointer *a, - gpointer *b, - gpointer user_data) -{ - GtkRecentInfo *info_a = (GtkRecentInfo *) a; - GtkRecentInfo *info_b = (GtkRecentInfo *) b; - GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (user_data); - - if (menu->priv->sort_func) - return (* menu->priv->sort_func) (info_a, - info_b, - menu->priv->sort_data); - - /* fallback */ - return 0; -} - static void chooser_set_sort_type (GtkRecentChooserMenu *menu, GtkRecentSortType sort_type) @@ -599,62 +658,12 @@ static GList * gtk_recent_chooser_menu_get_items (GtkRecentChooser *chooser) { GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser); - GtkRecentChooserMenuPrivate *priv; - gint limit; - GtkRecentSortType sort_type; - GList *items; - GCompareDataFunc compare_func; - gint length; - - priv = menu->priv; - - if (!priv->manager) - return NULL; - - limit = gtk_recent_chooser_get_limit (chooser); - sort_type = gtk_recent_chooser_get_sort_type (chooser); - - switch (sort_type) - { - case GTK_RECENT_SORT_NONE: - compare_func = NULL; - break; - case GTK_RECENT_SORT_MRU: - compare_func = (GCompareDataFunc) sort_recent_items_mru; - break; - case GTK_RECENT_SORT_LRU: - compare_func = (GCompareDataFunc) sort_recent_items_lru; - break; - case GTK_RECENT_SORT_CUSTOM: - compare_func = (GCompareDataFunc) sort_recent_items_proxy; - break; - default: - g_assert_not_reached (); - break; - } - - items = gtk_recent_manager_get_items (priv->manager); - if (!items) - return NULL; - - if (compare_func) - items = g_list_sort_with_data (items, compare_func, menu); - - length = g_list_length (items); - if ((limit != -1) && (length > limit)) - { - GList *clamp, *l; - - clamp = g_list_nth (items, limit - 1); + GtkRecentChooserMenuPrivate *priv = menu->priv; - l = clamp->next; - clamp->next = NULL; - - g_list_foreach (l, (GFunc) gtk_recent_info_unref, NULL); - g_list_free (l); - } - - return items; + return _gtk_recent_chooser_get_items (chooser, + priv->current_filter, + priv->sort_func, + priv->sort_data); } static GtkRecentManager * @@ -671,28 +680,42 @@ static void gtk_recent_chooser_menu_add_filter (GtkRecentChooser *chooser, GtkRecentFilter *filter) { - g_warning (_("This function is not implemented for " - "widgets of class '%s'"), - g_type_name (G_OBJECT_TYPE (chooser))); + 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) { - g_warning (_("This function is not implemented for " - "widgets of class '%s'"), - g_type_name (G_OBJECT_TYPE (chooser))); + 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) +gtk_recent_chooser_menu_list_filters (GtkRecentChooser *chooser) { - g_warning (_("This function is not implemented for " - "widgets of class '%s'"), - g_type_name (G_OBJECT_TYPE (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 NULL; + return retval; } static void @@ -707,75 +730,13 @@ gtk_recent_chooser_menu_set_current_filter (GtkRecentChooserMenu *menu, g_object_unref (G_OBJECT (priv->current_filter)); priv->current_filter = filter; - g_object_ref_sink (priv->current_filter); - - g_object_notify (G_OBJECT (menu), "filter"); -} - -static gboolean -get_is_recent_filtered (GtkRecentChooserMenu *menu, - GtkRecentInfo *info) -{ - GtkRecentChooserMenuPrivate *priv; - GtkRecentFilter *current_filter; - GtkRecentFilterInfo filter_info; - GtkRecentFilterFlags needed; - gboolean retval; - g_assert (info != NULL); - - priv = menu->priv; - - if (!priv->current_filter) - return FALSE; - - current_filter = priv->current_filter; - needed = gtk_recent_filter_get_needed (current_filter); - - filter_info.contains = GTK_RECENT_FILTER_URI | GTK_RECENT_FILTER_MIME_TYPE; - - filter_info.uri = gtk_recent_info_get_uri (info); - filter_info.mime_type = gtk_recent_info_get_mime_type (info); - - if (needed & GTK_RECENT_FILTER_DISPLAY_NAME) - { - filter_info.display_name = gtk_recent_info_get_display_name (info); - filter_info.contains |= GTK_RECENT_FILTER_DISPLAY_NAME; - } - else - filter_info.uri = NULL; - - if (needed & GTK_RECENT_FILTER_APPLICATION) - { - filter_info.applications = (const gchar **) gtk_recent_info_get_applications (info, NULL); - filter_info.contains |= GTK_RECENT_FILTER_APPLICATION; - } - else - filter_info.applications = NULL; + if (priv->current_filter) + g_object_ref_sink (priv->current_filter); - if (needed & GTK_RECENT_FILTER_GROUP) - { - filter_info.groups = (const gchar **) gtk_recent_info_get_groups (info, NULL); - filter_info.contains |= GTK_RECENT_FILTER_GROUP; - } - else - filter_info.groups = NULL; - - if (needed & GTK_RECENT_FILTER_AGE) - { - filter_info.age = gtk_recent_info_get_age (info); - filter_info.contains |= GTK_RECENT_FILTER_AGE; - } - else - filter_info.age = -1; - - retval = gtk_recent_filter_filter (current_filter, &filter_info); - - /* this we own */ - if (filter_info.applications) - g_strfreev ((gchar **) filter_info.applications); + gtk_recent_chooser_menu_populate (menu); - return !retval; + g_object_notify (G_OBJECT (menu), "filter"); } /* taken from libeel/eel-strings.c */ @@ -818,27 +779,24 @@ gtk_recent_chooser_menu_add_tip (GtkRecentChooserMenu *menu, GtkWidget *item) { GtkRecentChooserMenuPrivate *priv; - gchar *path, *tip_text; + gchar *path; g_assert (info != NULL); g_assert (item != NULL); priv = menu->priv; - if (!priv->tooltips) - return; - path = gtk_recent_info_get_uri_display (info); - - tip_text = g_strdup_printf (_("Open '%s'"), path); - - gtk_tooltips_set_tip (priv->tooltips, - item, - tip_text, - NULL); + 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); + g_free (path); + g_free (tip_text); + } } static GtkWidget * @@ -847,9 +805,9 @@ gtk_recent_chooser_menu_create_item (GtkRecentChooserMenu *menu, gint count) { GtkRecentChooserMenuPrivate *priv; - gchar *label; - GtkWidget *item, *image; - GdkPixbuf *icon; + gchar *text; + GtkWidget *item, *image, *label; + GIcon *icon; g_assert (info != NULL); @@ -867,46 +825,101 @@ gtk_recent_chooser_menu_create_item (GtkRecentChooserMenu *menu, /* avoid clashing mnemonics */ if (count <= 10) - label = g_strdup_printf ("_%d. %s", count, escaped); + /* 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 - label = g_strdup_printf ("%d. %s", count, escaped); + /* 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); - item = gtk_image_menu_item_new_with_mnemonic (label); + item = gtk_image_menu_item_new_with_mnemonic (text); g_free (escaped); g_free (name); } else { - label = g_strdup (gtk_recent_info_get_display_name (info)); - item = gtk_image_menu_item_new_with_label (label); + text = g_strdup (gtk_recent_info_get_display_name (info)); + item = gtk_image_menu_item_new_with_label (text); } - - if (priv->show_icons) + + g_free (text); + + gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (item), + TRUE); + + /* ellipsize the menu item label, in case the recent document + * display name is huge. + */ + label = gtk_bin_get_child (GTK_BIN (item)); + if (GTK_IS_LABEL (label)) { - icon = gtk_recent_info_get_icon (info, priv->icon_size); - - image = gtk_image_new_from_pixbuf (icon); - gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gtk_label_set_max_width_chars (GTK_LABEL (label), priv->label_width); } - if (!gtk_recent_info_exists (info)) + if (priv->show_icons) { - gtk_widget_set_sensitive (item, FALSE); - - goto out; + icon = gtk_recent_info_get_gicon (info); + + image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image); + gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (item), TRUE); + if (icon) + g_object_unref (icon); } - + g_signal_connect (item, "activate", G_CALLBACK (item_activate_cb), menu); -out: - g_free (label); - 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) @@ -917,12 +930,13 @@ gtk_recent_chooser_menu_dispose_items (GtkRecentChooserMenu *menu) for (l = children; l != NULL; l = l->next) { GtkWidget *menu_item = GTK_WIDGET (l->data); - gint mark = 0; + gboolean has_mark = FALSE; /* check for our mark, in order to remove just the items we own */ - mark = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), - "gtk-recent-menu-mark")); - if (mark == 1) + has_mark = + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), "gtk-recent-menu-mark")); + + if (has_mark) { GtkRecentInfo *info; @@ -937,120 +951,155 @@ gtk_recent_chooser_menu_dispose_items (GtkRecentChooserMenu *menu) } } + /* recalculate the position of the first recent item */ + menu->priv->first_recent_item_pos = -1; + g_list_free (children); } -/* GtkWidget::map method override - * - * We override this method in order to populate the menu with our - * menu items linked to the recently used resources. - */ +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 -gtk_recent_chooser_menu_map (GtkWidget *widget) +free_menu_populate_data (MenuPopulateData *pdata) { - GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (widget); - GtkRecentChooserMenuPrivate *priv = menu->priv; - GList *items, *l; - gint count; - gboolean has_items = FALSE; - - if (GTK_WIDGET_CLASS (gtk_recent_chooser_menu_parent_class)->map) - GTK_WIDGET_CLASS (gtk_recent_chooser_menu_parent_class)->map (widget); - - priv->icon_size = get_icon_size_for_widget (widget); - - /* dispose our menu items first */ - gtk_recent_chooser_menu_dispose_items (menu); - - items = gtk_recent_chooser_get_items (GTK_RECENT_CHOOSER (menu)); - - count = g_list_length (items); - items = g_list_reverse (items); + if (pdata->placeholder) + g_object_unref (pdata->placeholder); + g_slice_free (MenuPopulateData, pdata); +} - for (l = items; l != NULL; l = l->next) +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) { - GtkRecentInfo *info = (GtkRecentInfo *) l->data; - GtkWidget *item; + 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; - g_assert (info != NULL); - - /* skip non-local items on request */ - if (priv->local_only && !gtk_recent_info_is_local (info)) - continue; - - /* skip private items on request */ - if (!priv->show_private && gtk_recent_info_get_private_hint (info)) - continue; + return FALSE; + } + else + gtk_widget_hide (pdata->placeholder); - /* skip non-existing items on request */ - if (!priv->show_not_found && !gtk_recent_info_exists (info)) - continue; + pdata->n_items = g_list_length (pdata->items); + pdata->loaded_items = 0; + } - /* filter items based on the currently set filter object */ - if (get_is_recent_filtered (menu, info)) - continue; - - item = gtk_recent_chooser_menu_create_item (menu, info, count); - if (!item) - continue; - - gtk_recent_chooser_menu_add_tip (menu, info, item); - - /* FIXME - * - * We should really place our items taking into account user - * defined menu items; this would also remove the need of - * reverting the scan order. - */ - gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item); - gtk_widget_show (item); + 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; - /* mark the menu item as one of our own */ - g_object_set_data (G_OBJECT (item), "gtk-recent-menu-mark", - GINT_TO_POINTER (1)); + 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; - /* 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); + /* mark the menu item as one of our own */ + g_object_set_data (G_OBJECT (item), + "gtk-recent-menu-mark", + GINT_TO_POINTER (TRUE)); - /* we have at least one item */ - if (!has_items) - has_items = 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); - /* now, the RecentInfo objects are bound to the lifetime of the menu */ - if (items) +check_and_return: + pdata->loaded_items += 1; + + if (pdata->loaded_items == pdata->n_items) { - g_list_foreach (items, - (GFunc) gtk_recent_info_unref, - NULL); - g_list_free (items); + g_list_free_full (pdata->items, (GDestroyNotify) gtk_recent_info_unref); + + priv->populate_id = 0; + + retval = FALSE; } + else + retval = TRUE; - /* no recently used resources were found, or they were filtered out, so - * we build an item stating that no recently used resources were found - * (as night follows the day...). - */ - if (!has_items) + return retval; +} + +static void +idle_populate_clean_up (gpointer data) +{ + MenuPopulateData *pdata = data; + + if (pdata->menu->priv->populate_id == 0) { - GtkWidget *item; - - item = gtk_menu_item_new_with_label ("No items found"); - gtk_widget_set_sensitive (item, FALSE); - - /* we also mark this item, so that it gets removed when rebuilding - * the menu on the next map event + /* show the placeholder in case no item survived + * the filtering process in the idle loop */ - g_object_set_data (G_OBJECT (item), "gtk-recent-menu-mark", - GINT_TO_POINTER (1)); - - gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item); - gtk_widget_show (item); + 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); } /* bounce activate signal from the recent menu item widget @@ -1070,64 +1119,95 @@ static void manager_changed_cb (GtkRecentManager *manager, gpointer user_data) { - gtk_widget_queue_draw (GTK_WIDGET (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) { - if (menu->priv->manager) - g_signal_handler_disconnect (menu, menu->priv->manager_changed_id); - - menu->priv->manager = NULL; + 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) - menu->priv->manager = manager; + priv->manager = manager; else - menu->priv->manager = gtk_recent_manager_get_default (); + priv->manager = gtk_recent_manager_get_default (); - if (menu->priv->manager) - menu->priv->manager_changed_id = g_signal_connect (menu->priv->manager, "changed", - G_CALLBACK (manager_changed_cb), - menu); + if (priv->manager) + priv->manager_changed_id = g_signal_connect (priv->manager, "changed", + G_CALLBACK (manager_changed_cb), + menu); } -static gint -get_icon_size_for_widget (GtkWidget *widget) +static void +foreach_set_shot_tips (GtkWidget *widget, + gpointer user_data) { - GtkSettings *settings; - gint width, height; - - if (gtk_widget_has_screen (widget)) - settings = gtk_settings_get_for_screen (gtk_widget_get_screen (widget)); - else - settings = gtk_settings_get_default (); + GtkRecentChooserMenu *menu = user_data; + GtkRecentChooserMenuPrivate *priv = menu->priv; + gboolean has_mark; - if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_MENU, - &width, &height)) - return MAX (width, height); + /* 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")); - return FALLBACK_ICON_SIZE; + 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) { - if (menu->priv->show_tips == show_tips) + GtkRecentChooserMenuPrivate *priv = menu->priv; + + if (priv->show_tips == show_tips) return; - g_assert (menu->priv->tooltips != NULL); - - if (show_tips) - gtk_tooltips_enable (menu->priv->tooltips); - else - gtk_tooltips_disable (menu->priv->tooltips); - - menu->priv->show_tips = show_tips; + priv->show_tips = show_tips; + gtk_container_foreach (GTK_CONTAINER (menu), foreach_set_shot_tips, menu); } +static void +gtk_recent_chooser_update (GtkActivatable *activatable, + GtkAction *action, + const gchar *property_name) +{ + if (strcmp (property_name, "sensitive") == 0) + gtk_widget_set_sensitive (GTK_WIDGET (activatable), gtk_action_is_sensitive (action)); + + _gtk_recent_chooser_update (activatable, action, property_name); +} + +static void +gtk_recent_chooser_sync_action_properties (GtkActivatable *activatable, + GtkAction *action) +{ + gtk_widget_set_sensitive (GTK_WIDGET (activatable), gtk_action_is_sensitive (action)); + + _gtk_recent_chooser_sync_action_properties (activatable, action); +} + + /* * Public API */ @@ -1179,7 +1259,7 @@ gtk_recent_chooser_menu_new (void) GtkWidget * gtk_recent_chooser_menu_new_for_manager (GtkRecentManager *manager) { - g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), NULL); + 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, @@ -1211,7 +1291,7 @@ gtk_recent_chooser_menu_get_show_numbers (GtkRecentChooserMenu *menu) * * 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 the menu item's label. Only the first the items + * be used inside ten menu item's label. Only the first the items * get a number to avoid clashes. * * Since: 2.10 @@ -1228,6 +1308,3 @@ gtk_recent_chooser_menu_set_show_numbers (GtkRecentChooserMenu *menu, menu->priv->show_numbers = show_numbers; g_object_notify (G_OBJECT (menu), "show-numbers"); } - -#define __GTK_RECENT_CHOOSER_MENU_C__ -#include "gtkaliasdef.c"