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