1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 
3 /*
4  *  GThumb
5  *
6  *  Copyright (C) 2008 Free Software Foundation, Inc.
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 #include <string.h>
24 #include <glib/gi18n.h>
25 #include <gtk/gtk.h>
26 #include "gth-file-data.h"
27 #include "glib-utils.h"
28 #include "gtk-utils.h"
29 #include "gth-file-source.h"
30 #include "gth-folder-tree.h"
31 #include "gth-icon-cache.h"
32 #include "gth-main.h"
33 #include "gth-marshal.h"
34 #include "gth-request-dialog.h"
35 
36 
37 #define DEFAULT_URI "gthumb-vfs:///"
38 #define EMPTY_URI   "..."
39 #define LOADING_URI "."
40 #define PARENT_URI  ".."
41 #define UPDATE_MONITORED_LOCATIONS_DELAY 500
42 
43 
44 typedef enum {
45 	ENTRY_TYPE_FILE,
46 	ENTRY_TYPE_PARENT,
47 	ENTRY_TYPE_LOADING,
48 	ENTRY_TYPE_EMPTY
49 } EntryType;
50 
51 
52 enum {
53 	COLUMN_STYLE,
54 	COLUMN_WEIGHT,
55 	COLUMN_ICON,
56 	COLUMN_TYPE,
57 	COLUMN_FILE_DATA,
58 	COLUMN_SORT_KEY,
59 	COLUMN_SORT_ORDER,
60 	COLUMN_SECONDARY_SORT_ORDER,
61 	COLUMN_NAME,
62 	COLUMN_NO_CHILD,
63 	COLUMN_LOADED,
64 	NUM_COLUMNS
65 };
66 
67 
68 enum {
69 	PROP_0,
70 	PROP_ROOT_URI
71 };
72 
73 
74 enum {
75 	FOLDER_POPUP,
76 	LIST_CHILDREN,
77 	LOAD,
78 	OPEN,
79 	OPEN_PARENT,
80 	RENAME,
81 	LAST_SIGNAL
82 };
83 
84 
85 typedef struct {
86 	GHashTable *locations;
87 	GList      *sources;
88 	guint       update_id;
89 } MonitorData;
90 
91 
92 struct _GthFolderTreePrivate {
93 	GFile            *root;
94 	GHashTable       *entry_points;		/* An entry point is a root child */
95 	gboolean          recalc_entry_points;
96 	GtkTreeStore     *tree_store;
97 	GtkCellRenderer  *text_renderer;
98 	GtkTreePath      *hover_path;
99 
100 	/* drag-and-drop */
101 
102 	gboolean         drag_source_enabled;
103 	GdkModifierType  drag_start_button_mask;
104 	GtkTargetList   *drag_target_list;
105 	GdkDragAction    drag_actions;
106 
107 	gboolean         dragging : 1;        /* Whether the user is dragging items. */
108 	gboolean         drag_started : 1;    /* Whether the drag has started. */
109 	int              drag_start_x;        /* The position where the drag started. */
110 	int              drag_start_y;
111 
112 	/* monitored locations  */
113 
114 	MonitorData      monitor;
115 };
116 
117 
118 static guint gth_folder_tree_signals[LAST_SIGNAL] = { 0 };
119 
120 
G_DEFINE_TYPE_WITH_CODE(GthFolderTree,gth_folder_tree,GTK_TYPE_TREE_VIEW,G_ADD_PRIVATE (GthFolderTree))121 G_DEFINE_TYPE_WITH_CODE (GthFolderTree,
122 			 gth_folder_tree,
123 			 GTK_TYPE_TREE_VIEW,
124 			 G_ADD_PRIVATE (GthFolderTree))
125 
126 
127 static void
128 gth_folder_tree_set_property (GObject      *object,
129 			      guint         property_id,
130 			      const GValue *value,
131 			      GParamSpec   *pspec)
132 {
133 	GthFolderTree *self;
134 	const char    *uri;
135 
136 	self = GTH_FOLDER_TREE (object);
137 
138 	switch (property_id) {
139 	case PROP_ROOT_URI:
140 		uri = g_value_get_string (value);
141 		if (uri != NULL) {
142 			GFile *new_root;
143 
144 			new_root = g_file_new_for_uri (uri);
145 			if (new_root != NULL) {
146 				_g_object_unref (self->priv->root);
147 				self->priv->root = _g_object_ref (new_root);
148 			}
149 
150 			_g_object_unref (new_root);
151 		}
152 		break;
153 
154 	default:
155 		break;
156 	}
157 }
158 
159 
160 static void
gth_folder_tree_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)161 gth_folder_tree_get_property (GObject    *object,
162 				   guint       property_id,
163 				   GValue     *value,
164 				   GParamSpec *pspec)
165 {
166 	GthFolderTree *self;
167 	char          *uri;
168 
169 	self = GTH_FOLDER_TREE (object);
170 
171 	switch (property_id) {
172 	case PROP_ROOT_URI:
173 		uri = g_file_get_uri (self->priv->root);
174 		g_value_set_string (value, uri);
175 		g_free (uri);
176 		break;
177 
178 	default:
179 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
180 		break;
181 	}
182 }
183 
184 
185 static void remove_all_locations_from_the_monitor (GthFolderTree *folder_tree);
186 
187 
188 static void
gth_folder_tree_finalize(GObject * object)189 gth_folder_tree_finalize (GObject *object)
190 {
191 	GthFolderTree *folder_tree;
192 
193 	folder_tree = GTH_FOLDER_TREE (object);
194 
195 	if (folder_tree->priv->drag_target_list != NULL) {
196 		gtk_target_list_unref (folder_tree->priv->drag_target_list);
197 		folder_tree->priv->drag_target_list = NULL;
198 	}
199 	if (folder_tree->priv->monitor.update_id != 0) {
200 		g_source_remove (folder_tree->priv->monitor.update_id);
201 		folder_tree->priv->monitor.update_id = 0;
202 	}
203 	g_hash_table_unref (folder_tree->priv->entry_points);
204 	remove_all_locations_from_the_monitor (folder_tree);
205 	g_hash_table_unref (folder_tree->priv->monitor.locations);
206 	_g_object_list_unref (folder_tree->priv->monitor.sources);
207 	if (folder_tree->priv->root != NULL)
208 		g_object_unref (folder_tree->priv->root);
209 	g_object_unref (folder_tree->priv->tree_store);
210 
211 	G_OBJECT_CLASS (gth_folder_tree_parent_class)->finalize (object);
212 }
213 
214 
215 static void
gth_folder_tree_class_init(GthFolderTreeClass * class)216 gth_folder_tree_class_init (GthFolderTreeClass *class)
217 {
218 	GObjectClass   *object_class;
219 	GtkWidgetClass *widget_class;
220 
221 	object_class = (GObjectClass*) class;
222 	object_class->set_property = gth_folder_tree_set_property;
223 	object_class->get_property = gth_folder_tree_get_property;
224 	object_class->finalize = gth_folder_tree_finalize;
225 
226 	widget_class = (GtkWidgetClass*) class;
227 	widget_class->drag_begin = NULL;
228 
229 	/* properties */
230 
231 	g_object_class_install_property (object_class,
232 					 PROP_ROOT_URI,
233 					 g_param_spec_string ("root-uri",
234 							      "Root uri",
235 							      "The root of the folder tree as an uri",
236 							      NULL,
237 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
238 
239 	/* signals */
240 
241 	gth_folder_tree_signals[FOLDER_POPUP] =
242 		g_signal_new ("folder_popup",
243 			      G_TYPE_FROM_CLASS (class),
244 			      G_SIGNAL_RUN_LAST,
245 			      G_STRUCT_OFFSET (GthFolderTreeClass, folder_popup),
246 			      NULL, NULL,
247 			      gth_marshal_VOID__OBJECT_UINT,
248 			      G_TYPE_NONE,
249 			      2,
250 			      G_TYPE_OBJECT,
251 			      G_TYPE_UINT);
252 	gth_folder_tree_signals[LIST_CHILDREN] =
253 		g_signal_new ("list_children",
254 			      G_TYPE_FROM_CLASS (class),
255 			      G_SIGNAL_RUN_LAST,
256 			      G_STRUCT_OFFSET (GthFolderTreeClass, list_children),
257 			      NULL, NULL,
258 			      g_cclosure_marshal_VOID__OBJECT,
259 			      G_TYPE_NONE,
260 			      1,
261 			      G_TYPE_OBJECT);
262 	gth_folder_tree_signals[LOAD] =
263 		g_signal_new ("load",
264 			      G_TYPE_FROM_CLASS (class),
265 			      G_SIGNAL_RUN_LAST,
266 			      G_STRUCT_OFFSET (GthFolderTreeClass, load),
267 			      NULL, NULL,
268 			      g_cclosure_marshal_VOID__OBJECT,
269 			      G_TYPE_NONE,
270 			      1,
271 			      G_TYPE_OBJECT);
272 	gth_folder_tree_signals[OPEN] =
273 		g_signal_new ("open",
274 			      G_TYPE_FROM_CLASS (class),
275 			      G_SIGNAL_RUN_LAST,
276 			      G_STRUCT_OFFSET (GthFolderTreeClass, open),
277 			      NULL, NULL,
278 			      g_cclosure_marshal_VOID__OBJECT,
279 			      G_TYPE_NONE,
280 			      1,
281 			      G_TYPE_OBJECT);
282 	gth_folder_tree_signals[OPEN_PARENT] =
283 		g_signal_new ("open_parent",
284 			      G_TYPE_FROM_CLASS (class),
285 			      G_SIGNAL_RUN_LAST,
286 			      G_STRUCT_OFFSET (GthFolderTreeClass, open_parent),
287 			      NULL, NULL,
288 			      g_cclosure_marshal_VOID__VOID,
289 			      G_TYPE_NONE,
290 			      0);
291 	gth_folder_tree_signals[RENAME] =
292 		g_signal_new ("rename",
293 			      G_TYPE_FROM_CLASS (class),
294 			      G_SIGNAL_RUN_LAST,
295 			      G_STRUCT_OFFSET (GthFolderTreeClass, rename),
296 			      NULL, NULL,
297 			      gth_marshal_VOID__OBJECT_STRING,
298 			      G_TYPE_NONE,
299 			      2,
300 			      G_TYPE_OBJECT,
301 			      G_TYPE_STRING);
302 }
303 
304 
305 static void
text_renderer_edited_cb(GtkCellRendererText * renderer,char * path,char * new_text,gpointer user_data)306 text_renderer_edited_cb (GtkCellRendererText *renderer,
307 			 char                *path,
308 			 char                *new_text,
309 			 gpointer             user_data)
310 {
311 	GthFolderTree *folder_tree = user_data;
312 	GtkTreePath   *tree_path;
313 	GtkTreeIter    iter;
314 	EntryType      entry_type;
315 	GthFileData   *file_data;
316 	char          *name;
317 
318 	g_object_set (folder_tree->priv->text_renderer,
319 		      "editable", FALSE,
320 		      NULL);
321 
322 	tree_path = gtk_tree_path_new_from_string (path);
323 	if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (folder_tree->priv->tree_store),
324 				       &iter,
325 				       tree_path))
326 	{
327 		gtk_tree_path_free (tree_path);
328 		return;
329 	}
330 	gtk_tree_path_free (tree_path);
331 
332 	gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store),
333 			    &iter,
334 			    COLUMN_TYPE, &entry_type,
335 			    COLUMN_FILE_DATA, &file_data,
336 			    COLUMN_NAME, &name,
337 			    -1);
338 
339 	if ((entry_type == ENTRY_TYPE_FILE) && (g_utf8_collate (name, new_text) != 0))
340 		g_signal_emit (folder_tree, gth_folder_tree_signals[RENAME], 0, file_data->file, new_text);
341 
342 	_g_object_unref (file_data);
343 	g_free (name);
344 }
345 
346 
347 static void
text_renderer_editing_started_cb(GtkCellRenderer * cell,GtkCellEditable * editable,const char * path,gpointer user_data)348 text_renderer_editing_started_cb (GtkCellRenderer *cell,
349 				  GtkCellEditable *editable,
350 				  const char      *path,
351 				  gpointer         user_data)
352 {
353 	GthFolderTree *folder_tree = user_data;
354 	GtkTreePath   *tree_path;
355 	GtkTreeIter    iter;
356 	GthFileData   *file_data;
357 
358 	tree_path = gtk_tree_path_new_from_string (path);
359 	if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (folder_tree->priv->tree_store),
360 				       &iter,
361 				       tree_path))
362 	{
363 		gtk_tree_path_free (tree_path);
364 		return;
365 	}
366 	gtk_tree_path_free (tree_path);
367 
368 	gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store),
369 			    &iter,
370 			    COLUMN_FILE_DATA, &file_data,
371 			    -1);
372 
373 	if (GTK_IS_ENTRY (editable))
374 	      gtk_entry_set_text (GTK_ENTRY (editable), g_file_info_get_edit_name (file_data->info));
375 
376 	_g_object_unref (file_data);
377 }
378 
379 
380 static void
text_renderer_editing_canceled_cb(GtkCellRenderer * renderer,gpointer user_data)381 text_renderer_editing_canceled_cb (GtkCellRenderer *renderer,
382 				    gpointer         user_data)
383 {
384 	GthFolderTree *folder_tree = user_data;
385 
386 	g_object_set (folder_tree->priv->text_renderer,
387 		      "editable", FALSE,
388 		      NULL);
389 }
390 
391 
392 static void
add_columns(GthFolderTree * folder_tree,GtkTreeView * treeview)393 add_columns (GthFolderTree *folder_tree,
394 	     GtkTreeView   *treeview)
395 {
396 	GtkCellRenderer   *renderer;
397 	GtkTreeViewColumn *column;
398 
399 	column = gtk_tree_view_column_new ();
400 
401 	renderer = gtk_cell_renderer_pixbuf_new ();
402 	g_object_set (renderer,
403 		      "follow-state", TRUE,
404 		      NULL);
405 	gtk_tree_view_column_pack_start (column, renderer, FALSE);
406 	gtk_tree_view_column_set_attributes (column, renderer,
407 					     "gicon", COLUMN_ICON,
408 					     NULL);
409 
410 	folder_tree->priv->text_renderer = renderer = gtk_cell_renderer_text_new ();
411 	g_object_set (renderer,
412 		      "ellipsize", PANGO_ELLIPSIZE_END,
413 		      NULL);
414 	g_signal_connect (folder_tree->priv->text_renderer,
415 			  "edited",
416 			  G_CALLBACK (text_renderer_edited_cb),
417 			  folder_tree);
418 	g_signal_connect (folder_tree->priv->text_renderer,
419 			  "editing-started",
420 			  G_CALLBACK (text_renderer_editing_started_cb),
421 			  folder_tree);
422 	g_signal_connect (folder_tree->priv->text_renderer,
423 			  "editing-canceled",
424 			  G_CALLBACK (text_renderer_editing_canceled_cb),
425 			  folder_tree);
426 
427 	gtk_tree_view_column_pack_start (column, renderer, TRUE);
428 	gtk_tree_view_column_set_attributes (column, renderer,
429 					     "text", COLUMN_NAME,
430 					     "style", COLUMN_STYLE,
431 					     "weight", COLUMN_WEIGHT,
432 					     NULL);
433 	gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
434 	gtk_tree_view_column_set_sort_column_id (column, COLUMN_NAME);
435 
436 	gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
437 }
438 
439 
440 static void
open_uri(GthFolderTree * folder_tree,GthFileData * file_data,EntryType entry_type)441 open_uri (GthFolderTree *folder_tree,
442 	  GthFileData   *file_data,
443 	  EntryType      entry_type)
444 {
445 	if (entry_type == ENTRY_TYPE_PARENT)
446 		g_signal_emit (folder_tree, gth_folder_tree_signals[OPEN_PARENT], 0);
447 	else if (entry_type == ENTRY_TYPE_FILE)
448 		g_signal_emit (folder_tree, gth_folder_tree_signals[OPEN], 0, file_data->file);
449 }
450 
451 
452 static gboolean
row_activated_cb(GtkTreeView * tree_view,GtkTreePath * path,GtkTreeViewColumn * column,gpointer user_data)453 row_activated_cb (GtkTreeView       *tree_view,
454 		  GtkTreePath       *path,
455 		  GtkTreeViewColumn *column,
456 		  gpointer           user_data)
457 {
458 	GthFolderTree *folder_tree = user_data;
459 	GtkTreeIter    iter;
460 	EntryType      entry_type;
461 	GthFileData   *file_data;
462 
463 	if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (folder_tree->priv->tree_store),
464 				       &iter,
465 				       path))
466 	{
467 		return FALSE;
468 	}
469 
470 	gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store),
471 			    &iter,
472 			    COLUMN_TYPE, &entry_type,
473 			    COLUMN_FILE_DATA, &file_data,
474 			    -1);
475 	open_uri (folder_tree, file_data, entry_type);
476 
477 	_g_object_unref (file_data);
478 
479 	return TRUE;
480 }
481 
482 
483 /* -- update_monitored_locations  -- */
484 
485 
486 static GthFileSource *
get_monitor_file_source_for_file(GthFolderTree * folder_tree,GFile * file)487 get_monitor_file_source_for_file (GthFolderTree *folder_tree,
488 				  GFile         *file)
489 {
490 	GList *scan;
491 	char  *uri;
492 
493 	uri = g_file_get_uri (file);
494 	for (scan = folder_tree->priv->monitor.sources; scan; scan = scan->next) {
495 		GthFileSource *file_source = scan->data;
496 
497 		if (gth_file_source_supports_scheme (file_source, uri)) {
498 			g_free (uri);
499 			return g_object_ref (file_source);
500 		}
501 	}
502 
503 	g_free (uri);
504 
505 	return NULL;
506 }
507 
508 
509 static void
_gth_folder_tree_remove_from_monitor(GthFolderTree * folder_tree,GFile * file)510 _gth_folder_tree_remove_from_monitor (GthFolderTree *folder_tree,
511 				      GFile         *file)
512 {
513 	GthFileSource *file_source;
514 
515 	file_source = get_monitor_file_source_for_file (folder_tree, file);
516 	if (file_source == NULL)
517 		return;
518 
519 	gth_file_source_monitor_directory (file_source, file, FALSE);
520 
521 	g_object_unref (file_source);
522 }
523 
524 
525 static void
remove_all_locations_from_the_monitor(GthFolderTree * folder_tree)526 remove_all_locations_from_the_monitor (GthFolderTree *folder_tree)
527 {
528 	GList *locations;
529 	GList *scan;
530 
531 	locations = g_hash_table_get_keys (folder_tree->priv->monitor.locations);
532 	for (scan = locations; scan; scan = scan->next)
533 		_gth_folder_tree_remove_from_monitor (folder_tree, G_FILE (scan->data));
534 	g_hash_table_remove_all (folder_tree->priv->monitor.locations);
535 
536 	g_list_free (locations);
537 }
538 
539 
540 static void
_gth_folder_tree_add_to_monitor(GthFolderTree * folder_tree,GFile * file)541 _gth_folder_tree_add_to_monitor (GthFolderTree *folder_tree,
542 				 GFile         *file)
543 {
544 	GthFileSource *file_source;
545 
546 	file_source = get_monitor_file_source_for_file (folder_tree, file);
547 	if (file_source == NULL) {
548 		file_source = gth_main_get_file_source (file);
549 		if (file_source == NULL)
550 			return;
551 		folder_tree->priv->monitor.sources = g_list_prepend (folder_tree->priv->monitor.sources, g_object_ref (file_source));
552 	}
553 
554 	gth_file_source_monitor_directory (file_source, file, TRUE);
555 	g_hash_table_add (folder_tree->priv->monitor.locations, g_file_dup (file));
556 
557 	g_object_unref (file_source);
558 }
559 
560 
561 static void
add_to_open_locations(GtkTreeView * tree_view,GtkTreePath * path,gpointer user_data)562 add_to_open_locations (GtkTreeView *tree_view,
563 		       GtkTreePath *path,
564 		       gpointer     user_data)
565 {
566 	GHashTable  *open_locations = user_data;
567 	GthFileData *file_data;
568 
569 	file_data = gth_folder_tree_get_file (GTH_FOLDER_TREE (tree_view), path);
570 	if (file_data != NULL) {
571 		g_hash_table_add (open_locations, g_object_ref (file_data->file));
572 		g_object_unref (file_data);
573 	}
574 }
575 
576 
577 static gboolean
update_monitored_locations(gpointer user_data)578 update_monitored_locations (gpointer user_data)
579 {
580 	GthFolderTree *folder_tree = user_data;
581 	GHashTable    *open_locations;
582 	GList         *locations;
583 	GList         *locations_to_remove;
584 	GList         *scan;
585 
586 	if (folder_tree->priv->monitor.update_id != 0) {
587 		g_source_remove (folder_tree->priv->monitor.update_id);
588 		folder_tree->priv->monitor.update_id = 0;
589 	}
590 
591 	open_locations = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
592 	gtk_tree_view_map_expanded_rows (GTK_TREE_VIEW (folder_tree),
593 					 add_to_open_locations,
594 					 open_locations);
595 
596 #if 0
597 	{
598 		g_print ("** expanded locations **\n");
599 
600 		locations = g_hash_table_get_keys (open_locations);
601 		for (scan = locations; scan; scan = scan->next) {
602 			GFile *file = scan->data;
603 
604 			g_print ("\t%s\n", g_file_get_uri (file));
605 		}
606 		g_list_free (locations);
607 	}
608 #endif
609 
610 	/* remove the old locations */
611 
612 	locations_to_remove = NULL;
613 	locations = g_hash_table_get_keys (folder_tree->priv->monitor.locations);
614 	for (scan = locations; scan; scan = scan->next) {
615 		GFile *file = scan->data;
616 
617 		if (! g_hash_table_contains (open_locations, file)) {
618 			_gth_folder_tree_remove_from_monitor (folder_tree, file);
619 			locations_to_remove = g_list_prepend (locations_to_remove, g_object_ref (file));
620 		}
621 	}
622 
623 	for (scan = locations_to_remove; scan; scan = scan->next)
624 		g_hash_table_remove (folder_tree->priv->monitor.locations, G_FILE (scan->data));
625 
626 	g_list_free (locations);
627 	g_list_free (locations_to_remove);
628 
629 	/* add the new locations */
630 
631 	locations = g_hash_table_get_keys (open_locations);
632 	for (scan = locations; scan; scan = scan->next) {
633 		GFile *file = scan->data;
634 
635 		if (! g_hash_table_contains (folder_tree->priv->monitor.locations, file))
636 			_gth_folder_tree_add_to_monitor (folder_tree, file);
637 	}
638 
639 	g_list_free (locations);
640 	g_hash_table_unref (open_locations);
641 
642 	return FALSE;
643 }
644 
645 
646 static void
queue_update_monitored_locations(GthFolderTree * folder_tree)647 queue_update_monitored_locations (GthFolderTree *folder_tree)
648 {
649 	if (folder_tree->priv->monitor.update_id != 0)
650 		g_source_remove (folder_tree->priv->monitor.update_id);
651 	folder_tree->priv->monitor.update_id = g_timeout_add (UPDATE_MONITORED_LOCATIONS_DELAY, update_monitored_locations, folder_tree);
652 }
653 
654 
655 static gboolean
row_expanded_cb(GtkTreeView * tree_view,GtkTreeIter * expanded_iter,GtkTreePath * expanded_path,gpointer user_data)656 row_expanded_cb (GtkTreeView  *tree_view,
657 		 GtkTreeIter  *expanded_iter,
658 		 GtkTreePath  *expanded_path,
659 		 gpointer      user_data)
660 {
661 	GthFolderTree *folder_tree = user_data;
662 	EntryType      entry_type;
663 	GthFileData   *file_data;
664 	gboolean       loaded;
665 
666 	gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store),
667 			    expanded_iter,
668 			    COLUMN_TYPE, &entry_type,
669 			    COLUMN_FILE_DATA, &file_data,
670 			    COLUMN_LOADED, &loaded,
671 			    -1);
672 
673 	if ((entry_type == ENTRY_TYPE_FILE) && ! loaded)
674 		g_signal_emit (folder_tree, gth_folder_tree_signals[LIST_CHILDREN], 0, file_data->file);
675 
676 	queue_update_monitored_locations (folder_tree);
677 
678 	_g_object_unref (file_data);
679 
680 	return FALSE;
681 }
682 
683 
684 static gboolean
row_collapsed_cb(GtkTreeView * tree_view,GtkTreeIter * iter,GtkTreePath * path,gpointer user_data)685 row_collapsed_cb (GtkTreeView *tree_view,
686 		  GtkTreeIter *iter,
687 		  GtkTreePath *path,
688 		  gpointer     user_data)
689 {
690 	queue_update_monitored_locations (GTH_FOLDER_TREE (user_data));
691 	return FALSE;
692 }
693 
694 
695 static gboolean
popup_menu_cb(GtkWidget * widget,gpointer user_data)696 popup_menu_cb (GtkWidget *widget,
697 	       gpointer   user_data)
698 {
699 	GthFolderTree *folder_tree = user_data;
700 	GtkTreeStore  *tree_store = folder_tree->priv->tree_store;
701 	GthFileData   *file_data = NULL;
702 	GtkTreeIter    iter;
703 
704 	if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree)), NULL, &iter)) {
705 		EntryType entry_type;
706 
707 		gtk_tree_model_get (GTK_TREE_MODEL (tree_store),
708 				    &iter,
709 				    COLUMN_TYPE, &entry_type,
710 				    COLUMN_FILE_DATA, &file_data,
711 				    -1);
712 		if (entry_type != ENTRY_TYPE_FILE) {
713 			_g_object_unref (file_data);
714 			return FALSE;
715 		}
716 	}
717 
718 	g_signal_emit (folder_tree,
719 		       gth_folder_tree_signals[FOLDER_POPUP],
720 		       0,
721 		       file_data,
722 		       gtk_get_current_event_time ());
723 
724 	_g_object_unref (file_data);
725 
726 	return TRUE;
727 }
728 
729 
730 static int
button_press_cb(GtkWidget * widget,GdkEventButton * event,gpointer user_data)731 button_press_cb (GtkWidget      *widget,
732 		 GdkEventButton *event,
733 		 gpointer        user_data)
734 {
735 	GthFolderTree     *folder_tree = user_data;
736 	GtkTreeStore      *tree_store = folder_tree->priv->tree_store;
737 	GtkTreePath       *path;
738 	GtkTreeIter        iter;
739 	gboolean           retval;
740 	GtkTreeViewColumn *column;
741 	int                cell_x;
742 	int                cell_y;
743 
744 	retval = FALSE;
745 
746 	gtk_widget_grab_focus (widget);
747 
748 	if ((event->state & GDK_SHIFT_MASK) || (event->state & GDK_CONTROL_MASK))
749 		return retval;
750 
751 	if ((event->button != 1) && (event->button != 3))
752 		return retval;
753 
754 	if (! gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (folder_tree),
755 					     event->x, event->y,
756 					     &path,
757 					     &column,
758 					     &cell_x,
759 					     &cell_y))
760 	{
761 		if (event->button == 3) {
762 			g_signal_emit (folder_tree,
763 				       gth_folder_tree_signals[FOLDER_POPUP],
764 				       0,
765 				       NULL,
766 				       event->time);
767 			retval = TRUE;
768 		}
769 
770 		return retval;
771 	}
772 
773 	if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_store),
774 				       &iter,
775 				       path))
776 	{
777 		gtk_tree_path_free (path);
778 		return retval;
779 	}
780 
781  	if (event->button == 3) {
782  		EntryType    entry_type;
783 		GthFileData *file_data;
784 
785 		gtk_tree_model_get (GTK_TREE_MODEL (tree_store),
786 				    &iter,
787 				    COLUMN_TYPE, &entry_type,
788 				    COLUMN_FILE_DATA, &file_data,
789 				    -1);
790 
791 		if (entry_type == ENTRY_TYPE_FILE) {
792 			g_signal_emit (folder_tree,
793 				       gth_folder_tree_signals[FOLDER_POPUP],
794 				       0,
795 				       file_data,
796 				       event->time);
797 			retval = TRUE;
798 		}
799 
800 		_g_object_unref (file_data);
801  	}
802 	else if ((event->button == 1) && (event->type == GDK_BUTTON_PRESS)) {
803 		/* This can be the start of a dragging action. */
804 
805 		if (! (event->state & GDK_CONTROL_MASK)
806 		    && ! (event->state & GDK_SHIFT_MASK)
807 		    && folder_tree->priv->drag_source_enabled)
808 		{
809 			folder_tree->priv->dragging = TRUE;
810 			folder_tree->priv->drag_start_x = event->x;
811 			folder_tree->priv->drag_start_y = event->y;
812 		}
813 	}
814 	else if ((event->button == 1) && (event->type == GDK_2BUTTON_PRESS)) {
815 		if (! gtk_tree_view_row_expanded (GTK_TREE_VIEW (folder_tree), path))
816 			gtk_tree_view_expand_row (GTK_TREE_VIEW (folder_tree), path, FALSE);
817 		else
818 			gtk_tree_view_collapse_row (GTK_TREE_VIEW (folder_tree), path);
819 		retval = TRUE;
820 	}
821 
822 	gtk_tree_path_free (path);
823 
824 	return retval;
825 }
826 
827 
828 static gboolean
motion_notify_event_cb(GtkWidget * widget,GdkEventButton * event,gpointer user_data)829 motion_notify_event_cb (GtkWidget      *widget,
830 			GdkEventButton *event,
831 			gpointer        user_data)
832 {
833 	GthFolderTree *folder_tree = user_data;
834 
835 	if (! folder_tree->priv->drag_source_enabled)
836 		return FALSE;
837 
838 	if (folder_tree->priv->dragging) {
839 		if (! folder_tree->priv->drag_started
840 		    && gtk_drag_check_threshold (widget,
841 					         folder_tree->priv->drag_start_x,
842 					         folder_tree->priv->drag_start_y,
843 						 event->x,
844 						 event->y))
845 		{
846 			GtkTreePath     *path = NULL;
847 			GdkDragContext  *context;
848 			int              cell_x;
849 			int              cell_y;
850 			cairo_surface_t *dnd_surface;
851 
852 			if (! gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (folder_tree),
853 							     event->x,
854 							     event->y,
855 							     &path,
856 							     NULL,
857 							     &cell_x,
858 							     &cell_y))
859 			{
860 				return FALSE;
861 			}
862 
863 			gtk_tree_view_set_cursor (GTK_TREE_VIEW (folder_tree), path, NULL, FALSE);
864 			folder_tree->priv->drag_started = TRUE;
865 
866 			/**/
867 
868 			context = gtk_drag_begin_with_coordinates (widget,
869 								   folder_tree->priv->drag_target_list,
870 								   folder_tree->priv->drag_actions,
871 								   1,
872 								   (GdkEvent *) event,
873 								   -1,
874 								   -1);
875 
876 			dnd_surface = gtk_tree_view_create_row_drag_icon (GTK_TREE_VIEW (folder_tree), path);
877 			gtk_drag_set_icon_surface (context, dnd_surface);
878 
879 			cairo_surface_destroy (dnd_surface);
880 			gtk_tree_path_free (path);
881 		}
882 
883 		return TRUE;
884 	}
885 
886 	return FALSE;
887 }
888 
889 
890 static gboolean
button_release_event_cb(GtkWidget * widget,GdkEventButton * event,gpointer user_data)891 button_release_event_cb (GtkWidget      *widget,
892 			 GdkEventButton *event,
893 			 gpointer        user_data)
894 {
895 	GthFolderTree *folder_tree = user_data;
896 
897 	if (folder_tree->priv->dragging) {
898 		folder_tree->priv->dragging = FALSE;
899 		folder_tree->priv->drag_started = FALSE;
900 	}
901 
902 	return FALSE;
903 }
904 
905 
906 static void
load_uri(GthFolderTree * folder_tree,EntryType entry_type,GthFileData * file_data)907 load_uri (GthFolderTree *folder_tree,
908 	  EntryType      entry_type,
909 	  GthFileData   *file_data)
910 {
911 	if (entry_type == ENTRY_TYPE_FILE)
912 		g_signal_emit (folder_tree, gth_folder_tree_signals[LOAD], 0, file_data->file);
913 }
914 
915 
916 static gboolean
selection_changed_cb(GtkTreeSelection * selection,gpointer user_data)917 selection_changed_cb (GtkTreeSelection *selection,
918 		      gpointer          user_data)
919 {
920 	GthFolderTree *folder_tree = user_data;
921 	GtkTreeIter    iter;
922 	GtkTreePath   *selected_path;
923 	EntryType      entry_type;
924 	GthFileData   *file_data;
925 
926 	if (! gtk_tree_selection_get_selected (selection, NULL, &iter))
927 		return FALSE;
928 
929 	selected_path = gtk_tree_model_get_path (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter);
930 
931 	gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store),
932 			    &iter,
933 			    COLUMN_TYPE, &entry_type,
934 			    COLUMN_FILE_DATA, &file_data,
935 			    -1);
936 
937 	load_uri (folder_tree, entry_type, file_data);
938 
939 	_g_object_unref (file_data);
940 	gtk_tree_path_free (selected_path);
941 
942 	return FALSE;
943 }
944 
945 
946 static gint
column_name_compare_func(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user_data)947 column_name_compare_func (GtkTreeModel *model,
948 			  GtkTreeIter  *a,
949 			  GtkTreeIter  *b,
950 			  gpointer      user_data)
951 {
952 	char       *key_a, *key_b;
953 	int         order_a, order_b;
954 	int         sec_order_a, sec_order_b;
955 	PangoStyle  style_a, style_b;
956 	gboolean    result;
957 
958 	gtk_tree_model_get (model, a,
959 			    COLUMN_SORT_KEY, &key_a,
960 			    COLUMN_SORT_ORDER, &order_a,
961 			    COLUMN_SECONDARY_SORT_ORDER, &sec_order_a,
962 			    COLUMN_STYLE, &style_a,
963 			    -1);
964 	gtk_tree_model_get (model, b,
965 			    COLUMN_SORT_KEY, &key_b,
966 			    COLUMN_SORT_ORDER, &order_b,
967 			    COLUMN_SECONDARY_SORT_ORDER, &sec_order_b,
968 			    COLUMN_STYLE, &style_b,
969 			    -1);
970 
971 	if (order_a == order_b) {
972 		if (style_a == style_b) {
973 			result = strcmp (key_a, key_b);
974 			if (result == 0) {
975 				if (sec_order_a < sec_order_b)
976 					result = -1;
977 				else if (sec_order_a > sec_order_b)
978 					result = 1;
979 			}
980 		}
981 		else if (style_a == PANGO_STYLE_ITALIC)
982 			result = -1;
983 		else
984 			result = 1;
985 	}
986 	else if (order_a < order_b)
987 		result = -1;
988 	else
989 		result = 1;
990 
991 	g_free (key_a);
992 	g_free (key_b);
993 
994 	return result;
995 }
996 
997 
998 static gboolean
iter_stores_file(GtkTreeModel * tree_model,GtkTreeIter * iter,GFile * file,GtkTreeIter * file_iter)999 iter_stores_file (GtkTreeModel *tree_model,
1000 	          GtkTreeIter  *iter,
1001 	          GFile        *file,
1002 	          GtkTreeIter  *file_iter)
1003 {
1004 	GthFileData *iter_file_data;
1005 	EntryType    iter_type;
1006 	gboolean     found;
1007 
1008 	gtk_tree_model_get (tree_model, iter,
1009 			    COLUMN_FILE_DATA, &iter_file_data,
1010 			    COLUMN_TYPE, &iter_type,
1011 			    -1);
1012 	found = (iter_type == ENTRY_TYPE_FILE) && (iter_file_data != NULL) && g_file_equal (file, iter_file_data->file);
1013 
1014 	_g_object_unref (iter_file_data);
1015 
1016 	if (found)
1017 		*file_iter = *iter;
1018 
1019 	return found;
1020 }
1021 
1022 
1023 static gboolean
_gth_folder_tree_find_file_in_children(GtkTreeModel * tree_model,GFile * file,GtkTreeIter * file_iter,GtkTreeIter * root)1024 _gth_folder_tree_find_file_in_children (GtkTreeModel *tree_model,
1025 				        GFile        *file,
1026 				        GtkTreeIter  *file_iter,
1027 				        GtkTreeIter  *root)
1028 {
1029 	GtkTreeIter iter;
1030 
1031 	/* check the children... */
1032 
1033 	if (! gtk_tree_model_iter_children (tree_model, &iter, root))
1034 		return FALSE;
1035 
1036 	do {
1037 		if (iter_stores_file (tree_model, &iter, file, file_iter))
1038 			return TRUE;
1039 	}
1040 	while (gtk_tree_model_iter_next (tree_model, &iter));
1041 
1042 	/* ...if no child stores the file, search recursively */
1043 
1044 	if (gtk_tree_model_iter_children (tree_model, &iter, root)) {
1045 		do {
1046 			if (_gth_folder_tree_find_file_in_children (tree_model, file, file_iter, &iter))
1047 				return TRUE;
1048 		}
1049 		while (gtk_tree_model_iter_next (tree_model, &iter));
1050 	}
1051 
1052 	return FALSE;
1053 }
1054 
1055 
1056 static gboolean
gth_folder_tree_get_iter(GthFolderTree * folder_tree,GFile * file,GtkTreeIter * file_iter,GtkTreeIter * root)1057 gth_folder_tree_get_iter (GthFolderTree *folder_tree,
1058 			  GFile         *file,
1059 			  GtkTreeIter   *file_iter,
1060 			  GtkTreeIter   *root)
1061 {
1062 	GtkTreeModel *tree_model = GTK_TREE_MODEL (folder_tree->priv->tree_store);
1063 
1064 	if (file == NULL)
1065 		return FALSE;
1066 
1067 	if ((root != NULL) && iter_stores_file (tree_model, root, file, file_iter))
1068 		return TRUE;
1069 
1070 	/* This type of search is useful to give priority to the first level
1071 	 * of entries which contains all the entry points.
1072 	 * For example if file is "file:///media/usb-disk" this function must
1073 	 * return the entry point corresponding to the device instead of
1074 	 * returing the usb-disk folder located in "file:///media". */
1075 
1076 	if (_gth_folder_tree_find_file_in_children (tree_model, file, file_iter, root))
1077 		return TRUE;
1078 
1079 	return FALSE;
1080 }
1081 
1082 
1083 static gboolean
_gth_folder_tree_get_child(GthFolderTree * folder_tree,GFile * file,GtkTreeIter * file_iter,GtkTreeIter * parent)1084 _gth_folder_tree_get_child (GthFolderTree *folder_tree,
1085 			    GFile         *file,
1086 			    GtkTreeIter   *file_iter,
1087 			    GtkTreeIter   *parent)
1088 {
1089 	GtkTreeModel *tree_model = GTK_TREE_MODEL (folder_tree->priv->tree_store);
1090 	GtkTreeIter   iter;
1091 
1092 	if (! gtk_tree_model_iter_children (tree_model, &iter, parent))
1093 		return FALSE;
1094 
1095 	do {
1096 		GthFileData *test_file_data;
1097 		EntryType    file_entry_type;
1098 
1099 		gtk_tree_model_get (tree_model, &iter,
1100 				    COLUMN_FILE_DATA, &test_file_data,
1101 				    COLUMN_TYPE, &file_entry_type,
1102 				    -1);
1103 		if ((file_entry_type == ENTRY_TYPE_FILE) && (test_file_data != NULL) && g_file_equal (file, test_file_data->file)) {
1104 			_g_object_unref (test_file_data);
1105 			*file_iter = iter;
1106 			return TRUE;
1107 		}
1108 
1109 		_g_object_unref (test_file_data);
1110 	}
1111 	while (gtk_tree_model_iter_next (tree_model, &iter));
1112 
1113 	return FALSE;
1114 }
1115 
1116 
1117 static gboolean
_gth_folder_tree_child_type_present(GthFolderTree * folder_tree,GtkTreeIter * parent,EntryType entry_type)1118 _gth_folder_tree_child_type_present (GthFolderTree *folder_tree,
1119 				     GtkTreeIter   *parent,
1120 				     EntryType      entry_type)
1121 {
1122 	GtkTreeIter iter;
1123 
1124 	if (! gtk_tree_model_iter_children (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter, parent))
1125 		return FALSE;
1126 
1127 	do {
1128 		EntryType file_entry_type;
1129 
1130 		gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter,
1131 				    COLUMN_TYPE, &file_entry_type,
1132 				    -1);
1133 
1134 		if (entry_type == file_entry_type)
1135 			return TRUE;
1136 	}
1137 	while (gtk_tree_model_iter_next (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter));
1138 
1139 	return FALSE;
1140 }
1141 
1142 
1143 static void
_gth_folder_tree_add_loading_item(GthFolderTree * folder_tree,GtkTreeIter * parent,gboolean forced)1144 _gth_folder_tree_add_loading_item (GthFolderTree *folder_tree,
1145 				   GtkTreeIter   *parent,
1146 				   gboolean       forced)
1147 {
1148 	char        *sort_key;
1149 	GtkTreeIter  iter;
1150 
1151 	if (! forced && _gth_folder_tree_child_type_present (folder_tree, parent, ENTRY_TYPE_LOADING))
1152 		return;
1153 
1154 	sort_key = g_utf8_collate_key_for_filename (LOADING_URI, -1);
1155 
1156 	gtk_tree_store_append (folder_tree->priv->tree_store, &iter, parent);
1157 	gtk_tree_store_set (folder_tree->priv->tree_store, &iter,
1158 			    COLUMN_STYLE, PANGO_STYLE_ITALIC,
1159 			    COLUMN_TYPE, ENTRY_TYPE_LOADING,
1160 			    COLUMN_NAME, _("Loading…"),
1161 			    COLUMN_SORT_KEY, sort_key,
1162 			    COLUMN_SORT_ORDER, 0,
1163 			    COLUMN_SECONDARY_SORT_ORDER, 0,
1164 			    -1);
1165 
1166 	g_free (sort_key);
1167 }
1168 
1169 
1170 static void
_gth_folder_tree_add_empty_item(GthFolderTree * folder_tree,GtkTreeIter * parent)1171 _gth_folder_tree_add_empty_item (GthFolderTree *folder_tree,
1172 				 GtkTreeIter   *parent)
1173 {
1174 	char        *sort_key;
1175 	GtkTreeIter  iter;
1176 
1177 	if (_gth_folder_tree_child_type_present (folder_tree, parent, ENTRY_TYPE_EMPTY))
1178 		return;
1179 
1180 	sort_key = g_utf8_collate_key_for_filename (EMPTY_URI, -1);
1181 
1182 	gtk_tree_store_append (folder_tree->priv->tree_store, &iter, parent);
1183 	gtk_tree_store_set (folder_tree->priv->tree_store, &iter,
1184 			    COLUMN_STYLE, PANGO_STYLE_ITALIC,
1185 			    COLUMN_TYPE, ENTRY_TYPE_EMPTY,
1186 			    COLUMN_NAME, _("(Empty)"),
1187 			    COLUMN_SORT_KEY, sort_key,
1188 			    COLUMN_SORT_ORDER, 0,
1189 			    COLUMN_SECONDARY_SORT_ORDER, 0,
1190 			    -1);
1191 
1192 	g_free (sort_key);
1193 }
1194 
1195 
1196 static gboolean
_gth_folder_tree_set_file_data(GthFolderTree * folder_tree,GtkTreeIter * iter,GthFileData * file_data)1197 _gth_folder_tree_set_file_data (GthFolderTree *folder_tree,
1198 				GtkTreeIter   *iter,
1199 				GthFileData   *file_data)
1200 {
1201 	const char *display_name;
1202 	const char *name_for_sorting;
1203 	char       *sort_key;
1204 
1205 	display_name = g_file_info_get_display_name (file_data->info);
1206 	if (display_name == NULL)
1207 		return FALSE;
1208 
1209 	name_for_sorting = g_file_info_get_edit_name (file_data->info);
1210 	if (name_for_sorting == NULL)
1211 		name_for_sorting = display_name;
1212 
1213 	sort_key = g_utf8_collate_key_for_filename (name_for_sorting, -1);
1214 	gtk_tree_store_set (folder_tree->priv->tree_store, iter,
1215 			    COLUMN_STYLE, PANGO_STYLE_NORMAL,
1216 			    COLUMN_ICON, g_file_info_get_symbolic_icon (file_data->info),
1217 			    COLUMN_TYPE, ENTRY_TYPE_FILE,
1218 			    COLUMN_FILE_DATA, file_data,
1219 			    COLUMN_NAME, display_name,
1220 			    COLUMN_SORT_KEY, sort_key,
1221 			    COLUMN_SORT_ORDER, g_file_info_get_sort_order (file_data->info),
1222 			    COLUMN_SECONDARY_SORT_ORDER, _g_file_info_get_secondary_sort_order (file_data->info),
1223 			    COLUMN_NO_CHILD, g_file_info_get_attribute_boolean (file_data->info, "gthumb::no-child"),
1224 			    COLUMN_LOADED, FALSE,
1225 			    -1);
1226 
1227 	g_free (sort_key);
1228 
1229 	return TRUE;
1230 }
1231 
1232 
1233 static gboolean
_gth_folder_tree_iter_get_no_child(GthFolderTree * folder_tree,GtkTreeIter * iter)1234 _gth_folder_tree_iter_get_no_child (GthFolderTree *folder_tree,
1235 				    GtkTreeIter   *iter)
1236 {
1237 	gboolean no_child;
1238 
1239 	if (iter == NULL)
1240 		return FALSE;
1241 
1242 	gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store), iter,
1243 			    COLUMN_NO_CHILD, &no_child,
1244 			    -1);
1245 
1246 	return no_child;
1247 }
1248 
1249 
1250 static void
_gth_folder_tree_update_entry_points(GthFolderTree * folder_tree)1251 _gth_folder_tree_update_entry_points (GthFolderTree *folder_tree)
1252 {
1253 	GtkTreeIter iter;
1254 
1255 	if (! folder_tree->priv->recalc_entry_points)
1256 		return;
1257 
1258 	folder_tree->priv->recalc_entry_points = FALSE;
1259 
1260 	g_hash_table_remove_all (folder_tree->priv->entry_points);
1261 
1262 	if (! gtk_tree_model_iter_children (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter, NULL))
1263 		return;
1264 
1265 	do {
1266 		GthFileData *file_data;
1267 		EntryType    file_entry_type;
1268 
1269 		gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter,
1270 				    COLUMN_FILE_DATA, &file_data,
1271 				    COLUMN_TYPE, &file_entry_type,
1272 				    -1);
1273 		if ((file_entry_type == ENTRY_TYPE_FILE) && (file_data != NULL))
1274 			g_hash_table_add (folder_tree->priv->entry_points, g_object_ref (file_data->file));
1275 
1276 		_g_object_unref (file_data);
1277 	}
1278 	while (gtk_tree_model_iter_next (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter));
1279 }
1280 
1281 
1282 /*
1283  * Returns TRUE if file_data points to a folder contained in the entry point
1284  * list but it's not real entry point, for example it returns TRUE for
1285  * '/home/user/Images' or '/home/user/Documents'.
1286  * This entries are duplicates of the entry points and are treated in a special
1287  * way to avoid confusion.
1288  * */
1289 static gboolean
_gth_folder_tree_is_entry_point_dup(GthFolderTree * folder_tree,GtkTreeIter * iter,GthFileData * file_data)1290 _gth_folder_tree_is_entry_point_dup (GthFolderTree *folder_tree,
1291 				     GtkTreeIter   *iter,
1292 				     GthFileData   *file_data)
1293 {
1294 	_gth_folder_tree_update_entry_points (folder_tree);
1295 
1296 	if (g_hash_table_lookup (folder_tree->priv->entry_points, file_data->file) == NULL)
1297 		return FALSE;
1298 
1299 	return ! g_file_info_get_attribute_boolean (file_data->info, "gthumb::entry-point");
1300 }
1301 
1302 
1303 static gboolean
_gth_folder_tree_add_file(GthFolderTree * folder_tree,GtkTreeIter * parent,GthFileData * fd)1304 _gth_folder_tree_add_file (GthFolderTree *folder_tree,
1305 			   GtkTreeIter   *parent,
1306 			   GthFileData   *fd)
1307 {
1308 	GtkTreeIter iter;
1309 
1310 	if (g_file_info_get_file_type (fd->info) != G_FILE_TYPE_DIRECTORY)
1311 		return FALSE;
1312 
1313 	/* add the folder */
1314 
1315 	gtk_tree_store_append (folder_tree->priv->tree_store, &iter, parent);
1316 	if (! _gth_folder_tree_set_file_data (folder_tree, &iter, fd)) {
1317 		gtk_tree_store_remove (folder_tree->priv->tree_store, &iter);
1318 		return FALSE;
1319 	}
1320 
1321 	if (g_file_info_get_attribute_boolean (fd->info, "gthumb::entry-point"))
1322 		gtk_tree_store_set (folder_tree->priv->tree_store, &iter,
1323 				    COLUMN_WEIGHT, PANGO_WEIGHT_BOLD,
1324 				    -1);
1325 	else
1326 		gtk_tree_store_set (folder_tree->priv->tree_store, &iter,
1327 				    COLUMN_WEIGHT, PANGO_WEIGHT_NORMAL,
1328 				    -1);
1329 
1330 	if (! g_file_info_get_attribute_boolean (fd->info, "gthumb::no-child")
1331 	    && ! _gth_folder_tree_is_entry_point_dup (folder_tree, &iter, fd))
1332 	{
1333 		_gth_folder_tree_add_loading_item (folder_tree, &iter, TRUE);
1334 	}
1335 
1336 	return TRUE;
1337 }
1338 
1339 
1340 static void
gth_folder_tree_init(GthFolderTree * folder_tree)1341 gth_folder_tree_init (GthFolderTree *folder_tree)
1342 {
1343 	GtkTreeSelection *selection;
1344 
1345 	folder_tree->priv = gth_folder_tree_get_instance_private (folder_tree);
1346 	folder_tree->priv->root = g_file_new_for_uri (DEFAULT_URI);
1347 	folder_tree->priv->entry_points = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
1348 	folder_tree->priv->recalc_entry_points = FALSE;
1349 	folder_tree->priv->tree_store = gtk_tree_store_new (NUM_COLUMNS,
1350 							    PANGO_TYPE_STYLE,
1351 							    PANGO_TYPE_WEIGHT,
1352 							    G_TYPE_ICON,
1353 							    G_TYPE_INT,
1354 							    G_TYPE_OBJECT,
1355 							    G_TYPE_STRING,
1356 							    G_TYPE_INT,
1357 							    G_TYPE_INT,
1358 							    G_TYPE_STRING,
1359 							    G_TYPE_BOOLEAN,
1360 							    G_TYPE_BOOLEAN);
1361 	folder_tree->priv->text_renderer = NULL;
1362 	folder_tree->priv->hover_path = NULL;
1363 
1364 	folder_tree->priv->drag_source_enabled = FALSE;
1365 	folder_tree->priv->drag_start_button_mask = 0;
1366 	folder_tree->priv->drag_target_list = NULL;
1367 	folder_tree->priv->drag_actions = 0;
1368 	folder_tree->priv->dragging = FALSE;
1369 	folder_tree->priv->drag_started = FALSE;
1370 	folder_tree->priv->drag_start_x = 0;
1371 	folder_tree->priv->drag_start_y = 0;
1372 
1373 	folder_tree->priv->monitor.locations = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
1374 	folder_tree->priv->monitor.sources = NULL;
1375 	folder_tree->priv->monitor.update_id = 0;
1376 
1377 	gtk_tree_view_set_model (GTK_TREE_VIEW (folder_tree), GTK_TREE_MODEL (folder_tree->priv->tree_store));
1378 
1379 	add_columns (folder_tree, GTK_TREE_VIEW (folder_tree));
1380 
1381 	gtk_tree_view_set_activate_on_single_click (GTK_TREE_VIEW (folder_tree), TRUE);
1382 	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (folder_tree), FALSE);
1383 	gtk_tree_view_set_enable_search (GTK_TREE_VIEW (folder_tree), TRUE);
1384 	gtk_tree_view_set_search_column (GTK_TREE_VIEW (folder_tree), COLUMN_NAME);
1385 	gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (folder_tree->priv->tree_store), COLUMN_NAME, column_name_compare_func, folder_tree, NULL);
1386 	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (folder_tree->priv->tree_store), COLUMN_NAME, GTK_SORT_ASCENDING);
1387 
1388 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree));
1389 	gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
1390 	g_signal_connect (selection,
1391 			  "changed",
1392 			  G_CALLBACK (selection_changed_cb),
1393 			  folder_tree);
1394 
1395 	/**/
1396 
1397 	g_signal_connect (folder_tree,
1398 			  "popup-menu",
1399 			  G_CALLBACK (popup_menu_cb),
1400 			  folder_tree);
1401 	g_signal_connect (folder_tree,
1402 			  "button-press-event",
1403 			  G_CALLBACK (button_press_cb),
1404 			  folder_tree);
1405 	g_signal_connect (folder_tree,
1406 			  "motion-notify-event",
1407 			  G_CALLBACK (motion_notify_event_cb),
1408 			  folder_tree);
1409 	g_signal_connect (folder_tree,
1410 			  "button-release-event",
1411 			  G_CALLBACK (button_release_event_cb),
1412 			  folder_tree);
1413 	g_signal_connect (folder_tree,
1414 			  "row-activated",
1415 			  G_CALLBACK (row_activated_cb),
1416 			  folder_tree);
1417 	g_signal_connect (folder_tree,
1418 			  "row-expanded",
1419 			  G_CALLBACK (row_expanded_cb),
1420 			  folder_tree);
1421 	g_signal_connect (folder_tree,
1422 			  "row-collapsed",
1423 			  G_CALLBACK (row_collapsed_cb),
1424 			  folder_tree);
1425 }
1426 
1427 
1428 GtkWidget *
gth_folder_tree_new(const char * root)1429 gth_folder_tree_new (const char *root)
1430 {
1431 	return g_object_new (GTH_TYPE_FOLDER_TREE, "root-uri", root, NULL);
1432 }
1433 
1434 
1435 void
gth_folder_tree_set_list(GthFolderTree * folder_tree,GFile * root,GList * files,gboolean open_parent)1436 gth_folder_tree_set_list (GthFolderTree *folder_tree,
1437 			  GFile         *root,
1438 			  GList         *files,
1439 			  gboolean       open_parent)
1440 {
1441 	gtk_tree_store_clear (folder_tree->priv->tree_store);
1442 
1443 	if (folder_tree->priv->root != NULL) {
1444 		g_object_unref (folder_tree->priv->root);
1445 		folder_tree->priv->root = NULL;
1446 	}
1447 	if (root != NULL)
1448 		folder_tree->priv->root = g_file_dup (root);
1449 
1450 	/* add the parent folder item */
1451 
1452 	if (open_parent) {
1453 		char        *sort_key;
1454 		GIcon       *icon;
1455 		GtkTreeIter  iter;
1456 
1457 		sort_key = g_utf8_collate_key_for_filename (PARENT_URI, -1);
1458 		icon = g_themed_icon_new ("go-up-symbolic");
1459 
1460 		gtk_tree_store_append (folder_tree->priv->tree_store, &iter, NULL);
1461 		gtk_tree_store_set (folder_tree->priv->tree_store, &iter,
1462 				    COLUMN_STYLE, PANGO_STYLE_ITALIC,
1463 				    COLUMN_ICON, icon,
1464 				    COLUMN_TYPE, ENTRY_TYPE_PARENT,
1465 				    COLUMN_NAME, _("(Open Parent)"),
1466 				    COLUMN_SORT_KEY, sort_key,
1467 				    COLUMN_SORT_ORDER, 0,
1468 				    COLUMN_SECONDARY_SORT_ORDER, 0,
1469 				    -1);
1470 
1471 		g_object_unref (icon);
1472 		g_free (sort_key);
1473 	}
1474 
1475 	/* add the folder list */
1476 
1477 	gth_folder_tree_set_children (folder_tree, root, files);
1478 }
1479 
1480 
1481 /* After changing the children list, the node expander is not highlighted
1482  * anymore, this prevents the user to close the expander without moving the
1483  * mouse pointer.  The problem can be fixed emitting a fake motion notify
1484  * event, this way the expander gets highlighted again and a click on the
1485  * expander will correctly collapse the node. */
1486 static void
emit_fake_motion_notify_event(GthFolderTree * folder_tree)1487 emit_fake_motion_notify_event (GthFolderTree *folder_tree)
1488 {
1489 	GtkWidget      *widget = GTK_WIDGET (folder_tree);
1490 	GdkDevice      *device;
1491 	GdkWindow      *window;
1492 	GdkEventMotion  event;
1493 	int             x, y;
1494 
1495 	if (! gtk_widget_get_realized (widget))
1496 		return;
1497 
1498 	device = _gtk_widget_get_client_pointer (widget);
1499 	if (device == NULL)
1500 		return;
1501 	window = gdk_window_get_device_position (gtk_widget_get_window (widget),
1502 						 device,
1503 						 &x,
1504 						 &y,
1505 						 NULL);
1506 
1507 	event.type = GDK_MOTION_NOTIFY;
1508 	event.window = (window != NULL) ? window : gtk_tree_view_get_bin_window (GTK_TREE_VIEW (folder_tree));
1509 	event.send_event = TRUE;
1510 	event.time = GDK_CURRENT_TIME;
1511 	event.x = x;
1512 	event.y = y;
1513 	event.axes = NULL;
1514 	event.state = 0;
1515 	event.is_hint = FALSE;
1516 	event.device = device;
1517 
1518 	GTK_WIDGET_GET_CLASS (folder_tree)->motion_notify_event (widget, &event);
1519 }
1520 
1521 
1522 G_GNUC_UNUSED
1523 static GList *
_gth_folder_tree_get_children(GthFolderTree * folder_tree,GtkTreeIter * parent)1524 _gth_folder_tree_get_children (GthFolderTree *folder_tree,
1525 			       GtkTreeIter   *parent)
1526 {
1527 	GtkTreeModel *tree_model = GTK_TREE_MODEL (folder_tree->priv->tree_store);
1528 	GtkTreeIter   iter;
1529 	GList        *list;
1530 
1531 	if (! gtk_tree_model_iter_children (tree_model, &iter, parent))
1532 		return NULL;
1533 
1534 	list = NULL;
1535 	do {
1536 		GthFileData *file_data;
1537 		EntryType    file_type;
1538 
1539 		gtk_tree_model_get (tree_model, &iter,
1540 				    COLUMN_FILE_DATA, &file_data,
1541 				    COLUMN_TYPE, &file_type,
1542 				    -1);
1543 		if ((file_type == ENTRY_TYPE_FILE) && (file_data != NULL))
1544 			list = g_list_prepend (list, g_object_ref (file_data));
1545 
1546 		_g_object_unref (file_data);
1547 	}
1548 	while (gtk_tree_model_iter_next (tree_model, &iter));
1549 
1550 	return g_list_reverse (list);
1551 }
1552 
1553 
1554 static void
_gth_folder_tree_remove_child_type(GthFolderTree * folder_tree,GtkTreeIter * parent,EntryType entry_type)1555 _gth_folder_tree_remove_child_type (GthFolderTree *folder_tree,
1556 				    GtkTreeIter   *parent,
1557 				    EntryType      entry_type)
1558 {
1559 	GtkTreeIter iter;
1560 
1561 	if (! gtk_tree_model_iter_children (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter, parent))
1562 		return;
1563 
1564 	do {
1565 		EntryType file_entry_type;
1566 
1567 		gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter,
1568 				    COLUMN_TYPE, &file_entry_type,
1569 				    -1);
1570 
1571 		if (entry_type == file_entry_type) {
1572 			gtk_tree_store_remove (folder_tree->priv->tree_store, &iter);
1573 			break;
1574 		}
1575 	}
1576 	while (gtk_tree_model_iter_next (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter));
1577 }
1578 
1579 
1580 void
gth_folder_tree_set_children(GthFolderTree * folder_tree,GFile * parent,GList * files)1581 gth_folder_tree_set_children (GthFolderTree *folder_tree,
1582 			      GFile         *parent,
1583 			      GList         *files)
1584 {
1585 	GtkTreeIter   parent_iter;
1586 	GtkTreeIter  *p_parent_iter;
1587 	GHashTable   *file_hash;
1588 	GList        *scan;
1589 	GList        *old_files;
1590 	GtkTreeModel *tree_model;
1591 	GtkTreeIter   iter;
1592 
1593 	if (g_file_equal (parent, folder_tree->priv->root))
1594 		p_parent_iter = NULL;
1595 	else if (gth_folder_tree_get_iter (folder_tree, parent, &parent_iter, NULL))
1596 		p_parent_iter = &parent_iter;
1597 	else
1598 		return;
1599 
1600 	if (_gth_folder_tree_iter_get_no_child (folder_tree, p_parent_iter))
1601 		return;
1602 
1603 	tree_model = GTK_TREE_MODEL (folder_tree->priv->tree_store);
1604 	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (tree_model), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, 0);
1605 
1606 	/* add the empty item first to not allow the folder to collapse. */
1607 	_gth_folder_tree_add_empty_item (folder_tree, p_parent_iter);
1608 
1609 	/* delete the children not present in the new file list, update the
1610 	 * already existing files */
1611 
1612 	file_hash = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
1613 	for (scan = files; scan; scan = scan->next) {
1614 		GthFileData *file_data = scan->data;
1615 		g_hash_table_insert (file_hash, g_object_ref (file_data->file), GINT_TO_POINTER (1));
1616 	}
1617 
1618 	old_files = NULL;
1619 	if (gtk_tree_model_iter_children (tree_model, &iter, p_parent_iter)) {
1620 		gboolean valid = TRUE;
1621 
1622 		do {
1623 			GthFileData *file_data = NULL;
1624 			EntryType    file_type;
1625 
1626 			gtk_tree_model_get (tree_model, &iter,
1627 					    COLUMN_FILE_DATA, &file_data,
1628 					    COLUMN_TYPE, &file_type,
1629 					    -1);
1630 
1631 			if (file_type == ENTRY_TYPE_LOADING) {
1632 				valid = gtk_tree_store_remove (folder_tree->priv->tree_store, &iter);
1633 			}
1634 			else if (file_type == ENTRY_TYPE_FILE) {
1635 				/* save the old files list to compute the new files list below */
1636 				old_files = g_list_prepend (old_files, g_object_ref (file_data));
1637 
1638 				if (g_hash_table_lookup (file_hash, file_data->file)) {
1639 					/* file_data is already present in the list, just update it */
1640 					_gth_folder_tree_set_file_data (folder_tree, &iter, file_data);
1641 					valid = gtk_tree_model_iter_next (tree_model, &iter);
1642 				}
1643 				else {
1644 					/* file_data is not present anymore, remove it from the tree */
1645 					valid = gtk_tree_store_remove (folder_tree->priv->tree_store, &iter);
1646 				}
1647 			}
1648 			else
1649 				valid = gtk_tree_model_iter_next (tree_model, &iter);
1650 
1651 			_g_object_unref (file_data);
1652 		}
1653 		while (valid);
1654 	}
1655 
1656 	g_hash_table_unref (file_hash);
1657 
1658 	/* add the new files */
1659 
1660 	file_hash = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
1661 	for (scan = old_files; scan; scan = scan->next) {
1662 		GthFileData *file_data = scan->data;
1663 		g_hash_table_insert (file_hash, g_object_ref (file_data->file), GINT_TO_POINTER (1));
1664 	}
1665 
1666 	for (scan = files; scan; scan = scan->next) {
1667 		GthFileData *file_data = scan->data;
1668 
1669 		if (! g_hash_table_lookup (file_hash, file_data->file))
1670 			_gth_folder_tree_add_file (folder_tree, p_parent_iter, file_data);
1671 	}
1672 
1673 	_g_object_list_unref (old_files);
1674 	g_hash_table_unref (file_hash);
1675 
1676 	/**/
1677 
1678 	_gth_folder_tree_remove_child_type (folder_tree, p_parent_iter, ENTRY_TYPE_EMPTY);
1679 
1680 	if (p_parent_iter != NULL)
1681 		gtk_tree_store_set (folder_tree->priv->tree_store, p_parent_iter,
1682 				    COLUMN_LOADED, TRUE,
1683 				    -1);
1684 
1685 	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (folder_tree->priv->tree_store), COLUMN_NAME, GTK_SORT_ASCENDING);
1686 	folder_tree->priv->recalc_entry_points = TRUE;
1687 
1688 	emit_fake_motion_notify_event (folder_tree);
1689 }
1690 
1691 
1692 void
gth_folder_tree_loading_children(GthFolderTree * folder_tree,GFile * parent)1693 gth_folder_tree_loading_children (GthFolderTree *folder_tree,
1694 				  GFile         *parent)
1695 {
1696 	GtkTreeIter  parent_iter;
1697 	GtkTreeIter *p_parent_iter;
1698 	GtkTreeIter  iter;
1699 	gboolean     valid_iter;
1700 
1701 	if (g_file_equal (parent, folder_tree->priv->root))
1702 		p_parent_iter = NULL;
1703 	else if (gth_folder_tree_get_iter (folder_tree, parent, &parent_iter, NULL))
1704 		p_parent_iter = &parent_iter;
1705 	else
1706 		return;
1707 
1708 	if (_gth_folder_tree_iter_get_no_child (folder_tree, p_parent_iter))
1709 		return;
1710 
1711 	_gth_folder_tree_add_loading_item (folder_tree, p_parent_iter, FALSE);
1712 
1713 	/* remove anything but the loading item */
1714 	gtk_tree_model_iter_children (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter, p_parent_iter);
1715 	do {
1716 		EntryType file_entry_type;
1717 
1718 		gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter,
1719 				    COLUMN_TYPE, &file_entry_type,
1720 				    -1);
1721 
1722 		if (file_entry_type != ENTRY_TYPE_LOADING)
1723 			valid_iter = gtk_tree_store_remove (folder_tree->priv->tree_store, &iter);
1724 		else
1725 			valid_iter = gtk_tree_model_iter_next (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter);
1726 	}
1727 	while (valid_iter);
1728 
1729 	emit_fake_motion_notify_event (folder_tree);
1730 }
1731 
1732 
1733 static gboolean
_gth_folder_tree_file_is_in_children(GthFolderTree * folder_tree,GtkTreeIter * parent_iter,GFile * file)1734 _gth_folder_tree_file_is_in_children (GthFolderTree *folder_tree,
1735 				      GtkTreeIter   *parent_iter,
1736 	 			      GFile         *file)
1737 {
1738 	GtkTreeIter iter;
1739 	gboolean    found = FALSE;
1740 
1741 	if (! gtk_tree_model_iter_children (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter, parent_iter))
1742 		return FALSE;
1743 
1744 	do {
1745 		GthFileData *test_file_data;
1746 		EntryType    file_entry_type;
1747 
1748 		gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter,
1749 				    COLUMN_FILE_DATA, &test_file_data,
1750 				    COLUMN_TYPE, &file_entry_type,
1751 				    -1);
1752 		if ((file_entry_type == ENTRY_TYPE_FILE) && (test_file_data != NULL) && g_file_equal (file, test_file_data->file))
1753 			found = TRUE;
1754 
1755 		_g_object_unref (test_file_data);
1756 	}
1757 	while (! found && gtk_tree_model_iter_next (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter));
1758 
1759 	return found;
1760 }
1761 
1762 
1763 void
gth_folder_tree_add_children(GthFolderTree * folder_tree,GFile * parent,GList * files)1764 gth_folder_tree_add_children (GthFolderTree *folder_tree,
1765 			      GFile         *parent,
1766 			      GList         *files)
1767 {
1768 	GtkTreeIter  parent_iter;
1769 	GtkTreeIter *p_parent_iter;
1770 	GList       *scan;
1771 
1772 	if (g_file_equal (parent, folder_tree->priv->root))
1773 		p_parent_iter = NULL;
1774 	else if (gth_folder_tree_get_iter (folder_tree, parent, &parent_iter, NULL))
1775 		p_parent_iter = &parent_iter;
1776 	else
1777 		return;
1778 
1779 	for (scan = files; scan; scan = scan->next) {
1780 		GthFileData *file_data = scan->data;
1781 
1782 		if (_gth_folder_tree_file_is_in_children (folder_tree, p_parent_iter, file_data->file))
1783 			continue;
1784 		_gth_folder_tree_add_file (folder_tree, p_parent_iter, file_data);
1785 	}
1786 
1787 	folder_tree->priv->recalc_entry_points = TRUE;
1788 }
1789 
1790 
1791 void
gth_folder_tree_update_children(GthFolderTree * folder_tree,GFile * parent,GList * files)1792 gth_folder_tree_update_children (GthFolderTree *folder_tree,
1793 				 GFile         *parent,
1794 				 GList         *files)
1795 {
1796 	GtkTreeIter  parent_iter;
1797 	GtkTreeIter *p_parent_iter;
1798 	GList       *scan;
1799 	GtkTreeIter  iter;
1800 
1801 	if (g_file_equal (parent, folder_tree->priv->root))
1802 		p_parent_iter = NULL;
1803 	else if (gth_folder_tree_get_iter (folder_tree, parent, &parent_iter, NULL))
1804 		p_parent_iter = &parent_iter;
1805 	else
1806 		return;
1807 
1808 	/* update each file if already present */
1809 
1810 	for (scan = files; scan; scan = scan->next) {
1811 		GthFileData *file_data = scan->data;
1812 
1813 		if (_gth_folder_tree_get_child (folder_tree, file_data->file, &iter, p_parent_iter))
1814 			_gth_folder_tree_set_file_data (folder_tree, &iter, file_data);
1815 	}
1816 }
1817 
1818 
1819 void
gth_folder_tree_update_child(GthFolderTree * folder_tree,GFile * old_file,GthFileData * file_data)1820 gth_folder_tree_update_child (GthFolderTree *folder_tree,
1821 			      GFile         *old_file,
1822 			      GthFileData   *file_data)
1823 {
1824 	GtkTreeIter old_file_iter;
1825 	GtkTreeIter new_file_iter;
1826 
1827 	if (! gth_folder_tree_get_iter (folder_tree, old_file, &old_file_iter, NULL))
1828 		return;
1829 
1830 	if (gth_folder_tree_get_iter (folder_tree, file_data->file, &new_file_iter, NULL)) {
1831 		if (! _g_file_equal (old_file, file_data->file)) {
1832 			GFile *parent;
1833 			GList *files;
1834 
1835 			/* the new file is already present, remove the old file */
1836 
1837 			parent = g_file_get_parent (old_file);
1838 			files = g_list_prepend (NULL, g_object_ref (old_file));
1839 			gth_folder_tree_delete_children (folder_tree, parent, files);
1840 			_g_object_list_unref (files);
1841 			g_object_unref (parent);
1842 		}
1843 
1844 		/* update the data of the new file */
1845 
1846 		_gth_folder_tree_set_file_data (folder_tree, &new_file_iter, file_data);
1847 	}
1848 	else
1849 		_gth_folder_tree_set_file_data (folder_tree, &old_file_iter, file_data);
1850 }
1851 
1852 
1853 void
gth_folder_tree_delete_children(GthFolderTree * folder_tree,GFile * parent,GList * files)1854 gth_folder_tree_delete_children (GthFolderTree *folder_tree,
1855 				 GFile         *parent,
1856 				 GList         *files)
1857 {
1858 	GtkTreeIter  parent_iter;
1859 	GtkTreeIter *p_parent_iter;
1860 	GList       *scan;
1861 
1862 	if (g_file_equal (parent, folder_tree->priv->root))
1863 		p_parent_iter = NULL;
1864 	else if (gth_folder_tree_get_iter (folder_tree, parent, &parent_iter, NULL))
1865 		p_parent_iter = &parent_iter;
1866 	else
1867 		return;
1868 
1869 	if (_gth_folder_tree_iter_get_no_child (folder_tree, p_parent_iter))
1870 		return;
1871 
1872 	/* add the empty item first to not allow the folder to collapse. */
1873 	_gth_folder_tree_add_empty_item (folder_tree, p_parent_iter);
1874 
1875 	for (scan = files; scan; scan = scan->next) {
1876 		GFile       *file = scan->data;
1877 		GtkTreeIter  iter;
1878 
1879 		if (_gth_folder_tree_get_child (folder_tree, file, &iter, p_parent_iter))
1880 			gtk_tree_store_remove (folder_tree->priv->tree_store, &iter);
1881 	}
1882 
1883 	_gth_folder_tree_remove_child_type (folder_tree, p_parent_iter, ENTRY_TYPE_EMPTY);
1884 
1885 	folder_tree->priv->recalc_entry_points = TRUE;
1886 }
1887 
1888 
1889 /* -- gth_folder_tree_start_editing -- */
1890 
1891 
1892 typedef struct {
1893 	GthFolderTree *folder_tree;
1894 	GFile         *file;
1895 } RenameData;
1896 
1897 
1898 static void
rename_data_free(RenameData * data)1899 rename_data_free (RenameData *data)
1900 {
1901 	g_object_unref (data->folder_tree);
1902 	g_object_unref (data->file);
1903 	g_free (data);
1904 }
1905 
1906 
1907 static void
rename_dialog_response_cb(GtkWidget * dialog,int response_id,gpointer user_data)1908 rename_dialog_response_cb (GtkWidget *dialog,
1909 			   int        response_id,
1910 			   gpointer   user_data)
1911 {
1912 	RenameData *data = user_data;
1913 	char       *name;
1914 
1915 	if (response_id != GTK_RESPONSE_OK) {
1916 		rename_data_free (data);
1917 		gtk_widget_destroy (dialog);
1918 		return;
1919 	}
1920 
1921 	name = gth_request_dialog_get_normalized_text (GTH_REQUEST_DIALOG (dialog));
1922 	if (_g_utf8_all_spaces (name)) {
1923 		g_free (name);
1924 		gth_request_dialog_set_info_text (GTH_REQUEST_DIALOG (dialog), GTK_MESSAGE_ERROR, _("No name specified"));
1925 		return;
1926 	}
1927 
1928 	if (g_regex_match_simple ("/", name, 0, 0)) {
1929 		char *message;
1930 
1931 		message = g_strdup_printf (_("Invalid name. The following characters are not allowed: %s"), "/");
1932 		gth_request_dialog_set_info_text (GTH_REQUEST_DIALOG (dialog), GTK_MESSAGE_ERROR, message);
1933 
1934 		g_free (message);
1935 		g_free (name);
1936 
1937 		return;
1938 	}
1939 
1940 	g_signal_emit (data->folder_tree,
1941 		       gth_folder_tree_signals[RENAME],
1942 		       0,
1943 		       data->file,
1944 		       name);
1945 
1946 	g_free (name);
1947 	rename_data_free (data);
1948 	gtk_widget_destroy (dialog);
1949 }
1950 
1951 
1952 void
gth_folder_tree_start_editing(GthFolderTree * folder_tree,GFile * file)1953 gth_folder_tree_start_editing (GthFolderTree *folder_tree,
1954 			       GFile         *file)
1955 {
1956 	GtkTreeIter  iter;
1957 	GthFileData *file_data;
1958 	RenameData  *data;
1959 	GtkWidget   *dialog;
1960 	const char  *edit_name;
1961 
1962 	if (! gth_folder_tree_get_iter (folder_tree, file, &iter, NULL))
1963 		return;
1964 
1965 	data = g_new0 (RenameData, 1);
1966 	data->folder_tree = g_object_ref (folder_tree);
1967 	data->file = g_object_ref (file);
1968 
1969 	dialog = gth_request_dialog_new (_gtk_widget_get_toplevel_if_window (GTK_WIDGET (folder_tree)),
1970 					 GTK_DIALOG_MODAL,
1971 					 _("Rename"),
1972 					 _("Enter the new name:"),
1973 					 _GTK_LABEL_CANCEL,
1974 					 _("_Rename"));
1975 	g_signal_connect (dialog,
1976 			  "response",
1977 			  G_CALLBACK (rename_dialog_response_cb),
1978 			  data);
1979 
1980 	gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store),
1981 			    &iter,
1982 			    COLUMN_FILE_DATA, &file_data,
1983 			    -1);
1984 	edit_name = g_file_info_get_edit_name (file_data->info);
1985 	if (edit_name == NULL)
1986 		edit_name = g_file_info_get_display_name (file_data->info);
1987 	if (edit_name != NULL)
1988 		gtk_entry_set_text (GTK_ENTRY (gth_request_dialog_get_entry (GTH_REQUEST_DIALOG (dialog))), edit_name);
1989 
1990 	gtk_widget_show (dialog);
1991 
1992 	_g_object_unref (file_data);
1993 
1994 #if 0
1995 	GtkTreeIter        iter;
1996 	GtkTreePath       *tree_path;
1997 	GtkTreeViewColumn *tree_column;
1998 
1999 	if (! gth_folder_tree_get_iter (folder_tree, file, &iter, NULL))
2000 		return;
2001 
2002 	g_object_set (folder_tree->priv->text_renderer,
2003 		      "editable", TRUE,
2004 		      NULL);
2005 
2006 	tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter);
2007 	tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (folder_tree), 0);
2008 	gtk_tree_view_set_cursor (GTK_TREE_VIEW (folder_tree),
2009 				  tree_path,
2010 				  tree_column,
2011 				  TRUE);
2012 
2013 	gtk_tree_path_free (tree_path);
2014 #endif
2015 }
2016 
2017 
2018 GtkTreePath *
gth_folder_tree_get_path(GthFolderTree * folder_tree,GFile * file)2019 gth_folder_tree_get_path (GthFolderTree *folder_tree,
2020 			  GFile         *file)
2021 {
2022 	GtkTreeIter iter;
2023 
2024 	if (! gth_folder_tree_get_iter (folder_tree, file, &iter, NULL))
2025 		return NULL;
2026 	else
2027 		return gtk_tree_model_get_path (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter);
2028 }
2029 
2030 
2031 gboolean
gth_folder_tree_is_loaded(GthFolderTree * folder_tree,GtkTreePath * path)2032 gth_folder_tree_is_loaded (GthFolderTree *folder_tree,
2033 			   GtkTreePath   *path)
2034 {
2035 	GtkTreeIter iter;
2036 	gboolean    loaded;
2037 
2038 	if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter, path))
2039 		return FALSE;
2040 
2041 	gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter,
2042 			    COLUMN_LOADED, &loaded,
2043 			    -1);
2044 
2045 	return loaded;
2046 }
2047 
2048 
2049 gboolean
gth_folder_tree_has_no_child(GthFolderTree * folder_tree,GtkTreePath * path)2050 gth_folder_tree_has_no_child (GthFolderTree *folder_tree,
2051 			      GtkTreePath   *path)
2052 {
2053 	GtkTreeIter iter;
2054 	gboolean    no_child;
2055 
2056 	if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter, path))
2057 		return FALSE;
2058 
2059 	gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter,
2060 			    COLUMN_NO_CHILD, &no_child,
2061 			    -1);
2062 
2063 	return no_child;
2064 }
2065 
2066 
2067 static void
_gth_folder_tree_reset_loaded(GthFolderTree * folder_tree,GtkTreeIter * root)2068 _gth_folder_tree_reset_loaded (GthFolderTree *folder_tree,
2069 			       GtkTreeIter   *root)
2070 {
2071 	GtkTreeModel *tree_model = GTK_TREE_MODEL (folder_tree->priv->tree_store);
2072 	GtkTreeIter   iter;
2073 
2074 	if (root != NULL)
2075 		gtk_tree_store_set (folder_tree->priv->tree_store, root,
2076 				    COLUMN_LOADED, FALSE,
2077 				    -1);
2078 
2079 	if (gtk_tree_model_iter_children (tree_model, &iter, root)) {
2080 		do {
2081 			_gth_folder_tree_reset_loaded (folder_tree, &iter);
2082 		}
2083 		while (gtk_tree_model_iter_next (tree_model, &iter));
2084 	}
2085 }
2086 
2087 
2088 void
gth_folder_tree_reset_loaded(GthFolderTree * folder_tree)2089 gth_folder_tree_reset_loaded (GthFolderTree *folder_tree)
2090 {
2091 	_gth_folder_tree_reset_loaded (folder_tree, NULL);
2092 }
2093 
2094 
2095 void
gth_folder_tree_expand_row(GthFolderTree * folder_tree,GtkTreePath * path,gboolean open_all)2096 gth_folder_tree_expand_row (GthFolderTree *folder_tree,
2097 			    GtkTreePath   *path,
2098 			    gboolean       open_all)
2099 {
2100 	g_signal_handlers_block_by_func (folder_tree, row_expanded_cb, folder_tree);
2101 	gtk_tree_view_expand_row (GTK_TREE_VIEW (folder_tree), path, open_all);
2102 	g_signal_handlers_unblock_by_func (folder_tree, row_expanded_cb, folder_tree);
2103 }
2104 
2105 
2106 void
gth_folder_tree_collapse_all(GthFolderTree * folder_tree)2107 gth_folder_tree_collapse_all (GthFolderTree *folder_tree)
2108 {
2109 	GtkTreeSelection *selection;
2110 
2111 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree));
2112 	g_signal_handlers_block_by_func (selection, selection_changed_cb, folder_tree);
2113 	gtk_tree_view_collapse_all (GTK_TREE_VIEW (folder_tree));
2114 	g_signal_handlers_unblock_by_func (selection, selection_changed_cb, folder_tree);
2115 }
2116 
2117 
2118 GthFileData *
gth_folder_tree_get_file(GthFolderTree * folder_tree,GtkTreePath * path)2119 gth_folder_tree_get_file (GthFolderTree *folder_tree,
2120 			  GtkTreePath   *path)
2121 {
2122 	GtkTreeModel *tree_model;
2123 	GtkTreeIter   iter;
2124 	EntryType     entry_type;
2125 	GthFileData  *file_data;
2126 
2127 	tree_model = GTK_TREE_MODEL (folder_tree->priv->tree_store);
2128 	if (! gtk_tree_model_get_iter (tree_model, &iter, path))
2129 			return NULL;
2130 
2131 	file_data = NULL;
2132 	gtk_tree_model_get (tree_model,
2133 			    &iter,
2134 			    COLUMN_TYPE, &entry_type,
2135 			    COLUMN_FILE_DATA, &file_data,
2136 			    -1);
2137 	if (entry_type != ENTRY_TYPE_FILE) {
2138 		_g_object_unref (file_data);
2139 		file_data = NULL;
2140 	}
2141 
2142 	return file_data;
2143 }
2144 
2145 
2146 void
gth_folder_tree_select_path(GthFolderTree * folder_tree,GtkTreePath * path)2147 gth_folder_tree_select_path (GthFolderTree *folder_tree,
2148 			     GtkTreePath   *path)
2149 {
2150 	GtkTreeSelection *selection;
2151 
2152 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree));
2153 	g_signal_handlers_block_by_func (selection, selection_changed_cb, folder_tree);
2154 	gtk_tree_selection_select_path (selection, path);
2155 	g_signal_handlers_unblock_by_func (selection, selection_changed_cb, folder_tree);
2156 }
2157 
2158 
2159 GFile *
gth_folder_tree_get_root(GthFolderTree * folder_tree)2160 gth_folder_tree_get_root (GthFolderTree *folder_tree)
2161 {
2162 	return folder_tree->priv->root;
2163 }
2164 
2165 
2166 gboolean
gth_folder_tree_is_root(GthFolderTree * folder_tree,GFile * folder)2167 gth_folder_tree_is_root (GthFolderTree *folder_tree,
2168 			 GFile         *folder)
2169 {
2170 	return _g_file_equal (folder_tree->priv->root, folder);
2171 }
2172 
2173 
2174 GthFileData *
gth_folder_tree_get_selected(GthFolderTree * folder_tree)2175 gth_folder_tree_get_selected (GthFolderTree *folder_tree)
2176 {
2177 	GtkTreeSelection *selection;
2178 	GtkTreeModel     *tree_model;
2179 	GtkTreeIter       iter;
2180 	EntryType         entry_type;
2181 	GthFileData      *file_data;
2182 
2183 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree));
2184 	if (selection == NULL)
2185 		return NULL;
2186 
2187 	tree_model = GTK_TREE_MODEL (folder_tree->priv->tree_store);
2188 	if (! gtk_tree_selection_get_selected (selection, &tree_model, &iter))
2189 		return NULL;
2190 
2191 	gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store),
2192 			    &iter,
2193 			    COLUMN_TYPE, &entry_type,
2194 			    COLUMN_FILE_DATA, &file_data,
2195 			    -1);
2196 
2197 	if (entry_type != ENTRY_TYPE_FILE) {
2198 		_g_object_unref (file_data);
2199 		file_data = NULL;
2200 	}
2201 
2202 	return file_data;
2203 }
2204 
2205 
2206 GthFileData *
gth_folder_tree_get_selected_or_parent(GthFolderTree * folder_tree)2207 gth_folder_tree_get_selected_or_parent (GthFolderTree *folder_tree)
2208 {
2209 	GtkTreeSelection *selection;
2210 	GtkTreeModel     *tree_model;
2211 	GtkTreeIter       iter;
2212 	GtkTreeIter       parent;
2213 	EntryType         entry_type;
2214 	GthFileData      *file_data;
2215 
2216 	file_data = gth_folder_tree_get_selected (folder_tree);
2217 	if (file_data != NULL)
2218 		return file_data;
2219 
2220 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree));
2221 	if (selection == NULL)
2222 		return NULL;
2223 
2224 	tree_model = GTK_TREE_MODEL (folder_tree->priv->tree_store);
2225 	if (! gtk_tree_selection_get_selected (selection, &tree_model, &iter))
2226 		return NULL;
2227 
2228 	if (! gtk_tree_model_iter_parent (tree_model, &parent, &iter))
2229 		return NULL;
2230 
2231 	gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store),
2232 			    &parent,
2233 			    COLUMN_TYPE, &entry_type,
2234 			    COLUMN_FILE_DATA, &file_data,
2235 			    -1);
2236 
2237 	if (entry_type != ENTRY_TYPE_FILE) {
2238 		_g_object_unref (file_data);
2239 		file_data = NULL;
2240 	}
2241 
2242 	return file_data;
2243 }
2244 
2245 
2246 void
gth_folder_tree_enable_drag_source(GthFolderTree * self,GdkModifierType start_button_mask,const GtkTargetEntry * targets,int n_targets,GdkDragAction actions)2247 gth_folder_tree_enable_drag_source (GthFolderTree        *self,
2248 				    GdkModifierType       start_button_mask,
2249 				    const GtkTargetEntry *targets,
2250 				    int                   n_targets,
2251 				    GdkDragAction         actions)
2252 {
2253 	if (self->priv->drag_target_list != NULL)
2254 		gtk_target_list_unref (self->priv->drag_target_list);
2255 
2256 	self->priv->drag_source_enabled = TRUE;
2257 	self->priv->drag_start_button_mask = start_button_mask;
2258 	self->priv->drag_target_list = gtk_target_list_new (targets, n_targets);
2259 	self->priv->drag_actions = actions;
2260 }
2261 
2262 
2263 void
gth_folder_tree_unset_drag_source(GthFolderTree * self)2264 gth_folder_tree_unset_drag_source (GthFolderTree *self)
2265 {
2266 	self->priv->drag_source_enabled = FALSE;
2267 }
2268