1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 
3 /* fm-icon-container.h - the container widget for file manager icons
4 
5    Copyright (C) 2002 Sun Microsystems, 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    Author: Michael Meeks <michael@ximian.com>
23 */
24 #include <config.h>
25 #include <string.h>
26 
27 #include <glib/gi18n.h>
28 #include <gio/gio.h>
29 
30 #include <eel/eel-glib-extensions.h>
31 
32 #include <libcaja-private/caja-global-preferences.h>
33 #include <libcaja-private/caja-file-attributes.h>
34 #include <libcaja-private/caja-thumbnails.h>
35 #include <libcaja-private/caja-desktop-icon-file.h>
36 
37 #include "fm-icon-container.h"
38 
39 G_DEFINE_TYPE (FMIconContainer, fm_icon_container, CAJA_TYPE_ICON_CONTAINER);
40 
41 static GQuark attribute_none_q;
42 
43 static FMIconView *
get_icon_view(CajaIconContainer * container)44 get_icon_view (CajaIconContainer *container)
45 {
46     /* Type unsafe comparison for performance */
47     return ((FMIconContainer *)container)->view;
48 }
49 
50 static CajaIconInfo *
fm_icon_container_get_icon_images(CajaIconContainer * container,CajaIconData * data,int size,GList ** emblem_pixbufs,char ** embedded_text,gboolean for_drag_accept,gboolean need_large_embeddded_text,gboolean * embedded_text_needs_loading,gboolean * has_window_open)51 fm_icon_container_get_icon_images (CajaIconContainer *container,
52                                    CajaIconData      *data,
53                                    int                    size,
54                                    GList                **emblem_pixbufs,
55                                    char                 **embedded_text,
56                                    gboolean               for_drag_accept,
57                                    gboolean               need_large_embeddded_text,
58                                    gboolean              *embedded_text_needs_loading,
59                                    gboolean              *has_window_open)
60 {
61     FMIconView *icon_view;
62     CajaFile *file;
63     gboolean use_embedding;
64     CajaFileIconFlags flags;
65     guint emblem_size;
66     gint scale;
67 
68     file = (CajaFile *) data;
69 
70     g_assert (CAJA_IS_FILE (file));
71     icon_view = get_icon_view (container);
72     g_return_val_if_fail (icon_view != NULL, NULL);
73 
74     use_embedding = FALSE;
75     if (embedded_text)
76     {
77         *embedded_text = caja_file_peek_top_left_text (file, need_large_embeddded_text, embedded_text_needs_loading);
78         use_embedding = *embedded_text != NULL;
79     }
80 
81     if (emblem_pixbufs != NULL)
82     {
83         emblem_size = caja_icon_get_emblem_size_for_icon_size (size);
84         /* don't return images larger than the actual icon size */
85         emblem_size = MIN (emblem_size, size);
86 
87         if (emblem_size > 0)
88         {
89             char **emblems_to_ignore;
90 
91             emblems_to_ignore = fm_directory_view_get_emblem_names_to_exclude
92                                 (FM_DIRECTORY_VIEW (icon_view));
93             *emblem_pixbufs = caja_file_get_emblem_pixbufs (file,
94                               emblem_size,
95                               FALSE,
96                               emblems_to_ignore);
97             g_strfreev (emblems_to_ignore);
98         }
99     }
100 
101     *has_window_open = caja_file_has_open_window (file);
102 
103     flags = CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON_AS_EMBLEM;
104     if (!fm_icon_view_is_compact (icon_view) ||
105             caja_icon_container_get_zoom_level (container) > CAJA_ZOOM_LEVEL_STANDARD)
106     {
107         flags |= CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS;
108         if (fm_icon_view_is_compact (icon_view))
109         {
110             flags |= CAJA_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE;
111         }
112     }
113 
114     if (use_embedding)
115     {
116         flags |= CAJA_FILE_ICON_FLAGS_EMBEDDING_TEXT;
117     }
118     if (for_drag_accept)
119     {
120         flags |= CAJA_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT;
121     }
122 
123     scale = gtk_widget_get_scale_factor (GTK_WIDGET (icon_view));
124 
125     return caja_file_get_icon (file, size, scale, flags);
126 }
127 
128 static char *
fm_icon_container_get_icon_description(CajaIconContainer * container,CajaIconData * data)129 fm_icon_container_get_icon_description (CajaIconContainer *container,
130                                         CajaIconData      *data)
131 {
132     CajaFile *file;
133     char *mime_type;
134     const char *description;
135 
136     file = CAJA_FILE (data);
137     g_assert (CAJA_IS_FILE (file));
138 
139     if (CAJA_IS_DESKTOP_ICON_FILE (file))
140     {
141         return NULL;
142     }
143 
144     mime_type = caja_file_get_mime_type (file);
145     description = g_content_type_get_description (mime_type);
146     g_free (mime_type);
147     return g_strdup (description);
148 }
149 
150 static void
fm_icon_container_start_monitor_top_left(CajaIconContainer * container,CajaIconData * data,gconstpointer client,gboolean large_text)151 fm_icon_container_start_monitor_top_left (CajaIconContainer *container,
152         CajaIconData      *data,
153         gconstpointer          client,
154         gboolean               large_text)
155 {
156     CajaFile *file;
157     CajaFileAttributes attributes;
158 
159     file = (CajaFile *) data;
160 
161     g_assert (CAJA_IS_FILE (file));
162 
163     attributes = CAJA_FILE_ATTRIBUTE_TOP_LEFT_TEXT;
164     if (large_text)
165     {
166         attributes |= CAJA_FILE_ATTRIBUTE_LARGE_TOP_LEFT_TEXT;
167     }
168     caja_file_monitor_add (file, client, attributes);
169 }
170 
171 static void
fm_icon_container_stop_monitor_top_left(CajaIconContainer * container,CajaIconData * data,gconstpointer client)172 fm_icon_container_stop_monitor_top_left (CajaIconContainer *container,
173         CajaIconData      *data,
174         gconstpointer          client)
175 {
176     CajaFile *file;
177 
178     file = (CajaFile *) data;
179 
180     g_assert (CAJA_IS_FILE (file));
181 
182     caja_file_monitor_remove (file, client);
183 }
184 
185 static void
fm_icon_container_prioritize_thumbnailing(CajaIconContainer * container,CajaIconData * data)186 fm_icon_container_prioritize_thumbnailing (CajaIconContainer *container,
187         CajaIconData      *data)
188 {
189     CajaFile *file;
190 
191     file = (CajaFile *) data;
192 
193     g_assert (CAJA_IS_FILE (file));
194 
195     if (caja_file_is_thumbnailing (file))
196     {
197         char *uri;
198 
199         uri = caja_file_get_uri (file);
200         caja_thumbnail_prioritize (uri);
201         g_free (uri);
202     }
203 }
204 
205 /*
206  * Get the preference for which caption text should appear
207  * beneath icons.
208  */
209 static GQuark *
fm_icon_container_get_icon_text_attributes_from_preferences(void)210 fm_icon_container_get_icon_text_attributes_from_preferences (void)
211 {
212     static GQuark *attributes = NULL;
213 
214     if (attributes == NULL)
215     {
216         eel_g_settings_add_auto_strv_as_quarks (caja_icon_view_preferences,
217                                                 CAJA_PREFERENCES_ICON_VIEW_CAPTIONS,
218                                                 &attributes);
219     }
220 
221     /* We don't need to sanity check the attributes list even though it came
222      * from preferences.
223      *
224      * There are 2 ways that the values in the list could be bad.
225      *
226      * 1) The user picks "bad" values.  "bad" values are those that result in
227      *    there being duplicate attributes in the list.
228      *
229      * 2) Value stored in MateConf are tampered with.  Its possible physically do
230      *    this by pulling the rug underneath MateConf and manually editing its
231      *    config files.  Its also possible to use a third party MateConf key
232      *    editor and store garbage for the keys in question.
233      *
234      * Thankfully, the Caja preferences machinery deals with both of
235      * these cases.
236      *
237      * In the first case, the preferences dialog widgetry prevents
238      * duplicate attributes by making "bad" choices insensitive.
239      *
240      * In the second case, the preferences getter (and also the auto storage) for
241      * string_array values are always valid members of the enumeration associated
242      * with the preference.
243      *
244      * So, no more error checking on attributes is needed here and we can return
245      * a the auto stored value.
246      */
247     return attributes;
248 }
249 
250 static int
quarkv_length(GQuark * attributes)251 quarkv_length (GQuark *attributes)
252 {
253     int i;
254     i = 0;
255     while (attributes[i] != 0)
256     {
257         i++;
258     }
259     return i;
260 }
261 
262 /**
263  * fm_icon_view_get_icon_text_attribute_names:
264  *
265  * Get a list representing which text attributes should be displayed
266  * beneath an icon. The result is dependent on zoom level and possibly
267  * user configuration. Don't free the result.
268  * @view: FMIconView to query.
269  *
270  **/
271 static GQuark *
fm_icon_container_get_icon_text_attribute_names(CajaIconContainer * container,int * len)272 fm_icon_container_get_icon_text_attribute_names (CajaIconContainer *container,
273         int *len)
274 {
275     GQuark *attributes;
276     int piece_count;
277 
278     const int pieces_by_level[] =
279     {
280         0,	/* CAJA_ZOOM_LEVEL_SMALLEST */
281         0,	/* CAJA_ZOOM_LEVEL_SMALLER */
282         0,	/* CAJA_ZOOM_LEVEL_SMALL */
283         1,	/* CAJA_ZOOM_LEVEL_STANDARD */
284         2,	/* CAJA_ZOOM_LEVEL_LARGE */
285         2,	/* CAJA_ZOOM_LEVEL_LARGER */
286         3	/* CAJA_ZOOM_LEVEL_LARGEST */
287     };
288 
289     piece_count = pieces_by_level[caja_icon_container_get_zoom_level (container)];
290 
291     attributes = fm_icon_container_get_icon_text_attributes_from_preferences ();
292 
293     *len = MIN (piece_count, quarkv_length (attributes));
294 
295     return attributes;
296 }
297 
298 /* This callback returns the text, both the editable part, and the
299  * part below that is not editable.
300  */
301 static void
fm_icon_container_get_icon_text(CajaIconContainer * container,CajaIconData * data,char ** editable_text,char ** additional_text,gboolean include_invisible)302 fm_icon_container_get_icon_text (CajaIconContainer *container,
303                                  CajaIconData      *data,
304                                  char                 **editable_text,
305                                  char                 **additional_text,
306                                  gboolean               include_invisible)
307 {
308     GQuark *attributes;
309     char *text_array[4];
310     int i, j, num_attributes;
311     FMIconView *icon_view;
312     CajaFile *file;
313     gboolean use_additional;
314 
315     file = CAJA_FILE (data);
316 
317     g_assert (CAJA_IS_FILE (file));
318     g_assert (editable_text != NULL);
319     icon_view = get_icon_view (container);
320     g_return_if_fail (icon_view != NULL);
321 
322     use_additional = (additional_text != NULL);
323 
324     /* In the smallest zoom mode, no text is drawn. */
325     if (caja_icon_container_get_zoom_level (container) == CAJA_ZOOM_LEVEL_SMALLEST &&
326             !include_invisible)
327     {
328         *editable_text = NULL;
329     }
330     else
331     {
332         /* Strip the suffix for caja object xml files. */
333         *editable_text = caja_file_get_display_name (file);
334     }
335 
336     if (!use_additional)
337     {
338         return;
339     }
340 
341     if (fm_icon_view_is_compact (icon_view))
342     {
343         *additional_text = NULL;
344         return;
345     }
346 
347     if (CAJA_IS_DESKTOP_ICON_FILE (file))
348     {
349         /* Don't show the normal extra information for desktop icons, it doesn't
350          * make sense. */
351         *additional_text = NULL;
352         return;
353     }
354 
355     /* Handle link files specially. */
356     if (caja_file_is_caja_link (file))
357     {
358         /* FIXME bugzilla.gnome.org 42531: Does sync. I/O and works only locally. */
359         *additional_text = NULL;
360         if (caja_file_is_local (file))
361         {
362             char *actual_uri;
363             gchar *description;
364 
365             actual_uri = caja_file_get_uri (file);
366             description = caja_link_local_get_additional_text (actual_uri);
367 
368             if (description)
369                 *additional_text = g_strdup_printf (" \n%s\n ", description);
370 
371             g_free (description);
372             g_free (actual_uri);
373         }
374         /* Don't show the normal extra information for desktop files, it doesn't
375          * make sense. */
376         return;
377     }
378 
379     /* Find out what attributes go below each icon. */
380     attributes = fm_icon_container_get_icon_text_attribute_names (container,
381                  &num_attributes);
382 
383     /* Get the attributes. */
384     j = 0;
385     for (i = 0; i < num_attributes; ++i)
386     {
387         if (attributes[i] == attribute_none_q)
388         {
389             continue;
390         }
391 
392         text_array[j++] =
393             caja_file_get_string_attribute_with_default_q (file, attributes[i]);
394     }
395     text_array[j] = NULL;
396 
397     /* Return them. */
398     if (j == 0)
399     {
400         *additional_text = NULL;
401     }
402     else if (j == 1)
403     {
404         /* Only one item, avoid the strdup + free */
405         *additional_text = text_array[0];
406     }
407     else
408     {
409         *additional_text = g_strjoinv ("\n", text_array);
410 
411         for (i = 0; i < j; i++)
412         {
413             g_free (text_array[i]);
414         }
415     }
416 }
417 
418 /* Sort as follows:
419  *   0) computer link
420  *   1) home link
421  *   2) network link
422  *   3) mount links
423  *   4) other
424  *   5) trash link
425  */
426 typedef enum
427 {
428     SORT_COMPUTER_LINK,
429     SORT_HOME_LINK,
430     SORT_NETWORK_LINK,
431     SORT_MOUNT_LINK,
432     SORT_OTHER,
433     SORT_TRASH_LINK
434 } SortCategory;
435 
436 static SortCategory
get_sort_category(CajaFile * file)437 get_sort_category (CajaFile *file)
438 {
439     SortCategory category;
440 
441     category = SORT_OTHER;
442 
443     if (CAJA_IS_DESKTOP_ICON_FILE (file))
444     {
445         CajaDesktopLink *link;
446 
447         link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (file));
448 
449         if (link != NULL)
450         {
451             switch (caja_desktop_link_get_link_type (link))
452             {
453             case CAJA_DESKTOP_LINK_COMPUTER:
454                 category = SORT_COMPUTER_LINK;
455                 break;
456             case CAJA_DESKTOP_LINK_HOME:
457                 category = SORT_HOME_LINK;
458                 break;
459             case CAJA_DESKTOP_LINK_MOUNT:
460                 category = SORT_MOUNT_LINK;
461                 break;
462             case CAJA_DESKTOP_LINK_TRASH:
463                 category = SORT_TRASH_LINK;
464                 break;
465             case CAJA_DESKTOP_LINK_NETWORK:
466                 category = SORT_NETWORK_LINK;
467                 break;
468             default:
469                 category = SORT_OTHER;
470                 break;
471             }
472             g_object_unref (link);
473         }
474     }
475 
476     return category;
477 }
478 
479 static int
fm_desktop_icon_container_icons_compare(CajaIconContainer * container,CajaIconData * data_a,CajaIconData * data_b)480 fm_desktop_icon_container_icons_compare (CajaIconContainer *container,
481         CajaIconData      *data_a,
482         CajaIconData      *data_b)
483 {
484     CajaFile *file_a;
485     CajaFile *file_b;
486     FMDirectoryView *directory_view;
487     SortCategory category_a, category_b;
488 
489     file_a = (CajaFile *) data_a;
490     file_b = (CajaFile *) data_b;
491 
492     directory_view = FM_DIRECTORY_VIEW (FM_ICON_CONTAINER (container)->view);
493     g_return_val_if_fail (directory_view != NULL, 0);
494 
495     category_a = get_sort_category (file_a);
496     category_b = get_sort_category (file_b);
497 
498     if (category_a == category_b)
499     {
500         return caja_file_compare_for_sort
501                (file_a, file_b, CAJA_FILE_SORT_BY_DISPLAY_NAME,
502                 fm_directory_view_should_sort_directories_first (directory_view),
503                 FALSE);
504     }
505 
506     if (category_a < category_b)
507     {
508         return -1;
509     }
510     else
511     {
512         return +1;
513     }
514 }
515 
516 static int
fm_icon_container_compare_icons(CajaIconContainer * container,CajaIconData * icon_a,CajaIconData * icon_b)517 fm_icon_container_compare_icons (CajaIconContainer *container,
518                                  CajaIconData      *icon_a,
519                                  CajaIconData      *icon_b)
520 {
521     FMIconView *icon_view;
522 
523     icon_view = get_icon_view (container);
524     g_return_val_if_fail (icon_view != NULL, 0);
525 
526     if (FM_ICON_CONTAINER (container)->sort_for_desktop)
527     {
528         return fm_desktop_icon_container_icons_compare
529                (container, icon_a, icon_b);
530     }
531 
532     /* Type unsafe comparisons for performance */
533     return fm_icon_view_compare_files (icon_view,
534                                        (CajaFile *)icon_a,
535                                        (CajaFile *)icon_b);
536 }
537 
538 static int
fm_icon_container_compare_icons_by_name(CajaIconContainer * container,CajaIconData * icon_a,CajaIconData * icon_b)539 fm_icon_container_compare_icons_by_name (CajaIconContainer *container,
540         CajaIconData      *icon_a,
541         CajaIconData      *icon_b)
542 {
543     return caja_file_compare_for_sort
544            (CAJA_FILE (icon_a),
545             CAJA_FILE (icon_b),
546             CAJA_FILE_SORT_BY_DISPLAY_NAME,
547             FALSE, FALSE);
548 }
549 
550 static void
fm_icon_container_freeze_updates(CajaIconContainer * container)551 fm_icon_container_freeze_updates (CajaIconContainer *container)
552 {
553     FMIconView *icon_view;
554     icon_view = get_icon_view (container);
555     g_return_if_fail (icon_view != NULL);
556     fm_directory_view_freeze_updates (FM_DIRECTORY_VIEW (icon_view));
557 }
558 
559 static void
fm_icon_container_unfreeze_updates(CajaIconContainer * container)560 fm_icon_container_unfreeze_updates (CajaIconContainer *container)
561 {
562     FMIconView *icon_view;
563     icon_view = get_icon_view (container);
564     g_return_if_fail (icon_view != NULL);
565     fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (icon_view));
566 }
567 
568 static void
fm_icon_container_dispose(GObject * object)569 fm_icon_container_dispose (GObject *object)
570 {
571     FMIconContainer *icon_container;
572 
573     icon_container = FM_ICON_CONTAINER (object);
574 
575     icon_container->view = NULL;
576 
577     G_OBJECT_CLASS (fm_icon_container_parent_class)->dispose (object);
578 }
579 
580 static void
fm_icon_container_class_init(FMIconContainerClass * klass)581 fm_icon_container_class_init (FMIconContainerClass *klass)
582 {
583     CajaIconContainerClass *ic_class;
584 
585     ic_class = &klass->parent_class;
586 
587     attribute_none_q = g_quark_from_static_string ("none");
588 
589     ic_class->get_icon_text = fm_icon_container_get_icon_text;
590     ic_class->get_icon_images = fm_icon_container_get_icon_images;
591     ic_class->get_icon_description = fm_icon_container_get_icon_description;
592     ic_class->start_monitor_top_left = fm_icon_container_start_monitor_top_left;
593     ic_class->stop_monitor_top_left = fm_icon_container_stop_monitor_top_left;
594     ic_class->prioritize_thumbnailing = fm_icon_container_prioritize_thumbnailing;
595 
596     ic_class->compare_icons = fm_icon_container_compare_icons;
597     ic_class->compare_icons_by_name = fm_icon_container_compare_icons_by_name;
598     ic_class->freeze_updates = fm_icon_container_freeze_updates;
599     ic_class->unfreeze_updates = fm_icon_container_unfreeze_updates;
600 
601     G_OBJECT_CLASS (klass)->dispose = fm_icon_container_dispose;
602 }
603 
604 static void
fm_icon_container_init(FMIconContainer * icon_container)605 fm_icon_container_init (FMIconContainer *icon_container)
606 {
607     gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (icon_container)),
608                                  GTK_STYLE_CLASS_VIEW);
609 }
610 
611 CajaIconContainer *
fm_icon_container_construct(FMIconContainer * icon_container,FMIconView * view)612 fm_icon_container_construct (FMIconContainer *icon_container, FMIconView *view)
613 {
614     AtkObject *atk_obj;
615 
616     g_return_val_if_fail (FM_IS_ICON_VIEW (view), NULL);
617 
618     icon_container->view = view;
619     atk_obj = gtk_widget_get_accessible (GTK_WIDGET (icon_container));
620     atk_object_set_name (atk_obj, _("Icon View"));
621 
622     return CAJA_ICON_CONTAINER (icon_container);
623 }
624 
625 CajaIconContainer *
fm_icon_container_new(FMIconView * view)626 fm_icon_container_new (FMIconView *view)
627 {
628     return fm_icon_container_construct
629            (g_object_new (FM_TYPE_ICON_CONTAINER, NULL),
630             view);
631 }
632 
633 void
fm_icon_container_set_sort_desktop(FMIconContainer * container,gboolean desktop)634 fm_icon_container_set_sort_desktop (FMIconContainer *container,
635                                     gboolean         desktop)
636 {
637     container->sort_for_desktop = desktop;
638 }
639