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