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