1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 
3 /* caja-bookmark.c - implementation of individual bookmarks.
4 
5    Copyright (C) 1999, 2000 Eazel, Inc.
6 
7    The Mate Library is free software; you can redistribute it and/or
8    modify it under the terms of the GNU Library General Public License as
9    published by the Free Software Foundation; either version 2 of the
10    License, or (at your option) any later version.
11 
12    The Mate Library is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    Library General Public License for more details.
16 
17    You should have received a copy of the GNU Library General Public
18    License along with the Mate Library; see the file COPYING.LIB.  If not,
19    write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20    Boston, MA 02110-1301, USA.
21 
22    Authors: John Sullivan <sullivan@eazel.com>
23 */
24 
25 #include <config.h>
26 #include <gtk/gtk.h>
27 #include <gio/gio.h>
28 
29 #include <eel/eel-gdk-pixbuf-extensions.h>
30 #include <eel/eel-gtk-extensions.h>
31 #include <eel/eel-gtk-macros.h>
32 #include <eel/eel-vfs-extensions.h>
33 
34 #include "caja-bookmark.h"
35 #include "caja-file.h"
36 #include "caja-icon-names.h"
37 
38 enum
39 {
40     APPEARANCE_CHANGED,
41     CONTENTS_CHANGED,
42     LAST_SIGNAL
43 };
44 
45 #define ELLIPSISED_MENU_ITEM_MIN_CHARS  32
46 
47 static guint signals[LAST_SIGNAL] = { 0 };
48 
49 struct CajaBookmarkDetails
50 {
51     char *name;
52     gboolean has_custom_name;
53     GFile *location;
54     GIcon *icon;
55     CajaFile *file;
56 
57     char *scroll_file;
58 };
59 
60 static void	  caja_bookmark_connect_file	  (CajaBookmark	 *file);
61 static void	  caja_bookmark_disconnect_file	  (CajaBookmark	 *file);
62 
63 G_DEFINE_TYPE (CajaBookmark, caja_bookmark, G_TYPE_OBJECT);
64 
65 /* GObject methods.  */
66 
67 static void
caja_bookmark_finalize(GObject * object)68 caja_bookmark_finalize (GObject *object)
69 {
70     CajaBookmark *bookmark;
71 
72     g_assert (CAJA_IS_BOOKMARK (object));
73 
74     bookmark = CAJA_BOOKMARK (object);
75 
76     caja_bookmark_disconnect_file (bookmark);
77 
78     g_free (bookmark->details->name);
79     g_object_unref (bookmark->details->location);
80     if (bookmark->details->icon)
81     {
82         g_object_unref (bookmark->details->icon);
83     }
84     g_free (bookmark->details->scroll_file);
85     g_free (bookmark->details);
86 
87     G_OBJECT_CLASS (caja_bookmark_parent_class)->finalize (object);
88 }
89 
90 /* Initialization.  */
91 
92 static void
caja_bookmark_class_init(CajaBookmarkClass * class)93 caja_bookmark_class_init (CajaBookmarkClass *class)
94 {
95     G_OBJECT_CLASS (class)->finalize = caja_bookmark_finalize;
96 
97     signals[APPEARANCE_CHANGED] =
98         g_signal_new ("appearance_changed",
99                       G_TYPE_FROM_CLASS (class),
100                       G_SIGNAL_RUN_LAST,
101                       G_STRUCT_OFFSET (CajaBookmarkClass, appearance_changed),
102                       NULL, NULL,
103                       g_cclosure_marshal_VOID__VOID,
104                       G_TYPE_NONE, 0);
105 
106     signals[CONTENTS_CHANGED] =
107         g_signal_new ("contents_changed",
108                       G_TYPE_FROM_CLASS (class),
109                       G_SIGNAL_RUN_LAST,
110                       G_STRUCT_OFFSET (CajaBookmarkClass, contents_changed),
111                       NULL, NULL,
112                       g_cclosure_marshal_VOID__VOID,
113                       G_TYPE_NONE, 0);
114 
115 }
116 
117 static void
caja_bookmark_init(CajaBookmark * bookmark)118 caja_bookmark_init (CajaBookmark *bookmark)
119 {
120     bookmark->details = g_new0 (CajaBookmarkDetails, 1);
121 }
122 
123 /**
124  * caja_bookmark_compare_with:
125  *
126  * Check whether two bookmarks are considered identical.
127  * @a: first CajaBookmark*.
128  * @b: second CajaBookmark*.
129  *
130  * Return value: 0 if @a and @b have same name and uri, 1 otherwise
131  * (GCompareFunc style)
132  **/
133 int
caja_bookmark_compare_with(gconstpointer a,gconstpointer b)134 caja_bookmark_compare_with (gconstpointer a, gconstpointer b)
135 {
136     CajaBookmark *bookmark_a;
137     CajaBookmark *bookmark_b;
138 
139     g_return_val_if_fail (CAJA_IS_BOOKMARK (a), 1);
140     g_return_val_if_fail (CAJA_IS_BOOKMARK (b), 1);
141 
142     bookmark_a = CAJA_BOOKMARK (a);
143     bookmark_b = CAJA_BOOKMARK (b);
144 
145     if (g_strcmp0 (bookmark_a->details->name,
146                     bookmark_b->details->name) != 0)
147     {
148         return 1;
149     }
150 
151     if (!g_file_equal (bookmark_a->details->location,
152                        bookmark_b->details->location))
153     {
154         return 1;
155     }
156 
157     return 0;
158 }
159 
160 /**
161  * caja_bookmark_compare_uris:
162  *
163  * Check whether the uris of two bookmarks are for the same location.
164  * @a: first CajaBookmark*.
165  * @b: second CajaBookmark*.
166  *
167  * Return value: 0 if @a and @b have matching uri, 1 otherwise
168  * (GCompareFunc style)
169  **/
170 int
caja_bookmark_compare_uris(gconstpointer a,gconstpointer b)171 caja_bookmark_compare_uris (gconstpointer a, gconstpointer b)
172 {
173     CajaBookmark *bookmark_a;
174     CajaBookmark *bookmark_b;
175 
176     g_return_val_if_fail (CAJA_IS_BOOKMARK (a), 1);
177     g_return_val_if_fail (CAJA_IS_BOOKMARK (b), 1);
178 
179     bookmark_a = CAJA_BOOKMARK (a);
180     bookmark_b = CAJA_BOOKMARK (b);
181 
182     return !g_file_equal (bookmark_a->details->location,
183                           bookmark_b->details->location);
184 }
185 
186 CajaBookmark *
caja_bookmark_copy(CajaBookmark * bookmark)187 caja_bookmark_copy (CajaBookmark *bookmark)
188 {
189     g_return_val_if_fail (CAJA_IS_BOOKMARK (bookmark), NULL);
190 
191     return caja_bookmark_new (
192                bookmark->details->location,
193                bookmark->details->name,
194                bookmark->details->has_custom_name,
195                bookmark->details->icon);
196 }
197 
198 char *
caja_bookmark_get_name(CajaBookmark * bookmark)199 caja_bookmark_get_name (CajaBookmark *bookmark)
200 {
201     g_return_val_if_fail(CAJA_IS_BOOKMARK (bookmark), NULL);
202 
203     return g_strdup (bookmark->details->name);
204 }
205 
206 
207 gboolean
caja_bookmark_get_has_custom_name(CajaBookmark * bookmark)208 caja_bookmark_get_has_custom_name (CajaBookmark *bookmark)
209 {
210     g_return_val_if_fail(CAJA_IS_BOOKMARK (bookmark), FALSE);
211 
212     return (bookmark->details->has_custom_name);
213 }
214 
215 cairo_surface_t *
caja_bookmark_get_surface(CajaBookmark * bookmark,GtkIconSize stock_size)216 caja_bookmark_get_surface (CajaBookmark *bookmark,
217                            GtkIconSize stock_size)
218 {
219     cairo_surface_t *result;
220     GIcon *icon;
221     CajaIconInfo *info;
222     int pixel_size, pixel_scale;
223 
224     g_return_val_if_fail (CAJA_IS_BOOKMARK (bookmark), NULL);
225 
226     icon = caja_bookmark_get_icon (bookmark);
227     if (icon == NULL)
228     {
229         return NULL;
230     }
231 
232     pixel_size = caja_get_icon_size_for_stock_size (stock_size);
233     pixel_scale = gdk_window_get_scale_factor (gdk_get_default_root_window ());
234     info = caja_icon_info_lookup (icon, pixel_size, pixel_scale);
235     result = caja_icon_info_get_surface_at_size (info, pixel_size);
236     g_object_unref (info);
237 
238     g_object_unref (icon);
239 
240     return result;
241 }
242 
243 GIcon *
caja_bookmark_get_icon(CajaBookmark * bookmark)244 caja_bookmark_get_icon (CajaBookmark *bookmark)
245 {
246     g_return_val_if_fail (CAJA_IS_BOOKMARK (bookmark), NULL);
247 
248     /* Try to connect a file in case file exists now but didn't earlier. */
249     caja_bookmark_connect_file (bookmark);
250 
251     if (bookmark->details->icon)
252     {
253         return g_object_ref (bookmark->details->icon);
254     }
255     return NULL;
256 }
257 
258 GFile *
caja_bookmark_get_location(CajaBookmark * bookmark)259 caja_bookmark_get_location (CajaBookmark *bookmark)
260 {
261     g_return_val_if_fail(CAJA_IS_BOOKMARK (bookmark), NULL);
262 
263     /* Try to connect a file in case file exists now but didn't earlier.
264      * This allows a bookmark to update its image properly in the case
265      * where a new file appears with the same URI as a previously-deleted
266      * file. Calling connect_file here means that attempts to activate the
267      * bookmark will update its image if possible.
268      */
269     caja_bookmark_connect_file (bookmark);
270 
271     return g_object_ref (bookmark->details->location);
272 }
273 
274 char *
caja_bookmark_get_uri(CajaBookmark * bookmark)275 caja_bookmark_get_uri (CajaBookmark *bookmark)
276 {
277     GFile *file;
278     char *uri;
279 
280     file = caja_bookmark_get_location (bookmark);
281     uri = g_file_get_uri (file);
282     g_object_unref (file);
283     return uri;
284 }
285 
286 
287 /**
288  * caja_bookmark_set_name:
289  *
290  * Change the user-displayed name of a bookmark.
291  * @new_name: The new user-displayed name for this bookmark, mustn't be NULL.
292  *
293  * Returns: TRUE if the name changed else FALSE.
294  **/
295 gboolean
caja_bookmark_set_name(CajaBookmark * bookmark,const char * new_name)296 caja_bookmark_set_name (CajaBookmark *bookmark, const char *new_name)
297 {
298     g_return_val_if_fail (new_name != NULL, FALSE);
299     g_return_val_if_fail (CAJA_IS_BOOKMARK (bookmark), FALSE);
300 
301     if (g_strcmp0 (new_name, bookmark->details->name) == 0)
302     {
303         return FALSE;
304     }
305     else if (!bookmark->details->has_custom_name)
306     {
307         bookmark->details->has_custom_name = TRUE;
308     }
309 
310     g_free (bookmark->details->name);
311     bookmark->details->name = g_strdup (new_name);
312 
313     g_signal_emit (bookmark, signals[APPEARANCE_CHANGED], 0);
314 
315     if (bookmark->details->has_custom_name)
316     {
317         g_signal_emit (bookmark, signals[CONTENTS_CHANGED], 0);
318     }
319 
320     return TRUE;
321 }
322 
323 static gboolean
caja_bookmark_icon_is_different(CajaBookmark * bookmark,GIcon * new_icon)324 caja_bookmark_icon_is_different (CajaBookmark *bookmark,
325                                  GIcon *new_icon)
326 {
327     g_assert (CAJA_IS_BOOKMARK (bookmark));
328     g_assert (new_icon != NULL);
329 
330     if (bookmark->details->icon == NULL)
331     {
332         return TRUE;
333     }
334 
335     return !g_icon_equal (bookmark->details->icon, new_icon) != 0;
336 }
337 
338 /**
339  * Update icon if there's a better one available.
340  * Return TRUE if the icon changed.
341  */
342 static gboolean
caja_bookmark_update_icon(CajaBookmark * bookmark)343 caja_bookmark_update_icon (CajaBookmark *bookmark)
344 {
345     GIcon *new_icon;
346 
347     g_assert (CAJA_IS_BOOKMARK (bookmark));
348 
349     if (bookmark->details->file == NULL)
350     {
351         return FALSE;
352     }
353 
354     if (!caja_file_is_local (bookmark->details->file))
355     {
356         /* never update icons for remote bookmarks */
357         return FALSE;
358     }
359 
360     if (!caja_file_is_not_yet_confirmed (bookmark->details->file) &&
361             caja_file_check_if_ready (bookmark->details->file,
362                                       CAJA_FILE_ATTRIBUTES_FOR_ICON))
363     {
364         new_icon = caja_file_get_gicon (bookmark->details->file, 0);
365         if (caja_bookmark_icon_is_different (bookmark, new_icon))
366         {
367             if (bookmark->details->icon)
368             {
369                 g_object_unref (bookmark->details->icon);
370             }
371             bookmark->details->icon = new_icon;
372             return TRUE;
373         }
374         g_object_unref (new_icon);
375     }
376 
377     return FALSE;
378 }
379 
380 static void
bookmark_file_changed_callback(CajaFile * file,CajaBookmark * bookmark)381 bookmark_file_changed_callback (CajaFile *file, CajaBookmark *bookmark)
382 {
383     GFile *location;
384     gboolean should_emit_appearance_changed_signal;
385     gboolean should_emit_contents_changed_signal;
386     char *display_name;
387 
388     g_assert (CAJA_IS_FILE (file));
389     g_assert (CAJA_IS_BOOKMARK (bookmark));
390     g_assert (file == bookmark->details->file);
391 
392     should_emit_appearance_changed_signal = FALSE;
393     should_emit_contents_changed_signal = FALSE;
394     location = caja_file_get_location (file);
395 
396     if (!g_file_equal (bookmark->details->location, location) &&
397             !caja_file_is_in_trash (file))
398     {
399         g_object_unref (bookmark->details->location);
400         bookmark->details->location = location;
401         should_emit_contents_changed_signal = TRUE;
402     }
403     else
404     {
405         g_object_unref (location);
406     }
407 
408     if (caja_file_is_gone (file) ||
409             caja_file_is_in_trash (file))
410     {
411         /* The file we were monitoring has been trashed, deleted,
412          * or moved in a way that we didn't notice. We should make
413          * a spanking new CajaFile object for this
414          * location so if a new file appears in this place
415          * we will notice. However, we can't immediately do so
416          * because creating a new CajaFile directly as a result
417          * of noticing a file goes away may trigger i/o on that file
418          * again, noticeing it is gone, leading to a loop.
419          * So, the new CajaFile is created when the bookmark
420          * is used again. However, this is not really a problem, as
421          * we don't want to change the icon or anything about the
422          * bookmark just because its not there anymore.
423          */
424         caja_bookmark_disconnect_file (bookmark);
425     }
426     else if (caja_bookmark_update_icon (bookmark))
427     {
428         /* File hasn't gone away, but it has changed
429          * in a way that affected its icon.
430          */
431         should_emit_appearance_changed_signal = TRUE;
432     }
433 
434     if (!bookmark->details->has_custom_name)
435     {
436         display_name = caja_file_get_display_name (file);
437 
438         if (g_strcmp0 (bookmark->details->name, display_name) != 0)
439         {
440             g_free (bookmark->details->name);
441             bookmark->details->name = display_name;
442             should_emit_appearance_changed_signal = TRUE;
443         }
444         else
445         {
446             g_free (display_name);
447         }
448     }
449 
450     if (should_emit_appearance_changed_signal)
451     {
452         g_signal_emit (bookmark, signals[APPEARANCE_CHANGED], 0);
453     }
454 
455     if (should_emit_contents_changed_signal)
456     {
457         g_signal_emit (bookmark, signals[CONTENTS_CHANGED], 0);
458     }
459 }
460 
461 /**
462  * caja_bookmark_set_icon_to_default:
463  *
464  * Reset the icon to either the missing bookmark icon or the generic
465  * bookmark icon, depending on whether the file still exists.
466  */
467 static void
caja_bookmark_set_icon_to_default(CajaBookmark * bookmark)468 caja_bookmark_set_icon_to_default (CajaBookmark *bookmark)
469 {
470     GIcon *emblemed_icon, *folder;
471 
472     if (bookmark->details->icon)
473     {
474         g_object_unref (bookmark->details->icon);
475     }
476 
477     folder = g_themed_icon_new (CAJA_ICON_FOLDER);
478 
479     if (caja_bookmark_uri_known_not_to_exist (bookmark))
480     {
481         GIcon *icon;
482         GEmblem *emblem;
483 
484         icon = g_themed_icon_new ("dialog-warning");
485         emblem = g_emblem_new (icon);
486 
487         emblemed_icon = g_emblemed_icon_new (folder, emblem);
488 
489         g_object_unref (emblem);
490         g_object_unref (icon);
491         g_object_unref (folder);
492 
493         folder = emblemed_icon;
494     }
495 
496     bookmark->details->icon = folder;
497 }
498 
499 static void
caja_bookmark_disconnect_file(CajaBookmark * bookmark)500 caja_bookmark_disconnect_file (CajaBookmark *bookmark)
501 {
502     g_assert (CAJA_IS_BOOKMARK (bookmark));
503 
504     if (bookmark->details->file != NULL)
505     {
506         g_signal_handlers_disconnect_by_func (bookmark->details->file,
507                                               G_CALLBACK (bookmark_file_changed_callback),
508                                               bookmark);
509         caja_file_unref (bookmark->details->file);
510         bookmark->details->file = NULL;
511     }
512 
513     if (bookmark->details->icon != NULL)
514     {
515         g_object_unref (bookmark->details->icon);
516         bookmark->details->icon = NULL;
517     }
518 }
519 
520 static void
caja_bookmark_connect_file(CajaBookmark * bookmark)521 caja_bookmark_connect_file (CajaBookmark *bookmark)
522 {
523     char *display_name;
524 
525     g_assert (CAJA_IS_BOOKMARK (bookmark));
526 
527     if (bookmark->details->file != NULL)
528     {
529         return;
530     }
531 
532     if (!caja_bookmark_uri_known_not_to_exist (bookmark))
533     {
534         bookmark->details->file = caja_file_get (bookmark->details->location);
535         g_assert (!caja_file_is_gone (bookmark->details->file));
536 
537         g_signal_connect_object (bookmark->details->file, "changed",
538                                  G_CALLBACK (bookmark_file_changed_callback), bookmark, 0);
539     }
540 
541     /* Set icon based on available information; don't force network i/o
542      * to get any currently unknown information.
543      */
544     if (!caja_bookmark_update_icon (bookmark))
545     {
546         if (bookmark->details->icon == NULL || bookmark->details->file == NULL)
547         {
548             caja_bookmark_set_icon_to_default (bookmark);
549         }
550     }
551 
552     if (!bookmark->details->has_custom_name &&
553             bookmark->details->file &&
554             caja_file_check_if_ready (bookmark->details->file, CAJA_FILE_ATTRIBUTE_INFO))
555     {
556         display_name = caja_file_get_display_name (bookmark->details->file);
557         if (g_strcmp0 (bookmark->details->name, display_name) != 0)
558         {
559             g_free (bookmark->details->name);
560             bookmark->details->name = display_name;
561         }
562         else
563         {
564             g_free (display_name);
565         }
566     }
567 }
568 
569 CajaBookmark *
caja_bookmark_new(GFile * location,const char * name,gboolean has_custom_name,GIcon * icon)570 caja_bookmark_new (GFile *location, const char *name, gboolean has_custom_name,
571                    GIcon *icon)
572 {
573     CajaBookmark *new_bookmark;
574 
575     new_bookmark = CAJA_BOOKMARK (g_object_new (CAJA_TYPE_BOOKMARK, NULL));
576 
577     new_bookmark->details->name = g_strdup (name);
578     new_bookmark->details->location = g_object_ref (location);
579     new_bookmark->details->has_custom_name = has_custom_name;
580     if (icon)
581     {
582         new_bookmark->details->icon = g_object_ref (icon);
583     }
584 
585     caja_bookmark_connect_file (new_bookmark);
586 
587     return new_bookmark;
588 }
589 
590 static cairo_surface_t *
create_image_cairo_for_bookmark(CajaBookmark * bookmark)591 create_image_cairo_for_bookmark (CajaBookmark *bookmark)
592 {
593     cairo_surface_t *surface;
594 
595     surface = caja_bookmark_get_surface (bookmark, GTK_ICON_SIZE_MENU);
596     if (surface == NULL)
597     {
598         return NULL;
599     }
600 
601     return surface;
602 }
603 
604 static GtkWidget *
bookmark_image_menu_item_new_from_surface(cairo_surface_t * icon_surface,const gchar * label_name)605 bookmark_image_menu_item_new_from_surface (cairo_surface_t   *icon_surface,
606                                            const gchar       *label_name)
607 {
608     GtkWidget *icon;
609     GtkLabel *label;
610     gchar *concat;
611 
612     GtkWidget *box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
613 
614     if (icon_surface)
615         icon = gtk_image_new_from_surface (icon_surface);
616     else
617         icon = gtk_image_new ();
618 
619     concat = g_strconcat (label_name, "     ", NULL);
620     GtkWidget *label_menu = gtk_label_new (concat);
621     g_free (concat);
622 
623     label = GTK_LABEL (label_menu);
624     gtk_label_set_use_underline (label, FALSE);
625     gtk_label_set_ellipsize (label, PANGO_ELLIPSIZE_END);
626     gtk_label_set_max_width_chars (label, (ELLIPSISED_MENU_ITEM_MIN_CHARS + 2));
627 
628     GtkWidget *menuitem = gtk_menu_item_new ();
629 
630     gtk_container_add (GTK_CONTAINER (box), icon);
631     gtk_container_add (GTK_CONTAINER (box), label_menu);
632 
633     gtk_container_add (GTK_CONTAINER (menuitem), box);
634     gtk_widget_show_all (menuitem);
635 
636     return menuitem;
637 }
638 
639 /**
640  * caja_bookmark_menu_item_new:
641  *
642  * Return a menu item representing a bookmark.
643  * @bookmark: The bookmark the menu item represents.
644  * Return value: A newly-created bookmark, not yet shown.
645  **/
646 GtkWidget *
caja_bookmark_menu_item_new(CajaBookmark * bookmark)647 caja_bookmark_menu_item_new (CajaBookmark *bookmark)
648 {
649     cairo_surface_t *image_cairo;
650 
651     image_cairo = create_image_cairo_for_bookmark (bookmark);
652 
653     if (strlen (bookmark->details->name) > 0)
654     {
655         GtkWidget *menu_item;
656 
657         menu_item = bookmark_image_menu_item_new_from_surface (image_cairo, bookmark->details->name);
658 
659         return menu_item;
660     }
661     else
662         return NULL;
663 }
664 
665 gboolean
caja_bookmark_uri_known_not_to_exist(CajaBookmark * bookmark)666 caja_bookmark_uri_known_not_to_exist (CajaBookmark *bookmark)
667 {
668     char *path_name;
669     gboolean exists;
670 
671     /* Convert to a path, returning FALSE if not local. */
672     if (!g_file_is_native (bookmark->details->location))
673     {
674         return FALSE;
675     }
676     path_name = g_file_get_path (bookmark->details->location);
677 
678     /* Now check if the file exists (sync. call OK because it is local). */
679     exists = g_file_test (path_name, G_FILE_TEST_EXISTS);
680     g_free (path_name);
681 
682     return !exists;
683 }
684 
685 void
caja_bookmark_set_scroll_pos(CajaBookmark * bookmark,const char * uri)686 caja_bookmark_set_scroll_pos (CajaBookmark      *bookmark,
687                               const char            *uri)
688 {
689     g_free (bookmark->details->scroll_file);
690     bookmark->details->scroll_file = g_strdup (uri);
691 }
692 
693 char *
caja_bookmark_get_scroll_pos(CajaBookmark * bookmark)694 caja_bookmark_get_scroll_pos (CajaBookmark      *bookmark)
695 {
696     return g_strdup (bookmark->details->scroll_file);
697 }
698