1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 
3 /* fm-list-model.h - a GtkTreeModel for file lists.
4 
5    Copyright (C) 2001, 2002 Anders Carlsson
6    Copyright (C) 2003, Soeren Sandmann
7    Copyright (C) 2004, Novell, Inc.
8 
9    The Mate Library is free software; you can redistribute it and/or
10    modify it under the terms of the GNU Library General Public License as
11    published by the Free Software Foundation; either version 2 of the
12    License, or (at your option) any later version.
13 
14    The Mate Library is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17    Library General Public License for more details.
18 
19    You should have received a copy of the GNU Library General Public
20    License along with the Mate Library; see the file COPYING.LIB.  If not,
21    write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22    Boston, MA 02110-1301, USA.
23 
24    Authors: Anders Carlsson <andersca@gnu.org>, Soeren Sandmann (sandmann@daimi.au.dk), Dave Camp <dave@ximian.com>
25 */
26 
27 #include <config.h>
28 #include <glib.h>
29 #include <string.h>
30 #include <cairo-gobject.h>
31 
32 #include <gtk/gtk.h>
33 #include <glib/gi18n.h>
34 
35 #include <libegg/eggtreemultidnd.h>
36 
37 #include <eel/eel-graphic-effects.h>
38 
39 #include <libcaja-private/caja-dnd.h>
40 #include <libcaja-private/caja-global-preferences.h>
41 
42 #include "fm-list-model.h"
43 
44 enum
45 {
46     SUBDIRECTORY_UNLOADED,
47     GET_ICON_SCALE,
48     LAST_SIGNAL
49 };
50 
51 static GQuark attribute_name_q,
52        attribute_modification_date_q,
53        attribute_date_modified_q;
54 
55 static guint list_model_signals[LAST_SIGNAL] = { 0 };
56 
57 static int fm_list_model_file_entry_compare_func (gconstpointer a,
58         gconstpointer b,
59         gpointer      user_data);
60 static void fm_list_model_tree_model_init (GtkTreeModelIface *iface);
61 static void fm_list_model_sortable_init (GtkTreeSortableIface *iface);
62 static void fm_list_model_multi_drag_source_init (EggTreeMultiDragSourceIface *iface);
63 
64 struct FMListModelDetails
65 {
66     GSequence *files;
67     GHashTable *directory_reverse_map; /* map from directory to GSequenceIter's */
68     GHashTable *top_reverse_map;	   /* map from files in top dir to GSequenceIter's */
69 
70     int stamp;
71 
72     GQuark sort_attribute;
73     GtkSortType order;
74 
75     gboolean sort_directories_first;
76 
77     GtkTreeView *drag_view;
78     int drag_begin_x;
79     int drag_begin_y;
80 
81     GPtrArray *columns;
82 
83     GList *highlight_files;
84 };
85 
86 typedef struct
87 {
88     FMListModel *model;
89 
90     GList *path_list;
91 } DragDataGetInfo;
92 
93 typedef struct FileEntry FileEntry;
94 
95 struct FileEntry
96 {
97     CajaFile *file;
98     GHashTable *reverse_map;	/* map from files to GSequenceIter's */
99     CajaDirectory *subdirectory;
100     FileEntry *parent;
101     GSequence *files;
102     GSequenceIter *ptr;
103     guint loaded : 1;
104 };
105 
106 G_DEFINE_TYPE_WITH_CODE (FMListModel, fm_list_model, G_TYPE_OBJECT,
107                          G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
108                                  fm_list_model_tree_model_init)
109                          G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_SORTABLE,
110                                  fm_list_model_sortable_init)
111                          G_IMPLEMENT_INTERFACE (EGG_TYPE_TREE_MULTI_DRAG_SOURCE,
112                                  fm_list_model_multi_drag_source_init));
113 
114 static const GtkTargetEntry drag_types [] =
115 {
116     { CAJA_ICON_DND_MATE_ICON_LIST_TYPE, 0, CAJA_ICON_DND_MATE_ICON_LIST },
117     { CAJA_ICON_DND_URI_LIST_TYPE, 0, CAJA_ICON_DND_URI_LIST },
118 };
119 
120 static GtkTargetList *drag_target_list = NULL;
121 
122 static void
file_entry_free(FileEntry * file_entry)123 file_entry_free (FileEntry *file_entry)
124 {
125     caja_file_unref (file_entry->file);
126     if (file_entry->reverse_map)
127     {
128         g_hash_table_destroy (file_entry->reverse_map);
129         file_entry->reverse_map = NULL;
130     }
131     if (file_entry->subdirectory != NULL)
132     {
133         caja_directory_unref (file_entry->subdirectory);
134     }
135     if (file_entry->files != NULL)
136     {
137         g_sequence_free (file_entry->files);
138     }
139     g_free (file_entry);
140 }
141 
142 static GtkTreeModelFlags
fm_list_model_get_flags(GtkTreeModel * tree_model)143 fm_list_model_get_flags (GtkTreeModel *tree_model)
144 {
145     return GTK_TREE_MODEL_ITERS_PERSIST;
146 }
147 
148 static int
fm_list_model_get_n_columns(GtkTreeModel * tree_model)149 fm_list_model_get_n_columns (GtkTreeModel *tree_model)
150 {
151     return FM_LIST_MODEL_NUM_COLUMNS + FM_LIST_MODEL (tree_model)->details->columns->len;
152 }
153 
154 static GType
fm_list_model_get_column_type(GtkTreeModel * tree_model,int index)155 fm_list_model_get_column_type (GtkTreeModel *tree_model, int index)
156 {
157     switch (index)
158     {
159     case FM_LIST_MODEL_FILE_COLUMN:
160         return CAJA_TYPE_FILE;
161     case FM_LIST_MODEL_SUBDIRECTORY_COLUMN:
162         return CAJA_TYPE_DIRECTORY;
163     case FM_LIST_MODEL_SMALLEST_ICON_COLUMN:
164     case FM_LIST_MODEL_SMALLER_ICON_COLUMN:
165     case FM_LIST_MODEL_SMALL_ICON_COLUMN:
166     case FM_LIST_MODEL_STANDARD_ICON_COLUMN:
167     case FM_LIST_MODEL_LARGE_ICON_COLUMN:
168     case FM_LIST_MODEL_LARGER_ICON_COLUMN:
169     case FM_LIST_MODEL_LARGEST_ICON_COLUMN:
170         return CAIRO_GOBJECT_TYPE_SURFACE;
171     case FM_LIST_MODEL_FILE_NAME_IS_EDITABLE_COLUMN:
172         return G_TYPE_BOOLEAN;
173     default:
174         if (index < FM_LIST_MODEL_NUM_COLUMNS + FM_LIST_MODEL (tree_model)->details->columns->len)
175         {
176             return G_TYPE_STRING;
177         }
178         else
179         {
180             return G_TYPE_INVALID;
181         }
182     }
183 }
184 
185 static void
fm_list_model_ptr_to_iter(FMListModel * model,GSequenceIter * ptr,GtkTreeIter * iter)186 fm_list_model_ptr_to_iter (FMListModel *model, GSequenceIter *ptr, GtkTreeIter *iter)
187 {
188     g_assert (!g_sequence_iter_is_end (ptr));
189     if (iter != NULL)
190     {
191         iter->stamp = model->details->stamp;
192         iter->user_data = ptr;
193     }
194 }
195 
196 static gboolean
fm_list_model_get_iter(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreePath * path)197 fm_list_model_get_iter (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreePath *path)
198 {
199     FMListModel *model;
200     GSequence *files;
201     GSequenceIter *ptr;
202     FileEntry *file_entry;
203     int d;
204 
205     model = (FMListModel *)tree_model;
206     ptr = NULL;
207 
208     files = model->details->files;
209     for (d = 0; d < gtk_tree_path_get_depth (path); d++)
210     {
211         int i;
212 
213         i = gtk_tree_path_get_indices (path)[d];
214 
215         if (files == NULL || i >= g_sequence_get_length (files))
216         {
217             return FALSE;
218         }
219 
220         ptr = g_sequence_get_iter_at_pos (files, i);
221         file_entry = g_sequence_get (ptr);
222         files = file_entry->files;
223     }
224 
225     fm_list_model_ptr_to_iter (model, ptr, iter);
226 
227     return TRUE;
228 }
229 
230 static GtkTreePath *
fm_list_model_get_path(GtkTreeModel * tree_model,GtkTreeIter * iter)231 fm_list_model_get_path (GtkTreeModel *tree_model, GtkTreeIter *iter)
232 {
233     GtkTreePath *path;
234     FMListModel *model;
235     GSequenceIter *ptr;
236     FileEntry *file_entry;
237 
238 
239     model = (FMListModel *)tree_model;
240 
241     g_return_val_if_fail (iter->stamp == model->details->stamp, NULL);
242 
243     if (g_sequence_iter_is_end (iter->user_data))
244     {
245         /* FIXME is this right? */
246         return NULL;
247     }
248 
249     path = gtk_tree_path_new ();
250     ptr = iter->user_data;
251     while (ptr != NULL)
252     {
253         gtk_tree_path_prepend_index (path, g_sequence_iter_get_position (ptr));
254         file_entry = g_sequence_get (ptr);
255         if (file_entry->parent != NULL)
256         {
257             ptr = file_entry->parent->ptr;
258         }
259         else
260         {
261             ptr = NULL;
262         }
263     }
264 
265     return path;
266 }
267 
268 static gint
fm_list_model_get_icon_scale(FMListModel * model)269 fm_list_model_get_icon_scale (FMListModel *model)
270 {
271     gint retval = -1;
272 
273     g_signal_emit (model, list_model_signals[GET_ICON_SCALE], 0,
274                    &retval);
275 
276     if (retval == -1) {
277         retval = gdk_monitor_get_scale_factor (gdk_display_get_monitor (gdk_display_get_default (), 0));
278     }
279 
280     return retval;
281 }
282 
283 static void
fm_list_model_get_value(GtkTreeModel * tree_model,GtkTreeIter * iter,int column,GValue * value)284 fm_list_model_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter, int column, GValue *value)
285 {
286     FMListModel *model;
287     FileEntry *file_entry;
288     CajaFile *file;
289     GdkPixbuf *rendered_icon;
290     GIcon *emblemed_icon;
291     GList *emblem_icons, *l;
292     CajaZoomLevel zoom_level;
293     CajaFileIconFlags flags;
294 
295     model = (FMListModel *)tree_model;
296 
297     g_return_if_fail (model->details->stamp == iter->stamp);
298     g_return_if_fail (!g_sequence_iter_is_end (iter->user_data));
299 
300     file_entry = g_sequence_get (iter->user_data);
301     file = file_entry->file;
302 
303     switch (column)
304     {
305     case FM_LIST_MODEL_FILE_COLUMN:
306         g_value_init (value, CAJA_TYPE_FILE);
307 
308         g_value_set_object (value, file);
309         break;
310     case FM_LIST_MODEL_SUBDIRECTORY_COLUMN:
311         g_value_init (value, CAJA_TYPE_DIRECTORY);
312 
313         g_value_set_object (value, file_entry->subdirectory);
314         break;
315     case FM_LIST_MODEL_SMALLEST_ICON_COLUMN:
316     case FM_LIST_MODEL_SMALLER_ICON_COLUMN:
317     case FM_LIST_MODEL_SMALL_ICON_COLUMN:
318     case FM_LIST_MODEL_STANDARD_ICON_COLUMN:
319     case FM_LIST_MODEL_LARGE_ICON_COLUMN:
320     case FM_LIST_MODEL_LARGER_ICON_COLUMN:
321     case FM_LIST_MODEL_LARGEST_ICON_COLUMN:
322         if (!g_settings_get_boolean (caja_preferences, CAJA_PREFERENCES_SHOW_ICONS_IN_LIST_VIEW)) {
323             cairo_surface_t *surface;
324             int icon_size;
325 
326             g_value_init (value, CAIRO_GOBJECT_TYPE_SURFACE);
327 
328             zoom_level = fm_list_model_get_zoom_level_from_column_id (column);
329             icon_size = caja_get_icon_size_for_zoom_level (zoom_level);
330 
331             surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, icon_size, icon_size);
332             g_value_take_boxed (value, surface);
333             break;
334         }
335         g_value_init (value, CAIRO_GOBJECT_TYPE_SURFACE);
336 
337         if (file != NULL)
338         {
339             GdkPixbuf *icon;
340             GIcon *gicon;
341             CajaIconInfo *icon_info;
342             GEmblem *emblem;
343             int icon_size, icon_scale;
344             CajaFile *parent_file;
345             char *emblems_to_ignore[3];
346             int i;
347             cairo_surface_t *surface;
348             const char *icon_name;
349 
350             zoom_level = fm_list_model_get_zoom_level_from_column_id (column);
351             icon_size = caja_get_icon_size_for_zoom_level (zoom_level);
352             icon_scale = fm_list_model_get_icon_scale (model);
353 
354             flags = CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS |
355                     CAJA_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE |
356                     CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON_AS_EMBLEM;
357             if (model->details->drag_view != NULL)
358             {
359                 GtkTreePath *path_a;
360 
361                 gtk_tree_view_get_drag_dest_row (model->details->drag_view,
362                                                  &path_a,
363                                                  NULL);
364                 if (path_a != NULL)
365                 {
366                     GtkTreePath *path_b;
367 
368                     path_b = gtk_tree_model_get_path (tree_model, iter);
369 
370                     if (gtk_tree_path_compare (path_a, path_b) == 0)
371                     {
372                         flags |= CAJA_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT;
373                     }
374 
375                     gtk_tree_path_free (path_a);
376                     gtk_tree_path_free (path_b);
377                 }
378             }
379 
380             gicon = caja_file_get_gicon (file, flags);
381 
382             /* render emblems with GEmblemedIcon */
383             parent_file = caja_file_get_parent (file);
384             i = 0;
385             emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_TRASH;
386             if (parent_file) {
387             	if (!caja_file_can_write (parent_file)) {
388                     emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_CANT_WRITE;
389             	}
390             	caja_file_unref (parent_file);
391             }
392             emblems_to_ignore[i++] = NULL;
393 
394             emblem = NULL;
395             emblem_icons = caja_file_get_emblem_icons (file,
396             					       emblems_to_ignore);
397 
398             if (emblem_icons != NULL) {
399                 GIcon *emblem_icon;
400 
401                 emblem_icon = emblem_icons->data;
402                 emblem = g_emblem_new (emblem_icon);
403                 emblemed_icon = g_emblemed_icon_new (gicon, emblem);
404 
405                 g_object_unref (emblem);
406 
407             	for (l = emblem_icons->next; l != NULL; l = l->next) {
408             	    emblem_icon = l->data;
409             	    emblem = g_emblem_new (emblem_icon);
410             	    g_emblemed_icon_add_emblem
411             	        (G_EMBLEMED_ICON (emblemed_icon), emblem);
412 
413                     g_object_unref (emblem);
414             	}
415 
416                 g_list_free_full (emblem_icons, g_object_unref);
417 
418             	g_object_unref (gicon);
419             	gicon = emblemed_icon;
420             }
421 
422             icon_info = caja_file_get_icon (file, icon_size, icon_scale, flags);
423             icon_name = caja_icon_info_get_used_name (icon_info);
424 
425             if (icon_name != NULL) {
426                 g_object_unref (icon_info);
427                 icon_info = caja_icon_info_lookup (gicon, icon_size, icon_scale);
428             }
429             icon = caja_icon_info_get_pixbuf_at_size (icon_info, icon_size);
430 
431             g_object_unref (icon_info);
432             g_object_unref (gicon);
433 
434             if (model->details->highlight_files != NULL &&
435                     g_list_find_custom (model->details->highlight_files,
436                                         file, (GCompareFunc) caja_file_compare_location))
437             {
438                 rendered_icon = eel_create_spotlight_pixbuf (icon);
439 
440                 if (rendered_icon != NULL)
441                 {
442                     g_object_unref (icon);
443                     icon = rendered_icon;
444                 }
445             }
446 
447             surface = gdk_cairo_surface_create_from_pixbuf (icon, icon_scale, NULL);
448             g_value_take_boxed (value, surface);
449             g_object_unref (icon);
450         }
451         break;
452     case FM_LIST_MODEL_FILE_NAME_IS_EDITABLE_COLUMN:
453         g_value_init (value, G_TYPE_BOOLEAN);
454 
455         g_value_set_boolean (value, file != NULL && caja_file_can_rename (file));
456         break;
457     default:
458         if (column >= FM_LIST_MODEL_NUM_COLUMNS || column < FM_LIST_MODEL_NUM_COLUMNS + model->details->columns->len)
459         {
460             CajaColumn *caja_column;
461             GQuark attribute;
462             caja_column = model->details->columns->pdata[column - FM_LIST_MODEL_NUM_COLUMNS];
463 
464             g_value_init (value, G_TYPE_STRING);
465             g_object_get (caja_column,
466                           "attribute_q", &attribute,
467                           NULL);
468             if (file != NULL)
469             {
470                 char *str;
471 
472                 str = caja_file_get_string_attribute_with_default_q (file,
473                         attribute);
474                 g_value_take_string (value, str);
475             }
476             else if (attribute == attribute_name_q)
477             {
478                 if (file_entry->parent->loaded)
479                 {
480                     g_value_set_string (value, _("(Empty)"));
481                 }
482                 else
483                 {
484                     g_value_set_string (value, _("Loading..."));
485                 }
486             }
487         }
488         else
489         {
490             g_assert_not_reached ();
491         }
492     }
493 }
494 
495 static gboolean
fm_list_model_iter_next(GtkTreeModel * tree_model,GtkTreeIter * iter)496 fm_list_model_iter_next (GtkTreeModel *tree_model, GtkTreeIter *iter)
497 {
498     FMListModel *model;
499 
500     model = (FMListModel *)tree_model;
501 
502     g_return_val_if_fail (model->details->stamp == iter->stamp, FALSE);
503 
504     iter->user_data = g_sequence_iter_next (iter->user_data);
505 
506     return !g_sequence_iter_is_end (iter->user_data);
507 }
508 
509 static gboolean
fm_list_model_iter_children(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreeIter * parent)510 fm_list_model_iter_children (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent)
511 {
512     FMListModel *model;
513     GSequence *files;
514     FileEntry *file_entry;
515 
516     model = (FMListModel *)tree_model;
517 
518     if (parent == NULL)
519     {
520         files = model->details->files;
521     }
522     else
523     {
524         file_entry = g_sequence_get (parent->user_data);
525         files = file_entry->files;
526     }
527 
528     if (files == NULL || g_sequence_get_length (files) == 0)
529     {
530         return FALSE;
531     }
532 
533     iter->stamp = model->details->stamp;
534     iter->user_data = g_sequence_get_begin_iter (files);
535 
536     return TRUE;
537 }
538 
539 static gboolean
fm_list_model_iter_has_child(GtkTreeModel * tree_model,GtkTreeIter * iter)540 fm_list_model_iter_has_child (GtkTreeModel *tree_model, GtkTreeIter *iter)
541 {
542     FileEntry *file_entry;
543 
544     if (iter == NULL)
545     {
546         return !fm_list_model_is_empty (FM_LIST_MODEL (tree_model));
547     }
548 
549     file_entry = g_sequence_get (iter->user_data);
550 
551     return (file_entry->files != NULL && g_sequence_get_length (file_entry->files) > 0);
552 }
553 
554 static int
fm_list_model_iter_n_children(GtkTreeModel * tree_model,GtkTreeIter * iter)555 fm_list_model_iter_n_children (GtkTreeModel *tree_model, GtkTreeIter *iter)
556 {
557     FMListModel *model;
558     GSequence *files;
559     FileEntry *file_entry;
560 
561     model = (FMListModel *)tree_model;
562 
563     if (iter == NULL)
564     {
565         files = model->details->files;
566     }
567     else
568     {
569         file_entry = g_sequence_get (iter->user_data);
570         files = file_entry->files;
571     }
572 
573     return g_sequence_get_length (files);
574 }
575 
576 static gboolean
fm_list_model_iter_nth_child(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreeIter * parent,int n)577 fm_list_model_iter_nth_child (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, int n)
578 {
579     FMListModel *model;
580     GSequenceIter *child;
581     GSequence *files;
582     FileEntry *file_entry;
583 
584     model = (FMListModel *)tree_model;
585 
586     if (parent != NULL)
587     {
588         file_entry = g_sequence_get (parent->user_data);
589         files = file_entry->files;
590     }
591     else
592     {
593         files = model->details->files;
594     }
595 
596     child = g_sequence_get_iter_at_pos (files, n);
597 
598     if (g_sequence_iter_is_end (child))
599     {
600         return FALSE;
601     }
602 
603     iter->stamp = model->details->stamp;
604     iter->user_data = child;
605 
606     return TRUE;
607 }
608 
609 static gboolean
fm_list_model_iter_parent(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreeIter * child)610 fm_list_model_iter_parent (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child)
611 {
612     FMListModel *model;
613     FileEntry *file_entry;
614 
615     model = (FMListModel *)tree_model;
616 
617     file_entry = g_sequence_get (child->user_data);
618 
619     if (file_entry->parent == NULL)
620     {
621         return FALSE;
622     }
623 
624     iter->stamp = model->details->stamp;
625     iter->user_data = file_entry->parent->ptr;
626 
627     return TRUE;
628 }
629 
630 static GSequenceIter *
lookup_file(FMListModel * model,CajaFile * file,CajaDirectory * directory)631 lookup_file (FMListModel *model, CajaFile *file,
632              CajaDirectory *directory)
633 {
634     GSequenceIter *ptr, *parent_ptr;
635 
636     parent_ptr = NULL;
637     if (directory)
638     {
639         parent_ptr = g_hash_table_lookup (model->details->directory_reverse_map,
640                                           directory);
641     }
642 
643     if (parent_ptr)
644     {
645         FileEntry *file_entry;
646 
647         file_entry = g_sequence_get (parent_ptr);
648         ptr = g_hash_table_lookup (file_entry->reverse_map, file);
649     }
650     else
651     {
652         ptr = g_hash_table_lookup (model->details->top_reverse_map, file);
653     }
654 
655     if (ptr)
656     {
657         g_assert (((FileEntry *)g_sequence_get (ptr))->file == file);
658     }
659 
660     return ptr;
661 }
662 
663 
664 struct GetIters
665 {
666     FMListModel *model;
667     CajaFile *file;
668     GList *iters;
669 };
670 
671 static void
dir_to_iters(struct GetIters * data,GHashTable * reverse_map)672 dir_to_iters (struct GetIters *data,
673               GHashTable *reverse_map)
674 {
675     GSequenceIter *ptr;
676 
677     ptr = g_hash_table_lookup (reverse_map, data->file);
678     if (ptr)
679     {
680         GtkTreeIter *iter;
681         iter = g_new0 (GtkTreeIter, 1);
682         fm_list_model_ptr_to_iter (data->model, ptr, iter);
683         data->iters = g_list_prepend (data->iters, iter);
684     }
685 }
686 
687 static void
file_to_iter_cb(gpointer key,gpointer value,gpointer user_data)688 file_to_iter_cb (gpointer  key,
689                  gpointer  value,
690                  gpointer  user_data)
691 {
692     struct GetIters *data;
693     FileEntry *dir_file_entry;
694 
695     data = user_data;
696     dir_file_entry = g_sequence_get ((GSequenceIter *)value);
697     dir_to_iters (data, dir_file_entry->reverse_map);
698 }
699 
700 GList *
fm_list_model_get_all_iters_for_file(FMListModel * model,CajaFile * file)701 fm_list_model_get_all_iters_for_file (FMListModel *model, CajaFile *file)
702 {
703     struct GetIters data;
704 
705     data.file = file;
706     data.model = model;
707     data.iters = NULL;
708 
709     dir_to_iters (&data, model->details->top_reverse_map);
710     g_hash_table_foreach (model->details->directory_reverse_map,
711                           file_to_iter_cb, &data);
712 
713     return g_list_reverse (data.iters);
714 }
715 
716 gboolean
fm_list_model_get_first_iter_for_file(FMListModel * model,CajaFile * file,GtkTreeIter * iter)717 fm_list_model_get_first_iter_for_file (FMListModel          *model,
718                                        CajaFile         *file,
719                                        GtkTreeIter          *iter)
720 {
721     GList *list;
722     gboolean res;
723 
724     res = FALSE;
725 
726     list = fm_list_model_get_all_iters_for_file (model, file);
727     if (list != NULL)
728     {
729         res = TRUE;
730         *iter = *(GtkTreeIter *)list->data;
731     }
732     g_list_free_full (list, g_free);
733 
734     return res;
735 }
736 
737 
738 gboolean
fm_list_model_get_tree_iter_from_file(FMListModel * model,CajaFile * file,CajaDirectory * directory,GtkTreeIter * iter)739 fm_list_model_get_tree_iter_from_file (FMListModel *model, CajaFile *file,
740                                        CajaDirectory *directory,
741                                        GtkTreeIter *iter)
742 {
743     GSequenceIter *ptr;
744 
745     ptr = lookup_file (model, file, directory);
746     if (!ptr)
747     {
748         return FALSE;
749     }
750 
751     fm_list_model_ptr_to_iter (model, ptr, iter);
752 
753     return TRUE;
754 }
755 
756 static int
fm_list_model_file_entry_compare_func(gconstpointer a,gconstpointer b,gpointer user_data)757 fm_list_model_file_entry_compare_func (gconstpointer a,
758                                        gconstpointer b,
759                                        gpointer      user_data)
760 {
761     FileEntry *file_entry1;
762     FileEntry *file_entry2;
763     FMListModel *model;
764     int result;
765 
766     model = (FMListModel *)user_data;
767 
768     file_entry1 = (FileEntry *)a;
769     file_entry2 = (FileEntry *)b;
770 
771     if (file_entry1->file != NULL && file_entry2->file != NULL)
772     {
773         result = caja_file_compare_for_sort_by_attribute_q (file_entry1->file, file_entry2->file,
774                  model->details->sort_attribute,
775                  model->details->sort_directories_first,
776                  (model->details->order == GTK_SORT_DESCENDING));
777     }
778     else if (file_entry1->file == NULL)
779     {
780         return -1;
781     }
782     else
783     {
784         return 1;
785     }
786 
787     return result;
788 }
789 
790 int
fm_list_model_compare_func(FMListModel * model,CajaFile * file1,CajaFile * file2)791 fm_list_model_compare_func (FMListModel *model,
792                             CajaFile *file1,
793                             CajaFile *file2)
794 {
795     int result;
796 
797     result = caja_file_compare_for_sort_by_attribute_q (file1, file2,
798              model->details->sort_attribute,
799              model->details->sort_directories_first,
800              (model->details->order == GTK_SORT_DESCENDING));
801 
802     return result;
803 }
804 
805 static void
fm_list_model_sort_file_entries(FMListModel * model,GSequence * files,GtkTreePath * path)806 fm_list_model_sort_file_entries (FMListModel *model, GSequence *files, GtkTreePath *path)
807 {
808     GSequenceIter **old_order;
809     GtkTreeIter iter;
810     int *new_order;
811     int length;
812     int i;
813     gboolean has_iter;
814     FileEntry *file_entry = NULL;
815 
816     length = g_sequence_get_length (files);
817 
818     if (length <= 1)
819     {
820         return;
821     }
822 
823     /* generate old order of GSequenceIter's */
824     old_order = g_new (GSequenceIter *, length);
825     for (i = 0; i < length; ++i)
826     {
827         GSequenceIter *ptr = g_sequence_get_iter_at_pos (files, i);
828 
829         file_entry = g_sequence_get (ptr);
830 
831         if (file_entry->files != NULL)
832         {
833             gtk_tree_path_append_index (path, i);
834             fm_list_model_sort_file_entries (model, file_entry->files, path);
835             gtk_tree_path_up (path);
836         }
837 
838         old_order[i] = ptr;
839     }
840 
841     /* sort */
842     g_sequence_sort (files, fm_list_model_file_entry_compare_func, model);
843 
844     /* generate new order */
845     new_order = g_new (int, length);
846     /* Note: new_order[newpos] = oldpos */
847     for (i = 0; i < length; ++i)
848     {
849         new_order[g_sequence_iter_get_position (old_order[i])] = i;
850     }
851 
852     /* Let the world know about our new order */
853 
854     g_assert (new_order != NULL);
855 
856     has_iter = FALSE;
857     if (gtk_tree_path_get_depth (path) != 0)
858     {
859         gboolean get_iter_result;
860         has_iter = TRUE;
861         get_iter_result = gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path);
862         g_assert (get_iter_result);
863     }
864 
865     gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model),
866                                    path, has_iter ? &iter : NULL, new_order);
867 
868     g_free (old_order);
869     g_free (new_order);
870 }
871 
872 static void
fm_list_model_sort(FMListModel * model)873 fm_list_model_sort (FMListModel *model)
874 {
875     GtkTreePath *path;
876 
877     path = gtk_tree_path_new ();
878 
879     fm_list_model_sort_file_entries (model, model->details->files, path);
880 
881     gtk_tree_path_free (path);
882 }
883 
884 static gboolean
fm_list_model_get_sort_column_id(GtkTreeSortable * sortable,gint * sort_column_id,GtkSortType * order)885 fm_list_model_get_sort_column_id (GtkTreeSortable *sortable,
886                                   gint            *sort_column_id,
887                                   GtkSortType     *order)
888 {
889     FMListModel *model;
890     int id;
891 
892     model = (FMListModel *)sortable;
893 
894     id = fm_list_model_get_sort_column_id_from_attribute
895          (model, model->details->sort_attribute);
896 
897     if (id == -1)
898     {
899         return FALSE;
900     }
901 
902     if (sort_column_id != NULL)
903     {
904         *sort_column_id = id;
905     }
906 
907     if (order != NULL)
908     {
909         *order = model->details->order;
910     }
911 
912     return TRUE;
913 }
914 
915 static void
fm_list_model_set_sort_column_id(GtkTreeSortable * sortable,gint sort_column_id,GtkSortType order)916 fm_list_model_set_sort_column_id (GtkTreeSortable *sortable, gint sort_column_id, GtkSortType order)
917 {
918     FMListModel *model;
919 
920     model = (FMListModel *)sortable;
921 
922     model->details->sort_attribute = fm_list_model_get_attribute_from_sort_column_id (model, sort_column_id);
923 
924     model->details->order = order;
925 
926     fm_list_model_sort (model);
927     gtk_tree_sortable_sort_column_changed (sortable);
928 }
929 
930 static gboolean
fm_list_model_has_default_sort_func(GtkTreeSortable * sortable)931 fm_list_model_has_default_sort_func (GtkTreeSortable *sortable)
932 {
933     return FALSE;
934 }
935 
936 static gboolean
fm_list_model_multi_row_draggable(EggTreeMultiDragSource * drag_source,GList * path_list)937 fm_list_model_multi_row_draggable (EggTreeMultiDragSource *drag_source, GList *path_list)
938 {
939     return TRUE;
940 }
941 
942 static void
each_path_get_data_binder(CajaDragEachSelectedItemDataGet data_get,gpointer context,gpointer data)943 each_path_get_data_binder (CajaDragEachSelectedItemDataGet data_get,
944                            gpointer context,
945                            gpointer data)
946 {
947     DragDataGetInfo *info;
948     GList *l;
949     char *uri;
950     GdkRectangle cell_area;
951     GtkTreeViewColumn *column;
952     CajaFile *file = NULL;
953     GtkTreeRowReference *row = NULL;
954     GtkTreePath *path = NULL;
955 
956     info = context;
957 
958     g_return_if_fail (info->model->details->drag_view);
959 
960     column = gtk_tree_view_get_column (info->model->details->drag_view, 0);
961 
962     for (l = info->path_list; l != NULL; l = l->next)
963     {
964         row = l->data;
965 
966         path = gtk_tree_row_reference_get_path (row);
967         file = fm_list_model_file_for_path (info->model, path);
968 
969         if (file)
970         {
971             gtk_tree_view_get_cell_area
972             (info->model->details->drag_view,
973              path,
974              column,
975              &cell_area);
976 
977             uri = caja_file_get_uri (file);
978 
979             (*data_get) (uri,
980                          0,
981                          cell_area.y - info->model->details->drag_begin_y,
982                          cell_area.width, cell_area.height,
983                          data);
984 
985             g_free (uri);
986 
987             caja_file_unref (file);
988         }
989 
990         gtk_tree_path_free (path);
991     }
992 }
993 
994 static gboolean
fm_list_model_multi_drag_data_get(EggTreeMultiDragSource * drag_source,GList * path_list,GtkSelectionData * selection_data)995 fm_list_model_multi_drag_data_get (EggTreeMultiDragSource *drag_source,
996                                    GList *path_list,
997                                    GtkSelectionData *selection_data)
998 {
999     FMListModel *model;
1000     DragDataGetInfo context;
1001     guint target_info;
1002 
1003     model = FM_LIST_MODEL (drag_source);
1004 
1005     context.model = model;
1006     context.path_list = path_list;
1007 
1008     if (!drag_target_list)
1009     {
1010         drag_target_list = fm_list_model_get_drag_target_list ();
1011     }
1012 
1013     if (gtk_target_list_find (drag_target_list,
1014                               gtk_selection_data_get_target (selection_data),
1015                               &target_info))
1016     {
1017         caja_drag_drag_data_get (NULL,
1018                                  NULL,
1019                                  selection_data,
1020                                  target_info,
1021                                  GDK_CURRENT_TIME,
1022                                  &context,
1023                                  each_path_get_data_binder);
1024         return TRUE;
1025     }
1026     else
1027     {
1028         return FALSE;
1029     }
1030 }
1031 
1032 static gboolean
fm_list_model_multi_drag_data_delete(EggTreeMultiDragSource * drag_source,GList * path_list)1033 fm_list_model_multi_drag_data_delete (EggTreeMultiDragSource *drag_source, GList *path_list)
1034 {
1035     return TRUE;
1036 }
1037 
1038 static void
add_dummy_row(FMListModel * model,FileEntry * parent_entry)1039 add_dummy_row (FMListModel *model, FileEntry *parent_entry)
1040 {
1041     FileEntry *dummy_file_entry;
1042     GtkTreeIter iter;
1043     GtkTreePath *path;
1044 
1045     dummy_file_entry = g_new0 (FileEntry, 1);
1046     dummy_file_entry->parent = parent_entry;
1047     dummy_file_entry->ptr = g_sequence_insert_sorted (parent_entry->files, dummy_file_entry,
1048                             fm_list_model_file_entry_compare_func, model);
1049     iter.stamp = model->details->stamp;
1050     iter.user_data = dummy_file_entry->ptr;
1051 
1052     path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
1053     gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
1054     gtk_tree_path_free (path);
1055 }
1056 
1057 gboolean
fm_list_model_add_file(FMListModel * model,CajaFile * file,CajaDirectory * directory)1058 fm_list_model_add_file (FMListModel *model, CajaFile *file,
1059                         CajaDirectory *directory)
1060 {
1061     GtkTreeIter iter;
1062     GtkTreePath *path;
1063     FileEntry *file_entry;
1064     GSequenceIter *ptr, *parent_ptr;
1065     GSequence *files;
1066     gboolean replace_dummy;
1067     GHashTable *parent_hash;
1068 
1069     parent_ptr = g_hash_table_lookup (model->details->directory_reverse_map,
1070                                       directory);
1071     if (parent_ptr)
1072     {
1073         file_entry = g_sequence_get (parent_ptr);
1074         ptr = g_hash_table_lookup (file_entry->reverse_map, file);
1075     }
1076     else
1077     {
1078         file_entry = NULL;
1079         ptr = g_hash_table_lookup (model->details->top_reverse_map, file);
1080     }
1081 
1082     if (ptr != NULL)
1083     {
1084         g_warning ("file already in tree (parent_ptr: %p)!!!\n", parent_ptr);
1085         return FALSE;
1086     }
1087 
1088     file_entry = g_new0 (FileEntry, 1);
1089     file_entry->file = caja_file_ref (file);
1090     file_entry->parent = NULL;
1091     file_entry->subdirectory = NULL;
1092     file_entry->files = NULL;
1093 
1094     files = model->details->files;
1095     parent_hash = model->details->top_reverse_map;
1096 
1097     replace_dummy = FALSE;
1098 
1099     if (parent_ptr != NULL)
1100     {
1101         file_entry->parent = g_sequence_get (parent_ptr);
1102         /* At this point we set loaded. Either we saw
1103          * "done" and ignored it waiting for this, or we do this
1104          * earlier, but then we replace the dummy row anyway,
1105          * so it doesn't matter */
1106         file_entry->parent->loaded = 1;
1107         parent_hash = file_entry->parent->reverse_map;
1108         files = file_entry->parent->files;
1109         if (g_sequence_get_length (files) == 1)
1110         {
1111             GSequenceIter *dummy_ptr = g_sequence_get_iter_at_pos (files, 0);
1112             FileEntry *dummy_entry = g_sequence_get (dummy_ptr);
1113             if (dummy_entry->file == NULL)
1114             {
1115                 /* replace the dummy loading entry */
1116                 model->details->stamp++;
1117                 g_sequence_remove (dummy_ptr);
1118 
1119                 replace_dummy = TRUE;
1120             }
1121         }
1122     }
1123 
1124 
1125     file_entry->ptr = g_sequence_insert_sorted (files, file_entry,
1126                       fm_list_model_file_entry_compare_func, model);
1127 
1128     g_hash_table_insert (parent_hash, file, file_entry->ptr);
1129 
1130     iter.stamp = model->details->stamp;
1131     iter.user_data = file_entry->ptr;
1132 
1133     path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
1134     if (replace_dummy)
1135     {
1136         gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
1137     }
1138     else
1139     {
1140         gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
1141     }
1142 
1143     if (caja_file_is_directory (file))
1144     {
1145         file_entry->files = g_sequence_new ((GDestroyNotify)file_entry_free);
1146 
1147         add_dummy_row (model, file_entry);
1148 
1149         gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model),
1150                                               path, &iter);
1151     }
1152     gtk_tree_path_free (path);
1153 
1154     return TRUE;
1155 }
1156 
1157 void
fm_list_model_file_changed(FMListModel * model,CajaFile * file,CajaDirectory * directory)1158 fm_list_model_file_changed (FMListModel *model, CajaFile *file,
1159                             CajaDirectory *directory)
1160 {
1161     FileEntry *parent_file_entry;
1162     GtkTreeIter iter;
1163     GtkTreePath *path;
1164     GSequenceIter *ptr;
1165     int pos_before, pos_after;
1166     gboolean has_iter;
1167 
1168     ptr = lookup_file (model, file, directory);
1169     if (!ptr)
1170     {
1171         return;
1172     }
1173 
1174 
1175     pos_before = g_sequence_iter_get_position (ptr);
1176 
1177     g_sequence_sort_changed (ptr, fm_list_model_file_entry_compare_func, model);
1178 
1179     pos_after = g_sequence_iter_get_position (ptr);
1180 
1181     if (pos_before != pos_after)
1182     {
1183         GtkTreePath *parent_path;
1184         int length, i, old;
1185         int *new_order;
1186         GSequence *files;
1187 
1188         /* The file moved, we need to send rows_reordered */
1189 
1190         parent_file_entry = ((FileEntry *)g_sequence_get (ptr))->parent;
1191 
1192         if (parent_file_entry == NULL)
1193         {
1194             has_iter = FALSE;
1195             parent_path = gtk_tree_path_new ();
1196             files = model->details->files;
1197         }
1198         else
1199         {
1200             has_iter = TRUE;
1201             fm_list_model_ptr_to_iter (model, parent_file_entry->ptr, &iter);
1202             parent_path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
1203             files = parent_file_entry->files;
1204         }
1205 
1206         length = g_sequence_get_length (files);
1207         new_order = g_new (int, length);
1208         /* Note: new_order[newpos] = oldpos */
1209         for (i = 0, old = 0; i < length; ++i)
1210         {
1211             if (i == pos_after)
1212             {
1213                 new_order[i] = pos_before;
1214             }
1215             else
1216             {
1217                 if (old == pos_before)
1218                     old++;
1219                 new_order[i] = old++;
1220             }
1221         }
1222 
1223         gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model),
1224                                        parent_path, has_iter ? &iter : NULL, new_order);
1225 
1226         gtk_tree_path_free (parent_path);
1227         g_free (new_order);
1228     }
1229 
1230     fm_list_model_ptr_to_iter (model, ptr, &iter);
1231     path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
1232     gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
1233     gtk_tree_path_free (path);
1234 }
1235 
1236 gboolean
fm_list_model_is_empty(FMListModel * model)1237 fm_list_model_is_empty (FMListModel *model)
1238 {
1239     return (g_sequence_get_length (model->details->files) == 0);
1240 }
1241 
1242 guint
fm_list_model_get_length(FMListModel * model)1243 fm_list_model_get_length (FMListModel *model)
1244 {
1245     return g_sequence_get_length (model->details->files);
1246 }
1247 
1248 static void
fm_list_model_remove(FMListModel * model,GtkTreeIter * iter)1249 fm_list_model_remove (FMListModel *model, GtkTreeIter *iter)
1250 {
1251     GSequenceIter *ptr;
1252     FileEntry *file_entry, *parent_file_entry;
1253     GtkTreePath *path;
1254     GtkTreeIter parent_iter;
1255 
1256     ptr = iter->user_data;
1257     file_entry = g_sequence_get (ptr);
1258 
1259     if (file_entry->files != NULL)
1260     {
1261         GSequenceIter *child_ptr = NULL;
1262         FileEntry *child_file_entry = NULL;
1263 
1264         while (g_sequence_get_length (file_entry->files) > 0)
1265         {
1266             child_ptr = g_sequence_get_begin_iter (file_entry->files);
1267             child_file_entry = g_sequence_get (child_ptr);
1268 
1269             if (child_file_entry->file != NULL)
1270             {
1271                 fm_list_model_remove_file (model,
1272                                            child_file_entry->file,
1273                                            file_entry->subdirectory);
1274             }
1275             else
1276             {
1277                 path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter);
1278                 gtk_tree_path_append_index (path, 0);
1279                 model->details->stamp++;
1280                 g_sequence_remove (child_ptr);
1281                 gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
1282                 gtk_tree_path_free (path);
1283             }
1284 
1285             /* the parent iter didn't actually change */
1286             iter->stamp = model->details->stamp;
1287         }
1288 
1289     }
1290 
1291     if (file_entry->file != NULL)   /* Don't try to remove dummy row */
1292     {
1293         if (file_entry->parent != NULL)
1294         {
1295             g_hash_table_remove (file_entry->parent->reverse_map, file_entry->file);
1296         }
1297         else
1298         {
1299             g_hash_table_remove (model->details->top_reverse_map, file_entry->file);
1300         }
1301     }
1302 
1303     parent_file_entry = file_entry->parent;
1304     if (parent_file_entry && g_sequence_get_length (parent_file_entry->files) == 1 &&
1305             file_entry->file != NULL)
1306     {
1307         /* this is the last non-dummy child, add a dummy node */
1308         /* We need to do this before removing the last file to avoid
1309          * collapsing the row.
1310          */
1311         add_dummy_row (model, parent_file_entry);
1312     }
1313 
1314     if (file_entry->subdirectory != NULL)
1315     {
1316         g_signal_emit (model,
1317                        list_model_signals[SUBDIRECTORY_UNLOADED], 0,
1318                        file_entry->subdirectory);
1319         g_hash_table_remove (model->details->directory_reverse_map,
1320                              file_entry->subdirectory);
1321     }
1322 
1323     path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter);
1324 
1325     g_sequence_remove (ptr);
1326     model->details->stamp++;
1327     gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
1328 
1329     gtk_tree_path_free (path);
1330 
1331     if (parent_file_entry && g_sequence_get_length (parent_file_entry->files) == 0)
1332     {
1333         parent_iter.stamp = model->details->stamp;
1334         parent_iter.user_data = parent_file_entry->ptr;
1335         path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &parent_iter);
1336         gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model),
1337                                               path, &parent_iter);
1338         gtk_tree_path_free (path);
1339     }
1340 }
1341 
1342 void
fm_list_model_remove_file(FMListModel * model,CajaFile * file,CajaDirectory * directory)1343 fm_list_model_remove_file (FMListModel *model, CajaFile *file,
1344                            CajaDirectory *directory)
1345 {
1346     GtkTreeIter iter;
1347 
1348     if (fm_list_model_get_tree_iter_from_file (model, file, directory, &iter))
1349     {
1350         fm_list_model_remove (model, &iter);
1351     }
1352 }
1353 
1354 static void
fm_list_model_clear_directory(FMListModel * model,GSequence * files)1355 fm_list_model_clear_directory (FMListModel *model, GSequence *files)
1356 {
1357     GtkTreeIter iter;
1358     FileEntry *file_entry = NULL;
1359 
1360     while (g_sequence_get_length (files) > 0)
1361     {
1362         iter.user_data = g_sequence_get_begin_iter (files);
1363 
1364         file_entry = g_sequence_get (iter.user_data);
1365 
1366         if (file_entry->files != NULL)
1367         {
1368             fm_list_model_clear_directory (model, file_entry->files);
1369         }
1370 
1371         iter.stamp = model->details->stamp;
1372         fm_list_model_remove (model, &iter);
1373     }
1374 }
1375 
1376 void
fm_list_model_clear(FMListModel * model)1377 fm_list_model_clear (FMListModel *model)
1378 {
1379     g_return_if_fail (model != NULL);
1380 
1381     fm_list_model_clear_directory (model, model->details->files);
1382 }
1383 
1384 CajaFile *
fm_list_model_file_for_path(FMListModel * model,GtkTreePath * path)1385 fm_list_model_file_for_path (FMListModel *model, GtkTreePath *path)
1386 {
1387     CajaFile *file;
1388     GtkTreeIter iter;
1389 
1390     file = NULL;
1391     if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model),
1392                                  &iter, path))
1393     {
1394         gtk_tree_model_get (GTK_TREE_MODEL (model),
1395                             &iter,
1396                             FM_LIST_MODEL_FILE_COLUMN, &file,
1397                             -1);
1398     }
1399     return file;
1400 }
1401 
1402 gboolean
fm_list_model_load_subdirectory(FMListModel * model,GtkTreePath * path,CajaDirectory ** directory)1403 fm_list_model_load_subdirectory (FMListModel *model, GtkTreePath *path, CajaDirectory **directory)
1404 {
1405     GtkTreeIter iter;
1406     FileEntry *file_entry;
1407     CajaDirectory *subdirectory;
1408 
1409     if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path))
1410     {
1411         return FALSE;
1412     }
1413 
1414     file_entry = g_sequence_get (iter.user_data);
1415     if (file_entry->file == NULL ||
1416             file_entry->subdirectory != NULL)
1417     {
1418         return FALSE;
1419     }
1420 
1421     subdirectory = caja_directory_get_for_file (file_entry->file);
1422 
1423     if (g_hash_table_lookup (model->details->directory_reverse_map,
1424                              subdirectory) != NULL)
1425     {
1426         caja_directory_unref (subdirectory);
1427         g_warning ("Already in directory_reverse_map, failing\n");
1428         return FALSE;
1429     }
1430 
1431     file_entry->subdirectory = subdirectory,
1432                 g_hash_table_insert (model->details->directory_reverse_map,
1433                                      subdirectory, file_entry->ptr);
1434     file_entry->reverse_map = g_hash_table_new (g_direct_hash, g_direct_equal);
1435 
1436     /* Return a ref too */
1437     caja_directory_ref (subdirectory);
1438     *directory = subdirectory;
1439 
1440     return TRUE;
1441 }
1442 
1443 /* removes all children of the subfolder and unloads the subdirectory */
1444 void
fm_list_model_unload_subdirectory(FMListModel * model,GtkTreeIter * iter)1445 fm_list_model_unload_subdirectory (FMListModel *model, GtkTreeIter *iter)
1446 {
1447     FileEntry *file_entry;
1448     GtkTreeIter child_iter;
1449 
1450     file_entry = g_sequence_get (iter->user_data);
1451     if (file_entry->file == NULL ||
1452             file_entry->subdirectory == NULL)
1453     {
1454         return;
1455     }
1456 
1457     file_entry->loaded = 0;
1458 
1459     /* Remove all children */
1460     while (g_sequence_get_length (file_entry->files) > 0)
1461     {
1462         GSequenceIter *child_ptr;
1463         FileEntry *child_file_entry;
1464 
1465         child_ptr = g_sequence_get_begin_iter (file_entry->files);
1466         child_file_entry = g_sequence_get (child_ptr);
1467 
1468         if (child_file_entry->file == NULL)
1469         {
1470             /* Don't delete the dummy node */
1471             break;
1472         }
1473         else
1474         {
1475             fm_list_model_ptr_to_iter (model, child_ptr, &child_iter);
1476             fm_list_model_remove (model, &child_iter);
1477         }
1478     }
1479 
1480     /* Emit unload signal */
1481     g_signal_emit (model,
1482                    list_model_signals[SUBDIRECTORY_UNLOADED], 0,
1483                    file_entry->subdirectory);
1484 
1485     /* actually unload */
1486     g_hash_table_remove (model->details->directory_reverse_map,
1487                          file_entry->subdirectory);
1488     caja_directory_unref (file_entry->subdirectory);
1489     file_entry->subdirectory = NULL;
1490 
1491     g_assert (g_hash_table_size (file_entry->reverse_map) == 0);
1492     g_hash_table_destroy (file_entry->reverse_map);
1493     file_entry->reverse_map = NULL;
1494 }
1495 
1496 
1497 
1498 void
fm_list_model_set_should_sort_directories_first(FMListModel * model,gboolean sort_directories_first)1499 fm_list_model_set_should_sort_directories_first (FMListModel *model, gboolean sort_directories_first)
1500 {
1501     if (model->details->sort_directories_first == sort_directories_first)
1502     {
1503         return;
1504     }
1505 
1506     model->details->sort_directories_first = sort_directories_first;
1507     fm_list_model_sort (model);
1508 }
1509 
1510 int
fm_list_model_get_sort_column_id_from_attribute(FMListModel * model,GQuark attribute)1511 fm_list_model_get_sort_column_id_from_attribute (FMListModel *model,
1512         GQuark attribute)
1513 {
1514     guint i;
1515 
1516     if (attribute == 0)
1517     {
1518         return -1;
1519     }
1520 
1521     /* Hack - the preferences dialog sets modification_date for some
1522      * rather than date_modified for some reason.  Make sure that
1523      * works. */
1524     if (attribute == attribute_modification_date_q)
1525     {
1526         attribute = attribute_date_modified_q;
1527     }
1528 
1529     for (i = 0; i < model->details->columns->len; i++)
1530     {
1531         CajaColumn *column;
1532         GQuark column_attribute;
1533 
1534         column =
1535             CAJA_COLUMN (model->details->columns->pdata[i]);
1536         g_object_get (G_OBJECT (column),
1537                       "attribute_q", &column_attribute,
1538                       NULL);
1539         if (column_attribute == attribute)
1540         {
1541             return FM_LIST_MODEL_NUM_COLUMNS + i;
1542         }
1543     }
1544 
1545     return -1;
1546 }
1547 
1548 GQuark
fm_list_model_get_attribute_from_sort_column_id(FMListModel * model,int sort_column_id)1549 fm_list_model_get_attribute_from_sort_column_id (FMListModel *model,
1550         int sort_column_id)
1551 {
1552     CajaColumn *column;
1553     int index;
1554     GQuark attribute;
1555 
1556     index = sort_column_id - FM_LIST_MODEL_NUM_COLUMNS;
1557 
1558     if (index < 0 || index >= model->details->columns->len)
1559     {
1560         g_warning ("unknown sort column id: %d", sort_column_id);
1561         return 0;
1562     }
1563 
1564     column = CAJA_COLUMN (model->details->columns->pdata[index]);
1565     g_object_get (G_OBJECT (column), "attribute_q", &attribute, NULL);
1566 
1567     return attribute;
1568 }
1569 
1570 CajaZoomLevel
fm_list_model_get_zoom_level_from_column_id(int column)1571 fm_list_model_get_zoom_level_from_column_id (int column)
1572 {
1573     switch (column)
1574     {
1575     case FM_LIST_MODEL_SMALLEST_ICON_COLUMN:
1576         return CAJA_ZOOM_LEVEL_SMALLEST;
1577     case FM_LIST_MODEL_SMALLER_ICON_COLUMN:
1578         return CAJA_ZOOM_LEVEL_SMALLER;
1579     case FM_LIST_MODEL_SMALL_ICON_COLUMN:
1580         return CAJA_ZOOM_LEVEL_SMALL;
1581     case FM_LIST_MODEL_STANDARD_ICON_COLUMN:
1582         return CAJA_ZOOM_LEVEL_STANDARD;
1583     case FM_LIST_MODEL_LARGE_ICON_COLUMN:
1584         return CAJA_ZOOM_LEVEL_LARGE;
1585     case FM_LIST_MODEL_LARGER_ICON_COLUMN:
1586         return CAJA_ZOOM_LEVEL_LARGER;
1587     case FM_LIST_MODEL_LARGEST_ICON_COLUMN:
1588         return CAJA_ZOOM_LEVEL_LARGEST;
1589     }
1590 
1591     g_return_val_if_reached (CAJA_ZOOM_LEVEL_STANDARD);
1592 }
1593 
1594 int
fm_list_model_get_column_id_from_zoom_level(CajaZoomLevel zoom_level)1595 fm_list_model_get_column_id_from_zoom_level (CajaZoomLevel zoom_level)
1596 {
1597     switch (zoom_level)
1598     {
1599     case CAJA_ZOOM_LEVEL_SMALLEST:
1600         return FM_LIST_MODEL_SMALLEST_ICON_COLUMN;
1601     case CAJA_ZOOM_LEVEL_SMALLER:
1602         return FM_LIST_MODEL_SMALLER_ICON_COLUMN;
1603     case CAJA_ZOOM_LEVEL_SMALL:
1604         return FM_LIST_MODEL_SMALL_ICON_COLUMN;
1605     case CAJA_ZOOM_LEVEL_STANDARD:
1606         return FM_LIST_MODEL_STANDARD_ICON_COLUMN;
1607     case CAJA_ZOOM_LEVEL_LARGE:
1608         return FM_LIST_MODEL_LARGE_ICON_COLUMN;
1609     case CAJA_ZOOM_LEVEL_LARGER:
1610         return FM_LIST_MODEL_LARGER_ICON_COLUMN;
1611     case CAJA_ZOOM_LEVEL_LARGEST:
1612         return FM_LIST_MODEL_LARGEST_ICON_COLUMN;
1613     }
1614 
1615     g_return_val_if_reached (FM_LIST_MODEL_STANDARD_ICON_COLUMN);
1616 }
1617 
1618 void
fm_list_model_set_drag_view(FMListModel * model,GtkTreeView * view,int drag_begin_x,int drag_begin_y)1619 fm_list_model_set_drag_view (FMListModel *model,
1620                              GtkTreeView *view,
1621                              int drag_begin_x,
1622                              int drag_begin_y)
1623 {
1624     g_return_if_fail (model != NULL);
1625     g_return_if_fail (FM_IS_LIST_MODEL (model));
1626     g_return_if_fail (!view || GTK_IS_TREE_VIEW (view));
1627 
1628     model->details->drag_view = view;
1629     model->details->drag_begin_x = drag_begin_x;
1630     model->details->drag_begin_y = drag_begin_y;
1631 }
1632 
1633 GtkTargetList *
fm_list_model_get_drag_target_list()1634 fm_list_model_get_drag_target_list ()
1635 {
1636     GtkTargetList *target_list;
1637 
1638     target_list = gtk_target_list_new (drag_types, G_N_ELEMENTS (drag_types));
1639     gtk_target_list_add_text_targets (target_list, CAJA_ICON_DND_TEXT);
1640 
1641     return target_list;
1642 }
1643 
1644 int
fm_list_model_add_column(FMListModel * model,CajaColumn * column)1645 fm_list_model_add_column (FMListModel *model,
1646                           CajaColumn *column)
1647 {
1648     g_ptr_array_add (model->details->columns, column);
1649     g_object_ref (column);
1650 
1651     return FM_LIST_MODEL_NUM_COLUMNS + (model->details->columns->len - 1);
1652 }
1653 
1654 static void
fm_list_model_dispose(GObject * object)1655 fm_list_model_dispose (GObject *object)
1656 {
1657     FMListModel *model;
1658 
1659     model = FM_LIST_MODEL (object);
1660 
1661     if (model->details->columns)
1662     {
1663         int i;
1664 
1665         for (i = 0; i < model->details->columns->len; i++)
1666         {
1667             g_object_unref (model->details->columns->pdata[i]);
1668         }
1669         g_ptr_array_free (model->details->columns, TRUE);
1670         model->details->columns = NULL;
1671     }
1672 
1673     if (model->details->files)
1674     {
1675         g_sequence_free (model->details->files);
1676         model->details->files = NULL;
1677     }
1678 
1679     if (model->details->top_reverse_map)
1680     {
1681         g_hash_table_destroy (model->details->top_reverse_map);
1682         model->details->top_reverse_map = NULL;
1683     }
1684     if (model->details->directory_reverse_map)
1685     {
1686         g_hash_table_destroy (model->details->directory_reverse_map);
1687         model->details->directory_reverse_map = NULL;
1688     }
1689 
1690     G_OBJECT_CLASS (fm_list_model_parent_class)->dispose (object);
1691 }
1692 
1693 static void
fm_list_model_finalize(GObject * object)1694 fm_list_model_finalize (GObject *object)
1695 {
1696     FMListModel *model;
1697 
1698     model = FM_LIST_MODEL (object);
1699 
1700     if (model->details->highlight_files != NULL)
1701     {
1702         caja_file_list_free (model->details->highlight_files);
1703         model->details->highlight_files = NULL;
1704     }
1705 
1706     g_free (model->details);
1707 
1708     G_OBJECT_CLASS (fm_list_model_parent_class)->finalize (object);
1709 }
1710 
1711 static void
fm_list_model_init(FMListModel * model)1712 fm_list_model_init (FMListModel *model)
1713 {
1714     model->details = g_new0 (FMListModelDetails, 1);
1715     model->details->files = g_sequence_new ((GDestroyNotify)file_entry_free);
1716     model->details->top_reverse_map = g_hash_table_new (g_direct_hash, g_direct_equal);
1717     model->details->directory_reverse_map = g_hash_table_new (g_direct_hash, g_direct_equal);
1718     model->details->stamp = g_random_int ();
1719     model->details->sort_attribute = 0;
1720     model->details->columns = g_ptr_array_new ();
1721 }
1722 
1723 static void
fm_list_model_class_init(FMListModelClass * klass)1724 fm_list_model_class_init (FMListModelClass *klass)
1725 {
1726     GObjectClass *object_class;
1727 
1728     attribute_name_q = g_quark_from_static_string ("name");
1729     attribute_modification_date_q = g_quark_from_static_string ("modification_date");
1730     attribute_date_modified_q = g_quark_from_static_string ("date_modified");
1731 
1732     object_class = (GObjectClass *)klass;
1733     object_class->finalize = fm_list_model_finalize;
1734     object_class->dispose = fm_list_model_dispose;
1735 
1736     list_model_signals[SUBDIRECTORY_UNLOADED] =
1737         g_signal_new ("subdirectory_unloaded",
1738                       FM_TYPE_LIST_MODEL,
1739                       G_SIGNAL_RUN_FIRST,
1740                       G_STRUCT_OFFSET (FMListModelClass, subdirectory_unloaded),
1741                       NULL, NULL,
1742                       g_cclosure_marshal_VOID__OBJECT,
1743                       G_TYPE_NONE, 1,
1744                       CAJA_TYPE_DIRECTORY);
1745 
1746     list_model_signals[GET_ICON_SCALE] =
1747         g_signal_new ("get-icon-scale",
1748                       FM_TYPE_LIST_MODEL,
1749                       G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST,
1750                       0, NULL, NULL,
1751                       NULL,
1752                       G_TYPE_INT, 0);
1753 }
1754 
1755 static void
fm_list_model_tree_model_init(GtkTreeModelIface * iface)1756 fm_list_model_tree_model_init (GtkTreeModelIface *iface)
1757 {
1758     iface->get_flags = fm_list_model_get_flags;
1759     iface->get_n_columns = fm_list_model_get_n_columns;
1760     iface->get_column_type = fm_list_model_get_column_type;
1761     iface->get_iter = fm_list_model_get_iter;
1762     iface->get_path = fm_list_model_get_path;
1763     iface->get_value = fm_list_model_get_value;
1764     iface->iter_next = fm_list_model_iter_next;
1765     iface->iter_children = fm_list_model_iter_children;
1766     iface->iter_has_child = fm_list_model_iter_has_child;
1767     iface->iter_n_children = fm_list_model_iter_n_children;
1768     iface->iter_nth_child = fm_list_model_iter_nth_child;
1769     iface->iter_parent = fm_list_model_iter_parent;
1770 }
1771 
1772 static void
fm_list_model_sortable_init(GtkTreeSortableIface * iface)1773 fm_list_model_sortable_init (GtkTreeSortableIface *iface)
1774 {
1775     iface->get_sort_column_id = fm_list_model_get_sort_column_id;
1776     iface->set_sort_column_id = fm_list_model_set_sort_column_id;
1777     iface->has_default_sort_func = fm_list_model_has_default_sort_func;
1778 }
1779 
1780 static void
fm_list_model_multi_drag_source_init(EggTreeMultiDragSourceIface * iface)1781 fm_list_model_multi_drag_source_init (EggTreeMultiDragSourceIface *iface)
1782 {
1783     iface->row_draggable = fm_list_model_multi_row_draggable;
1784     iface->drag_data_get = fm_list_model_multi_drag_data_get;
1785     iface->drag_data_delete = fm_list_model_multi_drag_data_delete;
1786 }
1787 
1788 void
fm_list_model_subdirectory_done_loading(FMListModel * model,CajaDirectory * directory)1789 fm_list_model_subdirectory_done_loading (FMListModel *model, CajaDirectory *directory)
1790 {
1791     GtkTreeIter iter;
1792     FileEntry *file_entry;
1793     GSequenceIter *parent_ptr, *dummy_ptr;
1794     GSequence *files;
1795 
1796     if (model == NULL || model->details->directory_reverse_map == NULL)
1797     {
1798         return;
1799     }
1800     parent_ptr = g_hash_table_lookup (model->details->directory_reverse_map,
1801                                       directory);
1802     if (parent_ptr == NULL)
1803     {
1804         return;
1805     }
1806 
1807     file_entry = g_sequence_get (parent_ptr);
1808     files = file_entry->files;
1809 
1810     /* Only swap loading -> empty if we saw no files yet at "done",
1811      * otherwise, toggle loading at first added file to the model.
1812      */
1813     if (!caja_directory_is_not_empty (directory) &&
1814             g_sequence_get_length (files) == 1)
1815     {
1816         FileEntry *dummy_entry;
1817 
1818         dummy_ptr = g_sequence_get_iter_at_pos (file_entry->files, 0);
1819         dummy_entry = g_sequence_get (dummy_ptr);
1820         if (dummy_entry->file == NULL)
1821         {
1822             GtkTreePath *path;
1823 
1824             /* was the dummy file */
1825             file_entry->loaded = 1;
1826 
1827             iter.stamp = model->details->stamp;
1828             iter.user_data = dummy_ptr;
1829 
1830             path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
1831             gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
1832             gtk_tree_path_free (path);
1833         }
1834     }
1835 }
1836 
1837 static void
refresh_row(gpointer data,gpointer user_data)1838 refresh_row (gpointer data,
1839              gpointer user_data)
1840 {
1841     CajaFile *file;
1842     FMListModel *model;
1843     GList *iters, *l;
1844     GtkTreePath *path = NULL;
1845 
1846     model = user_data;
1847     file = data;
1848 
1849     iters = fm_list_model_get_all_iters_for_file (model, file);
1850     for (l = iters; l != NULL; l = l->next)
1851     {
1852         path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), l->data);
1853         gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, l->data);
1854 
1855         gtk_tree_path_free (path);
1856     }
1857 
1858     g_list_free_full (iters, g_free);
1859 }
1860 
1861 void
fm_list_model_set_highlight_for_files(FMListModel * model,GList * files)1862 fm_list_model_set_highlight_for_files (FMListModel *model,
1863                                        GList *files)
1864 {
1865     if (model->details->highlight_files != NULL)
1866     {
1867         g_list_foreach (model->details->highlight_files,
1868                         refresh_row, model);
1869         caja_file_list_free (model->details->highlight_files);
1870         model->details->highlight_files = NULL;
1871     }
1872 
1873     if (files != NULL)
1874     {
1875         model->details->highlight_files = caja_file_list_copy (files);
1876         g_list_foreach (model->details->highlight_files,
1877                         refresh_row, model);
1878 
1879     }
1880 }
1881