]> Pileus Git - ~andy/gtk/blob - gtk/gtkrecentmanager.c
Fix 336772
[~andy/gtk] / gtk / gtkrecentmanager.c
1 /* GTK - The GIMP Toolkit
2  * gtkrecentmanager.c: a manager for the recently used resources
3  *
4  * Copyright (C) 2006 Emmanuele Bassi
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  */
20
21 #include "config.h"
22
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #ifdef HAVE_UNISTD_H
26 #include <unistd.h>
27 #endif
28 #include <errno.h>
29 #include <string.h>
30 #include <stdlib.h>
31 #include <glib.h>
32 #include <glib/gstdio.h>
33
34 #include "gtkrecentmanager.h"
35 #include "gtkintl.h"
36 #include "gtkstock.h"
37 #include "gtkicontheme.h"
38 #include "gtktypebuiltins.h"
39 #include "gtkprivate.h"
40 #include "gtkmarshalers.h"
41 #include "gtkalias.h"
42
43 #ifdef G_OS_UNIX
44 #define XDG_PREFIX _gtk_xdg
45 #include "xdgmime/xdgmime.h"
46 #endif
47
48 /* the file where we store the recently used items */
49 #define GTK_RECENTLY_USED_FILE  ".recently-used.xbel"
50
51 /* a poll per second should be enough */
52 #define POLL_DELTA      1000
53
54 /* return all items by default */
55 #define DEFAULT_LIMIT   -1
56
57 /* keep in sync with xdgmime */
58 #define GTK_RECENT_DEFAULT_MIME "application/octet-stream"
59
60 typedef struct
61 {
62   gchar *name;
63   gchar *exec;
64   
65   guint count;
66   
67   time_t stamp;
68 } RecentAppInfo;
69
70 struct _GtkRecentInfo
71 {
72   gchar *uri;
73   
74   gchar *display_name;
75   gchar *description;
76   
77   time_t added;
78   time_t modified;
79   time_t visited;
80   
81   gchar *mime_type;
82   
83   GSList *applications;
84   GHashTable *apps_lookup;
85   
86   GSList *groups;
87   
88   gboolean is_private;
89   
90   GdkPixbuf *icon;
91   
92   gint ref_count;
93 };
94
95 struct _GtkRecentManagerPrivate
96 {
97   gchar *filename;
98
99   guint is_screen_singleton : 1;
100   guint is_dirty : 1;
101   guint write_in_progress : 1;
102   guint read_in_progress : 1;
103   
104   gint limit;
105   gint size;
106
107   GdkScreen *screen;
108   
109   GBookmarkFile *recent_items;
110   
111   time_t last_mtime;
112   guint poll_timeout;
113 };
114
115 enum
116 {
117   PROP_0,
118
119   PROP_FILENAME,  
120   PROP_LIMIT,
121   PROP_SIZE
122 };
123
124 static void           gtk_recent_manager_finalize     (GObject               *object);
125
126 static void           gtk_recent_manager_set_property (GObject               *object,
127                                                        guint                  prop_id,
128                                                        const GValue          *value,
129                                                        GParamSpec            *pspec);
130 static void           gtk_recent_manager_get_property (GObject               *object,
131                                                        guint                  prop_id,
132                                                        GValue                *value,
133                                                        GParamSpec            *pspec);
134 static void           gtk_recent_manager_changed      (GtkRecentManager      *manager);
135
136 static void           gtk_recent_manager_real_changed (GtkRecentManager      *manager);
137 static gboolean       gtk_recent_manager_poll_timeout (gpointer               data);
138 static void           gtk_recent_manager_set_filename (GtkRecentManager      *manager,
139                                                        const gchar           *filename);
140
141 static void           build_recent_items_list         (GtkRecentManager      *manager);
142 static void           purge_recent_items_list         (GtkRecentManager      *manager,
143                                                        GError               **error);
144
145 static RecentAppInfo *recent_app_info_new             (const gchar           *app_name);
146 static void           recent_app_info_free            (RecentAppInfo         *app_info);
147
148 static GtkRecentInfo *gtk_recent_info_new             (const gchar           *uri);
149 static void           gtk_recent_info_free            (GtkRecentInfo         *recent_info);
150
151 static guint signal_changed = 0;
152
153 G_DEFINE_TYPE (GtkRecentManager, gtk_recent_manager, G_TYPE_OBJECT);
154
155 static void
156 filename_warning (const gchar *format, 
157                   const gchar *filename, 
158                   const gchar *message)
159 {
160   gchar *utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
161   g_warning (format, utf8 ? utf8 : "(invalid filename)", message);
162   g_free (utf8);
163 }
164
165 /* Test of haystack has the needle prefix, comparing case
166  * insensitive. haystack may be UTF-8, but needle must
167  * contain only lowercase ascii. */
168 static gboolean
169 has_case_prefix (const gchar *haystack, 
170                  const gchar *needle)
171 {
172   const gchar *h, *n;
173
174   /* Eat one character at a time. */
175   h = haystack;
176   n = needle;
177
178   while (*n && *h && *n == g_ascii_tolower (*h))
179     {
180       n++;
181       h++;
182     }
183
184   return *n == '\0';
185 }
186
187 GQuark
188 gtk_recent_manager_error_quark (void)
189 {
190   return g_quark_from_static_string ("gtk-recent-manager-error-quark");
191 }
192
193
194 static void
195 gtk_recent_manager_class_init (GtkRecentManagerClass *klass)
196 {
197   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
198   
199   gtk_recent_manager_parent_class = g_type_class_peek_parent (klass);
200   
201   gobject_class->set_property = gtk_recent_manager_set_property;
202   gobject_class->get_property = gtk_recent_manager_get_property;
203   gobject_class->finalize = gtk_recent_manager_finalize;
204   
205   /**
206    * GtkRecentManager:filename
207    *
208    * The full path to the file to be used to store and read the recently
209    * used resources list
210    *
211    * Since: 2.10
212    */
213   g_object_class_install_property (gobject_class,
214                                    PROP_FILENAME,
215                                    g_param_spec_string ("filename",
216                                                         P_("Filename"),
217                                                         P_("The full path to the file to be used to store and read the list"),
218                                                         NULL,
219                                                         (G_PARAM_CONSTRUCT_ONLY | G_PARAM_READABLE | G_PARAM_WRITABLE)));
220   /**
221    * GtkRecentManager:limit
222    *
223    * The maximum number of items to be returned by the
224    * gtk_recent_manager_get_items() function.
225    *
226    * Since: 2.10
227    */
228   g_object_class_install_property (gobject_class,
229                                    PROP_LIMIT,
230                                    g_param_spec_int ("limit",
231                                                      P_("Limit"),
232                                                      P_("The maximum number of items to be returned by gtk_recent_manager_get_items()"),
233                                                      -1,
234                                                      G_MAXINT,
235                                                      DEFAULT_LIMIT,
236                                                      G_PARAM_READWRITE));
237   /**
238    * GtkRecentManager:size
239    * 
240    * The size of the recently used resources list.
241    *
242    * Since: 2.10
243    */
244   g_object_class_install_property (gobject_class,
245                                    PROP_SIZE,
246                                    g_param_spec_int ("size",
247                                                      P_("Size"),
248                                                      P_("The size of the recently used resources list"),
249                                                      -1,
250                                                      G_MAXINT,
251                                                      0,
252                                                      G_PARAM_READABLE));
253   
254   /**
255    * GtkRecentManager::changed
256    * @recent_manager: the recent manager
257    *
258    * Emitted when the current recently used resources manager changes its
259    * contents.
260    *
261    * Since: 2.10
262    */
263   signal_changed =
264     g_signal_new (I_("changed"),
265                   G_TYPE_FROM_CLASS (klass),
266                   G_SIGNAL_RUN_FIRST,
267                   G_STRUCT_OFFSET (GtkRecentManagerClass, changed),
268                   NULL, NULL,
269                   g_cclosure_marshal_VOID__VOID,
270                   G_TYPE_NONE, 0);
271   
272   klass->changed = gtk_recent_manager_real_changed;
273   
274   g_type_class_add_private (klass, sizeof (GtkRecentManagerPrivate));
275 }
276
277 static void
278 gtk_recent_manager_init (GtkRecentManager *manager)
279 {
280   GtkRecentManagerPrivate *priv;
281   
282   priv = g_type_instance_get_private ((GTypeInstance *) manager,
283                                       GTK_TYPE_RECENT_MANAGER);
284   manager->priv = priv;
285   
286   priv->filename = g_build_filename (g_get_home_dir (),
287                                      GTK_RECENTLY_USED_FILE,
288                                      NULL);
289   
290   priv->limit = DEFAULT_LIMIT;
291   priv->size = 0;
292   
293   priv->is_screen_singleton = FALSE;
294   priv->is_dirty = FALSE;
295   priv->write_in_progress = FALSE;
296   priv->read_in_progress = FALSE;
297
298   priv->screen = NULL;
299
300   build_recent_items_list (manager);
301 }
302
303 static void
304 gtk_recent_manager_set_property (GObject               *object,
305                                  guint                  prop_id,
306                                  const GValue          *value,
307                                  GParamSpec            *pspec)
308 {
309   GtkRecentManager *recent_manager = GTK_RECENT_MANAGER (object);
310  
311   switch (prop_id)
312     {
313     case PROP_FILENAME:
314       gtk_recent_manager_set_filename (recent_manager, g_value_get_string (value));
315       break;      
316     case PROP_LIMIT:
317       gtk_recent_manager_set_limit (recent_manager, g_value_get_int (value));
318       break;
319     default:
320       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
321       break;
322     }
323 }
324
325 static void
326 gtk_recent_manager_get_property (GObject               *object,
327                                  guint                  prop_id,
328                                  GValue                *value,
329                                  GParamSpec            *pspec)
330 {
331   GtkRecentManager *recent_manager = GTK_RECENT_MANAGER (object);
332   
333   switch (prop_id)
334     {
335     case PROP_FILENAME:
336       g_value_set_string (value, recent_manager->priv->filename);
337       break;
338     case PROP_LIMIT:
339       g_value_set_int (value, recent_manager->priv->limit);
340       break;
341     case PROP_SIZE:
342       g_value_set_int (value, recent_manager->priv->size);
343       break;
344     default:
345       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
346       break;
347     }
348
349
350 static void
351 gtk_recent_manager_finalize (GObject *object)
352 {
353   GtkRecentManager *manager = GTK_RECENT_MANAGER (object);
354   GtkRecentManagerPrivate *priv = manager->priv;
355
356   /* remove the poll timeout */
357   if (priv->poll_timeout)
358     g_source_remove (priv->poll_timeout);
359   
360   if (priv->filename)
361     g_free (priv->filename);
362   
363   if (priv->recent_items)
364     g_bookmark_file_free (priv->recent_items);
365   
366   /* chain up parent's finalize method */  
367   G_OBJECT_CLASS (gtk_recent_manager_parent_class)->finalize (object);
368 }
369
370 static void
371 gtk_recent_manager_real_changed (GtkRecentManager *manager)
372 {
373   GtkRecentManagerPrivate *priv = manager->priv;
374
375   g_object_freeze_notify (G_OBJECT (manager));
376
377   if (priv->is_dirty)
378     {
379       GError *write_error;
380       struct stat stat_buf;
381       
382       /* we are marked as dirty, so we dump the content of our
383        * recently used items list
384        */
385       g_assert (priv->filename != NULL);
386
387       priv->write_in_progress = TRUE;
388
389       /* if no container object has been defined, we create a new
390        * empty container, and dump it
391        */
392       if (!priv->recent_items)
393         {
394           priv->recent_items = g_bookmark_file_new ();
395           priv->size = 0;
396         }
397
398       write_error = NULL;
399       g_bookmark_file_to_file (priv->recent_items,
400                                priv->filename,
401                                &write_error);
402       if (write_error)
403         {
404           filename_warning ("Attempting to store changes into `%s', "
405                             "but failed: %s",
406                             priv->filename,
407                             write_error->message);
408           g_error_free (write_error);
409         }
410
411       /* we have sync'ed our list with the storage file, so we
412        * update the file mtime in order to skip the timed check
413        * and spare us from a re-read.
414        */
415       if (g_stat (priv->filename, &stat_buf) < 0)
416         {
417           filename_warning ("Unable to stat() the recently used resources file "
418                             "at `%s': %s.",
419                             priv->filename,
420                             g_strerror (errno));
421
422           priv->write_in_progress = FALSE;
423           
424           g_object_thaw_notify (G_OBJECT (manager));
425
426           return;
427         }
428       
429       priv->last_mtime = stat_buf.st_mtime;
430       
431       /* mark us as clean */
432       priv->is_dirty = FALSE;
433     }
434   else
435     {
436       /* we are not marked as dirty, so we have been called
437        * because the recently used resources file has been
438        * changed (and not from us).
439        */
440       build_recent_items_list (manager);
441     }
442
443   g_object_thaw_notify (G_OBJECT (manager));
444 }
445
446 /* timed poll()-ing of the recently used resources file.
447  * an event-based system would be more efficient.
448  */
449 static gboolean
450 gtk_recent_manager_poll_timeout (gpointer data)
451 {
452   GtkRecentManager *manager = GTK_RECENT_MANAGER (data);
453   GtkRecentManagerPrivate *priv = manager->priv;
454   struct stat stat_buf;
455   int stat_res;
456
457   /* wait for the next timeout if we have a read/write in progress */
458   if (priv->write_in_progress || priv->read_in_progress)
459     return TRUE;
460
461   stat_res = g_stat (priv->filename, &stat_buf);
462   if (stat_res < 0)
463     {
464       /* the file does not exist, yet, so we wait */
465       if (errno == ENOENT)
466         return TRUE;
467       
468       filename_warning ("Unable to stat() the recently used resources file "
469                         "at `%s': %s.",
470                         priv->filename,
471                         g_strerror (errno));
472       
473       return TRUE;
474     }
475
476   /* the file didn't change from the last poll(), so we bail out */
477   if (stat_buf.st_mtime == priv->last_mtime)
478     return TRUE;
479
480   /* the file has been changed, hence we emit the "changed" signal */
481   gtk_recent_manager_changed (manager);
482
483   return TRUE;
484 }
485
486 static void
487 gtk_recent_manager_set_filename (GtkRecentManager *manager,
488                                  const gchar      *filename)
489 {
490   GtkRecentManagerPrivate *priv;
491   
492   g_assert (GTK_IS_RECENT_MANAGER (manager));
493   priv = manager->priv;
494   
495   if (!filename || filename[0] == '\0')
496     return;
497   
498   g_free (manager->priv->filename);
499
500   if (manager->priv->poll_timeout)
501     {
502       g_source_remove (manager->priv->poll_timeout);
503       manager->priv->poll_timeout = 0;
504     }
505
506   priv->filename = g_strdup (filename);
507   priv->poll_timeout = g_timeout_add (POLL_DELTA,
508                                       gtk_recent_manager_poll_timeout,
509                                       manager);
510
511   /* mark us clean, so that we can re-read the list
512    * of recently used resources
513    */
514   priv->is_dirty = FALSE;
515   build_recent_items_list (manager);
516 }
517
518 /* reads the recently used resources file and builds the items list.
519  * we keep the items list inside the parser object, and build the
520  * RecentInfo object only on user's demand to avoid useless replication.
521  * this function resets the dirty bit of the manager.
522  */
523 static void
524 build_recent_items_list (GtkRecentManager *manager)
525 {
526   GtkRecentManagerPrivate *priv;
527   struct stat stat_buf;
528   int stat_res;
529   gboolean res;
530   GError *read_error;
531   gint size;
532
533   priv = manager->priv;
534   g_assert (priv->filename != NULL);
535   
536   if (!priv->recent_items)
537     {
538       priv->recent_items = g_bookmark_file_new ();
539       priv->size = 0;
540     }
541
542   stat_res = g_stat (priv->filename, &stat_buf);
543   if (stat_res < 0)
544     {
545       /* the file doesn't exists, so we bail out and wait for the first
546        * write operation
547        */
548
549       if (errno == ENOENT)
550         return;
551       else
552         {
553           filename_warning ("Attempting to read the recently used resources file "
554                             "at `%s', but an error occurred: %s. Aborting.",
555                             priv->filename,
556                             g_strerror (errno));
557
558           return;
559         }
560     }
561
562   /* record the last mtime, for later use */
563   priv->last_mtime = stat_buf.st_mtime;
564   
565   priv->read_in_progress = TRUE;
566
567   /* the file exists, and it's valid (we hope); if not, destroy the container
568    * object and hope for a better result when the next "changed" signal is
569    * fired. */
570   read_error = NULL;
571   res = g_bookmark_file_load_from_file (priv->recent_items,
572                                         priv->filename,
573                                         &read_error);
574   if (read_error)
575     {
576       filename_warning ("Attempting to read the recently used resources file "
577                         "at `%s', but the parser failed: %s.",
578                         priv->filename,
579                         read_error->message);
580
581       g_bookmark_file_free (priv->recent_items);
582       priv->recent_items = NULL;
583
584       g_error_free (read_error);
585     }
586
587   size = g_bookmark_file_get_size (priv->recent_items);
588   if (priv->size != size)
589     {
590       priv->size = size;
591       
592       g_object_notify (G_OBJECT (manager), "size");
593     }
594
595   priv->read_in_progress = FALSE;
596   priv->is_dirty = FALSE;
597 }
598
599
600 /********************
601  * GtkRecentManager *
602  ********************/
603
604
605 /**
606  * gtk_recent_manager_new:
607  * 
608  * Creates a new recent manager object.  Recent manager objects are used to
609  * handle the list of recently used resources.  A #GtkRecentManager object
610  * monitors the recently used resources list, and emits the "changed" signal
611  * each time something inside the list changes.
612  *
613  * #GtkRecentManager objects are expansive: be sure to create them only when
614  * needed. You should use the gtk_recent_manager_new_for_screen() or the
615  * gtk_recent_manager_get_default() functions instead.
616  *
617  * Return value: A newly created #GtkRecentManager object.
618  *
619  * Since: 2.10
620  */
621 GtkRecentManager *
622 gtk_recent_manager_new (void)
623 {
624   return g_object_new (GTK_TYPE_RECENT_MANAGER, NULL);
625 }
626
627 /**
628  * gtk_recent_manager_get_default:
629  *
630  * Gets the recent manager for the default screen. See
631  * gtk_recent_manager_get_for_screen().
632  *
633  * Return value: A unique #GtkRecentManager associated with the
634  *   default screen. This recent manager is associated to the
635  *   screen and can be used as long as the screen is open.
636  *   Do no ref or unref it.
637  *
638  * Since: 2.10
639  */
640 GtkRecentManager *
641 gtk_recent_manager_get_default (void)
642 {
643   return gtk_recent_manager_get_for_screen (gdk_screen_get_default ());
644 }
645
646 /**
647  * gtk_recent_manager_get_for_screen:
648  * @screen: a #GdkScreen
649  *
650  * Gets the recent manager object associated with @screen; if this
651  * function has not previously been called for the given screen,
652  * a new recent manager object will be created and associated with
653  * the screen. Recent manager objects are fairly expensive to create,
654  * so using this function is usually a better choice than calling 
655  * gtk_recent_manager_new() and setting the screen yourself; by using
656  * this function a single recent manager object will be shared between
657  * users.
658  *
659  * Return value: A unique #GtkRecentManager associated with the given
660  *   screen. This recent manager is associated to the with the screen
661  *   and can be used as long as the screen is open. Do not ref or
662  *   unref it.
663  *
664  * Since: 2.10
665  */
666 GtkRecentManager *
667 gtk_recent_manager_get_for_screen (GdkScreen *screen)
668 {
669   GtkRecentManager *manager;
670
671   g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
672   g_return_val_if_fail (!screen->closed, NULL);
673
674   manager = g_object_get_data (G_OBJECT (screen), "gtk-recent-manager-default");
675   if (!manager)
676     {
677       GtkRecentManagerPrivate *priv;
678       
679       manager = gtk_recent_manager_new ();
680       gtk_recent_manager_set_screen (manager, screen);
681
682       priv = manager->priv;
683       priv->is_screen_singleton = TRUE;
684
685       g_object_set_data (G_OBJECT (screen), I_("gtk-recent-manager-default"), manager);
686     }
687
688   return manager;
689 }
690
691 static void
692 display_closed (GdkDisplay       *display,
693                 gboolean          is_error,
694                 GtkRecentManager *manager)
695 {
696   GtkRecentManagerPrivate *priv = manager->priv;
697   GdkScreen *screen = priv->screen;
698   gboolean was_screen_singleton = priv->is_screen_singleton;
699
700   if (was_screen_singleton)
701     {
702       g_object_set_data (G_OBJECT (screen), I_("gtk-recent-manager-default"), NULL);
703       priv->is_screen_singleton = FALSE;
704     }
705
706   gtk_recent_manager_set_screen (manager, NULL);
707
708   if (was_screen_singleton)
709     g_object_unref (manager);
710 }
711
712 static void
713 unset_screen (GtkRecentManager *manager)
714 {
715   GtkRecentManagerPrivate *priv = manager->priv;
716   GdkDisplay *display;
717
718   if (priv->screen)
719     {
720       display = gdk_screen_get_display (priv->screen);
721
722       g_signal_handlers_disconnect_by_func (display,
723                                             (gpointer) display_closed,
724                                             manager);
725
726       priv->screen = NULL;
727     }
728 }
729
730 /**
731  * gtk_recent_manager_set_screen:
732  * @manager: a #GtkRecentManager
733  * @screen: a #GdkScreen
734  *
735  * Sets the screen for a recent manager; the screen is used to
736  * track the user's currently configured recently used documents
737  * storage.
738  * 
739  * Since: 2.10
740  */
741 void
742 gtk_recent_manager_set_screen (GtkRecentManager *manager,
743                                GdkScreen        *screen)
744 {
745   GtkRecentManagerPrivate *priv;
746   GdkDisplay *display;
747
748   g_return_if_fail (GTK_IS_RECENT_MANAGER (manager));
749   g_return_if_fail (screen == NULL || GDK_IS_SCREEN (screen));
750
751   priv = manager->priv;
752
753   unset_screen (manager);
754
755   if (screen)
756     {
757       display = gdk_screen_get_display (screen);
758
759       priv->screen = screen;
760
761       g_signal_connect (display, "closed",
762                         G_CALLBACK (display_closed), manager);
763     }
764 }
765
766 /**
767  * gtk_recent_manager_set_limit:
768  * @manager: a #GtkRecentManager
769  * @limit: the maximum number of items to return, or -1.
770  *
771  * Sets the maximum number of item that the gtk_recent_manager_get_items()
772  * function should return.  If @limit is set to -1, then return all the
773  * items.
774  *
775  * Since: 2.10
776  */
777 void
778 gtk_recent_manager_set_limit (GtkRecentManager *manager,
779                               gint              limit)
780 {
781   GtkRecentManagerPrivate *priv;
782   
783   g_return_if_fail (GTK_IS_RECENT_MANAGER (manager));
784   
785   priv = manager->priv;
786   priv->limit = limit;
787 }
788
789 /**
790  * gtk_recent_manager_get_limit:
791  * @manager: a #GtkRecentManager
792  *
793  * Gets the maximum number of items that the gtk_recent_manager_get_items()
794  * function should return.
795  *
796  * Return value: the number of items to return, or -1 for every item.
797  *
798  * Since: 2.10
799  */
800 gint
801 gtk_recent_manager_get_limit (GtkRecentManager *manager)
802 {
803   GtkRecentManagerPrivate *priv;
804   
805   g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), DEFAULT_LIMIT);
806   
807   priv = manager->priv;
808   return priv->limit;
809 }
810
811 /**
812  * gtk_recent_manager_add_item:
813  * @manager: a #GtkRecentManager
814  * @uri: a valid URI
815  * @error: return location for a #GError, or %NULL
816  *
817  * Adds a new resource, pointed by @uri, into the recently used
818  * resources list.
819  *
820  * This function automatically retrieving some of the needed
821  * metadata and setting other metadata to common default values; it
822  * then feeds the data to gtk_recent_manager_add_full().
823  *
824  * See gtk_recent_manager_add_full() if you want to explicitely
825  * define the metadata for the resource pointed by @uri.
826  *
827  * Return value: %TRUE if the new item was successfully added
828  *   to the recently used resources list
829  *
830  * Since: 2.10
831  */
832 gboolean
833 gtk_recent_manager_add_item (GtkRecentManager  *manager,
834                              const gchar       *uri,
835                              GError           **error)
836 {
837   GtkRecentData *recent_data;
838   GError *add_error;
839   gboolean retval;
840   
841   g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), FALSE);
842   g_return_val_if_fail (uri != NULL, FALSE);
843
844   recent_data = g_slice_new (GtkRecentData);
845   
846   recent_data->display_name = NULL;
847   recent_data->description = NULL;
848   
849 #ifdef G_OS_UNIX
850   if (has_case_prefix (uri, "file:/"))
851     {
852       gchar *filename;
853       const gchar *mime_type;
854       
855       filename = g_filename_from_uri (uri, NULL, NULL);
856       mime_type = xdg_mime_get_mime_type_for_file (filename, NULL);
857       if (!mime_type)
858         recent_data->mime_type = g_strdup (GTK_RECENT_DEFAULT_MIME);
859       else
860         recent_data->mime_type = g_strdup (mime_type);
861       
862       g_free (filename);
863     }
864   else
865 #endif
866     recent_data->mime_type = g_strdup (GTK_RECENT_DEFAULT_MIME);
867   
868   recent_data->app_name = g_strdup (g_get_application_name ());
869   recent_data->app_exec = g_strjoin (" ", g_get_prgname (), "%u", NULL);
870   
871   recent_data->groups = NULL;
872   
873   recent_data->is_private = FALSE;
874   
875   add_error = NULL;
876   retval = gtk_recent_manager_add_full (manager, uri, recent_data, &add_error);
877   
878   g_free (recent_data->mime_type);
879   g_free (recent_data->app_name);
880   g_free (recent_data->app_exec);
881
882   g_slice_free (GtkRecentData, recent_data);
883   
884   if (!retval)
885     {
886       g_propagate_error (error, add_error);
887       
888       return FALSE;
889     }
890   
891   return retval;
892 }
893
894 /**
895  * gtk_recent_manager_add_full:
896  * @manager: a #GtkRecentManager
897  * @uri: a valid URI
898  * @recent_data: metadata of the resource
899  * @error: return location for a #GError, or %NULL
900  *
901  * Adds a new resource, pointed by @uri, into the recently used
902  * resources list, using the metadata specified inside the #GtkRecentData
903  * structure passed in @recent_data.
904  *
905  * The passed URI will be used to identify this resource inside the
906  * list.
907  *
908  * In order to register the new recently used resource, metadata about
909  * the resource must be passed as well as the URI; the metadata is
910  * stored in a #GtkRecentData structure, which must contain the MIME
911  * type of the resource pointed by the URI; the name of the application
912  * that is registering the item, and a command line to be used when
913  * launching the item.
914  *
915  * Optionally, a #GtkRecentData structure might contain a UTF-8 string
916  * to be used when viewing the item instead of the last component of the
917  * URI; a short description of the item; whether the item should be
918  * considered private - that is, should be displayed only by the
919  * applications that have registered it.
920  *
921  * Return value: %TRUE if the new item was successfully added to the
922  * recently used resources list, %FALSE otherwise.
923  *
924  * Since: 2.10
925  */
926 gboolean
927 gtk_recent_manager_add_full (GtkRecentManager     *manager,
928                              const gchar          *uri,
929                              const GtkRecentData  *data,
930                              GError              **error)
931 {
932   GtkRecentManagerPrivate *priv;
933   
934   g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), FALSE);
935   g_return_val_if_fail (uri != NULL, FALSE);
936   g_return_val_if_fail (data != NULL, FALSE);
937
938   /* sanity checks */
939   if ((data->display_name) &&
940       (!g_utf8_validate (data->display_name, -1, NULL)))
941     {
942       g_set_error  (error, GTK_RECENT_MANAGER_ERROR,
943                     GTK_RECENT_MANAGER_ERROR_INVALID_ENCODING,
944                     _("The display name of the recently used resource "
945                       "must be a valid UTF-8 encoded string."));
946       return FALSE;
947     }
948   
949   if ((data->description) &&
950       (!g_utf8_validate (data->description, -1, NULL)))
951     {
952       g_set_error (error, GTK_RECENT_MANAGER_ERROR,
953                    GTK_RECENT_MANAGER_ERROR_INVALID_ENCODING,
954                    _("The description of the recently used resource "
955                      "must by a valid UTF-8 encoded string."));
956       return FALSE;
957     }
958
959  
960   if (!data->mime_type)
961     {
962       g_set_error (error, GTK_RECENT_MANAGER_ERROR,
963                    GTK_RECENT_MANAGER_ERROR_INVALID_MIME,
964                    _("You must specify the MIME type of the "
965                      "resource pointed by `%s'"),
966                    uri);
967       return FALSE;
968     }
969   
970   if (!data->app_name)
971     {
972       g_set_error (error, GTK_RECENT_MANAGER_ERROR,
973                    GTK_RECENT_MANAGER_ERROR_NOT_REGISTERED,
974                    _("You must specify the name of the application "
975                      "that is registering the recently used resource "
976                      "pointed by `%s'"),
977                    uri);
978       return FALSE;
979     }
980   
981   if (!data->app_exec)
982     {
983       g_set_error (error, GTK_RECENT_MANAGER_ERROR,
984                    GTK_RECENT_MANAGER_ERROR_BAD_EXEC_STRING,
985                    _("You must specify a command line to "
986                      "be used when launching the resource "
987                      "pointed by `%s'"),
988                    uri);
989       return FALSE;
990     }
991   
992   priv = manager->priv;
993
994   if (!priv->recent_items)
995     {
996       priv->recent_items = g_bookmark_file_new ();
997       priv->size = 0;
998     }
999
1000   if (data->display_name)  
1001     g_bookmark_file_set_title (priv->recent_items, uri, data->display_name);
1002   
1003   if (data->description)
1004     g_bookmark_file_set_description (priv->recent_items, uri, data->description);
1005
1006   g_bookmark_file_set_mime_type (priv->recent_items, uri, data->mime_type);
1007   
1008   if (data->groups && data->groups[0] != '\0')
1009     {
1010       gint j;
1011       
1012       for (j = 0; (data->groups)[j] != NULL; j++)
1013         g_bookmark_file_add_group (priv->recent_items, uri, (data->groups)[j]);
1014     }
1015   
1016   /* register the application; this will take care of updating the
1017    * registration count and time in case the application has
1018    * already registered the same document inside the list
1019    */
1020   g_bookmark_file_add_application (priv->recent_items, uri,
1021                                    data->app_name,
1022                                    data->app_exec);
1023   
1024   g_bookmark_file_set_is_private (priv->recent_items, uri,
1025                                   data->is_private);
1026   
1027   /* mark us as dirty, so that when emitting the "changed" signal we
1028    * will dump our changes
1029    */
1030   priv->is_dirty = TRUE;
1031   
1032   gtk_recent_manager_changed (manager);
1033   
1034   return TRUE;
1035 }
1036
1037 /**
1038  * gtk_recent_manager_remove_item:
1039  * @manager: a #GtkRecentManager
1040  * @uri: the URI of the item you wish to remove
1041  * @error: return location for a #GError, or %NULL
1042  *
1043  * Removes a resource pointed by @uri from the recently used resources
1044  * list handled by a recent manager.
1045  *
1046  * Return value: %TRUE if the item pointed by @uri has been successfully
1047  *   removed by the recently used resources list, and %FALSE otherwise.
1048  *
1049  * Since: 2.10
1050  */
1051 gboolean
1052 gtk_recent_manager_remove_item (GtkRecentManager  *manager,
1053                                 const gchar       *uri,
1054                                 GError           **error)
1055 {
1056   GtkRecentManagerPrivate *priv;
1057   GError *remove_error = NULL;
1058
1059   g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), FALSE);
1060   g_return_val_if_fail (uri != NULL, FALSE);
1061   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1062   
1063   priv = manager->priv;
1064   
1065   if (!priv->recent_items)
1066     {
1067       priv->recent_items = g_bookmark_file_new ();
1068       priv->size = 0;
1069
1070       g_set_error (error, GTK_RECENT_MANAGER_ERROR,
1071                    GTK_RECENT_MANAGER_ERROR_NOT_FOUND,
1072                    _("Unable to find an item with URI '%s'"),
1073                    uri);
1074
1075       return FALSE;
1076     }
1077
1078   g_bookmark_file_remove_item (priv->recent_items, uri, &remove_error);
1079   if (remove_error)
1080     {
1081       g_propagate_error (error, remove_error);
1082       
1083       return FALSE;
1084     }
1085
1086   priv->is_dirty = TRUE;
1087
1088   gtk_recent_manager_changed (manager);
1089   
1090   return TRUE;
1091 }
1092
1093 /**
1094  * gtk_recent_manager_has_item:
1095  * @manager: a #GtkRecentManager
1096  * @uri: a URI
1097  *
1098  * Checks whether there is a recently used resource registered
1099  * with @uri inside the recent manager.
1100  *
1101  * Return value: %TRUE if the resource was found, %FALSE otherwise.
1102  *
1103  * Since: 2.10
1104  */
1105 gboolean
1106 gtk_recent_manager_has_item (GtkRecentManager *manager,
1107                              const gchar      *uri)
1108 {
1109   GtkRecentManagerPrivate *priv;
1110
1111   g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), FALSE);
1112   g_return_val_if_fail (uri != NULL, FALSE);
1113
1114   priv = manager->priv;
1115   g_return_val_if_fail (priv->recent_items != NULL, FALSE);
1116
1117   return g_bookmark_file_has_item (priv->recent_items, uri);
1118 }
1119
1120 static gboolean
1121 build_recent_info (GBookmarkFile  *bookmarks,
1122                    GtkRecentInfo  *info)
1123 {
1124   gchar **apps, **groups;
1125   gsize apps_len, groups_len, i;
1126
1127   g_assert (bookmarks != NULL);
1128   g_assert (info != NULL);
1129   
1130   info->display_name = g_bookmark_file_get_title (bookmarks, info->uri, NULL);
1131   info->description = g_bookmark_file_get_description (bookmarks, info->uri, NULL);
1132   info->mime_type = g_bookmark_file_get_mime_type (bookmarks, info->uri, NULL);
1133     
1134   info->is_private = g_bookmark_file_get_is_private (bookmarks, info->uri, NULL);
1135   
1136   info->added = g_bookmark_file_get_added (bookmarks, info->uri, NULL);
1137   info->modified = g_bookmark_file_get_modified (bookmarks, info->uri, NULL);
1138   info->visited = g_bookmark_file_get_visited (bookmarks, info->uri, NULL);
1139   
1140   groups = g_bookmark_file_get_groups (bookmarks, info->uri, &groups_len, NULL);
1141   for (i = 0; i < groups_len; i++)
1142     {
1143       gchar *group_name = g_strdup (groups[i]);
1144       
1145       info->groups = g_slist_append (info->groups, group_name);
1146     }
1147
1148   g_strfreev (groups);
1149   
1150   apps = g_bookmark_file_get_applications (bookmarks, info->uri, &apps_len, NULL);
1151   for (i = 0; i < apps_len; i++)
1152     {
1153       gchar *app_name, *app_exec;
1154       guint count;
1155       time_t stamp;
1156       RecentAppInfo *app_info;
1157       gboolean res;
1158       
1159       app_name = apps[i];
1160       
1161       res = g_bookmark_file_get_app_info (bookmarks, info->uri, app_name,
1162                                           &app_exec,
1163                                           &count,
1164                                           &stamp,
1165                                           NULL);
1166       if (!res)
1167         continue;
1168       
1169       app_info = recent_app_info_new (app_name);
1170       app_info->exec = app_exec;
1171       app_info->count = count;
1172       app_info->stamp = stamp;
1173       
1174       info->applications = g_slist_append (info->applications,
1175                                            app_info);
1176       g_hash_table_replace (info->apps_lookup, app_info->name, app_info);
1177     }
1178   
1179   g_strfreev (apps);
1180   
1181   return TRUE; 
1182 }
1183
1184 /**
1185  * gtk_recent_manager_lookup_item:
1186  * @manager: a #GtkRecentManager
1187  * @uri: a URI
1188  * @error: a return location for a #GError, or %NULL
1189  *
1190  * Searches for a URI inside the recently used resources list, and
1191  * returns a structure containing informations about the resource
1192  * like its MIME type, or its display name.
1193  *
1194  * Return value: a #GtkRecentInfo structure containing information
1195  *   about the resource pointed by @uri, or %NULL if the URI was
1196  *   not registered in the recently used resources list.  Free with
1197  *   gtk_recent_info_unref().
1198  **/
1199 GtkRecentInfo *
1200 gtk_recent_manager_lookup_item (GtkRecentManager  *manager,
1201                                 const gchar       *uri,
1202                                 GError           **error)
1203 {
1204   GtkRecentManagerPrivate *priv;
1205   GtkRecentInfo *info = NULL;
1206   gboolean res;
1207   
1208   g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), NULL);
1209   g_return_val_if_fail (uri != NULL, NULL);
1210   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
1211   
1212   priv = manager->priv;
1213   if (!priv->recent_items)
1214     {
1215       priv->recent_items = g_bookmark_file_new ();
1216       priv->size = 0;
1217
1218       g_set_error (error, GTK_RECENT_MANAGER_ERROR,
1219                    GTK_RECENT_MANAGER_ERROR_NOT_FOUND,
1220                    _("Unable to find an item with URI '%s'"),
1221                    uri);
1222
1223       return NULL;
1224     }
1225   
1226   if (!g_bookmark_file_has_item (priv->recent_items, uri))
1227     {
1228       g_set_error (error, GTK_RECENT_MANAGER_ERROR,
1229                    GTK_RECENT_MANAGER_ERROR_NOT_FOUND,
1230                    _("Unable to find an item with URI '%s'"),
1231                    uri);
1232       return NULL;
1233     }
1234   
1235   info = gtk_recent_info_new (uri);
1236   g_return_val_if_fail (info != NULL, NULL);
1237   
1238   /* fill the RecentInfo structure with the data retrieved by our
1239    * parser object from the storage file 
1240    */
1241   res = build_recent_info (priv->recent_items, info);
1242   if (!res)
1243     {
1244       gtk_recent_info_free (info);
1245       
1246       return NULL;
1247     }
1248  
1249   return gtk_recent_info_ref (info);
1250 }
1251
1252 /**
1253  * gtk_recent_manager_move_item:
1254  * @manager: a #GtkRecentManager
1255  * @uri: the URI of a recently used resource
1256  * @new_uri: the new URI of the recently used resource, or %NULL to
1257  *    remove the item pointed by @uri in the list
1258  * @error: a return location for a #GError, or %NULL
1259  *
1260  * Changes the location of a recently used resource from @uri to @new_uri.
1261  * 
1262  * Please note that this function will not affect the resource pointed
1263  * by the URIs, but only the URI used in the recently used resources list.
1264  *
1265  * Return value: %TRUE on success.
1266  *
1267  * Since: 2.10
1268  */ 
1269 gboolean
1270 gtk_recent_manager_move_item (GtkRecentManager  *recent_manager,
1271                               const gchar       *uri,
1272                               const gchar       *new_uri,
1273                               GError           **error)
1274 {
1275   GtkRecentManagerPrivate *priv;
1276   GError *move_error;
1277   gboolean res;
1278   
1279   g_return_val_if_fail (GTK_IS_RECENT_MANAGER (recent_manager), FALSE);
1280   g_return_val_if_fail (uri != NULL, FALSE);
1281   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1282   
1283   priv = recent_manager->priv;
1284
1285   if (!g_bookmark_file_has_item (priv->recent_items, uri))
1286     {
1287       g_set_error (error, GTK_RECENT_MANAGER_ERROR,
1288                    GTK_RECENT_MANAGER_ERROR_NOT_FOUND,
1289                    _("Unable to find an item with URI '%s'"),
1290                    uri);
1291       return FALSE;
1292     }
1293   
1294   move_error = NULL;
1295   res = g_bookmark_file_move_item (priv->recent_items,
1296                                    uri, new_uri,
1297                                    &move_error);
1298   if (move_error)
1299     {
1300       g_propagate_error (error, move_error);
1301       return FALSE;
1302     }
1303   
1304   priv->is_dirty = TRUE;
1305
1306   gtk_recent_manager_changed (recent_manager);
1307   
1308   return TRUE;
1309 }
1310
1311 /**
1312  * gtk_recent_manager_get_items:
1313  * @manager: a #GtkRecentManager
1314  *
1315  * Gets the list of recently used resources.
1316  *
1317  * Return value: a list of newly allocated #GtkRecentInfo objects. Use
1318  *   gtk_recent_info_unref() on each item inside the list, and then
1319  *   free the list itself using g_list_free().
1320  *
1321  * Since: 2.10
1322  */
1323 GList *
1324 gtk_recent_manager_get_items (GtkRecentManager *manager)
1325 {
1326   GtkRecentManagerPrivate *priv;
1327   GList *retval = NULL;
1328   gchar **uris;
1329   gsize uris_len, i;
1330   
1331   g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), NULL);
1332   
1333   priv = manager->priv;
1334   if (!priv->recent_items)
1335     return NULL;
1336   
1337   uris = g_bookmark_file_get_uris (priv->recent_items, &uris_len);
1338   for (i = 0; i < uris_len; i++)
1339     {
1340       GtkRecentInfo *info;
1341       gboolean res;
1342       
1343       info = gtk_recent_info_new (uris[i]);
1344       res = build_recent_info (priv->recent_items, info);
1345       if (!res)
1346         {
1347           g_warning ("Unable to create a RecentInfo object for "
1348                      "item with URI `%s'",
1349                      uris[i]);
1350           gtk_recent_info_free (info);
1351           
1352           continue;
1353         }
1354       
1355       retval = g_list_prepend (retval, info);
1356     }
1357   
1358   g_strfreev (uris);
1359     
1360   /* clamp the list, if a limit is present */
1361   if ((priv->limit != -1) &&
1362       (g_list_length (retval) > priv->limit))
1363     {
1364       GList *clamp, *l;
1365       
1366       clamp = g_list_nth (retval, priv->limit - 1);
1367       
1368       if (!clamp)
1369         return retval;
1370       
1371       l = clamp->next;
1372       clamp->next = NULL;
1373       
1374       g_list_foreach (l, (GFunc) gtk_recent_info_free, NULL);
1375       g_list_free (l);
1376     }
1377   
1378   return retval;
1379 }
1380
1381 static void
1382 purge_recent_items_list (GtkRecentManager  *manager,
1383                          GError           **error)
1384 {
1385   GtkRecentManagerPrivate *priv = manager->priv;
1386
1387   if (!priv->recent_items)
1388     return;
1389   
1390   g_bookmark_file_free (priv->recent_items);
1391   priv->recent_items = NULL;
1392       
1393   priv->recent_items = g_bookmark_file_new ();
1394   priv->size = 0;
1395   priv->is_dirty = TRUE;
1396       
1397   /* emit the changed signal, to ensure that the purge is written */
1398   gtk_recent_manager_changed (manager);
1399 }
1400
1401 /**
1402  * gtk_recent_manager_purge_items:
1403  * @manager: a #GtkRecentManager
1404  * @error: a return location for a #GError, or %NULL
1405  *
1406  * Purges every item from the recently used resources list.
1407  *
1408  * Return value: the number of items that have been removed from the
1409  *   recently used resources list.
1410  *
1411  * Since: 2.10
1412  */
1413 gint
1414 gtk_recent_manager_purge_items (GtkRecentManager  *manager,
1415                                 GError           **error)
1416 {
1417   GtkRecentManagerPrivate *priv;
1418   gint count, purged;
1419   
1420   g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), -1);
1421
1422   priv = manager->priv;
1423   if (!priv->recent_items)
1424     return 0;
1425   
1426   count = g_bookmark_file_get_size (priv->recent_items);
1427   if (!count)
1428     return 0;
1429   
1430   purge_recent_items_list (manager, error);
1431   
1432   purged = count - g_bookmark_file_get_size (priv->recent_items);
1433
1434   return purged;
1435 }
1436
1437 static void
1438 gtk_recent_manager_changed (GtkRecentManager *recent_manager)
1439 {
1440   g_signal_emit (recent_manager, signal_changed, 0);
1441 }
1442
1443 /*****************
1444  * GtkRecentInfo *
1445  *****************/
1446  
1447 GType
1448 gtk_recent_info_get_type (void)
1449 {
1450   static GType info_type = 0;
1451   
1452   if (!info_type)
1453     info_type = g_boxed_type_register_static ("GtkRecentInfo",
1454                                               (GBoxedCopyFunc) gtk_recent_info_ref,
1455                                               (GBoxedFreeFunc) gtk_recent_info_unref);
1456   return info_type;
1457 }
1458
1459 static GtkRecentInfo *
1460 gtk_recent_info_new (const gchar *uri)
1461 {
1462   GtkRecentInfo *info;
1463
1464   g_assert (uri != NULL);
1465
1466   info = g_new0 (GtkRecentInfo, 1);
1467   info->uri = g_strdup (uri);
1468   
1469   info->applications = NULL;
1470   info->apps_lookup = g_hash_table_new (g_str_hash, g_str_equal);
1471   
1472   info->groups = NULL;
1473   
1474   info->ref_count = 1;
1475
1476   return info;
1477 }
1478
1479 static void
1480 gtk_recent_info_free (GtkRecentInfo *recent_info)
1481 {
1482   if (!recent_info)
1483     return;
1484
1485   g_free (recent_info->uri);
1486   g_free (recent_info->display_name);
1487   g_free (recent_info->description);
1488   g_free (recent_info->mime_type);
1489   
1490   if (recent_info->applications)
1491     {
1492       g_slist_foreach (recent_info->applications,
1493                        (GFunc) recent_app_info_free,
1494                        NULL);
1495       g_slist_free (recent_info->applications);
1496       
1497       recent_info->applications = NULL;
1498     }
1499   
1500   if (recent_info->apps_lookup)
1501     g_hash_table_destroy (recent_info->apps_lookup);
1502
1503   if (recent_info->groups)
1504     {
1505       g_slist_foreach (recent_info->groups,
1506                        (GFunc) g_free,
1507                        NULL);
1508       g_slist_free (recent_info->groups);
1509
1510       recent_info->groups = NULL;
1511     }
1512   
1513   if (recent_info->icon)
1514     g_object_unref (recent_info->icon);
1515
1516   g_free (recent_info);
1517 }
1518
1519 /**
1520  * gtk_recent_info_ref:
1521  * @info: a #GtkRecentInfo
1522  *
1523  * Increases the reference count of @recent_info by one.
1524  *
1525  * Return value: the recent info object with its reference count increased
1526  *   by one.
1527  *
1528  * Since: 2.10
1529  */
1530 GtkRecentInfo *
1531 gtk_recent_info_ref (GtkRecentInfo *info)
1532 {
1533   g_return_val_if_fail (info != NULL, NULL);
1534   g_return_val_if_fail (info->ref_count > 0, NULL);
1535   
1536   info->ref_count += 1;
1537     
1538   return info;
1539 }
1540
1541 /**
1542  * gtk_recent_info_unref:
1543  * @info: a #GtkRecentInfo
1544  *
1545  * Decreases the reference count of @info by one.  If the reference
1546  * count reaches zero, @info is deallocated, and the memory freed.
1547  *
1548  * Since: 2.10
1549  */
1550 void
1551 gtk_recent_info_unref (GtkRecentInfo *info)
1552 {
1553   g_return_if_fail (info != NULL);
1554   g_return_if_fail (info->ref_count > 0);
1555
1556   info->ref_count -= 1;
1557   
1558   if (info->ref_count == 0)
1559     gtk_recent_info_free (info);
1560 }
1561
1562 /**
1563  * gtk_recent_info_get_uri:
1564  * @info: a #GtkRecentInfo
1565  *
1566  * Gets the URI of the resource.
1567  *
1568  * Return value: the URI of the resource.  The returned string is
1569  *   owned by the recent manager, and should not be freed.
1570  *
1571  * Since: 2.10
1572  */
1573 G_CONST_RETURN gchar *
1574 gtk_recent_info_get_uri (GtkRecentInfo *info)
1575 {
1576   g_return_val_if_fail (info != NULL, NULL);
1577   
1578   return info->uri;
1579 }
1580
1581 /**
1582  * gtk_recent_info_get_display_name:
1583  * @info: a #GtkRecentInfo
1584  *
1585  * Gets the name of the resource.  If none has been defined, the basename
1586  * of the resource is obtained.
1587  *
1588  * Return value: the display name of the resource.  The returned string
1589  *   is owned by the recent manager, and should not be freed.
1590  *
1591  * Since: 2.10
1592  */
1593 G_CONST_RETURN gchar *
1594 gtk_recent_info_get_display_name (GtkRecentInfo *info)
1595 {
1596   g_return_val_if_fail (info != NULL, NULL);
1597   
1598   if (!info->display_name)
1599     info->display_name = gtk_recent_info_get_short_name (info);
1600   
1601   return info->display_name;
1602 }
1603
1604 /**
1605  * gtk_recent_info_get_description:
1606  * @info: a #GtkRecentInfo
1607  *
1608  * Gets the (short) description of the resource.
1609  *
1610  * Return value: the description of the resource.  The returned string
1611  *   is owned by the recent manager, and should not be freed.
1612  *
1613  * Since: 2.10
1614  **/
1615 G_CONST_RETURN gchar *
1616 gtk_recent_info_get_description (GtkRecentInfo *info)
1617 {
1618   g_return_val_if_fail (info != NULL, NULL);
1619   
1620   return info->description;
1621 }
1622
1623 /**
1624  * gtk_recent_info_get_mime_type:
1625  * @info: a #GtkRecentInfo
1626  *
1627  * Gets the MIME type of the resource.
1628  *
1629  * Return value: the MIME type of the resource.  The returned string
1630  *   is owned by the recent manager, and should not be freed.
1631  *
1632  * Since: 2.10
1633  */
1634 G_CONST_RETURN gchar *
1635 gtk_recent_info_get_mime_type (GtkRecentInfo *info)
1636 {
1637   g_return_val_if_fail (info != NULL, NULL);
1638   
1639   if (!info->mime_type)
1640     info->mime_type = g_strdup (GTK_RECENT_DEFAULT_MIME);
1641   
1642   return info->mime_type;
1643 }
1644
1645 /**
1646  * gtk_recent_info_get_added:
1647  * @info: a #GtkRecentInfo
1648  *
1649  * Gets the timestamp (seconds from system's Epoch) when the resource
1650  * was added to the recently used resources list.
1651  *
1652  * Return value: the number of seconds elapsed from system's Epoch when
1653  *   the resource was added to the list, or -1 on failure.
1654  *
1655  * Since: 2.10
1656  */
1657 time_t
1658 gtk_recent_info_get_added (GtkRecentInfo *info)
1659 {
1660   g_return_val_if_fail (info != NULL, (time_t) -1);
1661   
1662   return info->added;
1663 }
1664
1665 /**
1666  * gtk_recent_info_get_modified:
1667  * @info: a #GtkRecentInfo
1668  *
1669  * Gets the timestamp (seconds from system's Epoch) when the resource
1670  * was last modified.
1671  *
1672  * Return value: the number of seconds elapsed from system's Epoch when
1673  *   the resource was last modified, or -1 on failure.
1674  *
1675  * Since: 2.10
1676  */
1677 time_t
1678 gtk_recent_info_get_modified (GtkRecentInfo *info)
1679 {
1680   g_return_val_if_fail (info != NULL, (time_t) -1);
1681   
1682   return info->modified;
1683 }
1684
1685 /**
1686  * gtk_recent_info_get_visited:
1687  * @info: a #GtkRecentInfo
1688  *
1689  * Gets the timestamp (seconds from system's Epoch) when the resource
1690  * was last visited.
1691  *
1692  * Return value: the number of seconds elapsed from system's Epoch when
1693  *   the resource was last visited, or -1 on failure.
1694  *
1695  * Since: 2.10
1696  */
1697 time_t
1698 gtk_recent_info_get_visited (GtkRecentInfo *info)
1699 {
1700   g_return_val_if_fail (info != NULL, (time_t) -1);
1701   
1702   return info->visited;
1703 }
1704
1705 /**
1706  * gtk_recent_info_get_private_hint:
1707  * @info: a #GtkRecentInfo
1708  *
1709  * Gets the value of the "private" flag.  Resources in the recently used
1710  * list that have this flag set to %TRUE should only be displayed by the
1711  * applications that have registered them.
1712  *
1713  * Return value: %TRUE if the private flag was found, %FALSE otherwise.
1714  *
1715  * Since: 2.10
1716  */
1717 gboolean
1718 gtk_recent_info_get_private_hint (GtkRecentInfo *info)
1719 {
1720   g_return_val_if_fail (info != NULL, FALSE);
1721   
1722   return info->is_private;
1723 }
1724
1725
1726 static RecentAppInfo *
1727 recent_app_info_new (const gchar *app_name)
1728 {
1729   RecentAppInfo *app_info;
1730
1731   g_assert (app_name != NULL);
1732   
1733   app_info = g_new0 (RecentAppInfo, 1);
1734   app_info->name = g_strdup (app_name);
1735   app_info->exec = NULL;
1736   app_info->count = 1;
1737   app_info->stamp = time (NULL);
1738   
1739   return app_info;
1740 }
1741
1742 static void
1743 recent_app_info_free (RecentAppInfo *app_info)
1744 {
1745   if (!app_info)
1746     return;
1747   
1748   if (app_info->name)
1749     g_free (app_info->name);
1750   
1751   if (app_info->exec)
1752     g_free (app_info->exec);
1753   
1754   g_free (app_info);
1755 }
1756
1757 /**
1758  * gtk_recent_info_get_application_info:
1759  * @info: a #GtkRecentInfo
1760  * @app_name: the name of the application that has registered this item
1761  * @app_exec: return location for the string containing the command line
1762  * @count: return location for the number of times this item was registered
1763  * @time: return location for the timestamp this item was last registered
1764  *    for this application
1765  *
1766  * Gets the data regarding the application that has registered the resource
1767  * pointed by @info.
1768  *
1769  * If the command line contains any escape characters defined inside the
1770  * storage specification, they will be expanded.
1771  *
1772  * Return value: %TRUE if an application with @app_name has registered this
1773  *   resource inside the recently used list, or %FALSE otherwise.  You should
1774  *   free the returned command line using g_free().
1775  *
1776  * Since: 2.10
1777  */
1778 gboolean
1779 gtk_recent_info_get_application_info (GtkRecentInfo  *info,
1780                                       const gchar    *app_name,
1781                                       gchar         **app_exec,
1782                                       guint          *count,
1783                                       time_t         *time)
1784 {
1785   RecentAppInfo *ai;
1786   
1787   g_return_val_if_fail (info != NULL, FALSE);
1788   g_return_val_if_fail (app_name != NULL, FALSE);
1789   
1790   ai = (RecentAppInfo *) g_hash_table_lookup (info->apps_lookup,
1791                                               app_name);
1792   if (!ai)
1793     {
1794       g_warning ("No registered application with name '%s' "
1795                  "for item with URI '%s' found",
1796                  app_name,
1797                  info->uri);
1798       return FALSE;
1799     }
1800   
1801   if (app_exec)
1802     *app_exec = ai->exec;
1803   
1804   if (count)
1805     *count = ai->count;
1806   
1807   if (time)
1808     *time = ai->stamp;
1809
1810   return TRUE;
1811 }
1812
1813 /**
1814  * gtk_recent_info_get_applications:
1815  * @info: a #GtkRecentInfo
1816  * @length: return location for the length of the returned list, or %NULL
1817  *
1818  * Retrieves the list of applications that have registered this resource.
1819  *
1820  * Return value: a newly allocated %NULL-terminated array of strings.
1821  *   Use g_strfreev() to free it.
1822  *
1823  * Since: 2.10
1824  */                           
1825 gchar **
1826 gtk_recent_info_get_applications (GtkRecentInfo *info,
1827                                   gsize         *length)
1828 {
1829   GSList *l;
1830   gchar **retval;
1831   gsize n_apps, i;
1832   
1833   g_return_val_if_fail (info != NULL, NULL);
1834   
1835   if (!info->applications)
1836     {
1837       if (length)
1838         *length = 0;
1839       
1840       return NULL;    
1841     }
1842   
1843   n_apps = g_slist_length (info->applications);
1844   
1845   retval = g_new0 (gchar *, n_apps + 1);
1846   
1847   for (l = info->applications, i = 0;
1848        l != NULL;
1849        l = l->next)
1850     {
1851       RecentAppInfo *ai = (RecentAppInfo *) l->data;
1852       
1853       g_assert (ai != NULL);
1854       
1855       retval[i++] = g_strdup (ai->name);
1856     }
1857   retval[i] = NULL;
1858   
1859   if (length)
1860     *length = i;
1861   
1862   return retval;
1863 }
1864
1865 /**
1866  * gtk_recent_info_has_application:
1867  * @info: a #GtkRecentInfo
1868  * @app_name: a string containing an application name
1869  *
1870  * Checks whether an application registered this resource using @app_name.
1871  *
1872  * Return value: %TRUE if an application with name @app_name was found,
1873  *   %FALSE otherwise.
1874  *
1875  * Since: 2.10
1876  */
1877 gboolean
1878 gtk_recent_info_has_application (GtkRecentInfo *info,
1879                                  const gchar   *app_name)
1880 {
1881   g_return_val_if_fail (info != NULL, FALSE);
1882   g_return_val_if_fail (app_name != NULL, FALSE);
1883   
1884   return (NULL != g_hash_table_lookup (info->apps_lookup, app_name));
1885 }
1886
1887 /**
1888  * gtk_recent_info_last_application:
1889  * @info: a #GtkRecentInfo
1890  *
1891  * Gets the name of the last application that have registered the
1892  * recently used resource represented by @info.
1893  *
1894  * Return value: an application name.  Use g_free() to free it.
1895  *
1896  * Since: 2.10
1897  */
1898 gchar *
1899 gtk_recent_info_last_application (GtkRecentInfo  *info)
1900 {
1901   GSList *l;
1902   time_t last_stamp = (time_t) -1;
1903   gchar *name = NULL;
1904   
1905   g_return_val_if_fail (info != NULL, NULL);
1906   
1907   for (l = info->applications; l != NULL; l = l->next)
1908     {
1909       RecentAppInfo *ai = (RecentAppInfo *) l->data;
1910       
1911       if (ai->stamp > last_stamp)
1912         {
1913           name = ai->name;
1914           last_stamp = ai->stamp;
1915         }
1916     }
1917   
1918   return g_strdup (name);
1919 }
1920
1921 typedef struct
1922 {
1923   gint size;
1924   GdkPixbuf *pixbuf;
1925 } IconCacheElement;
1926
1927 static void
1928 icon_cache_element_free (IconCacheElement *element)
1929 {
1930   if (element->pixbuf)
1931     g_object_unref (element->pixbuf);
1932   g_free (element);
1933 }
1934
1935 static void
1936 icon_theme_changed (GtkIconTheme     *icon_theme)
1937 {
1938   GHashTable *cache;
1939
1940   /* Difference from the initial creation is that we don't
1941    * reconnect the signal
1942    */
1943   cache = g_hash_table_new_full (g_str_hash, g_str_equal,
1944                                  (GDestroyNotify)g_free,
1945                                  (GDestroyNotify)icon_cache_element_free);
1946   g_object_set_data_full (G_OBJECT (icon_theme), "gtk-recent-icon-cache",
1947                           cache, (GDestroyNotify)g_hash_table_destroy);
1948 }
1949
1950 /* TODO: use the GtkFileChooser's icon cache instead of our own to reduce
1951  * the memory footprint
1952  */
1953 static GdkPixbuf *
1954 get_cached_icon (const gchar *name,
1955                  gint         pixel_size)
1956 {
1957   GtkIconTheme *icon_theme;
1958   GHashTable *cache;
1959   IconCacheElement *element;
1960
1961   icon_theme = gtk_icon_theme_get_default ();
1962   cache = g_object_get_data (G_OBJECT (icon_theme), "gtk-recent-icon-cache");
1963
1964   if (!cache)
1965     {
1966       cache = g_hash_table_new_full (g_str_hash, g_str_equal,
1967                                      (GDestroyNotify)g_free,
1968                                      (GDestroyNotify)icon_cache_element_free);
1969
1970       g_object_set_data_full (G_OBJECT (icon_theme), "gtk-recent-icon-cache",
1971                               cache, (GDestroyNotify)g_hash_table_destroy);
1972       g_signal_connect (icon_theme, "changed",
1973                         G_CALLBACK (icon_theme_changed), NULL);
1974     }
1975
1976   element = g_hash_table_lookup (cache, name);
1977   if (!element)
1978     {
1979       element = g_new0 (IconCacheElement, 1);
1980       g_hash_table_insert (cache, g_strdup (name), element);
1981     }
1982
1983   if (element->size != pixel_size)
1984     {
1985       if (element->pixbuf)
1986         g_object_unref (element->pixbuf);
1987
1988       element->size = pixel_size;
1989       element->pixbuf = gtk_icon_theme_load_icon (icon_theme, name,
1990                                                   pixel_size, 0, NULL);
1991     }
1992
1993   return element->pixbuf ? g_object_ref (element->pixbuf) : NULL;
1994 }
1995
1996
1997 static GdkPixbuf *
1998 get_icon_for_mime_type (const char *mime_type,
1999                         gint        pixel_size)
2000 {
2001   const char *separator;
2002   GString *icon_name;
2003   GdkPixbuf *pixbuf;
2004
2005   separator = strchr (mime_type, '/');
2006   if (!separator)
2007     return NULL; /* maybe we should return a GError with "invalid MIME-type" */
2008
2009   icon_name = g_string_new ("gnome-mime-");
2010   g_string_append_len (icon_name, mime_type, separator - mime_type);
2011   g_string_append_c (icon_name, '-');
2012   g_string_append (icon_name, separator + 1);
2013   pixbuf = get_cached_icon (icon_name->str, pixel_size);
2014   g_string_free (icon_name, TRUE);
2015   if (pixbuf)
2016     return pixbuf;
2017
2018   icon_name = g_string_new ("gnome-mime-");
2019   g_string_append_len (icon_name, mime_type, separator - mime_type);
2020   pixbuf = get_cached_icon (icon_name->str, pixel_size);
2021   g_string_free (icon_name, TRUE);
2022
2023   return pixbuf;
2024 }
2025
2026 static GdkPixbuf *
2027 get_icon_fallback (const gchar *icon_name,
2028                    gint         size)
2029 {
2030   GtkIconTheme *icon_theme;
2031   GdkPixbuf *retval;
2032
2033   icon_theme = gtk_icon_theme_get_default ();
2034   
2035   retval = gtk_icon_theme_load_icon (icon_theme, icon_name,
2036                                      size,
2037                                      GTK_ICON_LOOKUP_USE_BUILTIN,
2038                                      NULL);
2039   g_assert (retval != NULL);
2040   
2041   return retval; 
2042 }
2043
2044 /**
2045  * gtk_recent_info_get_icon:
2046  * @info: a #GtkRecentInfo
2047  * @size: the size of the icon in pixels
2048  *
2049  * Retrieves the icon of size @size associated to the resource MIME type.
2050  *
2051  * Return value: a #GdkPixbuf containing the icon, or %NULL.
2052  *
2053  * Since: 2.10
2054  */
2055 GdkPixbuf *
2056 gtk_recent_info_get_icon (GtkRecentInfo *info,
2057                           gint           size)
2058 {
2059   GdkPixbuf *retval = NULL;
2060   
2061   g_return_val_if_fail (info != NULL, NULL);
2062   
2063   if (info->mime_type)
2064     retval = get_icon_for_mime_type (info->mime_type, size);
2065
2066   /* this should never fail */  
2067   if (!retval)
2068     retval = get_icon_fallback (GTK_STOCK_FILE, size);
2069   
2070   return retval;
2071 }
2072
2073 /**
2074  * gtk_recent_info_is_local:
2075  * @info: a #GtkRecentInfo
2076  *
2077  * Checks whether the resource is local or not by looking at the
2078  * scheme of its URI.
2079  *
2080  * Return value: %TRUE if the resource is local.
2081  *
2082  * Since: 2.10
2083  */
2084 gboolean
2085 gtk_recent_info_is_local (GtkRecentInfo *info)
2086 {
2087   g_return_val_if_fail (info != NULL, FALSE);
2088   
2089   return has_case_prefix (info->uri, "file:/");
2090 }
2091
2092 /**
2093  * gtk_recent_info_exists:
2094  * @info: a #GtkRecentInfo
2095  *
2096  * Checks whether the resource pointed by @info still exists.  At
2097  * the moment this check is done only on resources pointing to local files.
2098  *
2099  * Return value: %TRUE if the resource exists
2100  *
2101  * Since: 2.10
2102  */
2103 gboolean
2104 gtk_recent_info_exists (GtkRecentInfo *info)
2105 {
2106   gchar *filename;
2107   struct stat stat_buf;
2108   gboolean retval = FALSE;
2109   
2110   g_return_val_if_fail (info != NULL, FALSE);
2111   
2112   /* we guarantee only local resources */
2113   if (!gtk_recent_info_is_local (info))
2114     return FALSE;
2115   
2116   filename = g_filename_from_uri (info->uri, NULL, NULL);
2117   if (filename)
2118     {
2119       if (stat (filename, &stat_buf) == 0)
2120         retval = TRUE;
2121      
2122       g_free (filename);
2123     }
2124   
2125   return retval;
2126 }
2127
2128 /**
2129  * gtk_recent_info_match:
2130  * @info_a: a #GtkRecentInfo
2131  * @info_b: a #GtkRecentInfo
2132  *
2133  * Checks whether two #GtkRecentInfo structures point to the same
2134  * resource.
2135  *
2136  * Return value: %TRUE if both #GtkRecentInfo structures point to se same
2137  *   resource, %FALSE otherwise.
2138  *
2139  * Since: 2.10
2140  */
2141 gboolean
2142 gtk_recent_info_match (GtkRecentInfo *info_a,
2143                        GtkRecentInfo *info_b)
2144 {
2145   g_return_val_if_fail (info_a != NULL, FALSE);
2146   g_return_val_if_fail (info_b != NULL, FALSE);
2147   
2148   return (0 == strcmp (info_a->uri, info_b->uri));
2149 }
2150
2151 /* taken from gnome-vfs-uri.c */
2152 static const gchar *
2153 get_method_string (const gchar  *substring, 
2154                    gchar       **method_string)
2155 {
2156   const gchar *p;
2157   char *method;
2158         
2159   for (p = substring;
2160        g_ascii_isalnum (*p) || *p == '+' || *p == '-' || *p == '.';
2161        p++)
2162     ;
2163
2164   if (*p == ':'
2165 #ifdef G_OS_WIN32
2166                 &&
2167       !(p == substring + 1 && g_ascii_isalpha (*substring))
2168 #endif
2169                                                            )
2170     {
2171       /* Found toplevel method specification.  */
2172       method = g_strndup (substring, p - substring);
2173       *method_string = g_ascii_strdown (method, -1);
2174       g_free (method);
2175       p++;
2176     }
2177   else
2178     {
2179       *method_string = g_strdup ("file");
2180       p = substring;
2181     }
2182   
2183   return p;
2184 }
2185
2186 /* Stolen from gnome_vfs_make_valid_utf8() */
2187 static char *
2188 make_valid_utf8 (const char *name)
2189 {
2190   GString *string;
2191   const char *remainder, *invalid;
2192   int remaining_bytes, valid_bytes;
2193
2194   string = NULL;
2195   remainder = name;
2196   remaining_bytes = name ? strlen (name) : 0;
2197
2198   while (remaining_bytes != 0)
2199     {
2200       if (g_utf8_validate (remainder, remaining_bytes, &invalid))
2201         break;
2202       
2203       valid_bytes = invalid - remainder;
2204       
2205       if (string == NULL)
2206         string = g_string_sized_new (remaining_bytes);
2207       
2208       g_string_append_len (string, remainder, valid_bytes);
2209       g_string_append_c (string, '?');
2210       
2211       remaining_bytes -= valid_bytes + 1;
2212       remainder = invalid + 1;
2213     }
2214   
2215   if (string == NULL)
2216     return g_strdup (name);
2217
2218   g_string_append (string, remainder);
2219   g_assert (g_utf8_validate (string->str, -1, NULL));
2220
2221   return g_string_free (string, FALSE);
2222 }
2223
2224 static gchar *
2225 get_uri_shortname_for_display (const gchar *uri)
2226 {
2227   gchar *name = NULL;
2228   gboolean validated = FALSE;
2229
2230   if (has_case_prefix (uri, "file:/"))
2231     {
2232       gchar *local_file;
2233       
2234       local_file = g_filename_from_uri (uri, NULL, NULL);
2235       
2236       if (local_file != NULL)
2237         {
2238           name = g_filename_display_basename (local_file);
2239           validated = TRUE;
2240         }
2241                 
2242       g_free (local_file);
2243     } 
2244   else
2245     {
2246       gchar *method;
2247       gchar *local_file;
2248       const gchar *rest;
2249       
2250       rest = get_method_string (uri, &method);
2251       local_file = g_filename_display_basename (rest);
2252       
2253       name = g_strdup_printf ("%s: %s", method, local_file);
2254       
2255       g_free (local_file);
2256       g_free (method);
2257     }
2258   
2259   g_assert (name != NULL);
2260   
2261   if (!validated && !g_utf8_validate (name, -1, NULL))
2262     {
2263       gchar *utf8_name;
2264       
2265       utf8_name = make_valid_utf8 (name);
2266       g_free (name);
2267       
2268       name = utf8_name;
2269     }
2270
2271   return name;
2272 }
2273
2274 /**
2275  * gtk_recent_info_get_short_name:
2276  * @info: an #GtkRecentInfo
2277  *
2278  * Computes a valid UTF-8 string that can be used as the name of the item in a
2279  * menu or list.  For example, calling this function on an item that refers to
2280  * "file:///foo/bar.txt" will yield "bar.txt".
2281  *
2282  * Return value: A newly-allocated string in UTF-8 encoding; free it with
2283  *   g_free().
2284  *
2285  * Since: 2.10
2286  */
2287 gchar *
2288 gtk_recent_info_get_short_name (GtkRecentInfo *info)
2289 {
2290   gchar *short_name;
2291
2292   g_return_val_if_fail (info != NULL, NULL);
2293
2294   if (info->uri == NULL)
2295     return NULL;
2296
2297   short_name = get_uri_shortname_for_display (info->uri);
2298
2299   return short_name;
2300 }
2301
2302 /**
2303  * gtk_recent_info_get_uri_display:
2304  * @info: a #GtkRecentInfo
2305  *
2306  * Gets a displayable version of the resource's URI.
2307  *
2308  * Return value: a UTF-8 string containing the resource's URI or %NULL
2309  *
2310  * Since: 2.10
2311  */
2312 gchar *
2313 gtk_recent_info_get_uri_display (GtkRecentInfo *info)
2314 {
2315   gchar *filename, *filename_utf8;
2316   
2317   g_return_val_if_fail (info != NULL, NULL);
2318   
2319   filename = g_filename_from_uri (info->uri, NULL, NULL);
2320   if (!filename)
2321     return NULL;
2322       
2323   filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
2324   g_free (filename);
2325
2326   return filename_utf8;
2327 }
2328
2329 /**
2330  * gtk_recent_info_get_age:
2331  * @info: a #GtkRecentInfo
2332  *
2333  * Gets the number of days elapsed since the last update of the resource
2334  * pointed by @info.
2335  *
2336  * Return value: a positive integer containing the number of days elapsed
2337  *   since the time this resource was last modified.  
2338  *
2339  * Since: 2.10
2340  */
2341 gint
2342 gtk_recent_info_get_age (GtkRecentInfo *info)
2343 {
2344   time_t now, delta;
2345   gint retval;
2346
2347   g_return_val_if_fail (info != NULL, -1);
2348
2349   now = time (NULL);
2350   
2351   delta = now - info->modified;
2352   
2353   retval = (gint) (delta / (60 * 60 * 24));
2354   
2355   return retval;
2356 }
2357
2358 /**
2359  * gtk_recent_info_get_groups:
2360  * @info: a #GtkRecentInfo
2361  * @length: return location for the number of groups returned, or %NULL
2362  *
2363  * Returns all groups registered for the recently used item @info.  The
2364  * array of returned group names will be %NULL terminated, so length might
2365  * optionally be %NULL.
2366  *
2367  * Return value: a newly allocated %NULL terminated array of strings.  Use
2368  *   g_strfreev() to free it.
2369  *
2370  * Since: 2.10
2371  */
2372 gchar **
2373 gtk_recent_info_get_groups (GtkRecentInfo *info,
2374                             gsize         *length)
2375 {
2376   GSList *l;
2377   gchar **retval;
2378   gsize n_groups, i;
2379   
2380   g_return_val_if_fail (info != NULL, NULL);
2381   
2382   if (!info->groups)
2383     {
2384       if (length)
2385         *length = 0;
2386       
2387       return NULL;
2388     }
2389   
2390   n_groups = g_slist_length (info->groups);
2391   
2392   retval = g_new0 (gchar *, n_groups + 1);
2393   
2394   for (l = info->groups, i = 0;
2395        l != NULL;
2396        l = l->next)
2397     {
2398       gchar *group_name = (gchar *) l->data;
2399       
2400       g_assert (group_name != NULL);
2401       
2402       retval[i++] = g_strdup (group_name);
2403     }
2404   retval[i] = NULL;
2405   
2406   if (length)
2407     *length = i;
2408   
2409   return retval;
2410 }
2411
2412 /**
2413  * gtk_recent_info_has_group:
2414  * @info: a #GtkRecentInfo
2415  * @group_name: name of a group
2416  *
2417  * Checks whether @group_name appears inside the groups registered for the
2418  * recently used item @info.
2419  *
2420  * Return value: %TRUE if the group was found.
2421  *
2422  * Since: 2.10
2423  */
2424 gboolean
2425 gtk_recent_info_has_group (GtkRecentInfo *info,
2426                            const gchar   *group_name)
2427 {
2428   GSList *l;
2429   
2430   g_return_val_if_fail (info != NULL, FALSE);
2431   g_return_val_if_fail (group_name != NULL, FALSE);
2432
2433   if (!info->groups)
2434     return FALSE;
2435
2436   for (l = info->groups; l != NULL; l = l->next)
2437     {
2438       gchar *g = (gchar *) l->data;
2439
2440       if (strcmp (g, group_name) == 0)
2441         return TRUE;
2442     }
2443
2444   return FALSE;
2445 }
2446
2447 #define __GTK_RECENT_MANAGER_C__
2448 #include "gtkaliasdef.c"