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