]> Pileus Git - ~andy/gtk/blob - gtk/gtkrecentchoosermenu.c
Merge branch 'windows_list'
[~andy/gtk] / gtk / gtkrecentchoosermenu.c
1 /* GTK - The GIMP Toolkit
2  * gtkrecentchoosermenu.c - Recently used items menu widget
3  * Copyright (C) 2005, Emmanuele Bassi
4  * 
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 #include "config.h"
22
23 #include <string.h>
24
25 #include "gtkrecentmanager.h"
26 #include "gtkrecentfilter.h"
27 #include "gtkrecentchooser.h"
28 #include "gtkrecentchooserutils.h"
29 #include "gtkrecentchooserprivate.h"
30 #include "gtkrecentchoosermenu.h"
31
32 #include "gtkstock.h"
33 #include "gtkicontheme.h"
34 #include "gtkiconfactory.h"
35 #include "gtkintl.h"
36 #include "gtksettings.h"
37 #include "gtkmenushell.h"
38 #include "gtkmenuitem.h"
39 #include "gtkimagemenuitem.h"
40 #include "gtkseparatormenuitem.h"
41 #include "gtkmenu.h"
42 #include "gtkimage.h"
43 #include "gtklabel.h"
44 #include "gtktooltip.h"
45 #include "gtkactivatable.h"
46 #include "gtktypebuiltins.h"
47 #include "gtkprivate.h"
48
49 /**
50  * SECTION:gtkrecentchoosermenu
51  * @Short_description: Displays recently used files in a menu
52  * @Title: GtkRecentChooserMenu
53  * @See_also:#GtkRecentChooser
54  *
55  * #GtkRecentChooserMenu is a widget suitable for displaying recently used files
56  * inside a menu.  It can be used to set a sub-menu of a #GtkMenuItem using
57  * gtk_menu_item_set_submenu(), or as the menu of a #GtkMenuToolButton.
58  *
59  * Note that #GtkRecentChooserMenu does not have any methods of its own. Instead,
60  * you should use the functions that work on a #GtkRecentChooser.
61  *
62  * Note also that #GtkRecentChooserMenu does not support multiple filters, as it
63  * has no way to let the user choose between them as the #GtkRecentChooserWidget
64  * and #GtkRecentChooserDialog widgets do. Thus using gtk_recent_chooser_add_filter()
65  * on a #GtkRecentChooserMenu widget will yield the same effects as using
66  * gtk_recent_chooser_set_filter(), replacing any currently set filter
67  * with the supplied filter; gtk_recent_chooser_remove_filter() will remove
68  * any currently set #GtkRecentFilter object and will unset the current filter;
69  * gtk_recent_chooser_list_filters() will return a list containing a single
70  * #GtkRecentFilter object.
71  *
72  * Recently used files are supported since GTK+ 2.10.
73  */
74
75
76 struct _GtkRecentChooserMenuPrivate
77 {
78   /* the recent manager object */
79   GtkRecentManager *manager;
80   
81   /* size of the icons of the menu items */  
82   gint icon_size;
83
84   /* max size of the menu item label */
85   gint label_width;
86
87   gint first_recent_item_pos;
88   GtkWidget *placeholder;
89
90   /* RecentChooser properties */
91   gint limit;  
92   guint show_private : 1;
93   guint show_not_found : 1;
94   guint show_tips : 1;
95   guint show_icons : 1;
96   guint local_only : 1;
97   
98   guint show_numbers : 1;
99   
100   GtkRecentSortType sort_type;
101   GtkRecentSortFunc sort_func;
102   gpointer sort_data;
103   GDestroyNotify sort_data_destroy;
104   
105   GSList *filters;
106   GtkRecentFilter *current_filter;
107  
108   guint local_manager : 1;
109   gulong manager_changed_id;
110
111   gulong populate_id;
112 };
113
114 enum {
115   PROP_0,
116   PROP_SHOW_NUMBERS,
117
118   /* activatable properties */
119   PROP_ACTIVATABLE_RELATED_ACTION,
120   PROP_ACTIVATABLE_USE_ACTION_APPEARANCE
121 };
122
123
124 #define FALLBACK_ICON_SIZE      32
125 #define FALLBACK_ITEM_LIMIT     10
126 #define DEFAULT_LABEL_WIDTH     30
127
128
129 static void     gtk_recent_chooser_menu_finalize    (GObject                   *object);
130 static void     gtk_recent_chooser_menu_dispose     (GObject                   *object);
131 static GObject *gtk_recent_chooser_menu_constructor (GType                      type,
132                                                      guint                      n_construct_properties,
133                                                      GObjectConstructParam     *construct_params);
134
135 static void gtk_recent_chooser_iface_init      (GtkRecentChooserIface     *iface);
136
137 static void gtk_recent_chooser_menu_set_property (GObject      *object,
138                                                   guint         prop_id,
139                                                   const GValue *value,
140                                                   GParamSpec   *pspec);
141 static void gtk_recent_chooser_menu_get_property (GObject      *object,
142                                                   guint         prop_id,
143                                                   GValue       *value,
144                                                   GParamSpec   *pspec);
145
146 static gboolean          gtk_recent_chooser_menu_set_current_uri    (GtkRecentChooser  *chooser,
147                                                                      const gchar       *uri,
148                                                                      GError           **error);
149 static gchar *           gtk_recent_chooser_menu_get_current_uri    (GtkRecentChooser  *chooser);
150 static gboolean          gtk_recent_chooser_menu_select_uri         (GtkRecentChooser  *chooser,
151                                                                      const gchar       *uri,
152                                                                      GError           **error);
153 static void              gtk_recent_chooser_menu_unselect_uri       (GtkRecentChooser  *chooser,
154                                                                      const gchar       *uri);
155 static void              gtk_recent_chooser_menu_select_all         (GtkRecentChooser  *chooser);
156 static void              gtk_recent_chooser_menu_unselect_all       (GtkRecentChooser  *chooser);
157 static GList *           gtk_recent_chooser_menu_get_items          (GtkRecentChooser  *chooser);
158 static GtkRecentManager *gtk_recent_chooser_menu_get_recent_manager (GtkRecentChooser  *chooser);
159 static void              gtk_recent_chooser_menu_set_sort_func      (GtkRecentChooser  *chooser,
160                                                                      GtkRecentSortFunc  sort_func,
161                                                                      gpointer           sort_data,
162                                                                      GDestroyNotify     data_destroy);
163 static void              gtk_recent_chooser_menu_add_filter         (GtkRecentChooser  *chooser,
164                                                                      GtkRecentFilter   *filter);
165 static void              gtk_recent_chooser_menu_remove_filter      (GtkRecentChooser  *chooser,
166                                                                      GtkRecentFilter   *filter);
167 static GSList *          gtk_recent_chooser_menu_list_filters       (GtkRecentChooser  *chooser);
168 static void              gtk_recent_chooser_menu_set_current_filter (GtkRecentChooserMenu *menu,
169                                                                      GtkRecentFilter      *filter);
170
171 static void              gtk_recent_chooser_menu_populate           (GtkRecentChooserMenu *menu);
172 static void              gtk_recent_chooser_menu_set_show_tips      (GtkRecentChooserMenu *menu,
173                                                                      gboolean              show_tips);
174
175 static void     set_recent_manager (GtkRecentChooserMenu *menu,
176                                     GtkRecentManager     *manager);
177
178 static void     chooser_set_sort_type (GtkRecentChooserMenu *menu,
179                                        GtkRecentSortType     sort_type);
180
181 static gint     get_icon_size_for_widget (GtkWidget *widget);
182
183 static void     item_activate_cb   (GtkWidget        *widget,
184                                     gpointer          user_data);
185 static void     manager_changed_cb (GtkRecentManager *manager,
186                                     gpointer          user_data);
187
188 static void gtk_recent_chooser_activatable_iface_init (GtkActivatableIface  *iface);
189 static void gtk_recent_chooser_update                 (GtkActivatable       *activatable,
190                                                        GtkAction            *action,
191                                                        const gchar          *property_name);
192 static void gtk_recent_chooser_sync_action_properties (GtkActivatable       *activatable,
193                                                        GtkAction            *action);
194
195 G_DEFINE_TYPE_WITH_CODE (GtkRecentChooserMenu,
196                          gtk_recent_chooser_menu,
197                          GTK_TYPE_MENU,
198                          G_IMPLEMENT_INTERFACE (GTK_TYPE_RECENT_CHOOSER,
199                                                 gtk_recent_chooser_iface_init)
200                          G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIVATABLE,
201                                                 gtk_recent_chooser_activatable_iface_init))
202
203
204 static void
205 gtk_recent_chooser_iface_init (GtkRecentChooserIface *iface)
206 {
207   iface->set_current_uri = gtk_recent_chooser_menu_set_current_uri;
208   iface->get_current_uri = gtk_recent_chooser_menu_get_current_uri;
209   iface->select_uri = gtk_recent_chooser_menu_select_uri;
210   iface->unselect_uri = gtk_recent_chooser_menu_unselect_uri;
211   iface->select_all = gtk_recent_chooser_menu_select_all;
212   iface->unselect_all = gtk_recent_chooser_menu_unselect_all;
213   iface->get_items = gtk_recent_chooser_menu_get_items;
214   iface->get_recent_manager = gtk_recent_chooser_menu_get_recent_manager;
215   iface->set_sort_func = gtk_recent_chooser_menu_set_sort_func;
216   iface->add_filter = gtk_recent_chooser_menu_add_filter;
217   iface->remove_filter = gtk_recent_chooser_menu_remove_filter;
218   iface->list_filters = gtk_recent_chooser_menu_list_filters;
219 }
220
221 static void
222 gtk_recent_chooser_activatable_iface_init (GtkActivatableIface *iface)
223 {
224   iface->update = gtk_recent_chooser_update;
225   iface->sync_action_properties = gtk_recent_chooser_sync_action_properties;
226 }
227
228 static void
229 gtk_recent_chooser_menu_class_init (GtkRecentChooserMenuClass *klass)
230 {
231   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
232
233   gobject_class->constructor = gtk_recent_chooser_menu_constructor;
234   gobject_class->dispose = gtk_recent_chooser_menu_dispose;
235   gobject_class->finalize = gtk_recent_chooser_menu_finalize;
236   gobject_class->set_property = gtk_recent_chooser_menu_set_property;
237   gobject_class->get_property = gtk_recent_chooser_menu_get_property;
238
239   _gtk_recent_chooser_install_properties (gobject_class);
240
241   /**
242    * GtkRecentChooserMenu:show-numbers
243    *
244    * Whether the first ten items in the menu should be prepended by
245    * a number acting as a unique mnemonic.
246    *
247    * Since: 2.10
248    */
249   g_object_class_install_property (gobject_class,
250                                    PROP_SHOW_NUMBERS,
251                                    g_param_spec_boolean ("show-numbers",
252                                                          P_("Show Numbers"),
253                                                          P_("Whether the items should be displayed with a number"),
254                                                          FALSE,
255                                                          GTK_PARAM_READWRITE));
256   
257
258   g_object_class_override_property (gobject_class, PROP_ACTIVATABLE_RELATED_ACTION, "related-action");
259   g_object_class_override_property (gobject_class, PROP_ACTIVATABLE_USE_ACTION_APPEARANCE, "use-action-appearance");
260
261   g_type_class_add_private (klass, sizeof (GtkRecentChooserMenuPrivate));
262 }
263
264 static void
265 gtk_recent_chooser_menu_init (GtkRecentChooserMenu *menu)
266 {
267   GtkRecentChooserMenuPrivate *priv;
268
269   menu->priv = G_TYPE_INSTANCE_GET_PRIVATE (menu,
270                                             GTK_TYPE_RECENT_CHOOSER_MENU,
271                                             GtkRecentChooserMenuPrivate);
272
273   priv = menu->priv;
274
275   priv->show_icons= TRUE;
276   priv->show_numbers = FALSE;
277   priv->show_tips = FALSE;
278   priv->show_not_found = TRUE;
279   priv->show_private = FALSE;
280   priv->local_only = TRUE;
281   
282   priv->limit = FALLBACK_ITEM_LIMIT;
283   priv->sort_type = GTK_RECENT_SORT_NONE;
284   priv->icon_size = FALLBACK_ICON_SIZE;
285   priv->label_width = DEFAULT_LABEL_WIDTH;
286   
287   priv->first_recent_item_pos = -1;
288   priv->placeholder = NULL;
289
290   priv->current_filter = NULL;
291 }
292
293 static void
294 gtk_recent_chooser_menu_finalize (GObject *object)
295 {
296   GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (object);
297   GtkRecentChooserMenuPrivate *priv = menu->priv;
298   
299   priv->manager = NULL;
300   
301   if (priv->sort_data_destroy)
302     {
303       priv->sort_data_destroy (priv->sort_data);
304       priv->sort_data_destroy = NULL;
305     }
306
307   priv->sort_data = NULL;
308   priv->sort_func = NULL;
309   
310   G_OBJECT_CLASS (gtk_recent_chooser_menu_parent_class)->finalize (object);
311 }
312
313 static void
314 gtk_recent_chooser_menu_dispose (GObject *object)
315 {
316   GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (object);
317   GtkRecentChooserMenuPrivate *priv = menu->priv;
318
319   if (priv->manager_changed_id)
320     {
321       if (priv->manager)
322         g_signal_handler_disconnect (priv->manager, priv->manager_changed_id);
323
324       priv->manager_changed_id = 0;
325     }
326   
327   if (priv->populate_id)
328     {
329       g_source_remove (priv->populate_id);
330       priv->populate_id = 0;
331     }
332
333   if (priv->current_filter)
334     {
335       g_object_unref (priv->current_filter);
336       priv->current_filter = NULL;
337     }
338   
339   G_OBJECT_CLASS (gtk_recent_chooser_menu_parent_class)->dispose (object);
340 }
341
342 static GObject *
343 gtk_recent_chooser_menu_constructor (GType                  type,
344                                      guint                  n_params,
345                                      GObjectConstructParam *params)
346 {
347   GtkRecentChooserMenu *menu;
348   GtkRecentChooserMenuPrivate *priv;
349   GObjectClass *parent_class;
350   GObject *object;
351   
352   parent_class = G_OBJECT_CLASS (gtk_recent_chooser_menu_parent_class);
353   object = parent_class->constructor (type, n_params, params);
354   menu = GTK_RECENT_CHOOSER_MENU (object);
355   priv = menu->priv;
356   
357   g_assert (priv->manager);
358
359   /* we create a placeholder menuitem, to be used in case
360    * the menu is empty. this placeholder will stay around
361    * for the entire lifetime of the menu, and we just hide it
362    * when it's not used. we have to do this, and do it here,
363    * because we need a marker for the beginning of the recent
364    * items list, so that we can insert the new items at the
365    * right place when idly populating the menu in case the
366    * user appended or prepended custom menu items to the
367    * recent chooser menu widget.
368    */
369   priv->placeholder = gtk_menu_item_new_with_label (_("No items found"));
370   gtk_widget_set_sensitive (priv->placeholder, FALSE);
371   g_object_set_data (G_OBJECT (priv->placeholder),
372                      "gtk-recent-menu-placeholder",
373                      GINT_TO_POINTER (TRUE));
374
375   gtk_menu_shell_insert (GTK_MENU_SHELL (menu), priv->placeholder, 0);
376   gtk_widget_set_no_show_all (priv->placeholder, TRUE);
377   gtk_widget_show (priv->placeholder);
378
379   /* (re)populate the menu */
380   gtk_recent_chooser_menu_populate (menu);
381
382   return object;
383 }
384
385 static void
386 gtk_recent_chooser_menu_set_property (GObject      *object,
387                                       guint         prop_id,
388                                       const GValue *value,
389                                       GParamSpec   *pspec)
390 {
391   GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (object);
392   GtkRecentChooserMenuPrivate *priv = menu->priv;
393   
394   switch (prop_id)
395     {
396     case PROP_SHOW_NUMBERS:
397       priv->show_numbers = g_value_get_boolean (value);
398       break;
399     case GTK_RECENT_CHOOSER_PROP_RECENT_MANAGER:
400       set_recent_manager (menu, g_value_get_object (value));
401       break;
402     case GTK_RECENT_CHOOSER_PROP_SHOW_PRIVATE:
403       priv->show_private = g_value_get_boolean (value);
404       break;
405     case GTK_RECENT_CHOOSER_PROP_SHOW_NOT_FOUND:
406       priv->show_not_found = g_value_get_boolean (value);
407       break;
408     case GTK_RECENT_CHOOSER_PROP_SHOW_TIPS:
409       gtk_recent_chooser_menu_set_show_tips (menu, g_value_get_boolean (value));
410       break;
411     case GTK_RECENT_CHOOSER_PROP_SHOW_ICONS:
412       priv->show_icons = g_value_get_boolean (value);
413       break;
414     case GTK_RECENT_CHOOSER_PROP_SELECT_MULTIPLE:
415       g_warning ("%s: Choosers of type `%s' do not support selecting multiple items.",
416                  G_STRFUNC,
417                  G_OBJECT_TYPE_NAME (object));
418       break;
419     case GTK_RECENT_CHOOSER_PROP_LOCAL_ONLY:
420       priv->local_only = g_value_get_boolean (value);
421       break;
422     case GTK_RECENT_CHOOSER_PROP_LIMIT:
423       priv->limit = g_value_get_int (value);
424       break;
425     case GTK_RECENT_CHOOSER_PROP_SORT_TYPE:
426       chooser_set_sort_type (menu, g_value_get_enum (value));
427       break;
428     case GTK_RECENT_CHOOSER_PROP_FILTER:
429       gtk_recent_chooser_menu_set_current_filter (menu, g_value_get_object (value));
430       break;
431     case PROP_ACTIVATABLE_RELATED_ACTION:
432       _gtk_recent_chooser_set_related_action (GTK_RECENT_CHOOSER (menu), g_value_get_object (value));
433       break;
434     case PROP_ACTIVATABLE_USE_ACTION_APPEARANCE: 
435       _gtk_recent_chooser_set_use_action_appearance (GTK_RECENT_CHOOSER (menu), g_value_get_boolean (value));
436       break;
437     default:
438       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
439       break;
440     }
441 }
442
443 static void
444 gtk_recent_chooser_menu_get_property (GObject    *object,
445                                       guint       prop_id,
446                                       GValue     *value,
447                                       GParamSpec *pspec)
448 {
449   GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (object);
450   GtkRecentChooserMenuPrivate *priv = menu->priv;
451   
452   switch (prop_id)
453     {
454     case PROP_SHOW_NUMBERS:
455       g_value_set_boolean (value, priv->show_numbers);
456       break;
457     case GTK_RECENT_CHOOSER_PROP_SHOW_TIPS:
458       g_value_set_boolean (value, priv->show_tips);
459       break;
460     case GTK_RECENT_CHOOSER_PROP_LIMIT:
461       g_value_set_int (value, priv->limit);
462       break;
463     case GTK_RECENT_CHOOSER_PROP_LOCAL_ONLY:
464       g_value_set_boolean (value, priv->local_only);
465       break;
466     case GTK_RECENT_CHOOSER_PROP_SORT_TYPE:
467       g_value_set_enum (value, priv->sort_type);
468       break;
469     case GTK_RECENT_CHOOSER_PROP_SHOW_PRIVATE:
470       g_value_set_boolean (value, priv->show_private);
471       break;
472     case GTK_RECENT_CHOOSER_PROP_SHOW_NOT_FOUND:
473       g_value_set_boolean (value, priv->show_not_found);
474       break;
475     case GTK_RECENT_CHOOSER_PROP_SHOW_ICONS:
476       g_value_set_boolean (value, priv->show_icons);
477       break;
478     case GTK_RECENT_CHOOSER_PROP_SELECT_MULTIPLE:
479       g_value_set_boolean (value, FALSE);
480       break;
481     case GTK_RECENT_CHOOSER_PROP_FILTER:
482       g_value_set_object (value, priv->current_filter);
483       break;
484     case PROP_ACTIVATABLE_RELATED_ACTION:
485       g_value_set_object (value, _gtk_recent_chooser_get_related_action (GTK_RECENT_CHOOSER (menu)));
486       break;
487     case PROP_ACTIVATABLE_USE_ACTION_APPEARANCE: 
488       g_value_set_boolean (value, _gtk_recent_chooser_get_use_action_appearance (GTK_RECENT_CHOOSER (menu)));
489       break;
490     default:
491       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
492       break;
493     }
494 }
495
496 static gboolean
497 gtk_recent_chooser_menu_set_current_uri (GtkRecentChooser  *chooser,
498                                          const gchar       *uri,
499                                          GError           **error)
500 {
501   GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser);
502   GList *children, *l;
503   GtkWidget *menu_item = NULL;
504   gboolean found = FALSE;
505   
506   children = gtk_container_get_children (GTK_CONTAINER (menu));
507   
508   for (l = children; l != NULL; l = l->next)
509     {
510       GtkRecentInfo *info;
511       
512       menu_item = GTK_WIDGET (l->data);
513       
514       info = g_object_get_data (G_OBJECT (menu_item), "gtk-recent-info");
515       if (!info)
516         continue;
517       
518       if (strcmp (uri, gtk_recent_info_get_uri (info)) == 0)
519         {
520           gtk_menu_shell_activate_item (GTK_MENU_SHELL (menu),
521                                         menu_item,
522                                         TRUE);
523           found = TRUE;
524
525           break;
526         }
527     }
528
529   g_list_free (children);
530   
531   if (!found)  
532     {
533       g_set_error (error, GTK_RECENT_CHOOSER_ERROR,
534                    GTK_RECENT_CHOOSER_ERROR_NOT_FOUND,
535                    _("No recently used resource found with URI `%s'"),
536                    uri);
537     }
538   
539   return found;
540 }
541
542 static gchar *
543 gtk_recent_chooser_menu_get_current_uri (GtkRecentChooser  *chooser)
544 {
545   GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser);
546   GtkWidget *menu_item;
547   GtkRecentInfo *info;
548   
549   menu_item = gtk_menu_get_active (GTK_MENU (menu));
550   if (!menu_item)
551     return NULL;
552   
553   info = g_object_get_data (G_OBJECT (menu_item), "gtk-recent-info");
554   if (!info)
555     return NULL;
556   
557   return g_strdup (gtk_recent_info_get_uri (info));
558 }
559
560 static gboolean
561 gtk_recent_chooser_menu_select_uri (GtkRecentChooser  *chooser,
562                                     const gchar       *uri,
563                                     GError           **error)
564 {
565   GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser);
566   GList *children, *l;
567   GtkWidget *menu_item = NULL;
568   gboolean found = FALSE;
569   
570   children = gtk_container_get_children (GTK_CONTAINER (menu));
571   for (l = children; l != NULL; l = l->next)
572     {
573       GtkRecentInfo *info;
574       
575       menu_item = GTK_WIDGET (l->data);
576       
577       info = g_object_get_data (G_OBJECT (menu_item), "gtk-recent-info");
578       if (!info)
579         continue;
580       
581       if (0 == strcmp (uri, gtk_recent_info_get_uri (info)))
582         found = TRUE;
583     }
584
585   g_list_free (children);
586   
587   if (!found)  
588     {
589       g_set_error (error, GTK_RECENT_CHOOSER_ERROR,
590                    GTK_RECENT_CHOOSER_ERROR_NOT_FOUND,
591                    _("No recently used resource found with URI `%s'"),
592                    uri);
593       return FALSE;
594     }
595   else
596     {
597       gtk_menu_shell_select_item (GTK_MENU_SHELL (menu), menu_item);
598
599       return TRUE;
600     }
601 }
602
603 static void
604 gtk_recent_chooser_menu_unselect_uri (GtkRecentChooser *chooser,
605                                        const gchar     *uri)
606 {
607   GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser);
608   
609   gtk_menu_shell_deselect (GTK_MENU_SHELL (menu));
610 }
611
612 static void
613 gtk_recent_chooser_menu_select_all (GtkRecentChooser *chooser)
614 {
615   g_warning (_("This function is not implemented for "
616                "widgets of class '%s'"),
617              g_type_name (G_OBJECT_TYPE (chooser)));
618 }
619
620 static void
621 gtk_recent_chooser_menu_unselect_all (GtkRecentChooser *chooser)
622 {
623   g_warning (_("This function is not implemented for "
624                "widgets of class '%s'"),
625              g_type_name (G_OBJECT_TYPE (chooser)));
626 }
627
628 static void
629 gtk_recent_chooser_menu_set_sort_func (GtkRecentChooser  *chooser,
630                                        GtkRecentSortFunc  sort_func,
631                                        gpointer           sort_data,
632                                        GDestroyNotify     data_destroy)
633 {
634   GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser);
635   GtkRecentChooserMenuPrivate *priv = menu->priv;
636   
637   if (priv->sort_data_destroy)
638     {
639       priv->sort_data_destroy (priv->sort_data);
640       priv->sort_data_destroy = NULL;
641     }
642       
643   priv->sort_func = NULL;
644   priv->sort_data = NULL;
645   priv->sort_data_destroy = NULL;
646   
647   if (sort_func)
648     {
649       priv->sort_func = sort_func;
650       priv->sort_data = sort_data;
651       priv->sort_data_destroy = data_destroy;
652     }
653 }
654
655 static void
656 chooser_set_sort_type (GtkRecentChooserMenu *menu,
657                        GtkRecentSortType     sort_type)
658 {
659   if (menu->priv->sort_type == sort_type)
660     return;
661
662   menu->priv->sort_type = sort_type;
663 }
664
665
666 static GList *
667 gtk_recent_chooser_menu_get_items (GtkRecentChooser *chooser)
668 {
669   GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser);
670   GtkRecentChooserMenuPrivate *priv = menu->priv;
671
672   return _gtk_recent_chooser_get_items (chooser,
673                                         priv->current_filter,
674                                         priv->sort_func,
675                                         priv->sort_data);
676 }
677
678 static GtkRecentManager *
679 gtk_recent_chooser_menu_get_recent_manager (GtkRecentChooser *chooser)
680 {
681   GtkRecentChooserMenuPrivate *priv;
682  
683   priv = GTK_RECENT_CHOOSER_MENU (chooser)->priv;
684   
685   return priv->manager;
686 }
687
688 static void
689 gtk_recent_chooser_menu_add_filter (GtkRecentChooser *chooser,
690                                     GtkRecentFilter  *filter)
691 {
692   GtkRecentChooserMenu *menu;
693
694   menu = GTK_RECENT_CHOOSER_MENU (chooser);
695   
696   gtk_recent_chooser_menu_set_current_filter (menu, filter);
697 }
698
699 static void
700 gtk_recent_chooser_menu_remove_filter (GtkRecentChooser *chooser,
701                                        GtkRecentFilter  *filter)
702 {
703   GtkRecentChooserMenu *menu;
704
705   menu = GTK_RECENT_CHOOSER_MENU (chooser);
706   
707   if (filter == menu->priv->current_filter)
708     {
709       g_object_unref (menu->priv->current_filter);
710       menu->priv->current_filter = NULL;
711
712       g_object_notify (G_OBJECT (menu), "filter");
713     }
714 }
715
716 static GSList *
717 gtk_recent_chooser_menu_list_filters (GtkRecentChooser *chooser)
718 {
719   GtkRecentChooserMenu *menu;
720   GSList *retval = NULL;
721
722   menu = GTK_RECENT_CHOOSER_MENU (chooser);
723  
724   if (menu->priv->current_filter)
725     retval = g_slist_prepend (retval, menu->priv->current_filter);
726
727   return retval;
728 }
729
730 static void
731 gtk_recent_chooser_menu_set_current_filter (GtkRecentChooserMenu *menu,
732                                             GtkRecentFilter      *filter)
733 {
734   GtkRecentChooserMenuPrivate *priv;
735
736   priv = menu->priv;
737   
738   if (priv->current_filter)
739     g_object_unref (G_OBJECT (priv->current_filter));
740   
741   if (filter)
742     {
743       priv->current_filter = filter;
744       g_object_ref_sink (priv->current_filter);
745     }
746
747   gtk_recent_chooser_menu_populate (menu);
748   
749   g_object_notify (G_OBJECT (menu), "filter");
750 }
751
752 /* taken from libeel/eel-strings.c */
753 static gchar *
754 escape_underscores (const gchar *string)
755 {
756   gint underscores;
757   const gchar *p;
758   gchar *q;
759   gchar *escaped;
760
761   if (!string)
762     return NULL;
763         
764   underscores = 0;
765   for (p = string; *p != '\0'; p++)
766     underscores += (*p == '_');
767
768   if (underscores == 0)
769     return g_strdup (string);
770
771   escaped = g_new (char, strlen (string) + underscores + 1);
772   for (p = string, q = escaped; *p != '\0'; p++, q++)
773     {
774       /* Add an extra underscore. */
775       if (*p == '_')
776         *q++ = '_';
777       
778       *q = *p;
779     }
780   
781   *q = '\0';
782         
783   return escaped;
784 }
785
786 static void
787 gtk_recent_chooser_menu_add_tip (GtkRecentChooserMenu *menu,
788                                  GtkRecentInfo        *info,
789                                  GtkWidget            *item)
790 {
791   GtkRecentChooserMenuPrivate *priv;
792   gchar *path;
793
794   g_assert (info != NULL);
795   g_assert (item != NULL);
796
797   priv = menu->priv;
798   
799   path = gtk_recent_info_get_uri_display (info);
800   if (path)
801     {
802       gchar *tip_text = g_strdup_printf (_("Open '%s'"), path);
803
804       gtk_widget_set_tooltip_text (item, tip_text);
805       gtk_widget_set_has_tooltip (item, priv->show_tips);
806
807       g_free (path);
808       g_free (tip_text);
809     }
810 }
811
812 static GtkWidget *
813 gtk_recent_chooser_menu_create_item (GtkRecentChooserMenu *menu,
814                                      GtkRecentInfo        *info,
815                                      gint                  count)
816 {
817   GtkRecentChooserMenuPrivate *priv;
818   gchar *text;
819   GtkWidget *item, *image, *label;
820   GdkPixbuf *icon;
821
822   g_assert (info != NULL);
823
824   priv = menu->priv;
825
826   if (priv->show_numbers)
827     {
828       gchar *name, *escaped;
829       
830       name = g_strdup (gtk_recent_info_get_display_name (info));
831       if (!name)
832         name = g_strdup (_("Unknown item"));
833       
834       escaped = escape_underscores (name);
835       
836       /* avoid clashing mnemonics */
837       if (count <= 10)
838         /* This is the label format that is used for the first 10 items
839          * in a recent files menu. The %d is the number of the item,
840          * the %s is the name of the item. Please keep the _ in front
841          * of the number to give these menu items a mnemonic.
842          */
843         text = g_strdup_printf (C_("recent menu label", "_%d. %s"), count, escaped);
844       else
845         /* This is the format that is used for items in a recent files menu.
846          * The %d is the number of the item, the %s is the name of the item.
847          */
848         text = g_strdup_printf (C_("recent menu label", "%d. %s"), count, escaped);
849       
850       item = gtk_image_menu_item_new_with_mnemonic (text);
851       
852       g_free (escaped);
853       g_free (name);
854     }
855   else
856     {
857       text = g_strdup (gtk_recent_info_get_display_name (info));
858       item = gtk_image_menu_item_new_with_label (text);
859     }
860
861   g_free (text);
862
863   gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (item),
864                                              TRUE);
865
866   /* ellipsize the menu item label, in case the recent document
867    * display name is huge.
868    */
869   label = gtk_bin_get_child (GTK_BIN (item));
870   if (GTK_IS_LABEL (label))
871     {
872       gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
873       gtk_label_set_max_width_chars (GTK_LABEL (label), priv->label_width);
874     }
875   
876   if (priv->show_icons)
877     {
878       icon = gtk_recent_info_get_icon (info, priv->icon_size);
879         
880       image = gtk_image_new_from_pixbuf (icon);
881       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
882       g_object_unref (icon);
883     }
884
885   g_signal_connect (item, "activate",
886                     G_CALLBACK (item_activate_cb),
887                     menu);
888
889   return item;
890 }
891
892 static void
893 gtk_recent_chooser_menu_insert_item (GtkRecentChooserMenu *menu,
894                                      GtkWidget            *menuitem,
895                                      gint                  position)
896 {
897   GtkRecentChooserMenuPrivate *priv = menu->priv;
898   gint real_position;
899
900   if (priv->first_recent_item_pos == -1)
901     {
902       GList *children, *l;
903
904       children = gtk_container_get_children (GTK_CONTAINER (menu));
905
906       for (real_position = 0, l = children;
907            l != NULL;
908            real_position += 1, l = l->next)
909         {
910           GObject *child = l->data;
911           gboolean is_placeholder = FALSE;
912
913           is_placeholder =
914             GPOINTER_TO_INT (g_object_get_data (child, "gtk-recent-menu-placeholder"));
915
916           if (is_placeholder)
917             break;
918         }
919
920       g_list_free (children);
921       priv->first_recent_item_pos = real_position;
922     }
923   else
924     real_position = priv->first_recent_item_pos;
925
926   gtk_menu_shell_insert (GTK_MENU_SHELL (menu), menuitem,
927                          real_position + position);
928   gtk_widget_show (menuitem);
929 }
930
931 /* removes the items we own from the menu */
932 static void
933 gtk_recent_chooser_menu_dispose_items (GtkRecentChooserMenu *menu)
934 {
935   GList *children, *l;
936  
937   children = gtk_container_get_children (GTK_CONTAINER (menu));
938   for (l = children; l != NULL; l = l->next)
939     {
940       GtkWidget *menu_item = GTK_WIDGET (l->data);
941       gboolean has_mark = FALSE;
942       
943       /* check for our mark, in order to remove just the items we own */
944       has_mark =
945         GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), "gtk-recent-menu-mark"));
946
947       if (has_mark)
948         {
949           GtkRecentInfo *info;
950           
951           /* destroy the attached RecentInfo struct, if found */
952           info = g_object_get_data (G_OBJECT (menu_item), "gtk-recent-info");
953           if (info)
954             g_object_set_data_full (G_OBJECT (menu_item), "gtk-recent-info",
955                                     NULL, NULL);
956           
957           /* and finally remove the item from the menu */
958           gtk_container_remove (GTK_CONTAINER (menu), menu_item);
959         }
960     }
961
962   /* recalculate the position of the first recent item */
963   menu->priv->first_recent_item_pos = -1;
964
965   g_list_free (children);
966 }
967
968 typedef struct
969 {
970   GList *items;
971   gint n_items;
972   gint loaded_items;
973   gint displayed_items;
974   GtkRecentChooserMenu *menu;
975   GtkWidget *placeholder;
976 } MenuPopulateData;
977
978 static gboolean
979 idle_populate_func (gpointer data)
980 {
981   MenuPopulateData *pdata;
982   GtkRecentChooserMenuPrivate *priv;
983   GtkRecentInfo *info;
984   gboolean retval;
985   GtkWidget *item;
986
987   pdata = (MenuPopulateData *) data;
988   priv = pdata->menu->priv;
989
990   if (!pdata->items)
991     {
992       pdata->items = gtk_recent_chooser_get_items (GTK_RECENT_CHOOSER (pdata->menu));
993       if (!pdata->items)
994         {
995           /* show the placeholder here */
996           gtk_widget_show (pdata->placeholder);
997           pdata->displayed_items = 1;
998           priv->populate_id = 0;
999
1000           return FALSE;
1001         }
1002       else
1003         gtk_widget_hide (pdata->placeholder);
1004       
1005       pdata->n_items = g_list_length (pdata->items);
1006       pdata->loaded_items = 0;
1007     }
1008
1009   info = g_list_nth_data (pdata->items, pdata->loaded_items);
1010   item = gtk_recent_chooser_menu_create_item (pdata->menu,
1011                                               info,
1012                                               pdata->displayed_items);
1013   if (!item)
1014     goto check_and_return;
1015       
1016   gtk_recent_chooser_menu_add_tip (pdata->menu, info, item);
1017   gtk_recent_chooser_menu_insert_item (pdata->menu, item,
1018                                        pdata->displayed_items);
1019   
1020   pdata->displayed_items += 1;
1021       
1022   /* mark the menu item as one of our own */
1023   g_object_set_data (G_OBJECT (item),
1024                      "gtk-recent-menu-mark",
1025                      GINT_TO_POINTER (TRUE));
1026       
1027   /* attach the RecentInfo object to the menu item, and own a reference
1028    * to it, so that it will be destroyed with the menu item when it's
1029    * not needed anymore.
1030    */
1031   g_object_set_data_full (G_OBJECT (item), "gtk-recent-info",
1032                           gtk_recent_info_ref (info),
1033                           (GDestroyNotify) gtk_recent_info_unref);
1034   
1035 check_and_return:
1036   pdata->loaded_items += 1;
1037
1038   if (pdata->loaded_items == pdata->n_items)
1039     {
1040       g_list_foreach (pdata->items, (GFunc) gtk_recent_info_unref, NULL);
1041       g_list_free (pdata->items);
1042
1043       priv->populate_id = 0;
1044
1045       retval = FALSE;
1046     }
1047   else
1048     retval = TRUE;
1049
1050   return retval;
1051 }
1052
1053 static void
1054 idle_populate_clean_up (gpointer data)
1055 {
1056   MenuPopulateData *pdata = data;
1057
1058   if (pdata->menu->priv->populate_id == 0)
1059     {
1060       /* show the placeholder in case no item survived
1061        * the filtering process in the idle loop
1062        */
1063       if (!pdata->displayed_items)
1064         gtk_widget_show (pdata->placeholder);
1065       g_object_unref (pdata->placeholder);
1066
1067       g_slice_free (MenuPopulateData, data);
1068     }
1069 }
1070
1071 static void
1072 gtk_recent_chooser_menu_populate (GtkRecentChooserMenu *menu)
1073 {
1074   MenuPopulateData *pdata;
1075   GtkRecentChooserMenuPrivate *priv = menu->priv;
1076
1077   if (menu->priv->populate_id)
1078     return;
1079
1080   pdata = g_slice_new (MenuPopulateData);
1081   pdata->items = NULL;
1082   pdata->n_items = 0;
1083   pdata->loaded_items = 0;
1084   pdata->displayed_items = 0;
1085   pdata->menu = menu;
1086   pdata->placeholder = g_object_ref (priv->placeholder);
1087
1088   priv->icon_size = get_icon_size_for_widget (GTK_WIDGET (menu));
1089   
1090   /* remove our menu items first */
1091   gtk_recent_chooser_menu_dispose_items (menu);
1092   
1093   priv->populate_id = gdk_threads_add_idle_full (G_PRIORITY_HIGH_IDLE + 30,
1094                                                  idle_populate_func,
1095                                                  pdata,
1096                                                  idle_populate_clean_up);
1097 }
1098
1099 /* bounce activate signal from the recent menu item widget 
1100  * to the recent menu widget
1101  */
1102 static void
1103 item_activate_cb (GtkWidget *widget,
1104                   gpointer   user_data)
1105 {
1106   GtkRecentChooser *chooser = GTK_RECENT_CHOOSER (user_data);
1107   
1108   _gtk_recent_chooser_item_activated (chooser);
1109 }
1110
1111 /* we force a redraw if the manager changes when we are showing */
1112 static void
1113 manager_changed_cb (GtkRecentManager *manager,
1114                     gpointer          user_data)
1115 {
1116   GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (user_data);
1117
1118   gtk_recent_chooser_menu_populate (menu);
1119 }
1120
1121 static void
1122 set_recent_manager (GtkRecentChooserMenu *menu,
1123                     GtkRecentManager     *manager)
1124 {
1125   GtkRecentChooserMenuPrivate *priv = menu->priv;
1126
1127   if (priv->manager)
1128     {
1129       if (priv->manager_changed_id)
1130         {
1131           g_signal_handler_disconnect (priv->manager, priv->manager_changed_id);
1132           priv->manager_changed_id = 0;
1133         }
1134
1135       if (priv->populate_id)
1136         {
1137           g_source_remove (priv->populate_id);
1138           priv->populate_id = 0;
1139         }
1140
1141       priv->manager = NULL;
1142     }
1143   
1144   if (manager)
1145     priv->manager = manager;
1146   else
1147     priv->manager = gtk_recent_manager_get_default ();
1148   
1149   if (priv->manager)
1150     priv->manager_changed_id = g_signal_connect (priv->manager, "changed",
1151                                                  G_CALLBACK (manager_changed_cb),
1152                                                  menu);
1153 }
1154
1155 static gint
1156 get_icon_size_for_widget (GtkWidget *widget)
1157 {
1158   GtkSettings *settings;
1159   gint width, height;
1160
1161   if (gtk_widget_has_screen (widget))
1162     settings = gtk_settings_get_for_screen (gtk_widget_get_screen (widget));
1163   else
1164     settings = gtk_settings_get_default ();
1165
1166   if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_MENU,
1167                                          &width, &height))
1168     return MAX (width, height);
1169
1170   return FALLBACK_ICON_SIZE;
1171 }
1172
1173 static void
1174 foreach_set_shot_tips (GtkWidget *widget,
1175                        gpointer   user_data)
1176 {
1177   GtkRecentChooserMenu *menu = user_data;
1178   GtkRecentChooserMenuPrivate *priv = menu->priv;
1179   gboolean has_mark;
1180
1181   /* toggle the tooltip only on the items we create */
1182   has_mark =
1183     GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "gtk-recent-menu-mark"));
1184
1185   if (has_mark)
1186     gtk_widget_set_has_tooltip (widget, priv->show_tips);
1187 }
1188
1189 static void
1190 gtk_recent_chooser_menu_set_show_tips (GtkRecentChooserMenu *menu,
1191                                        gboolean              show_tips)
1192 {
1193   GtkRecentChooserMenuPrivate *priv = menu->priv;
1194
1195   if (priv->show_tips == show_tips)
1196     return;
1197   
1198   priv->show_tips = show_tips;
1199   gtk_container_foreach (GTK_CONTAINER (menu), foreach_set_shot_tips, menu);
1200 }
1201
1202 static void
1203 gtk_recent_chooser_update (GtkActivatable *activatable,
1204                            GtkAction      *action,
1205                            const gchar    *property_name)
1206 {
1207   if (strcmp (property_name, "sensitive") == 0)
1208     gtk_widget_set_sensitive (GTK_WIDGET (activatable), gtk_action_is_sensitive (action));
1209
1210   _gtk_recent_chooser_update (activatable, action, property_name);
1211 }
1212
1213 static void
1214 gtk_recent_chooser_sync_action_properties (GtkActivatable *activatable,
1215                                            GtkAction      *action)
1216 {
1217   gtk_widget_set_sensitive (GTK_WIDGET (activatable), gtk_action_is_sensitive (action));
1218
1219   _gtk_recent_chooser_sync_action_properties (activatable, action);
1220 }
1221
1222
1223 /*
1224  * Public API
1225  */
1226
1227 /**
1228  * gtk_recent_chooser_menu_new:
1229  *
1230  * Creates a new #GtkRecentChooserMenu widget.
1231  *
1232  * This kind of widget shows the list of recently used resources as
1233  * a menu, each item as a menu item.  Each item inside the menu might
1234  * have an icon, representing its MIME type, and a number, for mnemonic
1235  * access.
1236  *
1237  * This widget implements the #GtkRecentChooser interface.
1238  *
1239  * This widget creates its own #GtkRecentManager object.  See the
1240  * gtk_recent_chooser_menu_new_for_manager() function to know how to create
1241  * a #GtkRecentChooserMenu widget bound to another #GtkRecentManager object.
1242  *
1243  * Return value: a new #GtkRecentChooserMenu
1244  *
1245  * Since: 2.10
1246  */
1247 GtkWidget *
1248 gtk_recent_chooser_menu_new (void)
1249 {
1250   return g_object_new (GTK_TYPE_RECENT_CHOOSER_MENU,
1251                        "recent-manager", NULL,
1252                        NULL);
1253 }
1254
1255 /**
1256  * gtk_recent_chooser_menu_new_for_manager:
1257  * @manager: a #GtkRecentManager
1258  *
1259  * Creates a new #GtkRecentChooserMenu widget using @manager as
1260  * the underlying recently used resources manager.
1261  *
1262  * This is useful if you have implemented your own recent manager,
1263  * or if you have a customized instance of a #GtkRecentManager
1264  * object or if you wish to share a common #GtkRecentManager object
1265  * among multiple #GtkRecentChooser widgets.
1266  *
1267  * Return value: a new #GtkRecentChooserMenu, bound to @manager.
1268  *
1269  * Since: 2.10
1270  */
1271 GtkWidget *
1272 gtk_recent_chooser_menu_new_for_manager (GtkRecentManager *manager)
1273 {
1274   g_return_val_if_fail (manager == NULL || GTK_IS_RECENT_MANAGER (manager), NULL);
1275   
1276   return g_object_new (GTK_TYPE_RECENT_CHOOSER_MENU,
1277                        "recent-manager", manager,
1278                        NULL);
1279 }
1280
1281 /**
1282  * gtk_recent_chooser_menu_get_show_numbers:
1283  * @menu: a #GtkRecentChooserMenu
1284  *
1285  * Returns the value set by gtk_recent_chooser_menu_set_show_numbers().
1286  * 
1287  * Return value: %TRUE if numbers should be shown.
1288  *
1289  * Since: 2.10
1290  */
1291 gboolean
1292 gtk_recent_chooser_menu_get_show_numbers (GtkRecentChooserMenu *menu)
1293 {
1294   g_return_val_if_fail (GTK_IS_RECENT_CHOOSER_MENU (menu), FALSE);
1295
1296   return menu->priv->show_numbers;
1297 }
1298
1299 /**
1300  * gtk_recent_chooser_menu_set_show_numbers:
1301  * @menu: a #GtkRecentChooserMenu
1302  * @show_numbers: whether to show numbers
1303  *
1304  * Sets whether a number should be added to the items of @menu.  The
1305  * numbers are shown to provide a unique character for a mnemonic to
1306  * be used inside ten menu item's label.  Only the first the items
1307  * get a number to avoid clashes.
1308  *
1309  * Since: 2.10
1310  */
1311 void
1312 gtk_recent_chooser_menu_set_show_numbers (GtkRecentChooserMenu *menu,
1313                                           gboolean              show_numbers)
1314 {
1315   g_return_if_fail (GTK_IS_RECENT_CHOOSER_MENU (menu));
1316
1317   if (menu->priv->show_numbers == show_numbers)
1318     return;
1319
1320   menu->priv->show_numbers = show_numbers;
1321   g_object_notify (G_OBJECT (menu), "show-numbers");
1322 }