1 /* gtkrecentchooserutils.h - Private utility functions for implementing a
2 * GtkRecentChooser interface
3 *
4 * Copyright (C) 2006 Emmanuele Bassi
5 *
6 * All rights reserved
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public
19 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20 *
21 * Based on gtkfilechooserutils.c:
22 * Copyright (C) 2003 Red Hat, Inc.
23 */
24
25 #include "config.h"
26
27 #include "gtkrecentchooserutils.h"
28
29 /* Methods */
30 static void delegate_set_sort_func (GtkRecentChooser *chooser,
31 GtkRecentSortFunc sort_func,
32 gpointer sort_data,
33 GDestroyNotify data_destroy);
34 static void delegate_add_filter (GtkRecentChooser *chooser,
35 GtkRecentFilter *filter);
36 static void delegate_remove_filter (GtkRecentChooser *chooser,
37 GtkRecentFilter *filter);
38 static GSList *delegate_list_filters (GtkRecentChooser *chooser);
39 static gboolean delegate_select_uri (GtkRecentChooser *chooser,
40 const gchar *uri,
41 GError **error);
42 static void delegate_unselect_uri (GtkRecentChooser *chooser,
43 const gchar *uri);
44 static GList *delegate_get_items (GtkRecentChooser *chooser);
45 static GtkRecentManager *delegate_get_recent_manager (GtkRecentChooser *chooser);
46 static void delegate_select_all (GtkRecentChooser *chooser);
47 static void delegate_unselect_all (GtkRecentChooser *chooser);
48 static gboolean delegate_set_current_uri (GtkRecentChooser *chooser,
49 const gchar *uri,
50 GError **error);
51 static gchar * delegate_get_current_uri (GtkRecentChooser *chooser);
52
53 /* Signals */
54 static void delegate_notify (GObject *object,
55 GParamSpec *pspec,
56 gpointer user_data);
57 static void delegate_selection_changed (GtkRecentChooser *receiver,
58 gpointer user_data);
59 static void delegate_item_activated (GtkRecentChooser *receiver,
60 gpointer user_data);
61
62 /**
63 * _gtk_recent_chooser_install_properties:
64 * @klass: the class structure for a type deriving from #GObject
65 *
66 * Installs the necessary properties for a class implementing
67 * #GtkRecentChooser. A #GtkParamSpecOverride property is installed
68 * for each property, using the values from the #GtkRecentChooserProp
69 * enumeration. The caller must make sure itself that the enumeration
70 * values don’t collide with some other property values they
71 * are using.
72 */
73 void
_gtk_recent_chooser_install_properties(GObjectClass * klass)74 _gtk_recent_chooser_install_properties (GObjectClass *klass)
75 {
76 g_object_class_override_property (klass,
77 GTK_RECENT_CHOOSER_PROP_RECENT_MANAGER,
78 "recent-manager");
79 g_object_class_override_property (klass,
80 GTK_RECENT_CHOOSER_PROP_SHOW_PRIVATE,
81 "show-private");
82 g_object_class_override_property (klass,
83 GTK_RECENT_CHOOSER_PROP_SHOW_TIPS,
84 "show-tips");
85 g_object_class_override_property (klass,
86 GTK_RECENT_CHOOSER_PROP_SHOW_ICONS,
87 "show-icons");
88 g_object_class_override_property (klass,
89 GTK_RECENT_CHOOSER_PROP_SHOW_NOT_FOUND,
90 "show-not-found");
91 g_object_class_override_property (klass,
92 GTK_RECENT_CHOOSER_PROP_SELECT_MULTIPLE,
93 "select-multiple");
94 g_object_class_override_property (klass,
95 GTK_RECENT_CHOOSER_PROP_LIMIT,
96 "limit");
97 g_object_class_override_property (klass,
98 GTK_RECENT_CHOOSER_PROP_LOCAL_ONLY,
99 "local-only");
100 g_object_class_override_property (klass,
101 GTK_RECENT_CHOOSER_PROP_SORT_TYPE,
102 "sort-type");
103 g_object_class_override_property (klass,
104 GTK_RECENT_CHOOSER_PROP_FILTER,
105 "filter");
106 }
107
108 /**
109 * _gtk_recent_chooser_delegate_iface_init:
110 * @iface: a #GtkRecentChooserIface
111 *
112 * An interface-initialization function for use in cases where
113 * an object is simply delegating the methods, signals of
114 * the #GtkRecentChooser interface to another object.
115 * _gtk_recent_chooser_set_delegate() must be called on each
116 * instance of the object so that the delegate object can
117 * be found.
118 */
119 void
_gtk_recent_chooser_delegate_iface_init(GtkRecentChooserIface * iface)120 _gtk_recent_chooser_delegate_iface_init (GtkRecentChooserIface *iface)
121 {
122 iface->set_current_uri = delegate_set_current_uri;
123 iface->get_current_uri = delegate_get_current_uri;
124 iface->select_uri = delegate_select_uri;
125 iface->unselect_uri = delegate_unselect_uri;
126 iface->select_all = delegate_select_all;
127 iface->unselect_all = delegate_unselect_all;
128 iface->get_items = delegate_get_items;
129 iface->get_recent_manager = delegate_get_recent_manager;
130 iface->set_sort_func = delegate_set_sort_func;
131 iface->add_filter = delegate_add_filter;
132 iface->remove_filter = delegate_remove_filter;
133 iface->list_filters = delegate_list_filters;
134 }
135
136 /**
137 * _gtk_recent_chooser_set_delegate:
138 * @receiver: a #GObject implementing #GtkRecentChooser
139 * @delegate: another #GObject implementing #GtkRecentChooser
140 *
141 * Establishes that calls on @receiver for #GtkRecentChooser
142 * methods should be delegated to @delegate, and that
143 * #GtkRecentChooser signals emitted on @delegate should be
144 * forwarded to @receiver. Must be used in conjunction with
145 * _gtk_recent_chooser_delegate_iface_init().
146 */
147 void
_gtk_recent_chooser_set_delegate(GtkRecentChooser * receiver,GtkRecentChooser * delegate)148 _gtk_recent_chooser_set_delegate (GtkRecentChooser *receiver,
149 GtkRecentChooser *delegate)
150 {
151 g_return_if_fail (GTK_IS_RECENT_CHOOSER (receiver));
152 g_return_if_fail (GTK_IS_RECENT_CHOOSER (delegate));
153
154 g_object_set_data (G_OBJECT (receiver),
155 "gtk-recent-chooser-delegate", delegate);
156
157 g_signal_connect (delegate, "notify",
158 G_CALLBACK (delegate_notify), receiver);
159 g_signal_connect (delegate, "selection-changed",
160 G_CALLBACK (delegate_selection_changed), receiver);
161 g_signal_connect (delegate, "item-activated",
162 G_CALLBACK (delegate_item_activated), receiver);
163 }
164
165 GQuark
_gtk_recent_chooser_delegate_get_quark(void)166 _gtk_recent_chooser_delegate_get_quark (void)
167 {
168 static GQuark quark = 0;
169
170 if (G_UNLIKELY (quark == 0))
171 quark = g_quark_from_static_string ("gtk-recent-chooser-delegate");
172
173 return quark;
174 }
175
176 static GtkRecentChooser *
get_delegate(GtkRecentChooser * receiver)177 get_delegate (GtkRecentChooser *receiver)
178 {
179 return g_object_get_qdata (G_OBJECT (receiver),
180 GTK_RECENT_CHOOSER_DELEGATE_QUARK);
181 }
182
183 static void
delegate_set_sort_func(GtkRecentChooser * chooser,GtkRecentSortFunc sort_func,gpointer sort_data,GDestroyNotify data_destroy)184 delegate_set_sort_func (GtkRecentChooser *chooser,
185 GtkRecentSortFunc sort_func,
186 gpointer sort_data,
187 GDestroyNotify data_destroy)
188 {
189 gtk_recent_chooser_set_sort_func (get_delegate (chooser),
190 sort_func,
191 sort_data,
192 data_destroy);
193 }
194
195 static void
delegate_add_filter(GtkRecentChooser * chooser,GtkRecentFilter * filter)196 delegate_add_filter (GtkRecentChooser *chooser,
197 GtkRecentFilter *filter)
198 {
199 gtk_recent_chooser_add_filter (get_delegate (chooser), filter);
200 }
201
202 static void
delegate_remove_filter(GtkRecentChooser * chooser,GtkRecentFilter * filter)203 delegate_remove_filter (GtkRecentChooser *chooser,
204 GtkRecentFilter *filter)
205 {
206 gtk_recent_chooser_remove_filter (get_delegate (chooser), filter);
207 }
208
209 static GSList *
delegate_list_filters(GtkRecentChooser * chooser)210 delegate_list_filters (GtkRecentChooser *chooser)
211 {
212 return gtk_recent_chooser_list_filters (get_delegate (chooser));
213 }
214
215 static gboolean
delegate_select_uri(GtkRecentChooser * chooser,const gchar * uri,GError ** error)216 delegate_select_uri (GtkRecentChooser *chooser,
217 const gchar *uri,
218 GError **error)
219 {
220 return gtk_recent_chooser_select_uri (get_delegate (chooser), uri, error);
221 }
222
223 static void
delegate_unselect_uri(GtkRecentChooser * chooser,const gchar * uri)224 delegate_unselect_uri (GtkRecentChooser *chooser,
225 const gchar *uri)
226 {
227 gtk_recent_chooser_unselect_uri (get_delegate (chooser), uri);
228 }
229
230 static GList *
delegate_get_items(GtkRecentChooser * chooser)231 delegate_get_items (GtkRecentChooser *chooser)
232 {
233 return gtk_recent_chooser_get_items (get_delegate (chooser));
234 }
235
236 static GtkRecentManager *
delegate_get_recent_manager(GtkRecentChooser * chooser)237 delegate_get_recent_manager (GtkRecentChooser *chooser)
238 {
239 return _gtk_recent_chooser_get_recent_manager (get_delegate (chooser));
240 }
241
242 static void
delegate_select_all(GtkRecentChooser * chooser)243 delegate_select_all (GtkRecentChooser *chooser)
244 {
245 gtk_recent_chooser_select_all (get_delegate (chooser));
246 }
247
248 static void
delegate_unselect_all(GtkRecentChooser * chooser)249 delegate_unselect_all (GtkRecentChooser *chooser)
250 {
251 gtk_recent_chooser_unselect_all (get_delegate (chooser));
252 }
253
254 static gboolean
delegate_set_current_uri(GtkRecentChooser * chooser,const gchar * uri,GError ** error)255 delegate_set_current_uri (GtkRecentChooser *chooser,
256 const gchar *uri,
257 GError **error)
258 {
259 return gtk_recent_chooser_set_current_uri (get_delegate (chooser), uri, error);
260 }
261
262 static gchar *
delegate_get_current_uri(GtkRecentChooser * chooser)263 delegate_get_current_uri (GtkRecentChooser *chooser)
264 {
265 return gtk_recent_chooser_get_current_uri (get_delegate (chooser));
266 }
267
268 static void
delegate_notify(GObject * object,GParamSpec * pspec,gpointer user_data)269 delegate_notify (GObject *object,
270 GParamSpec *pspec,
271 gpointer user_data)
272 {
273 gpointer iface;
274
275 iface = g_type_interface_peek (g_type_class_peek (G_OBJECT_TYPE (object)),
276 gtk_recent_chooser_get_type ());
277 if (g_object_interface_find_property (iface, pspec->name))
278 g_object_notify (user_data, pspec->name);
279 }
280
281 static void
delegate_selection_changed(GtkRecentChooser * receiver,gpointer user_data)282 delegate_selection_changed (GtkRecentChooser *receiver,
283 gpointer user_data)
284 {
285 _gtk_recent_chooser_selection_changed (GTK_RECENT_CHOOSER (user_data));
286 }
287
288 static void
delegate_item_activated(GtkRecentChooser * receiver,gpointer user_data)289 delegate_item_activated (GtkRecentChooser *receiver,
290 gpointer user_data)
291 {
292 _gtk_recent_chooser_item_activated (GTK_RECENT_CHOOSER (user_data));
293 }
294
295 static gint
sort_recent_items_mru(GtkRecentInfo * a,GtkRecentInfo * b,gpointer unused)296 sort_recent_items_mru (GtkRecentInfo *a,
297 GtkRecentInfo *b,
298 gpointer unused)
299 {
300 g_assert (a != NULL && b != NULL);
301
302 return gtk_recent_info_get_modified (b) - gtk_recent_info_get_modified (a);
303 }
304
305 static gint
sort_recent_items_lru(GtkRecentInfo * a,GtkRecentInfo * b,gpointer unused)306 sort_recent_items_lru (GtkRecentInfo *a,
307 GtkRecentInfo *b,
308 gpointer unused)
309 {
310 g_assert (a != NULL && b != NULL);
311
312 return -1 * (gtk_recent_info_get_modified (b) - gtk_recent_info_get_modified (a));
313 }
314
315 typedef struct
316 {
317 GtkRecentSortFunc func;
318 gpointer data;
319 } SortRecentData;
320
321 /* our proxy sorting function */
322 static gint
sort_recent_items_proxy(gpointer * a,gpointer * b,gpointer user_data)323 sort_recent_items_proxy (gpointer *a,
324 gpointer *b,
325 gpointer user_data)
326 {
327 GtkRecentInfo *info_a = (GtkRecentInfo *) a;
328 GtkRecentInfo *info_b = (GtkRecentInfo *) b;
329 SortRecentData *sort_recent = user_data;
330
331 if (sort_recent->func)
332 return (* sort_recent->func) (info_a, info_b, sort_recent->data);
333
334 /* fallback */
335 return 0;
336 }
337
338 static gboolean
get_is_recent_filtered(GtkRecentFilter * filter,GtkRecentInfo * info)339 get_is_recent_filtered (GtkRecentFilter *filter,
340 GtkRecentInfo *info)
341 {
342 GtkRecentFilterInfo filter_info;
343 GtkRecentFilterFlags needed;
344 gboolean retval;
345
346 g_assert (info != NULL);
347
348 needed = gtk_recent_filter_get_needed (filter);
349
350 filter_info.contains = GTK_RECENT_FILTER_URI | GTK_RECENT_FILTER_MIME_TYPE;
351
352 filter_info.uri = gtk_recent_info_get_uri (info);
353 filter_info.mime_type = gtk_recent_info_get_mime_type (info);
354
355 if (needed & GTK_RECENT_FILTER_DISPLAY_NAME)
356 {
357 filter_info.display_name = gtk_recent_info_get_display_name (info);
358 filter_info.contains |= GTK_RECENT_FILTER_DISPLAY_NAME;
359 }
360 else
361 filter_info.display_name = NULL;
362
363 if (needed & GTK_RECENT_FILTER_APPLICATION)
364 {
365 filter_info.applications = (const gchar **) gtk_recent_info_get_applications (info, NULL);
366 filter_info.contains |= GTK_RECENT_FILTER_APPLICATION;
367 }
368 else
369 filter_info.applications = NULL;
370
371 if (needed & GTK_RECENT_FILTER_GROUP)
372 {
373 filter_info.groups = (const gchar **) gtk_recent_info_get_groups (info, NULL);
374 filter_info.contains |= GTK_RECENT_FILTER_GROUP;
375 }
376 else
377 filter_info.groups = NULL;
378
379 if (needed & GTK_RECENT_FILTER_AGE)
380 {
381 filter_info.age = gtk_recent_info_get_age (info);
382 filter_info.contains |= GTK_RECENT_FILTER_AGE;
383 }
384 else
385 filter_info.age = -1;
386
387 retval = gtk_recent_filter_filter (filter, &filter_info);
388
389 /* these we own */
390 if (filter_info.applications)
391 g_strfreev ((gchar **) filter_info.applications);
392 if (filter_info.groups)
393 g_strfreev ((gchar **) filter_info.groups);
394
395 return !retval;
396 }
397
398 /*
399 * _gtk_recent_chooser_get_items:
400 * @chooser: a #GtkRecentChooser
401 * @filter: a #GtkRecentFilter
402 * @sort_func: (allow-none): sorting function, or %NULL
403 * @sort_data: (allow-none): sorting function data, or %NULL
404 *
405 * Default implementation for getting the filtered, sorted and
406 * clamped list of recently used resources from a #GtkRecentChooser.
407 * This function should be used by implementations of the
408 * #GtkRecentChooser interface inside the GtkRecentChooser::get_items
409 * vfunc.
410 *
411 * Returns: a list of #GtkRecentInfo objects
412 */
413 GList *
_gtk_recent_chooser_get_items(GtkRecentChooser * chooser,GtkRecentFilter * filter,GtkRecentSortFunc sort_func,gpointer sort_data)414 _gtk_recent_chooser_get_items (GtkRecentChooser *chooser,
415 GtkRecentFilter *filter,
416 GtkRecentSortFunc sort_func,
417 gpointer sort_data)
418 {
419 GtkRecentManager *manager;
420 gint limit;
421 GtkRecentSortType sort_type;
422 GList *items;
423 GCompareDataFunc compare_func;
424 gint length;
425
426 g_return_val_if_fail (GTK_IS_RECENT_CHOOSER (chooser), NULL);
427
428 manager = _gtk_recent_chooser_get_recent_manager (chooser);
429 if (!manager)
430 return NULL;
431
432 items = gtk_recent_manager_get_items (manager);
433 if (!items)
434 return NULL;
435
436 limit = gtk_recent_chooser_get_limit (chooser);
437 if (limit == 0)
438 return NULL;
439
440 if (filter)
441 {
442 GList *filter_items, *l;
443 gboolean local_only = FALSE;
444 gboolean show_private = FALSE;
445 gboolean show_not_found = FALSE;
446
447 g_object_get (G_OBJECT (chooser),
448 "local-only", &local_only,
449 "show-private", &show_private,
450 "show-not-found", &show_not_found,
451 NULL);
452
453 filter_items = NULL;
454 for (l = items; l != NULL; l = l->next)
455 {
456 GtkRecentInfo *info = l->data;
457 gboolean remove_item = FALSE;
458
459 if (get_is_recent_filtered (filter, info))
460 remove_item = TRUE;
461
462 if (local_only && !gtk_recent_info_is_local (info))
463 remove_item = TRUE;
464
465 if (!show_private && gtk_recent_info_get_private_hint (info))
466 remove_item = TRUE;
467
468 if (!show_not_found && !gtk_recent_info_exists (info))
469 remove_item = TRUE;
470
471 if (!remove_item)
472 filter_items = g_list_prepend (filter_items, info);
473 else
474 gtk_recent_info_unref (info);
475 }
476
477 g_list_free (items);
478 items = filter_items;
479 }
480
481 if (!items)
482 return NULL;
483
484 sort_type = gtk_recent_chooser_get_sort_type (chooser);
485 switch (sort_type)
486 {
487 case GTK_RECENT_SORT_NONE:
488 compare_func = NULL;
489 break;
490 case GTK_RECENT_SORT_MRU:
491 compare_func = (GCompareDataFunc) sort_recent_items_mru;
492 break;
493 case GTK_RECENT_SORT_LRU:
494 compare_func = (GCompareDataFunc) sort_recent_items_lru;
495 break;
496 case GTK_RECENT_SORT_CUSTOM:
497 compare_func = (GCompareDataFunc) sort_recent_items_proxy;
498 break;
499 default:
500 g_assert_not_reached ();
501 break;
502 }
503
504 if (compare_func)
505 {
506 SortRecentData sort_recent;
507
508 sort_recent.func = sort_func;
509 sort_recent.data = sort_data;
510
511 items = g_list_sort_with_data (items, compare_func, &sort_recent);
512 }
513
514 length = g_list_length (items);
515 if ((limit != -1) && (length > limit))
516 {
517 GList *clamp, *l;
518
519 clamp = g_list_nth (items, limit - 1);
520 if (!clamp)
521 return items;
522
523 l = clamp->next;
524 clamp->next = NULL;
525
526 g_list_free_full (l, (GDestroyNotify) gtk_recent_info_unref);
527 }
528
529 return items;
530 }
531