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