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