1 /*
2  * This program is free software; you can redistribute it and/or modify it
3  * under the terms of the GNU Lesser General Public License as published by
4  * the Free Software Foundation.
5  *
6  * This program is distributed in the hope that it will be useful, but
7  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
9  * for more details.
10  *
11  * You should have received a copy of the GNU Lesser General Public License
12  * along with this program; if not, see <http://www.gnu.org/licenses/>.
13  *
14  *
15  * Authors:
16  *		Jeffrey Stedfast <fejj@ximian.com>
17  *
18  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
19  *
20  */
21 
22 #include "evolution-config.h"
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <errno.h>
32 
33 #include <libxml/tree.h>
34 
35 #include <glib/gi18n.h>
36 #include <gdk/gdkkeysyms.h>
37 
38 #include "em-vfolder-editor-rule.h"
39 
40 #include "em-utils.h"
41 #include "em-folder-utils.h"
42 #include "em-folder-selector.h"
43 #include "em-folder-properties.h"
44 #include "em-event.h"
45 #include "mail-send-recv.h"
46 #include "mail-vfolder-ui.h"
47 
48 #include "e-mail-ui-session.h"
49 
50 #include "em-folder-tree.h"
51 
52 #define d(x)
53 
54 #define EM_FOLDER_TREE_GET_PRIVATE(obj) \
55 	(G_TYPE_INSTANCE_GET_PRIVATE \
56 	((obj), EM_TYPE_FOLDER_TREE, EMFolderTreePrivate))
57 
58 typedef struct _AsyncContext AsyncContext;
59 
60 #define EM_FOLDER_TREE_GET_PRIVATE(obj) \
61 	(G_TYPE_INSTANCE_GET_PRIVATE \
62 	((obj), EM_TYPE_FOLDER_TREE, EMFolderTreePrivate))
63 
64 struct _selected_uri {
65 	gchar *key;		/* store:path or account/path */
66 	gchar *uri;
67 	CamelService *service;
68 	gchar *path;
69 };
70 
71 struct _EMFolderTreePrivate {
72 	EMailSession *session;
73 	EAlertSink *alert_sink;
74 
75 	/* selected_uri structures of each path pending selection. */
76 	GSList *select_uris;
77 
78 	/* Removed as they're encountered, so use this
79 	 * to find URI's not presnet but selected. */
80 	GHashTable *select_uris_table;
81 
82 	guint32 excluded;
83 	gboolean	(*excluded_func)	(EMFolderTree *folder_tree,
84 						 GtkTreeModel *model,
85 						 GtkTreeIter *iter,
86 						 gpointer data);
87 	gpointer excluded_data;
88 
89 	guint cursor_set:1;	/* set to TRUE means we or something
90 				 * else has set the cursor, otherwise
91 				 * we need to set it when we set the
92 				 * selection */
93 
94 	guint autoscroll_id;
95 	guint autoexpand_id;
96 	GtkTreeRowReference *autoexpand_row;
97 
98 	guint loaded_row_id;
99 	guint row_changed_id;
100 
101 	GtkTreeRowReference *drag_row;
102 	gboolean skip_double_click;
103 
104 	GtkCellRenderer *text_renderer;
105 
106 	GtkWidget *selectable; /* an ESelectable, where to pass selectable calls */
107 
108 	gchar *select_store_uid_when_added;
109 
110 	/* Signal handler IDs */
111 	gulong selection_changed_handler_id;
112 };
113 
114 struct _AsyncContext {
115 	EActivity *activity;
116 	EMFolderTree *folder_tree;
117 	GtkTreeRowReference *root;
118 	gchar *full_name;
119 };
120 
121 enum {
122 	PROP_0,
123 	PROP_ALERT_SINK,
124 	PROP_COPY_TARGET_LIST,
125 	PROP_MODEL,
126 	PROP_PASTE_TARGET_LIST,
127 	PROP_SESSION
128 };
129 
130 enum {
131 	FOLDER_ACTIVATED,  /* aka double-clicked or user hit enter */
132 	FOLDER_SELECTED,
133 	FOLDER_RENAMED,
134 	POPUP_EVENT,
135 	HIDDEN_KEY_EVENT,
136 	LAST_SIGNAL
137 };
138 
139 /* Drag & Drop types */
140 enum DndDragType {
141 	DND_DRAG_TYPE_FOLDER,          /* drag an evo folder */
142 	DND_DRAG_TYPE_TEXT_URI_LIST,   /* drag to an mbox file */
143 	NUM_DRAG_TYPES
144 };
145 
146 enum DndDropType {
147 	DND_DROP_TYPE_UID_LIST,        /* drop a list of message uids */
148 	DND_DROP_TYPE_FOLDER,          /* drop an evo folder */
149 	DND_DROP_TYPE_MESSAGE_RFC822,  /* drop a message/rfc822 stream */
150 	DND_DROP_TYPE_TEXT_URI_LIST,   /* drop an mbox file */
151 	NUM_DROP_TYPES
152 };
153 
154 static GtkTargetEntry drag_types[] = {
155 	{ (gchar *) "x-folder",         0, DND_DRAG_TYPE_FOLDER         },
156 	{ (gchar *) "text/uri-list",    0, DND_DRAG_TYPE_TEXT_URI_LIST  },
157 };
158 
159 static GtkTargetEntry drop_types[] = {
160 	{ (gchar *) "x-uid-list" ,      0, DND_DROP_TYPE_UID_LIST       },
161 	{ (gchar *) "x-folder",         0, DND_DROP_TYPE_FOLDER         },
162 	{ (gchar *) "message/rfc822",   0, DND_DROP_TYPE_MESSAGE_RFC822 },
163 	{ (gchar *) "text/uri-list",    0, DND_DROP_TYPE_TEXT_URI_LIST  },
164 };
165 
166 static GdkAtom drag_atoms[NUM_DRAG_TYPES];
167 static GdkAtom drop_atoms[NUM_DROP_TYPES];
168 
169 static guint signals[LAST_SIGNAL] = { 0 };
170 
171 struct _folder_tree_selection_data {
172 	GtkTreeModel *model;
173 	GtkTreeIter *iter;
174 	gboolean set;
175 };
176 
177 /* Forward Declarations */
178 static void em_folder_tree_selectable_init (ESelectableInterface *iface);
179 
G_DEFINE_TYPE_WITH_CODE(EMFolderTree,em_folder_tree,GTK_TYPE_TREE_VIEW,G_IMPLEMENT_INTERFACE (E_TYPE_SELECTABLE,em_folder_tree_selectable_init))180 G_DEFINE_TYPE_WITH_CODE (
181 	EMFolderTree,
182 	em_folder_tree,
183 	GTK_TYPE_TREE_VIEW,
184 	G_IMPLEMENT_INTERFACE (
185 		E_TYPE_SELECTABLE,
186 		em_folder_tree_selectable_init))
187 
188 static void
189 async_context_free (AsyncContext *context)
190 {
191 	if (context->activity != NULL)
192 		g_object_unref (context->activity);
193 
194 	if (context->folder_tree != NULL)
195 		g_object_unref (context->folder_tree);
196 
197 	gtk_tree_row_reference_free (context->root);
198 
199 	g_free (context->full_name);
200 
201 	g_slice_free (AsyncContext, context);
202 }
203 
204 static void
folder_tree_get_folder_info_cb(CamelStore * store,GAsyncResult * result,AsyncContext * context)205 folder_tree_get_folder_info_cb (CamelStore *store,
206                                 GAsyncResult *result,
207                                 AsyncContext *context)
208 {
209 	CamelFolderInfo *folder_info;
210 	CamelFolderInfo *child_info;
211 	EAlertSink *alert_sink;
212 	GtkTreeView *tree_view;
213 	GtkTreeModel *model;
214 	GtkTreePath *path;
215 	GtkTreeIter root;
216 	GtkTreeIter iter;
217 	GtkTreeIter titer;
218 	gboolean is_store;
219 	gboolean iter_is_placeholder;
220 	gboolean valid;
221 	GError *error = NULL;
222 
223 	alert_sink = e_activity_get_alert_sink (context->activity);
224 
225 	folder_info = camel_store_get_folder_info_finish (
226 		store, result, &error);
227 
228 	tree_view = GTK_TREE_VIEW (context->folder_tree);
229 	model = gtk_tree_view_get_model (tree_view);
230 
231 	/* Check if our parent folder has been deleted/unsubscribed. */
232 	if (!gtk_tree_row_reference_valid (context->root)) {
233 		g_clear_error (&error);
234 		goto exit;
235 	}
236 
237 	path = gtk_tree_row_reference_get_path (context->root);
238 	valid = gtk_tree_model_get_iter (model, &root, path);
239 	g_return_if_fail (valid);
240 
241 	gtk_tree_model_get (model, &root, COL_BOOL_IS_STORE, &is_store, -1);
242 
243 	/* If we had an error, then we need to re-set the
244 	 * load subdirs state and collapse the node. */
245 	if (error != NULL) {
246 		gtk_tree_store_set (
247 			GTK_TREE_STORE (model), &root,
248 			COL_BOOL_LOAD_SUBDIRS, TRUE, -1);
249 		gtk_tree_view_collapse_row (tree_view, path);
250 	}
251 
252 	gtk_tree_path_free (path);
253 
254 	if (e_activity_handle_cancellation (context->activity, error)) {
255 		g_warn_if_fail (folder_info == NULL);
256 		async_context_free (context);
257 		g_error_free (error);
258 		return;
259 
260 	/* XXX POP3 stores always return a "no folder" error because they
261 	 *     have no folder hierarchy to scan.  Just ignore the error. */
262 	} else if (g_error_matches (
263 			error, CAMEL_STORE_ERROR,
264 			CAMEL_STORE_ERROR_NO_FOLDER)) {
265 		g_warn_if_fail (folder_info == NULL);
266 		async_context_free (context);
267 		g_error_free (error);
268 		return;
269 
270 	} else if (error != NULL) {
271 		g_warn_if_fail (folder_info == NULL);
272 		e_alert_submit (
273 			alert_sink, "mail:folder-open",
274 			error->message, NULL);
275 		async_context_free (context);
276 		g_error_free (error);
277 		return;
278 	}
279 
280 	/* If we've just set up an NNTP account, for example, and haven't
281 	 * subscribed to any folders yet, folder_info may legitimately be
282 	 * NULL at this point.  We handle that case below.  Proceed. */
283 
284 	/* Make sure we still need to load the tree subfolders. */
285 
286 	iter_is_placeholder = FALSE;
287 
288 	/* Get the first child (which will be a placeholder row). */
289 	valid = gtk_tree_model_iter_children (model, &iter, &root);
290 
291 	/* Traverse to the last valid iter, or the placeholder row. */
292 	while (valid) {
293 		gboolean is_store_node = FALSE;
294 		gboolean is_folder_node = FALSE;
295 
296 		titer = iter; /* Preserve the last valid iter */
297 
298 		gtk_tree_model_get (
299 			model, &iter,
300 			COL_BOOL_IS_STORE, &is_store_node,
301 			COL_BOOL_IS_FOLDER, &is_folder_node, -1);
302 
303 		/* Stop on a "Loading..." placeholder row. */
304 		if (!is_store_node && !is_folder_node) {
305 			iter_is_placeholder = TRUE;
306 			break;
307 		}
308 
309 		valid = gtk_tree_model_iter_next (model, &iter);
310 	}
311 
312 	iter = titer;
313 	child_info = folder_info;
314 
315 	/* FIXME Camel's IMAP code is totally on crack here: the
316 	 *       folder_info we got back should be for the folder
317 	 *       we're expanding, and folder_info->child should be
318 	 *       what we want to fill our tree with... *sigh* */
319 	if (folder_info != NULL) {
320 		gboolean names_match;
321 
322 		names_match = (g_strcmp0 (
323 			folder_info->full_name,
324 			context->full_name) == 0);
325 		if (names_match) {
326 			child_info = folder_info->child;
327 			if (child_info == NULL)
328 				child_info = folder_info->next;
329 		}
330 	}
331 
332 	if (is_store)
333 		em_folder_tree_model_mark_store_loaded (EM_FOLDER_TREE_MODEL (model), store);
334 
335 	/* The folder being expanded has no children after all.  Remove
336 	 * the "Loading..." placeholder row and collapse the parent. */
337 	if (child_info == NULL) {
338 		if (iter_is_placeholder)
339 			gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
340 
341 		if (is_store) {
342 			path = gtk_tree_model_get_path (model, &root);
343 			gtk_tree_view_collapse_row (tree_view, path);
344 			gtk_tree_path_free (path);
345 			goto exit;
346 		}
347 
348 	} else {
349 		while (child_info != NULL) {
350 			GtkTreeRowReference *reference;
351 
352 			/* Check if we already have this row cached. */
353 			reference = em_folder_tree_model_get_row_reference (
354 				EM_FOLDER_TREE_MODEL (model),
355 				store, child_info->full_name);
356 
357 			if (reference == NULL) {
358 				/* If we're on a placeholder row, reuse
359 				 * the row for the first child folder. */
360 				if (iter_is_placeholder)
361 					iter_is_placeholder = FALSE;
362 				else
363 					gtk_tree_store_append (
364 						GTK_TREE_STORE (model),
365 						&iter, &root);
366 
367 				if (!em_folder_tree_model_set_folder_info (EM_FOLDER_TREE_MODEL (model), &iter, store, child_info, TRUE))
368 					gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
369 			}
370 
371 			child_info = child_info->next;
372 		}
373 
374 		/* Remove the "Loading..." placeholder row. */
375 		if (iter_is_placeholder)
376 			gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
377 	}
378 
379 	gtk_tree_store_set (
380 		GTK_TREE_STORE (model), &root,
381 		COL_BOOL_LOAD_SUBDIRS, FALSE, -1);
382 
383 exit:
384 	camel_folder_info_free (folder_info);
385 
386 	async_context_free (context);
387 }
388 
389 static void
folder_tree_emit_popup_event(EMFolderTree * folder_tree,GdkEvent * event)390 folder_tree_emit_popup_event (EMFolderTree *folder_tree,
391                               GdkEvent *event)
392 {
393 	g_signal_emit (folder_tree, signals[POPUP_EVENT], 0, event);
394 }
395 
396 static void
folder_tree_free_select_uri(struct _selected_uri * u)397 folder_tree_free_select_uri (struct _selected_uri *u)
398 {
399 	g_free (u->uri);
400 	if (u->service)
401 		g_object_unref (u->service);
402 	g_free (u->key);
403 	g_free (u->path);
404 	g_free (u);
405 }
406 
407 static gboolean
folder_tree_select_func(GtkTreeSelection * selection,GtkTreeModel * model,GtkTreePath * path,gboolean selected)408 folder_tree_select_func (GtkTreeSelection *selection,
409                          GtkTreeModel *model,
410                          GtkTreePath *path,
411                          gboolean selected)
412 {
413 	EMFolderTreePrivate *priv;
414 	GtkTreeView *tree_view;
415 	gboolean is_store;
416 	guint32 flags;
417 	GtkTreeIter iter;
418 
419 	tree_view = gtk_tree_selection_get_tree_view (selection);
420 
421 	priv = EM_FOLDER_TREE_GET_PRIVATE (tree_view);
422 
423 	if (selected)
424 		return TRUE;
425 
426 	if (priv->excluded == 0 && priv->excluded_func == NULL)
427 		return TRUE;
428 
429 	if (!gtk_tree_model_get_iter (model, &iter, path))
430 		return TRUE;
431 
432 	if (priv->excluded_func != NULL)
433 		return priv->excluded_func (
434 			EM_FOLDER_TREE (tree_view), model,
435 			&iter, priv->excluded_data);
436 
437 	gtk_tree_model_get (
438 		model, &iter, COL_UINT_FLAGS, &flags,
439 		COL_BOOL_IS_STORE, &is_store, -1);
440 
441 	if (is_store)
442 		flags |= CAMEL_FOLDER_NOSELECT;
443 
444 	return (flags & priv->excluded) == 0;
445 }
446 
447 /* NOTE: Removes and frees the selected uri structure */
448 static void
folder_tree_select_uri(EMFolderTree * folder_tree,GtkTreePath * path,struct _selected_uri * u)449 folder_tree_select_uri (EMFolderTree *folder_tree,
450                         GtkTreePath *path,
451                         struct _selected_uri *u)
452 {
453 	EMFolderTreePrivate *priv = folder_tree->priv;
454 	GtkTreeView *tree_view;
455 	GtkTreeSelection *selection;
456 
457 	tree_view = GTK_TREE_VIEW (folder_tree);
458 	selection = gtk_tree_view_get_selection (tree_view);
459 	gtk_tree_selection_select_path (selection, path);
460 	if (!priv->cursor_set) {
461 		gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
462 		priv->cursor_set = TRUE;
463 	}
464 	gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.8f, 0.0f);
465 	g_hash_table_remove (priv->select_uris_table, u->key);
466 	priv->select_uris = g_slist_remove (priv->select_uris, u);
467 	folder_tree_free_select_uri (u);
468 }
469 
470 static void
folder_tree_expand_node(const gchar * key,EMFolderTree * folder_tree)471 folder_tree_expand_node (const gchar *key,
472                          EMFolderTree *folder_tree)
473 {
474 	GtkTreeRowReference *row = NULL;
475 	GtkTreeView *tree_view;
476 	GtkTreeModel *model;
477 	GtkTreePath *path;
478 	EMailSession *session;
479 	CamelService *service;
480 	const gchar *p;
481 	gchar *uid;
482 	gsize n;
483 	struct _selected_uri *u;
484 
485 	if (!(p = strchr (key, '/')))
486 		n = strlen (key);
487 	else
488 		n = (p - key);
489 
490 	uid = g_alloca (n + 1);
491 	memcpy (uid, key, n);
492 	uid[n] = '\0';
493 
494 	tree_view = GTK_TREE_VIEW (folder_tree);
495 	model = gtk_tree_view_get_model (tree_view);
496 
497 	session = em_folder_tree_get_session (folder_tree);
498 
499 	service = camel_session_ref_service (CAMEL_SESSION (session), uid);
500 
501 	if (CAMEL_IS_STORE (service)) {
502 		const gchar *folder_name;
503 
504 		if (p != NULL && p[1] != '\0')
505 			folder_name = p + 1;
506 		else
507 			folder_name = NULL;
508 
509 		row = em_folder_tree_model_get_row_reference (
510 			EM_FOLDER_TREE_MODEL (model),
511 			CAMEL_STORE (service), folder_name);
512 	}
513 
514 	g_clear_object (&service);
515 
516 	if (row == NULL)
517 		return;
518 
519 	path = gtk_tree_row_reference_get_path (row);
520 	gtk_tree_view_expand_to_path (tree_view, path);
521 
522 	u = g_hash_table_lookup (folder_tree->priv->select_uris_table, key);
523 	if (u)
524 		folder_tree_select_uri (folder_tree, path, u);
525 
526 	gtk_tree_path_free (path);
527 }
528 
529 static void
folder_tree_maybe_expand_row(EMFolderTreeModel * model,GtkTreePath * tree_path,GtkTreeIter * iter,EMFolderTree * folder_tree)530 folder_tree_maybe_expand_row (EMFolderTreeModel *model,
531                               GtkTreePath *tree_path,
532                               GtkTreeIter *iter,
533                               EMFolderTree *folder_tree)
534 {
535 	EMFolderTreePrivate *priv = folder_tree->priv;
536 	CamelStore *store;
537 	gchar *full_name;
538 	gchar *key;
539 	const gchar *uid;
540 	struct _selected_uri *u;
541 
542 	gtk_tree_model_get (
543 		GTK_TREE_MODEL (model), iter,
544 		COL_STRING_FULL_NAME, &full_name,
545 		COL_OBJECT_CAMEL_STORE, &store, -1);
546 
547 	uid = camel_service_get_uid (CAMEL_SERVICE (store));
548 	key = g_strdup_printf ("%s/%s", uid, full_name ? full_name : "");
549 	g_object_unref (store);
550 
551 	u = g_hash_table_lookup (priv->select_uris_table, key);
552 	if (u) {
553 		gchar *c = strrchr (key, '/');
554 
555 		/* 'c' cannot be NULL, because the above constructed 'key' has it there */
556 		/* coverity[dereference] */
557 		*c = '\0';
558 		folder_tree_expand_node (key, folder_tree);
559 
560 		folder_tree_select_uri (folder_tree, tree_path, u);
561 	}
562 
563 	g_free (full_name);
564 	g_free (key);
565 }
566 
567 static void
folder_tree_row_changed_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,EMFolderTree * folder_tree)568 folder_tree_row_changed_cb (GtkTreeModel *model,
569                             GtkTreePath *path,
570                             GtkTreeIter *iter,
571                             EMFolderTree *folder_tree)
572 {
573 	CamelService *service = NULL;
574 	gboolean is_store = FALSE;
575 	gboolean select_store = FALSE;
576 
577 	g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
578 
579 	if (!folder_tree->priv->select_store_uid_when_added)
580 		return;
581 
582 	if (gtk_tree_path_get_depth (path) != 1)
583 		return;
584 
585 	gtk_tree_model_get (model, iter,
586 		COL_OBJECT_CAMEL_STORE, &service,
587 		COL_BOOL_IS_STORE, &is_store,
588 		-1);
589 
590 	if (is_store && service != NULL) {
591 		const gchar *uid1;
592 		const gchar *uid2;
593 
594 		uid1 = camel_service_get_uid (service);
595 		uid2 = folder_tree->priv->select_store_uid_when_added;
596 		select_store = (g_strcmp0 (uid1, uid2) == 0);
597 	}
598 
599 	if (select_store) {
600 		GtkTreeView *tree_view;
601 		GtkTreeSelection *selection;
602 
603 		g_free (folder_tree->priv->select_store_uid_when_added);
604 		folder_tree->priv->select_store_uid_when_added = NULL;
605 
606 		tree_view = GTK_TREE_VIEW (folder_tree);
607 		selection = gtk_tree_view_get_selection (tree_view);
608 
609 		gtk_tree_selection_select_iter (selection, iter);
610 		gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
611 		folder_tree->priv->cursor_set = TRUE;
612 		gtk_tree_view_expand_row (tree_view, path, FALSE);
613 	}
614 
615 	g_clear_object (&service);
616 }
617 
618 static void
folder_tree_clear_selected_list(EMFolderTree * folder_tree)619 folder_tree_clear_selected_list (EMFolderTree *folder_tree)
620 {
621 	EMFolderTreePrivate *priv = folder_tree->priv;
622 
623 	g_slist_free_full (
624 		priv->select_uris,
625 		(GDestroyNotify) folder_tree_free_select_uri);
626 	g_hash_table_destroy (priv->select_uris_table);
627 	priv->select_uris = NULL;
628 	priv->select_uris_table = g_hash_table_new (g_str_hash, g_str_equal);
629 	priv->cursor_set = FALSE;
630 }
631 
632 static void
folder_tree_cell_edited_cb(EMFolderTree * folder_tree,const gchar * path_string,const gchar * new_name)633 folder_tree_cell_edited_cb (EMFolderTree *folder_tree,
634                             const gchar *path_string,
635                             const gchar *new_name)
636 {
637 	CamelFolderInfo *folder_info;
638 	CamelStore *store;
639 	GtkTreeView *tree_view;
640 	GtkTreeModel *model;
641 	GtkTreePath *path;
642 	GtkTreeIter iter;
643 	gchar *old_name = NULL;
644 	gchar *old_full_name = NULL;
645 	gchar *new_full_name = NULL;
646 	gchar *folder_uri;
647 	gchar **strv;
648 	gpointer parent;
649 	guint index;
650 	GError *local_error = NULL;
651 
652 	/* XXX Consider splitting this into separate async functions:
653 	 *     em_folder_tree_rename_folder_async()
654 	 *     em_folder_tree_rename_folder_finish() */
655 
656 	parent = gtk_widget_get_toplevel (GTK_WIDGET (folder_tree));
657 	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
658 
659 	tree_view = GTK_TREE_VIEW (folder_tree);
660 	model = gtk_tree_view_get_model (tree_view);
661 	path = gtk_tree_path_new_from_string (path_string);
662 	gtk_tree_model_get_iter (model, &iter, path);
663 	gtk_tree_path_free (path);
664 
665 	gtk_tree_model_get (
666 		model, &iter,
667 		COL_OBJECT_CAMEL_STORE, &store,
668 		COL_STRING_DISPLAY_NAME, &old_name,
669 		COL_STRING_FULL_NAME, &old_full_name, -1);
670 
671 	if (old_name == NULL)
672 		goto exit;
673 
674 	if (old_full_name == NULL)
675 		goto exit;
676 
677 	if (g_strcmp0 (new_name, old_name) == 0)
678 		goto exit;
679 
680 	/* Check for invalid characters. */
681 	if (strchr (new_name, '/') != NULL) {
682 		e_alert_run_dialog_for_args (
683 			parent, "mail:no-rename-folder",
684 			old_name, new_name,
685 			_("Folder names cannot contain “/”"), NULL);
686 		goto exit;
687 	}
688 
689 	/* Build the new name from the old name. */
690 	strv = g_strsplit_set (old_full_name, "/", 0);
691 	index = g_strv_length (strv) - 1;
692 	g_free (strv[index]);
693 	strv[index] = g_strdup (new_name);
694 	new_full_name = g_strjoinv ("/", strv);
695 	g_strfreev (strv);
696 
697 	/* Check for duplicate folder name. */
698 	/* FIXME camel_store_get_folder_info() may block. */
699 	folder_info = camel_store_get_folder_info_sync (
700 		store, new_full_name,
701 		CAMEL_STORE_FOLDER_INFO_FAST, NULL, NULL);
702 	if (folder_info != NULL) {
703 		e_alert_run_dialog_for_args (
704 			parent, "mail:no-rename-folder-exists",
705 			old_name, new_name, NULL);
706 		camel_folder_info_free (folder_info);
707 		goto exit;
708 	}
709 
710 	/* FIXME camel_store_rename_folder_sync() may block. */
711 	camel_store_rename_folder_sync (
712 		store, old_full_name, new_full_name, NULL, &local_error);
713 
714 	if (local_error != NULL) {
715 		e_alert_run_dialog_for_args (
716 			parent, "mail:no-rename-folder",
717 			old_full_name, new_full_name,
718 			local_error->message, NULL);
719 		g_error_free (local_error);
720 		goto exit;
721 	}
722 
723 	folder_uri = e_mail_folder_uri_build (store, new_full_name);
724 	em_folder_tree_set_selected (folder_tree, folder_uri, FALSE);
725 	g_free (folder_uri);
726 
727  exit:
728 	g_signal_emit (folder_tree, signals[FOLDER_RENAMED], 0, FALSE, NULL);
729 
730 	g_free (old_name);
731 	g_free (old_full_name);
732 	g_free (new_full_name);
733 	g_clear_object (&store);
734 }
735 
736 static void
folder_tree_editing_canceled_cb(EMFolderTree * folder_tree)737 folder_tree_editing_canceled_cb (EMFolderTree *folder_tree)
738 {
739 	g_signal_emit (folder_tree, signals[FOLDER_RENAMED], 0, TRUE, NULL);
740 }
741 
742 static void
folder_tree_render_store_icon(GtkTreeViewColumn * column,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer text_renderer)743 folder_tree_render_store_icon (GtkTreeViewColumn *column,
744 			       GtkCellRenderer *renderer,
745 			       GtkTreeModel *model,
746 			       GtkTreeIter *iter,
747 			       gpointer text_renderer)
748 {
749 	GtkTreeIter parent;
750 	gboolean expanded = TRUE, children_has_unread_mismatch = FALSE;
751 
752 	/* The first prerequisite: it's a root node and has children. */
753 	if (gtk_tree_model_iter_parent (model, &parent, iter) ||
754 	    !gtk_tree_model_iter_has_child (model, iter)) {
755 		g_object_set (renderer, "visible", FALSE, NULL);
756 		return;
757 	}
758 
759 	g_object_get (text_renderer, "is-expanded", &expanded, NULL);
760 
761 	/* The second prerequisite: it's not expanded and children has unread mismatch. */
762 	if (!expanded) {
763 		guint unread, unread_last_sel;
764 
765 		gtk_tree_model_get (model, iter,
766 			COL_UINT_UNREAD, &unread,
767 			COL_UINT_UNREAD_LAST_SEL, &unread_last_sel,
768 			-1);
769 
770 		children_has_unread_mismatch = unread != unread_last_sel;
771 	}
772 
773 	g_object_set (renderer, "visible", !expanded && children_has_unread_mismatch, NULL);
774 }
775 
776 static void
folder_tree_reset_store_unread_value_cb(GtkTreeView * tree_view,GtkTreeIter * iter,GtkTreePath * path,gpointer user_data)777 folder_tree_reset_store_unread_value_cb (GtkTreeView *tree_view,
778 					 GtkTreeIter *iter,
779 					 GtkTreePath *path,
780 					 gpointer user_data)
781 {
782 	GtkTreeIter parent;
783 	GtkTreeModel *model;
784 
785 	g_return_if_fail (GTK_IS_TREE_VIEW (tree_view));
786 
787 	model = gtk_tree_view_get_model (tree_view);
788 	if (!model)
789 		return;
790 
791 	if (!gtk_tree_model_iter_parent (model, &parent, iter)) {
792 		gtk_tree_store_set (GTK_TREE_STORE (model), iter,
793 			COL_UINT_UNREAD_LAST_SEL, 0,
794 			COL_UINT_UNREAD, 0,
795 			-1);
796 	}
797 }
798 
799 static gboolean
subdirs_contain_unread(GtkTreeModel * model,GtkTreeIter * root)800 subdirs_contain_unread (GtkTreeModel *model,
801                         GtkTreeIter *root)
802 {
803 	guint unread;
804 	GtkTreeIter iter;
805 
806 	if (!gtk_tree_model_iter_children (model, &iter, root))
807 		return FALSE;
808 
809 	do {
810 		gtk_tree_model_get (model, &iter, COL_UINT_UNREAD, &unread, -1);
811 		if (unread)
812 			return TRUE;
813 
814 		if (gtk_tree_model_iter_has_child (model, &iter))
815 			if (subdirs_contain_unread (model, &iter))
816 				return TRUE;
817 	} while (gtk_tree_model_iter_next (model, &iter));
818 
819 	return FALSE;
820 }
821 
822 static void
folder_tree_render_display_name(GtkTreeViewColumn * column,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter)823 folder_tree_render_display_name (GtkTreeViewColumn *column,
824                                  GtkCellRenderer *renderer,
825                                  GtkTreeModel *model,
826                                  GtkTreeIter *iter)
827 {
828 	CamelService *service;
829 	PangoWeight weight;
830 	gboolean is_store, bold, subdirs_unread = FALSE;
831 	gboolean editable;
832 	guint unread;
833 	gchar *name;
834 
835 	gtk_tree_model_get (
836 		model, iter,
837 		COL_STRING_DISPLAY_NAME, &name,
838 		COL_OBJECT_CAMEL_STORE, &service,
839 		COL_BOOL_IS_STORE, &is_store,
840 		COL_UINT_UNREAD, &unread, -1);
841 
842 	g_object_get (renderer, "editable", &editable, NULL);
843 
844 	bold = is_store || unread;
845 
846 	if (gtk_tree_model_iter_has_child (model, iter)) {
847 		gboolean expanded = TRUE;
848 
849 		g_object_get (renderer, "is-expanded", &expanded, NULL);
850 
851 		if (!bold || !expanded)
852 			subdirs_unread = subdirs_contain_unread (model, iter);
853 	}
854 
855 	bold = !editable && (bold || subdirs_unread);
856 	weight = bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL;
857 	g_object_set (renderer, "weight", weight, NULL);
858 
859 	if (is_store) {
860 		const gchar *display_name;
861 
862 		display_name = camel_service_get_display_name (service);
863 		g_object_set (renderer, "text", display_name, NULL);
864 
865 	} else if (!editable && unread > 0) {
866 		gchar *name_and_unread;
867 
868 		name_and_unread = g_strdup_printf (
869 			/* Translators: This is the string used for displaying the
870 			 * folder names in folder trees. The first "%s" will be
871 			 * replaced by the folder's name and "%u" will be replaced
872 			 * with the number of unread messages in the folder. The
873 			 * second %s will be replaced with a "+" letter for collapsed
874 			 * folders with unread messages in some subfolder too,
875 			 * or with an empty string for other cases.
876 			 *
877 			 * Most languages should translate this as "%s (%u%s)". The
878 			 * languages that use localized digits (like Persian) may
879 			 * need to replace "%u" with "%Iu". Right-to-left languages
880 			 * (like Arabic and Hebrew) may need to add bidirectional
881 			 * formatting codes to take care of the cases the folder
882 			 * name appears in either direction.
883 			 *
884 			 * Do not translate the "folder-display|" part. Remove it
885 			 * from your translation.
886 			 */
887 			C_("folder-display", "%s (%u%s)"),
888 			name, unread, subdirs_unread ? "+" : "");
889 		g_object_set (renderer, "text", name_and_unread, NULL);
890 		g_free (name_and_unread);
891 
892 	} else {
893 		g_object_set (renderer, "text", name, NULL);
894 	}
895 
896 	g_free (name);
897 	g_clear_object (&service);
898 }
899 
900 static void
folder_tree_render_icon(GtkTreeViewColumn * column,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter)901 folder_tree_render_icon (GtkTreeViewColumn *column,
902                          GtkCellRenderer *renderer,
903                          GtkTreeModel *model,
904                          GtkTreeIter *iter)
905 {
906 	GtkTreeSelection *selection;
907 	GtkWidget *tree_view;
908 	GIcon *icon, *custom_icon = NULL;
909 	guint unread;
910 	guint old_unread;
911 	gchar *icon_name;
912 	gboolean is_selected;
913 	gboolean is_drafts = FALSE;
914 	gboolean show_new_mail_emblem;
915 	guint32 fi_flags = 0;
916 
917 	gtk_tree_model_get (
918 		model, iter,
919 		COL_STRING_ICON_NAME, &icon_name,
920 		COL_UINT_UNREAD_LAST_SEL, &old_unread,
921 		COL_UINT_UNREAD, &unread,
922 		COL_BOOL_IS_DRAFT, &is_drafts,
923 		COL_UINT_FLAGS, &fi_flags,
924 		COL_GICON_CUSTOM_ICON, &custom_icon,
925 		-1);
926 
927 	if (!icon_name && !custom_icon)
928 		return;
929 
930 	tree_view = gtk_tree_view_column_get_tree_view (column);
931 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
932 	is_selected = gtk_tree_selection_iter_is_selected (selection, iter);
933 
934 	if (!custom_icon && g_strcmp0 (icon_name, "folder") == 0) {
935 		GtkTreePath *drag_dest_row;
936 		gboolean is_drag_dest = FALSE;
937 
938 		gtk_tree_view_get_drag_dest_row (GTK_TREE_VIEW (tree_view), &drag_dest_row, NULL);
939 		if (drag_dest_row != NULL) {
940 			GtkTreePath *path;
941 
942 			path = gtk_tree_model_get_path (model, iter);
943 			if (gtk_tree_path_compare (path, drag_dest_row) == 0)
944 				is_drag_dest = TRUE;
945 			gtk_tree_path_free (path);
946 
947 			gtk_tree_path_free (drag_dest_row);
948 		}
949 
950 		if (is_selected) {
951 			g_free (icon_name);
952 			icon_name = g_strdup ("folder-open");
953 		} else if (is_drag_dest) {
954 			g_free (icon_name);
955 			icon_name = g_strdup ("folder-drag-accept");
956 		}
957 	}
958 
959 	if (custom_icon)
960 		icon = g_object_ref (custom_icon);
961 	else
962 		icon = g_themed_icon_new (icon_name);
963 
964 	show_new_mail_emblem =
965 		(unread > old_unread) &&
966 		!is_selected && !is_drafts &&
967 		((fi_flags & CAMEL_FOLDER_VIRTUAL) == 0);
968 
969 	/* Show an emblem if there's new mail. */
970 	if (show_new_mail_emblem) {
971 		GIcon *temp_icon;
972 		GEmblem *emblem;
973 
974 		temp_icon = g_themed_icon_new ("emblem-new");
975 		emblem = g_emblem_new (temp_icon);
976 		g_object_unref (temp_icon);
977 
978 		temp_icon = g_emblemed_icon_new (icon, emblem);
979 		g_object_unref (emblem);
980 		g_object_unref (icon);
981 
982 		icon = temp_icon;
983 	}
984 
985 	g_object_set (renderer, "gicon", icon, NULL);
986 
987 	g_clear_object (&custom_icon);
988 	g_object_unref (icon);
989 	g_free (icon_name);
990 }
991 
992 static gboolean
em_folder_tree_query_tooltip_cb(GtkTreeView * tree_view,gint xx,gint yy,gboolean keyboard_mode,GtkTooltip * tooltip,gpointer user_data)993 em_folder_tree_query_tooltip_cb (GtkTreeView *tree_view,
994 				 gint xx,
995 				 gint yy,
996 				 gboolean keyboard_mode,
997 				 GtkTooltip *tooltip,
998 				 gpointer user_data)
999 {
1000 	GtkCellRenderer *renderer = user_data;
1001 	GtkTreeModel *model = NULL;
1002 	GtkTreePath *path = NULL;
1003 	GtkTreeIter iter;
1004 	CamelService *service = NULL;
1005 	gboolean is_store = FALSE;
1006 	gboolean has_tooltip = FALSE;
1007 	guint status_code = EMFT_STATUS_CODE_UNKNOWN;
1008 
1009 	g_return_val_if_fail (EM_IS_FOLDER_TREE (tree_view), FALSE);
1010 	g_return_val_if_fail (GTK_IS_CELL_RENDERER (renderer), FALSE);
1011 
1012 	if (keyboard_mode)
1013 		return FALSE;
1014 
1015 	if (!gtk_tree_view_get_tooltip_context (tree_view, &xx, &yy, keyboard_mode, &model, &path, &iter))
1016 		return FALSE;
1017 
1018 	gtk_tree_model_get (model, &iter,
1019 		COL_OBJECT_CAMEL_STORE, &service,
1020 		COL_BOOL_IS_STORE, &is_store,
1021 		COL_UINT_STATUS_CODE, &status_code,
1022 		-1);
1023 
1024 	if (is_store && service && status_code != EMFT_STATUS_CODE_UNKNOWN && CAMEL_IS_NETWORK_SERVICE (service)) {
1025 		GtkTreeViewColumn *column;
1026 
1027 		column = gtk_tree_view_get_column (tree_view, 1);
1028 
1029 		gtk_tree_view_set_tooltip_cell (tree_view, tooltip, path, column, renderer);
1030 
1031 		has_tooltip = TRUE;
1032 
1033 		switch (status_code) {
1034 		case EMFT_STATUS_CODE_DISCONNECTED:
1035 			gtk_tooltip_set_text (tooltip, C_("Status", "Offline"));
1036 			break;
1037 		case EMFT_STATUS_CODE_CONNECTED:
1038 			gtk_tooltip_set_text (tooltip, C_("Status", "Online"));
1039 			break;
1040 		case EMFT_STATUS_CODE_NO_ROUTE:
1041 			gtk_tooltip_set_text (tooltip, C_("Status", "Unreachable"));
1042 			break;
1043 		case EMFT_STATUS_CODE_OTHER_ERROR:
1044 			gtk_tooltip_set_text (tooltip, C_("Status", "Failed to connect"));
1045 			break;
1046 		default:
1047 			has_tooltip = FALSE;
1048 			break;
1049 		}
1050 	}
1051 
1052 	gtk_tree_path_free (path);
1053 	g_clear_object (&service);
1054 
1055 	return has_tooltip;
1056 }
1057 
1058 static void
folder_tree_selection_changed_cb(EMFolderTree * folder_tree,GtkTreeSelection * selection)1059 folder_tree_selection_changed_cb (EMFolderTree *folder_tree,
1060                                   GtkTreeSelection *selection)
1061 {
1062 	GtkTreeModel *model;
1063 	GtkTreeIter iter;
1064 	GList *list;
1065 	CamelStore *store = NULL;
1066 	CamelFolderInfoFlags flags = 0;
1067 	guint unread = 0;
1068 	guint old_unread = 0;
1069 	gchar *folder_name = NULL;
1070 
1071 	list = gtk_tree_selection_get_selected_rows (selection, &model);
1072 
1073 	if (list == NULL)
1074 		goto exit;
1075 
1076 	gtk_tree_model_get_iter (model, &iter, list->data);
1077 
1078 	gtk_tree_model_get (
1079 		model, &iter,
1080 		COL_OBJECT_CAMEL_STORE, &store,
1081 		COL_STRING_FULL_NAME, &folder_name,
1082 		COL_UINT_FLAGS, &flags,
1083 		COL_UINT_UNREAD, &unread,
1084 		COL_UINT_UNREAD_LAST_SEL, &old_unread, -1);
1085 
1086 	/* Sync unread counts to distinguish new incoming mail. */
1087 	if (unread != old_unread) {
1088 		gtk_tree_store_set (
1089 			GTK_TREE_STORE (model), &iter,
1090 			COL_UINT_UNREAD_LAST_SEL, unread, -1);
1091 	}
1092 
1093 exit:
1094 	g_signal_emit (
1095 		folder_tree, signals[FOLDER_SELECTED], 0,
1096 		store, folder_name, flags);
1097 
1098 	g_free (folder_name);
1099 	g_clear_object (&store);
1100 
1101 	g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
1102 	g_list_free (list);
1103 }
1104 
1105 static void
folder_tree_copy_expanded_cb(GtkTreeView * unused,GtkTreePath * path,GtkTreeView * tree_view)1106 folder_tree_copy_expanded_cb (GtkTreeView *unused,
1107                               GtkTreePath *path,
1108                               GtkTreeView *tree_view)
1109 {
1110 	gtk_tree_view_expand_row (tree_view, path, FALSE);
1111 }
1112 
1113 static void
folder_tree_copy_selection_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,GtkTreeView * tree_view)1114 folder_tree_copy_selection_cb (GtkTreeModel *model,
1115                                GtkTreePath *path,
1116                                GtkTreeIter *iter,
1117                                GtkTreeView *tree_view)
1118 {
1119 	GtkTreeSelection *selection;
1120 
1121 	selection = gtk_tree_view_get_selection (tree_view);
1122 	gtk_tree_selection_select_path (selection, path);
1123 
1124 	/* Center the tree view on the selected path. */
1125 	gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.5, 0.0);
1126 }
1127 
1128 static void
folder_tree_copy_state(EMFolderTree * folder_tree)1129 folder_tree_copy_state (EMFolderTree *folder_tree)
1130 {
1131 	GtkTreeSelection *selection;
1132 	GtkTreeView *tree_view;
1133 	GtkTreeModel *model;
1134 
1135 	tree_view = GTK_TREE_VIEW (folder_tree);
1136 	model = gtk_tree_view_get_model (tree_view);
1137 
1138 	selection = em_folder_tree_model_get_selection (
1139 		EM_FOLDER_TREE_MODEL (model));
1140 	if (selection == NULL)
1141 		return;
1142 
1143 	gtk_tree_view_map_expanded_rows (
1144 		tree_view, (GtkTreeViewMappingFunc)
1145 		folder_tree_copy_expanded_cb, folder_tree);
1146 
1147 	gtk_tree_selection_selected_foreach (
1148 		selection, (GtkTreeSelectionForeachFunc)
1149 		folder_tree_copy_selection_cb, folder_tree);
1150 }
1151 
1152 static void
folder_tree_set_alert_sink(EMFolderTree * folder_tree,EAlertSink * alert_sink)1153 folder_tree_set_alert_sink (EMFolderTree *folder_tree,
1154                             EAlertSink *alert_sink)
1155 {
1156 	g_return_if_fail (E_IS_ALERT_SINK (alert_sink));
1157 	g_return_if_fail (folder_tree->priv->alert_sink == NULL);
1158 
1159 	folder_tree->priv->alert_sink = g_object_ref (alert_sink);
1160 }
1161 
1162 static void
folder_tree_set_session(EMFolderTree * folder_tree,EMailSession * session)1163 folder_tree_set_session (EMFolderTree *folder_tree,
1164                          EMailSession *session)
1165 {
1166 	g_return_if_fail (E_IS_MAIL_SESSION (session));
1167 	g_return_if_fail (folder_tree->priv->session == NULL);
1168 
1169 	folder_tree->priv->session = g_object_ref (session);
1170 }
1171 
1172 static GtkTargetList *
folder_tree_get_copy_target_list(EMFolderTree * folder_tree)1173 folder_tree_get_copy_target_list (EMFolderTree *folder_tree)
1174 {
1175 	GtkTargetList *target_list = NULL;
1176 
1177 	if (E_IS_SELECTABLE (folder_tree->priv->selectable)) {
1178 		ESelectable *selectable;
1179 
1180 		selectable = E_SELECTABLE (folder_tree->priv->selectable);
1181 		target_list = e_selectable_get_copy_target_list (selectable);
1182 	}
1183 
1184 	return target_list;
1185 }
1186 
1187 static GtkTargetList *
folder_tree_get_paste_target_list(EMFolderTree * folder_tree)1188 folder_tree_get_paste_target_list (EMFolderTree *folder_tree)
1189 {
1190 	GtkTargetList *target_list = NULL;
1191 
1192 	if (E_IS_SELECTABLE (folder_tree->priv->selectable)) {
1193 		ESelectable *selectable;
1194 
1195 		selectable = E_SELECTABLE (folder_tree->priv->selectable);
1196 		target_list = e_selectable_get_paste_target_list (selectable);
1197 	}
1198 
1199 	return target_list;
1200 }
1201 
1202 static void
folder_tree_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)1203 folder_tree_set_property (GObject *object,
1204                           guint property_id,
1205                           const GValue *value,
1206                           GParamSpec *pspec)
1207 {
1208 	switch (property_id) {
1209 		case PROP_ALERT_SINK:
1210 			folder_tree_set_alert_sink (
1211 				EM_FOLDER_TREE (object),
1212 				g_value_get_object (value));
1213 			return;
1214 
1215 		case PROP_MODEL:
1216 			gtk_tree_view_set_model (
1217 				GTK_TREE_VIEW (object),
1218 				g_value_get_object (value));
1219 			return;
1220 
1221 		case PROP_SESSION:
1222 			folder_tree_set_session (
1223 				EM_FOLDER_TREE (object),
1224 				g_value_get_object (value));
1225 			return;
1226 	}
1227 
1228 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1229 }
1230 
1231 static void
folder_tree_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)1232 folder_tree_get_property (GObject *object,
1233                           guint property_id,
1234                           GValue *value,
1235                           GParamSpec *pspec)
1236 {
1237 	switch (property_id) {
1238 		case PROP_ALERT_SINK:
1239 			g_value_set_object (
1240 				value,
1241 				em_folder_tree_get_alert_sink (
1242 				EM_FOLDER_TREE (object)));
1243 			return;
1244 
1245 		case PROP_COPY_TARGET_LIST:
1246 			g_value_set_boxed (
1247 				value,
1248 				folder_tree_get_copy_target_list (
1249 				EM_FOLDER_TREE (object)));
1250 			return;
1251 
1252 		case PROP_MODEL:
1253 			g_value_set_object (
1254 				value,
1255 				gtk_tree_view_get_model (
1256 				GTK_TREE_VIEW (object)));
1257 			return;
1258 
1259 		case PROP_PASTE_TARGET_LIST:
1260 			g_value_set_boxed (
1261 				value,
1262 				folder_tree_get_paste_target_list (
1263 				EM_FOLDER_TREE (object)));
1264 			return;
1265 
1266 		case PROP_SESSION:
1267 			g_value_set_object (
1268 				value,
1269 				em_folder_tree_get_session (
1270 				EM_FOLDER_TREE (object)));
1271 			return;
1272 	}
1273 
1274 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1275 }
1276 
1277 static void
folder_tree_dispose(GObject * object)1278 folder_tree_dispose (GObject *object)
1279 {
1280 	EMFolderTreePrivate *priv;
1281 	GtkTreeModel *model;
1282 	GtkTreeSelection *selection;
1283 
1284 	priv = EM_FOLDER_TREE_GET_PRIVATE (object);
1285 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (object));
1286 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (object));
1287 
1288 	if (priv->loaded_row_id != 0) {
1289 		g_signal_handler_disconnect (model, priv->loaded_row_id);
1290 		priv->loaded_row_id = 0;
1291 	}
1292 
1293 	if (priv->row_changed_id != 0) {
1294 		g_signal_handler_disconnect (model, priv->row_changed_id);
1295 		priv->row_changed_id = 0;
1296 	}
1297 
1298 	if (priv->selection_changed_handler_id != 0) {
1299 		g_signal_handler_disconnect (selection, priv->selection_changed_handler_id);
1300 		priv->selection_changed_handler_id = 0;
1301 	}
1302 
1303 	if (priv->autoscroll_id != 0) {
1304 		g_source_remove (priv->autoscroll_id);
1305 		priv->autoscroll_id = 0;
1306 	}
1307 
1308 	if (priv->autoexpand_id != 0) {
1309 		gtk_tree_row_reference_free (priv->autoexpand_row);
1310 		priv->autoexpand_row = NULL;
1311 
1312 		g_source_remove (priv->autoexpand_id);
1313 		priv->autoexpand_id = 0;
1314 	}
1315 
1316 	g_clear_object (&priv->alert_sink);
1317 	g_clear_object (&priv->session);
1318 	g_clear_object (&priv->text_renderer);
1319 
1320 	/* Chain up to parent's dispose() method. */
1321 	G_OBJECT_CLASS (em_folder_tree_parent_class)->dispose (object);
1322 }
1323 
1324 static void
folder_tree_finalize(GObject * object)1325 folder_tree_finalize (GObject *object)
1326 {
1327 	EMFolderTreePrivate *priv;
1328 
1329 	priv = EM_FOLDER_TREE_GET_PRIVATE (object);
1330 
1331 	g_slist_free_full (
1332 		priv->select_uris,
1333 		(GDestroyNotify) folder_tree_free_select_uri);
1334 
1335 	if (priv->select_uris_table != NULL)
1336 		g_hash_table_destroy (priv->select_uris_table);
1337 
1338 	g_free (priv->select_store_uid_when_added);
1339 
1340 	/* Chain up to parent's finalize() method. */
1341 	G_OBJECT_CLASS (em_folder_tree_parent_class)->finalize (object);
1342 }
1343 
1344 static void
folder_tree_constructed(GObject * object)1345 folder_tree_constructed (GObject *object)
1346 {
1347 	EMFolderTreePrivate *priv;
1348 	GtkTreeSelection *selection;
1349 	GtkTreeViewColumn *column;
1350 	GtkCellRenderer *renderer;
1351 	GtkTreeView *tree_view;
1352 	GtkTreeModel *model;
1353 	gulong handler_id;
1354 
1355 	priv = EM_FOLDER_TREE_GET_PRIVATE (object);
1356 
1357 	/* Chain up to parent's constructed() method. */
1358 	G_OBJECT_CLASS (em_folder_tree_parent_class)->constructed (object);
1359 
1360 	tree_view = GTK_TREE_VIEW (object);
1361 	model = gtk_tree_view_get_model (tree_view);
1362 	selection = gtk_tree_view_get_selection (tree_view);
1363 
1364 	handler_id = g_signal_connect (
1365 		model, "loaded-row",
1366 		G_CALLBACK (folder_tree_maybe_expand_row), object);
1367 	priv->loaded_row_id = handler_id;
1368 
1369 	/* Cannot attach to "row-inserted", because the row is inserted empty */
1370 	handler_id = g_signal_connect (
1371 		model, "row-changed",
1372 		G_CALLBACK (folder_tree_row_changed_cb), object);
1373 	priv->row_changed_id = handler_id;
1374 
1375 	handler_id = g_signal_connect_swapped (
1376 		selection, "changed",
1377 		G_CALLBACK (folder_tree_selection_changed_cb), object);
1378 	priv->selection_changed_handler_id = handler_id;
1379 
1380 	column = gtk_tree_view_column_new ();
1381 	gtk_tree_view_column_set_expand (column, TRUE);
1382 	gtk_tree_view_column_set_sizing (
1383 		column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
1384 	gtk_tree_view_append_column (tree_view, column);
1385 
1386 	renderer = gtk_cell_renderer_pixbuf_new ();
1387 	gtk_tree_view_column_pack_start (column, renderer, FALSE);
1388 	gtk_tree_view_column_add_attribute (
1389 		column, renderer, "visible", COL_BOOL_IS_FOLDER);
1390 	gtk_tree_view_column_set_cell_data_func (
1391 		column, renderer, (GtkTreeCellDataFunc)
1392 		folder_tree_render_icon, NULL, NULL);
1393 
1394 	renderer = gtk_cell_renderer_pixbuf_new ();
1395 	g_object_set (G_OBJECT (renderer), "icon-name", "mail-unread", NULL);
1396 	gtk_tree_view_column_pack_start (column, renderer, FALSE);
1397 
1398 	priv->text_renderer = g_object_ref (gtk_cell_renderer_text_new ());
1399 
1400 	gtk_tree_view_column_set_cell_data_func (
1401 		column, renderer, folder_tree_render_store_icon,
1402 		g_object_ref (priv->text_renderer), g_object_unref);
1403 
1404 	renderer = priv->text_renderer;
1405 	g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1406 	gtk_tree_view_column_pack_start (column, renderer, TRUE);
1407 	gtk_tree_view_column_add_attribute (
1408 		column, renderer, "foreground-rgba", COL_RGBA_FOREGROUND_RGBA);
1409 	gtk_tree_view_column_set_cell_data_func (
1410 		column, renderer, (GtkTreeCellDataFunc)
1411 		folder_tree_render_display_name, NULL, NULL);
1412 
1413 	g_signal_connect_swapped (
1414 		renderer, "edited",
1415 		G_CALLBACK (folder_tree_cell_edited_cb), object);
1416 
1417 	g_signal_connect_swapped (
1418 		renderer, "editing-canceled",
1419 		G_CALLBACK (folder_tree_editing_canceled_cb), object);
1420 
1421 	column = gtk_tree_view_column_new ();
1422 	gtk_tree_view_append_column (tree_view, column);
1423 
1424 	renderer = gtk_cell_renderer_pixbuf_new ();
1425 	g_object_set (renderer, "xalign", 1.0, NULL);
1426 	gtk_tree_view_column_pack_end (column, renderer, FALSE);
1427 	gtk_tree_view_column_add_attribute (
1428 		column, renderer, "gicon", COL_STATUS_ICON);
1429 	gtk_tree_view_column_add_attribute (
1430 		column, renderer, "visible", COL_STATUS_ICON_VISIBLE);
1431 
1432 	g_signal_connect_object (tree_view, "query-tooltip",
1433 		G_CALLBACK (em_folder_tree_query_tooltip_cb), renderer, 0);
1434 	gtk_widget_set_has_tooltip (GTK_WIDGET (tree_view), TRUE);
1435 
1436 	renderer = gtk_cell_renderer_spinner_new ();
1437 	g_object_set (renderer, "xalign", 1.0, NULL);
1438 	gtk_tree_view_column_pack_end (column, renderer, FALSE);
1439 	gtk_tree_view_column_add_attribute (
1440 		column, renderer, "active", COL_BOOL_IS_STORE);
1441 	gtk_tree_view_column_add_attribute (
1442 		column, renderer, "pulse", COL_STATUS_SPINNER_PULSE);
1443 	gtk_tree_view_column_add_attribute (
1444 		column, renderer, "visible", COL_STATUS_SPINNER_VISIBLE);
1445 
1446 	gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
1447 	gtk_tree_selection_set_select_function (
1448 		selection, (GtkTreeSelectionFunc)
1449 		folder_tree_select_func, NULL, NULL);
1450 
1451 	gtk_tree_view_set_headers_visible (tree_view, FALSE);
1452 	gtk_tree_view_set_search_column (tree_view, COL_STRING_DISPLAY_NAME);
1453 
1454 	folder_tree_copy_state (EM_FOLDER_TREE (object));
1455 	gtk_widget_show (GTK_WIDGET (object));
1456 
1457 	g_signal_connect (tree_view, "row-expanded",
1458 		G_CALLBACK (folder_tree_reset_store_unread_value_cb), NULL);
1459 
1460 	g_signal_connect (tree_view, "row-collapsed",
1461 		G_CALLBACK (folder_tree_reset_store_unread_value_cb), NULL);
1462 }
1463 
1464 static gboolean
folder_tree_button_press_event(GtkWidget * widget,GdkEventButton * event)1465 folder_tree_button_press_event (GtkWidget *widget,
1466                                 GdkEventButton *event)
1467 {
1468 	EMFolderTreePrivate *priv;
1469 	GtkWidgetClass *widget_class;
1470 	GtkTreeSelection *selection;
1471 	GtkTreeView *tree_view;
1472 	GtkTreePath *path;
1473 	gulong handler_id;
1474 
1475 	priv = EM_FOLDER_TREE_GET_PRIVATE (widget);
1476 
1477 	tree_view = GTK_TREE_VIEW (widget);
1478 	selection = gtk_tree_view_get_selection (tree_view);
1479 
1480 	if (gtk_tree_selection_get_mode (selection) == GTK_SELECTION_SINGLE)
1481 		folder_tree_clear_selected_list (EM_FOLDER_TREE (widget));
1482 
1483 	priv->cursor_set = TRUE;
1484 
1485 	if (event->button != 3)
1486 		goto chainup;
1487 
1488 	if (!gtk_tree_view_get_path_at_pos (
1489 		tree_view, event->x, event->y,
1490 		&path, NULL, NULL, NULL))
1491 		goto chainup;
1492 
1493 	/* Select and focus the row that was right-clicked, but prevent
1494 	 * a "folder-selected" signal emission since this does not count
1495 	 * as a folder selection in the sense we mean. */
1496 	handler_id = priv->selection_changed_handler_id;
1497 	g_signal_handler_block (selection, handler_id);
1498 	gtk_tree_selection_select_path (selection, path);
1499 	gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
1500 	g_signal_handler_unblock (selection, handler_id);
1501 
1502 	gtk_tree_path_free (path);
1503 
1504 	folder_tree_emit_popup_event (
1505 		EM_FOLDER_TREE (tree_view), (GdkEvent *) event);
1506 
1507 chainup:
1508 
1509 	/* Chain up to parent's button_press_event() method. */
1510 	widget_class = GTK_WIDGET_CLASS (em_folder_tree_parent_class);
1511 	return widget_class->button_press_event (widget, event);
1512 }
1513 
1514 static gboolean
folder_tree_key_press_event(GtkWidget * widget,GdkEventKey * event)1515 folder_tree_key_press_event (GtkWidget *widget,
1516                              GdkEventKey *event)
1517 {
1518 	EMFolderTreePrivate *priv;
1519 	GtkWidgetClass *widget_class;
1520 	GtkTreeSelection *selection;
1521 	GtkTreeView *tree_view;
1522 
1523 	if (event && event->type == GDK_KEY_PRESS &&
1524 		(event->keyval == GDK_KEY_space ||
1525 		 event->keyval == '.' ||
1526 		 event->keyval == ',' ||
1527 		 event->keyval == '[' ||
1528 		 event->keyval == ']')) {
1529 		g_signal_emit (widget, signals[HIDDEN_KEY_EVENT], 0, event);
1530 
1531 		return TRUE;
1532 	}
1533 
1534 	priv = EM_FOLDER_TREE_GET_PRIVATE (widget);
1535 
1536 	tree_view = GTK_TREE_VIEW (widget);
1537 	selection = gtk_tree_view_get_selection (tree_view);
1538 
1539 	if (gtk_tree_selection_get_mode (selection) == GTK_SELECTION_SINGLE)
1540 		folder_tree_clear_selected_list (EM_FOLDER_TREE (widget));
1541 
1542 	priv->cursor_set = TRUE;
1543 
1544 	/* Chain up to parent's key_press_event() method. */
1545 	widget_class = GTK_WIDGET_CLASS (em_folder_tree_parent_class);
1546 	return widget_class->key_press_event (widget, event);
1547 }
1548 
1549 static gboolean
folder_tree_popup_menu(GtkWidget * widget)1550 folder_tree_popup_menu (GtkWidget *widget)
1551 {
1552 	folder_tree_emit_popup_event (EM_FOLDER_TREE (widget), NULL);
1553 
1554 	return TRUE;
1555 }
1556 
1557 static void
folder_tree_row_activated(GtkTreeView * tree_view,GtkTreePath * path,GtkTreeViewColumn * column)1558 folder_tree_row_activated (GtkTreeView *tree_view,
1559                            GtkTreePath *path,
1560                            GtkTreeViewColumn *column)
1561 {
1562 	EMFolderTreePrivate *priv;
1563 	GtkTreeModel *model;
1564 	gchar *folder_name;
1565 	GtkTreeIter iter;
1566 	CamelStore *store;
1567 	CamelFolderInfoFlags flags;
1568 
1569 	priv = EM_FOLDER_TREE_GET_PRIVATE (tree_view);
1570 
1571 	model = gtk_tree_view_get_model (tree_view);
1572 
1573 	if (priv->skip_double_click)
1574 		return;
1575 
1576 	if (!gtk_tree_model_get_iter (model, &iter, path))
1577 		return;
1578 
1579 	gtk_tree_model_get (
1580 		model, &iter,
1581 		COL_OBJECT_CAMEL_STORE, &store,
1582 		COL_STRING_FULL_NAME, &folder_name,
1583 		COL_UINT_FLAGS, &flags, -1);
1584 
1585 	folder_tree_clear_selected_list (EM_FOLDER_TREE (tree_view));
1586 
1587 	g_signal_emit (
1588 		tree_view, signals[FOLDER_SELECTED], 0,
1589 		store, folder_name, flags);
1590 
1591 	g_signal_emit (
1592 		tree_view, signals[FOLDER_ACTIVATED], 0,
1593 		store, folder_name);
1594 
1595 	g_free (folder_name);
1596 	g_clear_object (&store);
1597 }
1598 
1599 static gboolean
folder_tree_test_collapse_row(GtkTreeView * tree_view,GtkTreeIter * iter,GtkTreePath * path)1600 folder_tree_test_collapse_row (GtkTreeView *tree_view,
1601                                GtkTreeIter *iter,
1602                                GtkTreePath *path)
1603 {
1604 	GtkTreeSelection *selection;
1605 	GtkTreeModel *model;
1606 	GtkTreeIter cursor;
1607 
1608 	selection = gtk_tree_view_get_selection (tree_view);
1609 
1610 	if (!gtk_tree_selection_get_selected (selection, &model, &cursor))
1611 		goto exit;
1612 
1613 	/* Select the collapsed node IFF it is a
1614 	 * parent of the currently selected folder. */
1615 	if (gtk_tree_store_is_ancestor (GTK_TREE_STORE (model), iter, &cursor))
1616 		gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
1617 
1618 exit:
1619 	return FALSE;
1620 }
1621 
1622 static void
folder_tree_row_expanded(GtkTreeView * tree_view,GtkTreeIter * iter,GtkTreePath * path)1623 folder_tree_row_expanded (GtkTreeView *tree_view,
1624                           GtkTreeIter *iter,
1625                           GtkTreePath *path)
1626 {
1627 	EActivity *activity;
1628 	GCancellable *cancellable;
1629 	EMFolderTree *folder_tree;
1630 	AsyncContext *context;
1631 	GtkTreeModel *model;
1632 	CamelStore *store;
1633 	gchar *full_name;
1634 	gboolean load;
1635 
1636 	folder_tree = EM_FOLDER_TREE (tree_view);
1637 	model = gtk_tree_view_get_model (tree_view);
1638 
1639 	gtk_tree_model_get (
1640 		model, iter,
1641 		COL_STRING_FULL_NAME, &full_name,
1642 		COL_OBJECT_CAMEL_STORE, &store,
1643 		COL_BOOL_LOAD_SUBDIRS, &load, -1);
1644 
1645 	if (!load)
1646 		goto exit;
1647 
1648 	gtk_tree_store_set (
1649 		GTK_TREE_STORE (model), iter,
1650 		COL_BOOL_LOAD_SUBDIRS, FALSE, -1);
1651 
1652 	/* Retrieve folder info asynchronously. */
1653 
1654 	activity = em_folder_tree_new_activity (folder_tree);
1655 	cancellable = e_activity_get_cancellable (activity);
1656 
1657 	context = g_slice_new0 (AsyncContext);
1658 	context->activity = activity;
1659 	context->folder_tree = g_object_ref (folder_tree);
1660 	context->root = gtk_tree_row_reference_new (model, path);
1661 	context->full_name = g_strdup (full_name);
1662 
1663 	camel_store_get_folder_info (
1664 		store, full_name,
1665 		CAMEL_STORE_FOLDER_INFO_FAST |
1666 		CAMEL_STORE_FOLDER_INFO_RECURSIVE |
1667 		CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
1668 		G_PRIORITY_DEFAULT, cancellable,
1669 		(GAsyncReadyCallback) folder_tree_get_folder_info_cb,
1670 		context);
1671 
1672 exit:
1673 	g_free (full_name);
1674 	g_clear_object (&store);
1675 }
1676 
1677 static void
em_folder_tree_class_init(EMFolderTreeClass * class)1678 em_folder_tree_class_init (EMFolderTreeClass *class)
1679 {
1680 	GObjectClass *object_class;
1681 	GtkWidgetClass *widget_class;
1682 	GtkTreeViewClass *tree_view_class;
1683 
1684 	g_type_class_add_private (class, sizeof (EMFolderTreePrivate));
1685 
1686 	object_class = G_OBJECT_CLASS (class);
1687 	object_class->set_property = folder_tree_set_property;
1688 	object_class->get_property = folder_tree_get_property;
1689 	object_class->dispose = folder_tree_dispose;
1690 	object_class->finalize = folder_tree_finalize;
1691 	object_class->constructed = folder_tree_constructed;
1692 
1693 	widget_class = GTK_WIDGET_CLASS (class);
1694 	widget_class->button_press_event = folder_tree_button_press_event;
1695 	widget_class->key_press_event = folder_tree_key_press_event;
1696 	widget_class->popup_menu = folder_tree_popup_menu;
1697 
1698 	tree_view_class = GTK_TREE_VIEW_CLASS (class);
1699 	tree_view_class->row_activated = folder_tree_row_activated;
1700 	tree_view_class->test_collapse_row = folder_tree_test_collapse_row;
1701 	tree_view_class->row_expanded = folder_tree_row_expanded;
1702 
1703 	g_object_class_install_property (
1704 		object_class,
1705 		PROP_ALERT_SINK,
1706 		g_param_spec_object (
1707 			"alert-sink",
1708 			NULL,
1709 			NULL,
1710 			E_TYPE_ALERT_SINK,
1711 			G_PARAM_READWRITE |
1712 			G_PARAM_CONSTRUCT_ONLY |
1713 			G_PARAM_STATIC_STRINGS));
1714 
1715 	/* Inherited from ESelectableInterface */
1716 	g_object_class_override_property (
1717 		object_class,
1718 		PROP_COPY_TARGET_LIST,
1719 		"copy-target-list");
1720 
1721 	/* XXX We override the GtkTreeView:model property to add
1722 	 *     G_PARAM_CONSTRUCT_ONLY so the model is set by the
1723 	 *     time we get to folder_tree_constructed(). */
1724 	g_object_class_install_property (
1725 		object_class,
1726 		PROP_MODEL,
1727 		g_param_spec_object (
1728 			"model",
1729 			"TreeView Model",
1730 			"The model for the tree view",
1731 			GTK_TYPE_TREE_MODEL,
1732 			G_PARAM_READWRITE |
1733 			G_PARAM_CONSTRUCT_ONLY));
1734 
1735 	/* Inherited from ESelectableInterface */
1736 	g_object_class_override_property (
1737 		object_class,
1738 		PROP_PASTE_TARGET_LIST,
1739 		"paste-target-list");
1740 
1741 	g_object_class_install_property (
1742 		object_class,
1743 		PROP_SESSION,
1744 		g_param_spec_object (
1745 			"session",
1746 			NULL,
1747 			NULL,
1748 			E_TYPE_MAIL_SESSION,
1749 			G_PARAM_READWRITE |
1750 			G_PARAM_CONSTRUCT_ONLY |
1751 			G_PARAM_STATIC_STRINGS));
1752 
1753 	signals[FOLDER_SELECTED] = g_signal_new (
1754 		"folder-selected",
1755 		G_OBJECT_CLASS_TYPE (object_class),
1756 		G_SIGNAL_RUN_FIRST,
1757 		G_STRUCT_OFFSET (EMFolderTreeClass, folder_selected),
1758 		NULL, NULL,
1759 		e_marshal_VOID__OBJECT_STRING_UINT,
1760 		G_TYPE_NONE, 3,
1761 		CAMEL_TYPE_STORE,
1762 		G_TYPE_STRING,
1763 		G_TYPE_UINT);
1764 
1765 	signals[FOLDER_ACTIVATED] = g_signal_new (
1766 		"folder-activated",
1767 		G_OBJECT_CLASS_TYPE (object_class),
1768 		G_SIGNAL_RUN_FIRST,
1769 		G_STRUCT_OFFSET (EMFolderTreeClass, folder_activated),
1770 		NULL, NULL,
1771 		e_marshal_VOID__OBJECT_STRING,
1772 		G_TYPE_NONE, 2,
1773 		CAMEL_TYPE_STORE,
1774 		G_TYPE_STRING);
1775 
1776 	signals[FOLDER_RENAMED] = g_signal_new (
1777 		"folder-renamed",
1778 		G_OBJECT_CLASS_TYPE (object_class),
1779 		G_SIGNAL_RUN_FIRST,
1780 		0,
1781 		NULL, NULL,
1782 		g_cclosure_marshal_VOID__BOOLEAN,
1783 		G_TYPE_NONE, 1,
1784 		G_TYPE_BOOLEAN);
1785 
1786 	signals[POPUP_EVENT] = g_signal_new (
1787 		"popup-event",
1788 		G_OBJECT_CLASS_TYPE (object_class),
1789 		G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
1790 		G_STRUCT_OFFSET (EMFolderTreeClass, popup_event),
1791 		NULL, NULL,
1792 		g_cclosure_marshal_VOID__BOXED,
1793 		G_TYPE_NONE, 1,
1794 		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
1795 
1796 	signals[HIDDEN_KEY_EVENT] = g_signal_new (
1797 		"hidden-key-event",
1798 		G_OBJECT_CLASS_TYPE (object_class),
1799 		G_SIGNAL_RUN_LAST,
1800 		G_STRUCT_OFFSET (EMFolderTreeClass, hidden_key_event),
1801 		NULL, NULL,
1802 		g_cclosure_marshal_VOID__BOXED,
1803 		G_TYPE_NONE, 1,
1804 		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
1805 }
1806 
1807 static void
em_folder_tree_init(EMFolderTree * folder_tree)1808 em_folder_tree_init (EMFolderTree *folder_tree)
1809 {
1810 	GHashTable *select_uris_table;
1811 	AtkObject *a11y;
1812 
1813 	select_uris_table = g_hash_table_new (g_str_hash, g_str_equal);
1814 
1815 	folder_tree->priv = EM_FOLDER_TREE_GET_PRIVATE (folder_tree);
1816 	folder_tree->priv->select_uris_table = select_uris_table;
1817 
1818 	/* FIXME Gross hack. */
1819 	gtk_widget_set_can_focus (GTK_WIDGET (folder_tree), TRUE);
1820 
1821 	a11y = gtk_widget_get_accessible (GTK_WIDGET (folder_tree));
1822 	atk_object_set_name (a11y, _("Mail Folder Tree"));
1823 }
1824 
1825 /* Sets a selectable widget, which will be used for update-actions and
1826  * select-all selectable interface functions. This can be NULL, then nothing
1827  * can be selected and calling selectable function does nothing. */
1828 void
em_folder_tree_set_selectable_widget(EMFolderTree * folder_tree,GtkWidget * selectable)1829 em_folder_tree_set_selectable_widget (EMFolderTree *folder_tree,
1830                                       GtkWidget *selectable)
1831 {
1832 	g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
1833 
1834 	if (selectable != NULL)
1835 		g_return_if_fail (E_IS_SELECTABLE (selectable));
1836 
1837 	folder_tree->priv->selectable = selectable;
1838 }
1839 
1840 static void
folder_tree_selectable_update_actions(ESelectable * selectable,EFocusTracker * focus_tracker,GdkAtom * clipboard_targets,gint n_clipboard_targets)1841 folder_tree_selectable_update_actions (ESelectable *selectable,
1842 				       EFocusTracker *focus_tracker,
1843 				       GdkAtom *clipboard_targets,
1844 				       gint n_clipboard_targets)
1845 {
1846 	EMFolderTree *folder_tree;
1847 
1848 	folder_tree = EM_FOLDER_TREE (selectable);
1849 	g_return_if_fail (folder_tree != NULL);
1850 
1851 	if (folder_tree->priv->selectable != NULL) {
1852 		ESelectableInterface *iface;
1853 		ESelectable *inner_selectable;
1854 
1855 		inner_selectable = E_SELECTABLE (folder_tree->priv->selectable);
1856 		iface = E_SELECTABLE_GET_INTERFACE (inner_selectable);
1857 		g_return_if_fail (iface != NULL);
1858 		g_return_if_fail (iface->update_actions != NULL);
1859 
1860 		iface->update_actions (
1861 			inner_selectable, focus_tracker,
1862 			clipboard_targets, n_clipboard_targets);
1863 	}
1864 }
1865 
1866 static void
folder_tree_selectable_cut_clipboard(ESelectable * selectable)1867 folder_tree_selectable_cut_clipboard (ESelectable *selectable)
1868 {
1869 	ESelectableInterface *iface;
1870 	EMFolderTree *folder_tree;
1871 	GtkWidget *proxy;
1872 
1873 	folder_tree = EM_FOLDER_TREE (selectable);
1874 	proxy = folder_tree->priv->selectable;
1875 
1876 	if (!E_IS_SELECTABLE (proxy))
1877 		return;
1878 
1879 	iface = E_SELECTABLE_GET_INTERFACE (proxy);
1880 
1881 	if (iface->cut_clipboard == NULL)
1882 		return;
1883 
1884 	if (gtk_widget_get_can_focus (proxy))
1885 		gtk_widget_grab_focus (proxy);
1886 
1887 	iface->cut_clipboard (E_SELECTABLE (proxy));
1888 }
1889 
1890 static void
folder_tree_selectable_copy_clipboard(ESelectable * selectable)1891 folder_tree_selectable_copy_clipboard (ESelectable *selectable)
1892 {
1893 	ESelectableInterface *iface;
1894 	EMFolderTree *folder_tree;
1895 	GtkWidget *proxy;
1896 
1897 	folder_tree = EM_FOLDER_TREE (selectable);
1898 	proxy = folder_tree->priv->selectable;
1899 
1900 	if (!E_IS_SELECTABLE (proxy))
1901 		return;
1902 
1903 	iface = E_SELECTABLE_GET_INTERFACE (proxy);
1904 
1905 	if (iface->copy_clipboard == NULL)
1906 		return;
1907 
1908 	if (gtk_widget_get_can_focus (proxy))
1909 		gtk_widget_grab_focus (proxy);
1910 
1911 	iface->copy_clipboard (E_SELECTABLE (proxy));
1912 }
1913 
1914 static void
folder_tree_selectable_paste_clipboard(ESelectable * selectable)1915 folder_tree_selectable_paste_clipboard (ESelectable *selectable)
1916 {
1917 	ESelectableInterface *iface;
1918 	EMFolderTree *folder_tree;
1919 	GtkWidget *proxy;
1920 
1921 	folder_tree = EM_FOLDER_TREE (selectable);
1922 	proxy = folder_tree->priv->selectable;
1923 
1924 	if (!E_IS_SELECTABLE (proxy))
1925 		return;
1926 
1927 	iface = E_SELECTABLE_GET_INTERFACE (proxy);
1928 
1929 	if (iface->paste_clipboard == NULL)
1930 		return;
1931 
1932 	if (gtk_widget_get_can_focus (proxy))
1933 		gtk_widget_grab_focus (proxy);
1934 
1935 	iface->paste_clipboard (E_SELECTABLE (proxy));
1936 }
1937 
1938 static void
folder_tree_selectable_delete_selection(ESelectable * selectable)1939 folder_tree_selectable_delete_selection (ESelectable *selectable)
1940 {
1941 	ESelectableInterface *iface;
1942 	EMFolderTree *folder_tree;
1943 	GtkWidget *proxy;
1944 
1945 	folder_tree = EM_FOLDER_TREE (selectable);
1946 	proxy = folder_tree->priv->selectable;
1947 
1948 	if (!E_IS_SELECTABLE (proxy))
1949 		return;
1950 
1951 	iface = E_SELECTABLE_GET_INTERFACE (proxy);
1952 
1953 	if (iface->delete_selection == NULL)
1954 		return;
1955 
1956 	if (gtk_widget_get_can_focus (proxy))
1957 		gtk_widget_grab_focus (proxy);
1958 
1959 	iface->delete_selection (E_SELECTABLE (proxy));
1960 }
1961 
1962 static void
folder_tree_selectable_select_all(ESelectable * selectable)1963 folder_tree_selectable_select_all (ESelectable *selectable)
1964 {
1965 	ESelectableInterface *iface;
1966 	EMFolderTree *folder_tree;
1967 	GtkWidget *proxy;
1968 
1969 	folder_tree = EM_FOLDER_TREE (selectable);
1970 	proxy = folder_tree->priv->selectable;
1971 
1972 	if (!E_IS_SELECTABLE (proxy))
1973 		return;
1974 
1975 	iface = E_SELECTABLE_GET_INTERFACE (proxy);
1976 
1977 	if (iface->select_all == NULL)
1978 		return;
1979 
1980 	if (gtk_widget_get_can_focus (proxy))
1981 		gtk_widget_grab_focus (proxy);
1982 
1983 	iface->select_all (E_SELECTABLE (proxy));
1984 }
1985 
1986 static void
em_folder_tree_selectable_init(ESelectableInterface * iface)1987 em_folder_tree_selectable_init (ESelectableInterface *iface)
1988 {
1989 	iface->update_actions = folder_tree_selectable_update_actions;
1990 	iface->cut_clipboard = folder_tree_selectable_cut_clipboard;
1991 	iface->copy_clipboard = folder_tree_selectable_copy_clipboard;
1992 	iface->paste_clipboard = folder_tree_selectable_paste_clipboard;
1993 	iface->delete_selection = folder_tree_selectable_delete_selection;
1994 	iface->select_all = folder_tree_selectable_select_all;
1995 }
1996 
1997 GtkWidget *
em_folder_tree_new(EMailSession * session,EAlertSink * alert_sink)1998 em_folder_tree_new (EMailSession *session,
1999                     EAlertSink *alert_sink)
2000 {
2001 	EMFolderTreeModel *model;
2002 
2003 	g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
2004 	g_return_val_if_fail (E_IS_ALERT_SINK (alert_sink), NULL);
2005 
2006 	model = em_folder_tree_model_get_default ();
2007 
2008 	return em_folder_tree_new_with_model (session, alert_sink, model);
2009 }
2010 
2011 GtkWidget *
em_folder_tree_new_with_model(EMailSession * session,EAlertSink * alert_sink,EMFolderTreeModel * model)2012 em_folder_tree_new_with_model (EMailSession *session,
2013                                EAlertSink *alert_sink,
2014                                EMFolderTreeModel *model)
2015 {
2016 	g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
2017 	g_return_val_if_fail (E_IS_ALERT_SINK (alert_sink), NULL);
2018 	g_return_val_if_fail (EM_IS_FOLDER_TREE_MODEL (model), NULL);
2019 
2020 	return g_object_new (
2021 		EM_TYPE_FOLDER_TREE,
2022 		"alert-sink", alert_sink,
2023 		"session", session,
2024 		"model", model, NULL);
2025 }
2026 
2027 EActivity *
em_folder_tree_new_activity(EMFolderTree * folder_tree)2028 em_folder_tree_new_activity (EMFolderTree *folder_tree)
2029 {
2030 	EActivity *activity;
2031 	EMailSession *session;
2032 	EAlertSink *alert_sink;
2033 	GCancellable *cancellable;
2034 
2035 	g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
2036 
2037 	activity = e_activity_new ();
2038 
2039 	alert_sink = em_folder_tree_get_alert_sink (folder_tree);
2040 	e_activity_set_alert_sink (activity, alert_sink);
2041 
2042 	cancellable = camel_operation_new ();
2043 	e_activity_set_cancellable (activity, cancellable);
2044 	g_object_unref (cancellable);
2045 
2046 	session = em_folder_tree_get_session (folder_tree);
2047 	e_mail_ui_session_add_activity (
2048 		E_MAIL_UI_SESSION (session), activity);
2049 
2050 	return activity;
2051 }
2052 
2053 EAlertSink *
em_folder_tree_get_alert_sink(EMFolderTree * folder_tree)2054 em_folder_tree_get_alert_sink (EMFolderTree *folder_tree)
2055 {
2056 	g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
2057 
2058 	return folder_tree->priv->alert_sink;
2059 }
2060 
2061 EMailSession *
em_folder_tree_get_session(EMFolderTree * folder_tree)2062 em_folder_tree_get_session (EMFolderTree *folder_tree)
2063 {
2064 	g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
2065 
2066 	return folder_tree->priv->session;
2067 }
2068 
2069 static void
tree_drag_begin(GtkWidget * widget,GdkDragContext * context,EMFolderTree * folder_tree)2070 tree_drag_begin (GtkWidget *widget,
2071                  GdkDragContext *context,
2072                  EMFolderTree *folder_tree)
2073 {
2074 	EMFolderTreePrivate *priv = folder_tree->priv;
2075 	GtkTreeSelection *selection;
2076 	GtkTreeView *tree_view;
2077 	cairo_surface_t *s;
2078 	GtkTreeModel *model;
2079 	GtkTreePath *path;
2080 	GtkTreeIter iter;
2081 
2082 	tree_view = GTK_TREE_VIEW (widget);
2083 	selection = gtk_tree_view_get_selection (tree_view);
2084 	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2085 		return;
2086 
2087 	path = gtk_tree_model_get_path (model, &iter);
2088 	priv->drag_row = gtk_tree_row_reference_new (model, path);
2089 
2090 	s = gtk_tree_view_create_row_drag_icon (tree_view, path);
2091 	gtk_drag_set_icon_surface (context, s);
2092 
2093 	gtk_tree_path_free (path);
2094 }
2095 
2096 static void
tree_drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection,guint info,guint time,EMFolderTree * folder_tree)2097 tree_drag_data_get (GtkWidget *widget,
2098                     GdkDragContext *context,
2099                     GtkSelectionData *selection,
2100                     guint info,
2101                     guint time,
2102                     EMFolderTree *folder_tree)
2103 {
2104 	EMFolderTreePrivate *priv = folder_tree->priv;
2105 	GtkTreeModel *model;
2106 	GtkTreePath *src_path;
2107 	CamelFolder *folder;
2108 	CamelStore *store = NULL;
2109 	GtkTreeIter iter;
2110 	gchar *folder_name = NULL;
2111 	gchar *folder_uri;
2112 
2113 	if (!priv->drag_row || !(src_path =
2114 		gtk_tree_row_reference_get_path (priv->drag_row)))
2115 		return;
2116 
2117 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_tree));
2118 
2119 	if (!gtk_tree_model_get_iter (model, &iter, src_path))
2120 		goto fail;
2121 
2122 	gtk_tree_model_get (
2123 		model, &iter,
2124 		COL_OBJECT_CAMEL_STORE, &store,
2125 		COL_STRING_FULL_NAME, &folder_name, -1);
2126 
2127 	/* make sure user isn't trying to drag on a placeholder row */
2128 	if (folder_name == NULL)
2129 		goto fail;
2130 
2131 	folder_uri = e_mail_folder_uri_build (store, folder_name);
2132 
2133 	switch (info) {
2134 	case DND_DRAG_TYPE_FOLDER:
2135 		/* dragging to a new location in the folder tree */
2136 		gtk_selection_data_set (
2137 			selection, drag_atoms[info], 8,
2138 			(guchar *) folder_uri, strlen (folder_uri) + 1);
2139 		break;
2140 	case DND_DRAG_TYPE_TEXT_URI_LIST:
2141 		/* dragging to nautilus or something, probably */
2142 		/* FIXME camel_store_get_folder_sync() may block. */
2143 		if ((folder = camel_store_get_folder_sync (
2144 			store, folder_name, 0, NULL, NULL))) {
2145 
2146 			GPtrArray *uids = camel_folder_get_uids (folder);
2147 
2148 			em_utils_selection_set_urilist (selection, folder, uids);
2149 			camel_folder_free_uids (folder, uids);
2150 			g_object_unref (folder);
2151 		}
2152 		break;
2153 	default:
2154 		abort ();
2155 	}
2156 
2157 	g_free (folder_uri);
2158 
2159 fail:
2160 	gtk_tree_path_free (src_path);
2161 	g_clear_object (&store);
2162 	g_free (folder_name);
2163 }
2164 
2165 static gboolean
ask_drop_folder(EMFolderTree * folder_tree,const gchar * src_folder_uri,const gchar * des_full_name,CamelStore * des_store,gboolean is_move)2166 ask_drop_folder (EMFolderTree *folder_tree,
2167                  const gchar *src_folder_uri,
2168                  const gchar *des_full_name,
2169                  CamelStore *des_store,
2170                  gboolean is_move)
2171 {
2172 	const gchar *key = is_move ? "prompt-on-folder-drop-move" : "prompt-on-folder-drop-copy";
2173 	const gchar *src_display_name, *des_display_name;
2174 	EMailSession *session;
2175 	GSettings *settings;
2176 	gchar *set_value, *src_folder_name = NULL;
2177 	gchar *src_folder = NULL, *des_folder = NULL;
2178 	CamelProvider *src_provider, *des_provider;
2179 	CamelStore *src_store = NULL;
2180 	GError *error = NULL;
2181 	GtkWidget *widget;
2182 	GtkWindow *parent;
2183 	gint response;
2184 	gboolean src_store_is_local, des_store_is_local, session_is_online;
2185 
2186 	g_return_val_if_fail (folder_tree != NULL, FALSE);
2187 	g_return_val_if_fail (src_folder_uri != NULL, FALSE);
2188 	g_return_val_if_fail (des_full_name != NULL || des_store != NULL, FALSE);
2189 
2190 	settings = e_util_ref_settings ("org.gnome.evolution.mail");
2191 	set_value = g_settings_get_string (settings, key);
2192 
2193 	if (g_strcmp0 (set_value, "never") == 0) {
2194 		g_object_unref (settings);
2195 		g_free (set_value);
2196 
2197 		return FALSE;
2198 	} else if (g_strcmp0 (set_value, "always") == 0) {
2199 		g_object_unref (settings);
2200 		g_free (set_value);
2201 
2202 		return TRUE;
2203 	}
2204 
2205 	g_free (set_value);
2206 
2207 	session = em_folder_tree_get_session (folder_tree);
2208 
2209 	e_mail_folder_uri_parse (
2210 		CAMEL_SESSION (session),
2211 		src_folder_uri, &src_store, &src_folder_name, &error);
2212 
2213 	if (error != NULL) {
2214 		g_warning (
2215 			"%s: Failed to convert '%s' to folder name: %s",
2216 			G_STRFUNC, src_folder_uri, error->message);
2217 		g_object_unref (settings);
2218 		g_error_free (error);
2219 
2220 		return FALSE;
2221 	}
2222 
2223 	session_is_online = camel_session_get_online (CAMEL_SESSION (session));
2224 
2225 	src_provider = camel_service_get_provider (CAMEL_SERVICE (src_store));
2226 	src_store_is_local = (src_provider->flags & CAMEL_PROVIDER_IS_LOCAL) != 0;
2227 	src_display_name = camel_service_get_display_name (CAMEL_SERVICE (src_store));
2228 	src_folder = g_strdup_printf ("%s: %s", src_display_name, src_folder_name);
2229 
2230 	des_provider = camel_service_get_provider (CAMEL_SERVICE (des_store));
2231 	des_store_is_local = (des_provider->flags & CAMEL_PROVIDER_IS_LOCAL) != 0;
2232 	des_display_name = camel_service_get_display_name (CAMEL_SERVICE (des_store));
2233 	des_folder = g_strdup_printf ("%s: %s", des_display_name, des_full_name);
2234 
2235 	if (!session_is_online && (!src_store_is_local || !des_store_is_local)) {
2236 		EAlertSink *alert_sink;
2237 
2238 		alert_sink = em_folder_tree_get_alert_sink (folder_tree);
2239 		e_alert_submit (
2240 			alert_sink,
2241 			"mail:online-operation",
2242 			src_store_is_local ? des_folder : src_folder,
2243 			NULL);
2244 		g_free (src_folder_name);
2245 		g_free (src_folder);
2246 		g_free (des_folder);
2247 		g_object_unref (src_store);
2248 		g_object_unref (settings);
2249 
2250 		return FALSE;
2251 	}
2252 
2253 	parent = NULL;
2254 	widget = gtk_widget_get_toplevel (GTK_WIDGET (folder_tree));
2255 	if (widget && gtk_widget_is_toplevel (widget) && GTK_IS_WINDOW (widget))
2256 		parent = GTK_WINDOW (widget);
2257 
2258 	widget = e_alert_dialog_new_for_args (
2259 		parent,
2260 		is_move ? "mail:ask-folder-drop-move" : "mail:ask-folder-drop-copy",
2261 		src_folder_name,
2262 		des_full_name && *des_full_name ? des_folder :
2263 			camel_service_get_display_name (CAMEL_SERVICE (des_store)),
2264 		NULL);
2265 	response = gtk_dialog_run (GTK_DIALOG (widget));
2266 	gtk_widget_destroy (widget);
2267 
2268 	if (response == GTK_RESPONSE_OK)
2269 		g_settings_set_string (settings, key, "always");
2270 	else if (response == GTK_RESPONSE_CANCEL)
2271 		g_settings_set_string (settings, key, "never");
2272 
2273 	g_free (src_folder_name);
2274 	g_free (src_folder);
2275 	g_free (des_folder);
2276 	g_object_unref (src_store);
2277 	g_object_unref (settings);
2278 
2279 	return response == GTK_RESPONSE_YES || response == GTK_RESPONSE_OK;
2280 }
2281 
2282 /* Drop handling */
2283 struct _DragDataReceivedAsync {
2284 	MailMsg base;
2285 
2286 	/* input data */
2287 	GdkDragContext *context;
2288 
2289 	/* Only selection->data and selection->length are valid */
2290 	GtkSelectionData *selection;
2291 
2292 	EMFolderTree *folder_tree;
2293 	EMailSession *session;
2294 	CamelStore *store;
2295 	gchar *full_name;
2296 	gchar *dest_folder_uri;
2297 	guint32 action;
2298 	guint info;
2299 
2300 	guint move : 1;
2301 	guint moved : 1;
2302 	guint aborted : 1;
2303 };
2304 
2305 static void
folder_tree_drop_folder(struct _DragDataReceivedAsync * m)2306 folder_tree_drop_folder (struct _DragDataReceivedAsync *m)
2307 {
2308 	CamelFolder *folder;
2309 	CamelStore *parent_store;
2310 	GCancellable *cancellable;
2311 	const gchar *folder_name;
2312 	const gchar *full_name;
2313 	const guchar *data;
2314 
2315 	data = gtk_selection_data_get_data (m->selection);
2316 
2317 	d (printf (" * Drop folder '%s' onto '%s'\n", data, m->full_name));
2318 
2319 	cancellable = m->base.cancellable;
2320 
2321 	folder = e_mail_session_uri_to_folder_sync (
2322 		m->session, (gchar *) data, 0,
2323 		cancellable, &m->base.error);
2324 	if (folder == NULL)
2325 		return;
2326 
2327 	full_name = camel_folder_get_full_name (folder);
2328 	parent_store = camel_folder_get_parent_store (folder);
2329 
2330 	em_folder_utils_copy_folders (
2331 		parent_store, full_name, m->store,
2332 		m->full_name ? m->full_name : "", m->move);
2333 
2334 	folder_name = strrchr (full_name, '/');
2335 	if (folder_name)
2336 		folder_name++;
2337 	else
2338 		folder_name = full_name;
2339 
2340 	if (m->full_name) {
2341 		gchar *dest_root_name;
2342 
2343 		dest_root_name = g_strconcat (m->full_name, "/", folder_name, NULL);
2344 		m->dest_folder_uri = e_mail_folder_uri_build (m->store, dest_root_name);
2345 		g_free (dest_root_name);
2346 	} else {
2347 		m->dest_folder_uri = e_mail_folder_uri_build (m->store, folder_name);
2348 	}
2349 
2350 	g_object_unref (folder);
2351 }
2352 
2353 static gchar *
folder_tree_drop_async__desc(struct _DragDataReceivedAsync * m)2354 folder_tree_drop_async__desc (struct _DragDataReceivedAsync *m)
2355 {
2356 	const guchar *data;
2357 
2358 	data = gtk_selection_data_get_data (m->selection);
2359 
2360 	if (m->info == DND_DROP_TYPE_FOLDER) {
2361 		gchar *folder_name = NULL;
2362 		gchar *res;
2363 
2364 		e_mail_folder_uri_parse (
2365 			CAMEL_SESSION (m->session),
2366 			(gchar *) data, NULL, &folder_name, NULL);
2367 		g_return_val_if_fail (folder_name != NULL, NULL);
2368 
2369 		if (m->move)
2370 			res = g_strdup_printf (
2371 				_("Moving folder %s"), folder_name);
2372 		else
2373 			res = g_strdup_printf (
2374 				_("Copying folder %s"), folder_name);
2375 		g_free (folder_name);
2376 
2377 		return res;
2378 	} else {
2379 		if (m->move)
2380 			return g_strdup_printf (
2381 				_("Moving messages into folder %s"),
2382 				m->full_name);
2383 		else
2384 			return g_strdup_printf (
2385 				_("Copying messages into folder %s"),
2386 				m->full_name);
2387 	}
2388 }
2389 
2390 static void
folder_tree_drop_async__exec(struct _DragDataReceivedAsync * m,GCancellable * cancellable,GError ** error)2391 folder_tree_drop_async__exec (struct _DragDataReceivedAsync *m,
2392                               GCancellable *cancellable,
2393                               GError **error)
2394 {
2395 	CamelFolder *folder;
2396 
2397 	/* for types other than folder, we can't drop to the root path */
2398 	if (m->info == DND_DROP_TYPE_FOLDER) {
2399 		/* copy or move (aka rename) a folder */
2400 		folder_tree_drop_folder (m);
2401 	} else if (m->full_name == NULL) {
2402 		g_set_error (
2403 			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
2404 			_("Cannot drop message(s) into toplevel store"));
2405 	} else if ((folder = camel_store_get_folder_sync (
2406 		m->store, m->full_name, 0, cancellable, error))) {
2407 
2408 		switch (m->info) {
2409 		case DND_DROP_TYPE_UID_LIST:
2410 			/* import a list of uids from another evo folder */
2411 			em_utils_selection_get_uidlist (
2412 				m->selection, m->session, folder, m->move,
2413 				cancellable, error);
2414 			m->moved = m->move && (!error || !*error);
2415 			break;
2416 		case DND_DROP_TYPE_MESSAGE_RFC822:
2417 			/* import a message/rfc822 stream */
2418 			em_utils_selection_get_message (m->selection, folder);
2419 			break;
2420 		case DND_DROP_TYPE_TEXT_URI_LIST:
2421 			/* import an mbox, maildir, or mh folder? */
2422 			em_utils_selection_get_urilist (m->selection, folder);
2423 			break;
2424 		default:
2425 			abort ();
2426 		}
2427 		g_object_unref (folder);
2428 	}
2429 }
2430 
2431 static void
folder_tree_drop_async__free(struct _DragDataReceivedAsync * m)2432 folder_tree_drop_async__free (struct _DragDataReceivedAsync *m)
2433 {
2434 	if (m->move && m->dest_folder_uri) {
2435 		GList *selected_list;
2436 
2437 		selected_list = g_list_append (NULL, m->dest_folder_uri);
2438 		em_folder_tree_set_selected_list (m->folder_tree, selected_list, FALSE);
2439 		g_list_free (selected_list);
2440 	}
2441 
2442 	g_object_unref (m->folder_tree);
2443 	g_object_unref (m->session);
2444 	g_object_unref (m->context);
2445 	g_object_unref (m->store);
2446 	g_free (m->full_name);
2447 	g_free (m->dest_folder_uri);
2448 	gtk_selection_data_free (m->selection);
2449 }
2450 
2451 static MailMsgInfo folder_tree_drop_async_info = {
2452 	sizeof (struct _DragDataReceivedAsync),
2453 	(MailMsgDescFunc) folder_tree_drop_async__desc,
2454 	(MailMsgExecFunc) folder_tree_drop_async__exec,
2455 	(MailMsgDoneFunc) NULL,
2456 	(MailMsgFreeFunc) folder_tree_drop_async__free
2457 };
2458 
2459 static void
tree_drag_data_action(struct _DragDataReceivedAsync * m)2460 tree_drag_data_action (struct _DragDataReceivedAsync *m)
2461 {
2462 	m->move = m->action == GDK_ACTION_MOVE;
2463 	mail_msg_unordered_push (m);
2464 }
2465 
2466 static void
tree_drag_data_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection,guint info,guint time,EMFolderTree * folder_tree)2467 tree_drag_data_received (GtkWidget *widget,
2468                          GdkDragContext *context,
2469                          gint x,
2470                          gint y,
2471                          GtkSelectionData *selection,
2472                          guint info,
2473                          guint time,
2474                          EMFolderTree *folder_tree)
2475 {
2476 	GtkTreeViewDropPosition pos;
2477 	GtkTreeModel *model;
2478 	GtkTreeView *tree_view;
2479 	GtkTreePath *dest_path = NULL;
2480 	EMailSession *session;
2481 	struct _DragDataReceivedAsync *m;
2482 	gboolean is_store;
2483 	CamelStore *store;
2484 	GtkTreeIter iter;
2485 	gchar *full_name;
2486 
2487 	tree_view = GTK_TREE_VIEW (folder_tree);
2488 	model = gtk_tree_view_get_model (tree_view);
2489 
2490 	session = em_folder_tree_get_session (folder_tree);
2491 
2492 	if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &dest_path, &pos))
2493 		return;
2494 
2495 	/* this means we are receiving no data */
2496 	if (gtk_selection_data_get_data (selection) == NULL) {
2497 		gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
2498 		gtk_tree_path_free (dest_path);
2499 		return;
2500 	}
2501 
2502 	if (gtk_selection_data_get_length (selection) == -1) {
2503 		gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
2504 		gtk_tree_path_free (dest_path);
2505 		return;
2506 	}
2507 
2508 	if (!gtk_tree_model_get_iter (model, &iter, dest_path)) {
2509 		gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
2510 		gtk_tree_path_free (dest_path);
2511 		return;
2512 	}
2513 
2514 	gtk_tree_model_get (
2515 		model, &iter,
2516 		COL_OBJECT_CAMEL_STORE, &store,
2517 		COL_BOOL_IS_STORE, &is_store,
2518 		COL_STRING_FULL_NAME, &full_name, -1);
2519 
2520 	/* make sure user isn't try to drop on a placeholder row */
2521 	if (full_name == NULL && !is_store) {
2522 		gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
2523 		gtk_tree_path_free (dest_path);
2524 		g_clear_object (&store);
2525 		return;
2526 	}
2527 
2528 	if (info == DND_DROP_TYPE_FOLDER &&
2529 	    !ask_drop_folder (folder_tree,
2530 			      (const gchar *) gtk_selection_data_get_data (selection),
2531 			      full_name, store,
2532 			      gdk_drag_context_get_selected_action (context) == GDK_ACTION_MOVE)) {
2533 		gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
2534 		gtk_tree_path_free (dest_path);
2535 		g_clear_object (&store);
2536 		g_free (full_name);
2537 		return;
2538 	}
2539 
2540 	m = mail_msg_new (&folder_tree_drop_async_info);
2541 	m->folder_tree = g_object_ref (folder_tree);
2542 	m->session = g_object_ref (session);
2543 	m->context = g_object_ref (context);
2544 	m->store = g_object_ref (store);
2545 	m->full_name = full_name;
2546 	m->dest_folder_uri = NULL;
2547 	m->action = gdk_drag_context_get_selected_action (context);
2548 	m->info = info;
2549 
2550 	/* need to copy, goes away once we exit */
2551 	m->selection = gtk_selection_data_copy (selection);
2552 
2553 	tree_drag_data_action (m);
2554 	gtk_tree_path_free (dest_path);
2555 	g_clear_object (&store);
2556 }
2557 
2558 static gboolean
is_special_local_folder(const gchar * name)2559 is_special_local_folder (const gchar *name)
2560 {
2561 	return strcmp (name, "Drafts") == 0
2562 		|| strcmp (name, "Inbox") == 0
2563 		|| strcmp (name, "Outbox") == 0
2564 		|| strcmp (name, "Sent") == 0
2565 		|| strcmp (name, "Templates") == 0;
2566 }
2567 
2568 static GdkAtom
folder_tree_drop_target(EMFolderTree * folder_tree,GdkDragContext * context,GtkTreePath * path,GdkDragAction * actions,GdkDragAction * suggested_action)2569 folder_tree_drop_target (EMFolderTree *folder_tree,
2570                          GdkDragContext *context,
2571                          GtkTreePath *path,
2572                          GdkDragAction *actions,
2573                          GdkDragAction *suggested_action)
2574 {
2575 	EMFolderTreePrivate *p = folder_tree->priv;
2576 	gchar *dst_full_name = NULL;
2577 	gchar *src_full_name = NULL;
2578 	CamelStore *dst_store = NULL;
2579 	CamelStore *src_store = NULL;
2580 	GdkAtom atom = GDK_NONE;
2581 	gboolean is_store;
2582 	GtkTreeModel *model;
2583 	GtkTreeIter iter;
2584 	GList *targets;
2585 	const gchar *uid;
2586 	gboolean src_is_local;
2587 	gboolean src_is_vfolder;
2588 	gboolean dst_is_vfolder;
2589 	guint32 flags = 0;
2590 
2591 	/* This is a bit of a mess, but should handle all the cases properly */
2592 
2593 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_tree));
2594 
2595 	if (!gtk_tree_model_get_iter (model, &iter, path))
2596 		return GDK_NONE;
2597 
2598 	/* We may override these further down. */
2599 	*actions = gdk_drag_context_get_actions (context);
2600 	*suggested_action = gdk_drag_context_get_suggested_action (context);
2601 
2602 	gtk_tree_model_get (
2603 		model, &iter,
2604 		COL_BOOL_IS_STORE, &is_store,
2605 		COL_OBJECT_CAMEL_STORE, &dst_store,
2606 		COL_STRING_FULL_NAME, &dst_full_name,
2607 		COL_UINT_FLAGS, &flags, -1);
2608 
2609 	uid = camel_service_get_uid (CAMEL_SERVICE (dst_store));
2610 	dst_is_vfolder = (g_strcmp0 (uid, E_MAIL_SESSION_VFOLDER_UID) == 0);
2611 
2612 	targets = gdk_drag_context_list_targets (context);
2613 
2614 	/* Check for special destinations */
2615 
2616 	/* Don't allow copying/moving into the UNMATCHED vfolder. */
2617 	if (dst_is_vfolder)
2618 		if (g_strcmp0 (dst_full_name, CAMEL_UNMATCHED_NAME) == 0)
2619 			goto done;
2620 
2621 	/* Don't allow copying/moving into a vTrash folder. */
2622 	if (g_strcmp0 (dst_full_name, CAMEL_VTRASH_NAME) == 0)
2623 		goto done;
2624 
2625 	/* Don't allow copying/moving into a vJunk folder. */
2626 	if (g_strcmp0 (dst_full_name, CAMEL_VJUNK_NAME) == 0)
2627 		goto done;
2628 
2629 	if (flags & CAMEL_FOLDER_NOSELECT)
2630 		goto done;
2631 
2632 	if (p->drag_row) {
2633 		GtkTreePath *src_path = gtk_tree_row_reference_get_path (p->drag_row);
2634 
2635 		if (src_path) {
2636 			guint32 src_flags = 0;
2637 
2638 			if (gtk_tree_model_get_iter (model, &iter, src_path))
2639 				gtk_tree_model_get (
2640 					model, &iter,
2641 					COL_OBJECT_CAMEL_STORE, &src_store,
2642 					COL_STRING_FULL_NAME, &src_full_name,
2643 					COL_UINT_FLAGS, &src_flags, -1);
2644 
2645 			/* can't dnd onto itself or below itself - bad things happen,
2646 			 * no point dragging to where we were either */
2647 			if (gtk_tree_path_compare (path, src_path) == 0
2648 			    || gtk_tree_path_is_descendant (path, src_path)
2649 			    || (gtk_tree_path_is_ancestor (path, src_path)
2650 				&& gtk_tree_path_get_depth (path) ==
2651 				   gtk_tree_path_get_depth (src_path) - 1)) {
2652 				gtk_tree_path_free (src_path);
2653 				goto done;
2654 			}
2655 
2656 			gtk_tree_path_free (src_path);
2657 
2658 			if ((src_flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_INBOX ||
2659 			    (src_flags & CAMEL_FOLDER_SYSTEM) != 0) {
2660 				/* allow only copy of the Inbox and other system folders */
2661 				GdkAtom xfolder;
2662 
2663 				/* force copy for special local folders */
2664 				*suggested_action = GDK_ACTION_COPY;
2665 				*actions = GDK_ACTION_COPY;
2666 				xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
2667 				while (targets != NULL) {
2668 					if (targets->data == (gpointer) xfolder) {
2669 						atom = xfolder;
2670 						goto done;
2671 					}
2672 
2673 					targets = targets->next;
2674 				}
2675 
2676 				goto done;
2677 			}
2678 		}
2679 	}
2680 
2681 	/* Check for special sources, and vfolder stuff */
2682 	if (src_store != NULL && src_full_name != NULL) {
2683 
2684 		uid = camel_service_get_uid (CAMEL_SERVICE (src_store));
2685 
2686 		src_is_local =
2687 			(g_strcmp0 (uid, E_MAIL_SESSION_LOCAL_UID) == 0);
2688 		src_is_vfolder =
2689 			(g_strcmp0 (uid, E_MAIL_SESSION_VFOLDER_UID) == 0);
2690 
2691 		/* FIXME: this is a total hack, but i think all we can do at present */
2692 		/* Check for dragging from special folders which can't be moved/copied */
2693 
2694 		/* Don't allow moving any of the special local folders. */
2695 		if (src_is_local && is_special_local_folder (src_full_name)) {
2696 			GdkAtom xfolder;
2697 
2698 			/* force copy for special local folders */
2699 			*suggested_action = GDK_ACTION_COPY;
2700 			*actions = GDK_ACTION_COPY;
2701 			xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
2702 			while (targets != NULL) {
2703 				if (targets->data == (gpointer) xfolder) {
2704 					atom = xfolder;
2705 					goto done;
2706 				}
2707 
2708 				targets = targets->next;
2709 			}
2710 
2711 			goto done;
2712 		}
2713 
2714 		/* Don't allow copying/moving the UNMATCHED vfolder. */
2715 		if (src_is_vfolder)
2716 			if (g_strcmp0 (src_full_name, CAMEL_UNMATCHED_NAME) == 0)
2717 				goto done;
2718 
2719 		/* Don't allow copying/moving any vTrash folder. */
2720 		if (g_strcmp0 (src_full_name, CAMEL_VTRASH_NAME) == 0)
2721 			goto done;
2722 
2723 		/* Don't allow copying/moving any vJunk folder. */
2724 		if (g_strcmp0 (src_full_name, CAMEL_VJUNK_NAME) == 0)
2725 			goto done;
2726 
2727 		/* Don't allow copying/moving any maildir 'inbox'. */
2728 		if (g_strcmp0 (src_full_name, ".") == 0)
2729 			goto done;
2730 
2731 		/* Search Folders can only be dropped into other
2732 		 * Search Folders. */
2733 		if (src_is_vfolder) {
2734 			/* force move only for vfolders */
2735 			*suggested_action = GDK_ACTION_MOVE;
2736 
2737 			if (dst_is_vfolder) {
2738 				GdkAtom xfolder;
2739 
2740 				xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
2741 				while (targets != NULL) {
2742 					if (targets->data == (gpointer) xfolder) {
2743 						atom = xfolder;
2744 						goto done;
2745 					}
2746 
2747 					targets = targets->next;
2748 				}
2749 			}
2750 
2751 			goto done;
2752 		}
2753 	}
2754 
2755 	/* Can't drag anything but a Search Folder into a Search Folder. */
2756 	if (dst_is_vfolder)
2757 		goto done;
2758 
2759 	/* Now we either have a store or a normal folder. */
2760 
2761 	if (is_store) {
2762 		GdkAtom xfolder;
2763 
2764 		xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
2765 		while (targets != NULL) {
2766 			if (targets->data == (gpointer) xfolder) {
2767 				atom = xfolder;
2768 				goto done;
2769 			}
2770 
2771 			targets = targets->next;
2772 		}
2773 	} else {
2774 		GList *link;
2775 		gint ii;
2776 
2777 		/* The drop_atoms[] is sorted in the preference order. */
2778 		for (ii = 0; ii < NUM_DROP_TYPES; ii++) {
2779 			for (link = targets; link; link = g_list_next (link)) {
2780 				if (link->data == (gpointer) drop_atoms[ii]) {
2781 					atom = drop_atoms[ii];
2782 					goto done;
2783 				}
2784 			}
2785 		}
2786 	}
2787 
2788 done:
2789 	g_free (dst_full_name);
2790 	g_free (src_full_name);
2791 	g_clear_object (&dst_store);
2792 	g_clear_object (&src_store);
2793 
2794 	return atom;
2795 }
2796 
2797 static gboolean
tree_drag_drop(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,EMFolderTree * folder_tree)2798 tree_drag_drop (GtkWidget *widget,
2799                 GdkDragContext *context,
2800                 gint x,
2801                 gint y,
2802                 guint time,
2803                 EMFolderTree *folder_tree)
2804 {
2805 	EMFolderTreePrivate *priv = folder_tree->priv;
2806 	GtkTreeViewColumn *column;
2807 	GtkTreeView *tree_view;
2808 	gint cell_x, cell_y;
2809 	GdkDragAction actions;
2810 	GdkDragAction suggested_action;
2811 	GtkTreePath *path;
2812 	GdkAtom target;
2813 
2814 	tree_view = GTK_TREE_VIEW (folder_tree);
2815 
2816 	if (priv->autoscroll_id != 0) {
2817 		g_source_remove (priv->autoscroll_id);
2818 		priv->autoscroll_id = 0;
2819 	}
2820 
2821 	if (priv->autoexpand_id != 0) {
2822 		gtk_tree_row_reference_free (priv->autoexpand_row);
2823 		priv->autoexpand_row = NULL;
2824 
2825 		g_source_remove (priv->autoexpand_id);
2826 		priv->autoexpand_id = 0;
2827 	}
2828 
2829 	if (!gtk_tree_view_get_path_at_pos (
2830 		tree_view, x, y, &path, &column, &cell_x, &cell_y))
2831 		return FALSE;
2832 
2833 	target = folder_tree_drop_target (
2834 		folder_tree, context, path,
2835 		&actions, &suggested_action);
2836 
2837 	gtk_tree_path_free (path);
2838 
2839 	return (target != GDK_NONE);
2840 }
2841 
2842 static void
tree_drag_end(GtkWidget * widget,GdkDragContext * context,EMFolderTree * folder_tree)2843 tree_drag_end (GtkWidget *widget,
2844                GdkDragContext *context,
2845                EMFolderTree *folder_tree)
2846 {
2847 	EMFolderTreePrivate *priv = folder_tree->priv;
2848 
2849 	g_clear_pointer (&priv->drag_row, gtk_tree_row_reference_free);
2850 
2851 	/* FIXME: undo anything done in drag-begin */
2852 }
2853 
2854 static void
tree_drag_leave(GtkWidget * widget,GdkDragContext * context,guint time,EMFolderTree * folder_tree)2855 tree_drag_leave (GtkWidget *widget,
2856                  GdkDragContext *context,
2857                  guint time,
2858                  EMFolderTree *folder_tree)
2859 {
2860 	EMFolderTreePrivate *priv = folder_tree->priv;
2861 	GtkTreeView *tree_view;
2862 
2863 	tree_view = GTK_TREE_VIEW (folder_tree);
2864 
2865 	if (priv->autoscroll_id != 0) {
2866 		g_source_remove (priv->autoscroll_id);
2867 		priv->autoscroll_id = 0;
2868 	}
2869 
2870 	if (priv->autoexpand_id != 0) {
2871 		gtk_tree_row_reference_free (priv->autoexpand_row);
2872 		priv->autoexpand_row = NULL;
2873 
2874 		g_source_remove (priv->autoexpand_id);
2875 		priv->autoexpand_id = 0;
2876 	}
2877 
2878 	gtk_tree_view_set_drag_dest_row (
2879 		tree_view, NULL, GTK_TREE_VIEW_DROP_BEFORE);
2880 }
2881 
2882 #define SCROLL_EDGE_SIZE 15
2883 
2884 static gboolean
tree_autoscroll(gpointer user_data)2885 tree_autoscroll (gpointer user_data)
2886 {
2887 	EMFolderTree *folder_tree;
2888 	GtkAdjustment *adjustment;
2889 	GtkTreeView *tree_view;
2890 	GtkScrollable *scrollable;
2891 	GdkRectangle rect;
2892 	GdkWindow *window;
2893 	GdkDisplay *display;
2894 	GdkDeviceManager *device_manager;
2895 	GdkDevice *device;
2896 	gdouble value;
2897 	gint offset, y;
2898 
2899 	folder_tree = EM_FOLDER_TREE (user_data);
2900 
2901 	/* Get the y pointer position relative to the treeview. */
2902 	tree_view = GTK_TREE_VIEW (folder_tree);
2903 	window = gtk_tree_view_get_bin_window (tree_view);
2904 	display = gdk_window_get_display (window);
2905 	device_manager = gdk_display_get_device_manager (display);
2906 	device = gdk_device_manager_get_client_pointer (device_manager);
2907 	gdk_window_get_device_position (window, device, NULL, &y, NULL);
2908 
2909 	/* Rect is in coorinates relative to the scrolled window,
2910 	 * relative to the treeview. */
2911 	gtk_tree_view_get_visible_rect (tree_view, &rect);
2912 
2913 	/* Move y into the same coordinate system as rect. */
2914 	y += rect.y;
2915 
2916 	/* See if we are near the top edge. */
2917 	offset = y - (rect.y + 2 * SCROLL_EDGE_SIZE);
2918 	if (offset > 0) {
2919 		/* See if we are near the bottom edge. */
2920 		offset = y - (rect.y + rect.height - 2 * SCROLL_EDGE_SIZE);
2921 		if (offset < 0)
2922 			return TRUE;
2923 	}
2924 
2925 	scrollable = GTK_SCROLLABLE (folder_tree);
2926 	adjustment = gtk_scrollable_get_vadjustment (scrollable);
2927 	value = gtk_adjustment_get_value (adjustment);
2928 	gtk_adjustment_set_value (adjustment, MAX (value + offset, 0.0));
2929 
2930 	return TRUE;
2931 }
2932 
2933 static gboolean
tree_autoexpand(gpointer user_data)2934 tree_autoexpand (gpointer user_data)
2935 {
2936 	EMFolderTreePrivate *priv;
2937 	GtkTreeView *tree_view;
2938 	GtkTreePath *path;
2939 
2940 	tree_view = GTK_TREE_VIEW (user_data);
2941 	priv = EM_FOLDER_TREE_GET_PRIVATE (tree_view);
2942 
2943 	path = gtk_tree_row_reference_get_path (priv->autoexpand_row);
2944 	gtk_tree_view_expand_row (tree_view, path, FALSE);
2945 	gtk_tree_path_free (path);
2946 
2947 	return TRUE;
2948 }
2949 
2950 static gboolean
tree_drag_motion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,EMFolderTree * folder_tree)2951 tree_drag_motion (GtkWidget *widget,
2952                   GdkDragContext *context,
2953                   gint x,
2954                   gint y,
2955                   guint time,
2956                   EMFolderTree *folder_tree)
2957 {
2958 	EMFolderTreePrivate *priv = folder_tree->priv;
2959 	GtkTreeViewDropPosition pos;
2960 	GtkTreeView *tree_view;
2961 	GtkTreeModel *model;
2962 	GdkDragAction actions;
2963 	GdkDragAction suggested_action;
2964 	GdkDragAction chosen_action = 0;
2965 	GtkTreePath *path = NULL;
2966 	GtkTreeIter iter;
2967 	GdkAtom target;
2968 	gint i;
2969 
2970 	tree_view = GTK_TREE_VIEW (folder_tree);
2971 	model = gtk_tree_view_get_model (tree_view);
2972 
2973 	if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &path, &pos))
2974 		return FALSE;
2975 
2976 	if (priv->autoscroll_id == 0) {
2977 		priv->autoscroll_id = e_named_timeout_add (
2978 			150, tree_autoscroll, folder_tree);
2979 	}
2980 
2981 	gtk_tree_model_get_iter (model, &iter, path);
2982 
2983 	if (gtk_tree_model_iter_has_child (model, &iter) &&
2984 		!gtk_tree_view_row_expanded (tree_view, path)) {
2985 
2986 		if (priv->autoexpand_id != 0) {
2987 			GtkTreePath *autoexpand_path;
2988 
2989 			autoexpand_path = gtk_tree_row_reference_get_path (
2990 				priv->autoexpand_row);
2991 			if (gtk_tree_path_compare (autoexpand_path, path) != 0) {
2992 				/* row changed, restart timer */
2993 				gtk_tree_row_reference_free (priv->autoexpand_row);
2994 				priv->autoexpand_row =
2995 					gtk_tree_row_reference_new (model, path);
2996 				g_source_remove (priv->autoexpand_id);
2997 				priv->autoexpand_id = e_named_timeout_add (
2998 					600, tree_autoexpand, folder_tree);
2999 			}
3000 
3001 			gtk_tree_path_free (autoexpand_path);
3002 		} else {
3003 			priv->autoexpand_id = e_named_timeout_add (
3004 				600, tree_autoexpand, folder_tree);
3005 			priv->autoexpand_row =
3006 				gtk_tree_row_reference_new (model, path);
3007 		}
3008 	} else if (priv->autoexpand_id != 0) {
3009 		gtk_tree_row_reference_free (priv->autoexpand_row);
3010 		priv->autoexpand_row = NULL;
3011 
3012 		g_source_remove (priv->autoexpand_id);
3013 		priv->autoexpand_id = 0;
3014 	}
3015 
3016 	target = folder_tree_drop_target (
3017 		folder_tree, context, path,
3018 		&actions, &suggested_action);
3019 	for (i = 0; target != GDK_NONE && i < NUM_DROP_TYPES; i++) {
3020 		if (drop_atoms[i] != target)
3021 			continue;
3022 		switch (i) {
3023 			case DND_DROP_TYPE_UID_LIST:
3024 			case DND_DROP_TYPE_FOLDER:
3025 				chosen_action = suggested_action;
3026 				if (chosen_action == GDK_ACTION_COPY &&
3027 				   (actions & GDK_ACTION_MOVE))
3028 					chosen_action = GDK_ACTION_MOVE;
3029 				gtk_tree_view_set_drag_dest_row (
3030 					tree_view, path,
3031 					GTK_TREE_VIEW_DROP_INTO_OR_AFTER);
3032 				break;
3033 			default:
3034 				gtk_tree_view_set_drag_dest_row (
3035 					tree_view, path,
3036 					GTK_TREE_VIEW_DROP_INTO_OR_AFTER);
3037 				chosen_action = suggested_action;
3038 				break;
3039 		}
3040 
3041 		break;
3042 	}
3043 
3044 	gdk_drag_status (context, chosen_action, time);
3045 	gtk_tree_path_free (path);
3046 
3047 	return chosen_action != 0;
3048 }
3049 
3050 void
em_folder_tree_enable_drag_and_drop(EMFolderTree * folder_tree)3051 em_folder_tree_enable_drag_and_drop (EMFolderTree *folder_tree)
3052 {
3053 	GtkTreeView *tree_view;
3054 	static gint setup = 0;
3055 	gint i;
3056 
3057 	g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
3058 
3059 	tree_view = GTK_TREE_VIEW (folder_tree);
3060 
3061 	if (!setup) {
3062 		for (i = 0; i < NUM_DRAG_TYPES; i++)
3063 			drag_atoms[i] = gdk_atom_intern (drag_types[i].target, FALSE);
3064 
3065 		for (i = 0; i < NUM_DROP_TYPES; i++)
3066 			drop_atoms[i] = gdk_atom_intern (drop_types[i].target, FALSE);
3067 
3068 		setup = 1;
3069 	}
3070 
3071 	gtk_drag_source_set (
3072 		GTK_WIDGET (tree_view), GDK_BUTTON1_MASK,drag_types,
3073 		NUM_DRAG_TYPES, GDK_ACTION_COPY | GDK_ACTION_MOVE);
3074 	gtk_drag_dest_set (
3075 		GTK_WIDGET (tree_view), GTK_DEST_DEFAULT_ALL, drop_types,
3076 		NUM_DROP_TYPES, GDK_ACTION_COPY | GDK_ACTION_MOVE);
3077 
3078 	g_signal_connect (
3079 		tree_view, "drag-begin",
3080 		G_CALLBACK (tree_drag_begin), folder_tree);
3081 	g_signal_connect (
3082 		tree_view, "drag-data-get",
3083 		G_CALLBACK (tree_drag_data_get), folder_tree);
3084 	g_signal_connect (
3085 		tree_view, "drag-data-received",
3086 		G_CALLBACK (tree_drag_data_received), folder_tree);
3087 	g_signal_connect (
3088 		tree_view, "drag-drop",
3089 		G_CALLBACK (tree_drag_drop), folder_tree);
3090 	g_signal_connect (
3091 		tree_view, "drag-end",
3092 		G_CALLBACK (tree_drag_end), folder_tree);
3093 	g_signal_connect (
3094 		tree_view, "drag-leave",
3095 		G_CALLBACK (tree_drag_leave), folder_tree);
3096 	g_signal_connect (
3097 		tree_view, "drag-motion",
3098 		G_CALLBACK (tree_drag_motion), folder_tree);
3099 }
3100 
3101 void
em_folder_tree_set_excluded(EMFolderTree * folder_tree,guint32 flags)3102 em_folder_tree_set_excluded (EMFolderTree *folder_tree,
3103                              guint32 flags)
3104 {
3105 	g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
3106 
3107 	folder_tree->priv->excluded = flags;
3108 }
3109 
3110 void
em_folder_tree_set_excluded_func(EMFolderTree * folder_tree,EMFTExcludeFunc exclude,gpointer data)3111 em_folder_tree_set_excluded_func (EMFolderTree *folder_tree,
3112                                   EMFTExcludeFunc exclude,
3113                                   gpointer data)
3114 {
3115 	g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
3116 	g_return_if_fail (exclude != NULL);
3117 
3118 	folder_tree->priv->excluded_func = exclude;
3119 	folder_tree->priv->excluded_data = data;
3120 }
3121 
3122 GList *
em_folder_tree_get_selected_uris(EMFolderTree * folder_tree)3123 em_folder_tree_get_selected_uris (EMFolderTree *folder_tree)
3124 {
3125 	GtkTreeSelection *selection;
3126 	GtkTreeView *tree_view;
3127 	GtkTreeModel *model;
3128 	GList *list = NULL, *rows, *l;
3129 	GSList *sl;
3130 
3131 	tree_view = GTK_TREE_VIEW (folder_tree);
3132 	selection = gtk_tree_view_get_selection (tree_view);
3133 
3134 	/* at first, add lost uris */
3135 	for (sl = folder_tree->priv->select_uris; sl; sl = g_slist_next (sl)) {
3136 		const gchar *uri;
3137 		uri = ((struct _selected_uri *) sl->data)->uri;
3138 		list = g_list_append (list, g_strdup (uri));
3139 	}
3140 
3141 	rows = gtk_tree_selection_get_selected_rows (selection, &model);
3142 	for (l = rows; l; l = g_list_next (l)) {
3143 		GtkTreeIter iter;
3144 		GtkTreePath *path = l->data;
3145 
3146 		if (gtk_tree_model_get_iter (model, &iter, path)) {
3147 			CamelStore *store;
3148 			gchar *folder_name;
3149 
3150 			gtk_tree_model_get (
3151 				model, &iter,
3152 				COL_OBJECT_CAMEL_STORE, &store,
3153 				COL_STRING_FULL_NAME, &folder_name, -1);
3154 
3155 			if (CAMEL_IS_STORE (store) && folder_name != NULL) {
3156 				gchar *folder_uri;
3157 
3158 				folder_uri = e_mail_folder_uri_build (
3159 					store, folder_name);
3160 				list = g_list_prepend (list, folder_uri);
3161 			}
3162 
3163 			g_free (folder_name);
3164 			g_clear_object (&store);
3165 		}
3166 		gtk_tree_path_free (path);
3167 	}
3168 	g_list_free (rows);
3169 
3170 	return g_list_reverse (list);
3171 }
3172 
3173 static void
get_selected_uris_path_iterate(GtkTreeModel * model,GtkTreePath * treepath,GtkTreeIter * iter,gpointer data)3174 get_selected_uris_path_iterate (GtkTreeModel *model,
3175                                 GtkTreePath *treepath,
3176                                 GtkTreeIter *iter,
3177                                 gpointer data)
3178 {
3179 	GList **list = (GList **) data;
3180 	gchar *full_name;
3181 
3182 	gtk_tree_model_get (model, iter, COL_STRING_FULL_NAME, &full_name, -1);
3183 	*list = g_list_append (*list, full_name);
3184 }
3185 
3186 GList *
em_folder_tree_get_selected_paths(EMFolderTree * folder_tree)3187 em_folder_tree_get_selected_paths (EMFolderTree *folder_tree)
3188 {
3189 	GtkTreeSelection *selection;
3190 	GtkTreeView *tree_view;
3191 	GList *list = NULL;
3192 
3193 	tree_view = GTK_TREE_VIEW (folder_tree);
3194 	selection = gtk_tree_view_get_selection (tree_view);
3195 
3196 	gtk_tree_selection_selected_foreach (
3197 		selection, get_selected_uris_path_iterate, &list);
3198 
3199 	return list;
3200 }
3201 
3202 void
em_folder_tree_set_selected_list(EMFolderTree * folder_tree,GList * list,gboolean expand_only)3203 em_folder_tree_set_selected_list (EMFolderTree *folder_tree,
3204                                   GList *list,
3205                                   gboolean expand_only)
3206 {
3207 	EMFolderTreePrivate *priv = folder_tree->priv;
3208 	EMailSession *session;
3209 
3210 	session = em_folder_tree_get_session (folder_tree);
3211 
3212 	/* FIXME: need to remove any currently selected stuff? */
3213 	if (!expand_only)
3214 		folder_tree_clear_selected_list (folder_tree);
3215 
3216 	for (; list; list = list->next) {
3217 		CamelStore *store;
3218 		struct _selected_uri *u;
3219 		const gchar *folder_uri;
3220 		const gchar *uid;
3221 		gchar *folder_name;
3222 		gchar *expand_key;
3223 		gchar *end;
3224 		gboolean success;
3225 
3226 		/* This makes sure all our parents up to the root are
3227 		 * expanded. */
3228 
3229 		folder_uri = list->data;
3230 
3231 		success = e_mail_folder_uri_parse (
3232 			CAMEL_SESSION (session), folder_uri,
3233 			&store, &folder_name, NULL);
3234 
3235 		if (!success)
3236 			continue;
3237 
3238 		uid = camel_service_get_uid (CAMEL_SERVICE (store));
3239 		expand_key = g_strdup_printf ("%s/%s", uid, folder_name);
3240 		g_free (folder_name);
3241 
3242 		u = g_malloc0 (sizeof (*u));
3243 		u->uri = g_strdup (folder_uri);
3244 		u->service = CAMEL_SERVICE (store);  /* takes ownership */
3245 		u->key = g_strdup (expand_key);
3246 
3247 		if (!expand_only) {
3248 			g_hash_table_insert (
3249 				priv->select_uris_table, u->key, u);
3250 			priv->select_uris =
3251 				g_slist_append (priv->select_uris, u);
3252 		}
3253 
3254 		while (end = strrchr (expand_key, '/'), end) {
3255 			folder_tree_expand_node (expand_key, folder_tree);
3256 			*end = 0;
3257 		}
3258 
3259 		if (expand_only)
3260 			folder_tree_free_select_uri (u);
3261 
3262 		g_free (expand_key);
3263 	}
3264 }
3265 
3266 #if 0
3267 static void
3268 dump_fi (CamelFolderInfo *fi,
3269          gint depth)
3270 {
3271 	gint i;
3272 
3273 	while (fi != NULL) {
3274 		for (i = 0; i < depth; i++)
3275 			fputs ("  ", stdout);
3276 
3277 		printf ("path='%s'; full_name='%s'\n", fi->path, fi->full_name);
3278 
3279 		if (fi->child)
3280 			dump_fi (fi->child, depth + 1);
3281 
3282 		fi = fi->sibling;
3283 	}
3284 }
3285 #endif
3286 
3287 void
em_folder_tree_set_selected(EMFolderTree * folder_tree,const gchar * uri,gboolean expand_only)3288 em_folder_tree_set_selected (EMFolderTree *folder_tree,
3289                              const gchar *uri,
3290                              gboolean expand_only)
3291 {
3292 	GList *l = NULL;
3293 
3294 	g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
3295 
3296 	if (uri && uri[0])
3297 		l = g_list_append (l, (gpointer) uri);
3298 
3299 	em_folder_tree_set_selected_list (folder_tree, l, expand_only);
3300 	g_list_free (l);
3301 }
3302 
3303 gboolean
em_folder_tree_select_next_path(EMFolderTree * folder_tree,gboolean skip_read_folders)3304 em_folder_tree_select_next_path (EMFolderTree *folder_tree,
3305                                  gboolean skip_read_folders)
3306 {
3307 	GtkTreeView *tree_view;
3308 	GtkTreeSelection *selection;
3309 	GtkTreeModel *model;
3310 	GtkTreeIter iter, parent, child;
3311 	GtkTreePath *current_path = NULL, *path = NULL;
3312 	gboolean changed = FALSE;
3313 	guint unread = 0;
3314 	EMFolderTreePrivate *priv;
3315 
3316 	g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), FALSE);
3317 
3318 	priv = folder_tree->priv;
3319 	tree_view = GTK_TREE_VIEW (folder_tree);
3320 	selection = gtk_tree_view_get_selection (tree_view);
3321 	if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
3322 
3323 		current_path = gtk_tree_model_get_path (model, &iter);
3324 
3325 		do {
3326 			g_clear_pointer (&path, gtk_tree_path_free);
3327 
3328 			if (gtk_tree_model_iter_has_child (model, &iter)) {
3329 				if (!gtk_tree_model_iter_children (model, &child, &iter))
3330 					break;
3331 				path = gtk_tree_model_get_path (model, &child);
3332 				iter = child;
3333 			} else {
3334 				while (1) {
3335 					gboolean has_parent;
3336 
3337 					has_parent = gtk_tree_model_iter_parent (
3338 						model, &parent, &iter);
3339 
3340 					if (gtk_tree_model_iter_next (model, &iter)) {
3341 						path = gtk_tree_model_get_path (model, &iter);
3342 						break;
3343 					} else {
3344 						if (has_parent) {
3345 							iter = parent;
3346 						} else {
3347 							/* Reached end. Wrapup*/
3348 							if (gtk_tree_model_get_iter_first (model, &iter))
3349 								path = gtk_tree_model_get_path (model, &iter);
3350 							else
3351 								path = NULL;
3352 							break;
3353 						}
3354 					}
3355 				}
3356 
3357 				if (!path)
3358 					break;
3359 			}
3360 			gtk_tree_model_get (model, &iter, COL_UINT_UNREAD, &unread, -1);
3361 
3362 		/* TODO : Flags here for better options */
3363 		} while (skip_read_folders && unread <=0 &&
3364 			gtk_tree_path_compare (current_path, path));
3365 	}
3366 
3367 	if (current_path && path) {
3368 		if (gtk_tree_path_compare (current_path, path) != 0) {
3369 			if (!gtk_tree_view_row_expanded (tree_view, path))
3370 				gtk_tree_view_expand_to_path (tree_view, path);
3371 
3372 			gtk_tree_selection_select_path (selection, path);
3373 
3374 			if (!priv->cursor_set) {
3375 				gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
3376 				priv->cursor_set = TRUE;
3377 			}
3378 
3379 			gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.5f, 0.0f);
3380 
3381 			changed = TRUE;
3382 		}
3383 	}
3384 
3385 	if (path)
3386 		gtk_tree_path_free (path);
3387 
3388 	if (current_path)
3389 		gtk_tree_path_free (current_path);
3390 
3391 	return changed;
3392 }
3393 
3394 static gboolean
folder_tree_descend(GtkTreeModel * model,GtkTreeIter * iter,GtkTreeIter * root)3395 folder_tree_descend (GtkTreeModel *model,
3396                      GtkTreeIter *iter,
3397                      GtkTreeIter *root)
3398 {
3399 	GtkTreeIter parent;
3400 	gint n_children;
3401 
3402 	/* Finds the rightmost descendant of the given root. */
3403 
3404 	if (root == NULL) {
3405 		n_children = gtk_tree_model_iter_n_children (model, NULL);
3406 
3407 		/* This will invalidate the iterator and return FALSE. */
3408 		if (n_children == 0)
3409 			return gtk_tree_model_get_iter_first (model, iter);
3410 
3411 		if (!gtk_tree_model_iter_nth_child (model, &parent, NULL, n_children - 1))
3412 			return FALSE;
3413 	} else
3414 		parent = *root;
3415 
3416 	n_children = gtk_tree_model_iter_n_children (model, &parent);
3417 
3418 	while (n_children > 0) {
3419 		GtkTreeIter child;
3420 
3421 		if (!gtk_tree_model_iter_nth_child (model, &child, &parent, n_children - 1))
3422 			break;
3423 
3424 		parent = child;
3425 
3426 		n_children = gtk_tree_model_iter_n_children (model, &parent);
3427 	}
3428 
3429 	*iter = parent;
3430 
3431 	return TRUE;
3432 }
3433 
3434 gboolean
em_folder_tree_select_prev_path(EMFolderTree * folder_tree,gboolean skip_read_folders)3435 em_folder_tree_select_prev_path (EMFolderTree *folder_tree,
3436                                  gboolean skip_read_folders)
3437 {
3438 	GtkTreeView *tree_view;
3439 	GtkTreeSelection *selection;
3440 	GtkTreeModel *model;
3441 	GtkTreePath *path = NULL;
3442 	GtkTreePath *sentinel;
3443 	GtkTreeIter iter;
3444 	guint unread = 0;
3445 	gboolean changed = FALSE;
3446 	EMFolderTreePrivate *priv;
3447 
3448 	g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), FALSE);
3449 
3450 	priv = folder_tree->priv;
3451 
3452 	tree_view = GTK_TREE_VIEW (folder_tree);
3453 	selection = gtk_tree_view_get_selection (tree_view);
3454 
3455 	/* Nothing selected means nothing to do. */
3456 	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
3457 		return FALSE;
3458 
3459 	/* This prevents us from looping over the model indefinitely,
3460 	 * looking for unread messages when there are none. */
3461 	sentinel = gtk_tree_model_get_path (model, &iter);
3462 
3463 	do {
3464 		GtkTreeIter descendant;
3465 
3466 		if (path != NULL)
3467 			gtk_tree_path_free (path);
3468 
3469 		path = gtk_tree_model_get_path (model, &iter);
3470 
3471 		if (gtk_tree_path_prev (path)) {
3472 			gtk_tree_model_get_iter (model, &iter, path);
3473 			folder_tree_descend (model, &descendant, &iter);
3474 
3475 			gtk_tree_path_free (path);
3476 			path = gtk_tree_model_get_path (model, &descendant);
3477 
3478 		} else if (gtk_tree_path_get_depth (path) > 1) {
3479 			gtk_tree_path_up (path);
3480 
3481 		} else {
3482 			folder_tree_descend (model, &descendant, NULL);
3483 
3484 			gtk_tree_path_free (path);
3485 			path = gtk_tree_model_get_path (model, &descendant);
3486 		}
3487 
3488 		gtk_tree_model_get_iter (model, &iter, path);
3489 		gtk_tree_model_get (model, &iter, COL_UINT_UNREAD, &unread, -1);
3490 
3491 	} while (skip_read_folders && unread <= 0 &&
3492 		gtk_tree_path_compare (path, sentinel) != 0);
3493 
3494 	if (gtk_tree_path_compare (path, sentinel) != 0) {
3495 		if (!gtk_tree_view_row_expanded (tree_view, path))
3496 			gtk_tree_view_expand_to_path (tree_view, path);
3497 
3498 		gtk_tree_selection_select_path (selection, path);
3499 
3500 		if (!priv->cursor_set) {
3501 			gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
3502 			priv->cursor_set = TRUE;
3503 		}
3504 
3505 		gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.5f, 0.0f);
3506 
3507 		changed = TRUE;
3508 	}
3509 
3510 	gtk_tree_path_free (sentinel);
3511 	gtk_tree_path_free (path);
3512 
3513 	return changed;
3514 }
3515 
3516 void
em_folder_tree_edit_selected(EMFolderTree * folder_tree)3517 em_folder_tree_edit_selected (EMFolderTree *folder_tree)
3518 {
3519 	GtkTreeSelection *selection;
3520 	GtkTreeViewColumn *column;
3521 	GtkCellRenderer *renderer;
3522 	GtkTreeView *tree_view;
3523 	GtkTreeModel *model;
3524 	GtkTreePath *path = NULL;
3525 	GtkTreeIter iter;
3526 
3527 	g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
3528 
3529 	tree_view = GTK_TREE_VIEW (folder_tree);
3530 	column = gtk_tree_view_get_column (tree_view, 0);
3531 	selection = gtk_tree_view_get_selection (tree_view);
3532 	renderer = folder_tree->priv->text_renderer;
3533 
3534 	if (gtk_tree_selection_get_selected (selection, &model, &iter))
3535 		path = gtk_tree_model_get_path (model, &iter);
3536 
3537 	if (path == NULL)
3538 		return;
3539 
3540 	/* Make the text cell renderer editable, but only temporarily.
3541 	 * We don't want editing to be activated by simply clicking on
3542 	 * the folder name.  Too easy for accidental edits to occur. */
3543 	g_object_set (renderer, "editable", TRUE, NULL);
3544 	gtk_tree_view_expand_to_path (tree_view, path);
3545 	gtk_tree_view_set_cursor_on_cell (
3546 		tree_view, path, column, renderer, TRUE);
3547 	g_object_set (renderer, "editable", FALSE, NULL);
3548 
3549 	gtk_tree_path_free (path);
3550 }
3551 
3552 gboolean
em_folder_tree_get_selected(EMFolderTree * folder_tree,CamelStore ** out_store,gchar ** out_folder_name)3553 em_folder_tree_get_selected (EMFolderTree *folder_tree,
3554                              CamelStore **out_store,
3555                              gchar **out_folder_name)
3556 {
3557 	GtkTreeView *tree_view;
3558 	GtkTreeSelection *selection;
3559 	GtkTreeModel *model;
3560 	GtkTreeIter iter;
3561 	CamelStore *store = NULL;
3562 	gchar *folder_name = NULL;
3563 
3564 	g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), FALSE);
3565 
3566 	tree_view = GTK_TREE_VIEW (folder_tree);
3567 	selection = gtk_tree_view_get_selection (tree_view);
3568 
3569 	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
3570 		return FALSE;
3571 
3572 	gtk_tree_model_get (
3573 		model, &iter,
3574 		COL_OBJECT_CAMEL_STORE, &store,
3575 		COL_STRING_FULL_NAME, &folder_name, -1);
3576 
3577 	/* We should always get a valid store. */
3578 	g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
3579 
3580 	/* If a store is selected, the folder name will be NULL.
3581 	 * Treat this as though nothing is selected, so that callers
3582 	 * can assume a TRUE return value means a folder is selected. */
3583 	if (folder_name == NULL) {
3584 		g_clear_object (&store);
3585 		return FALSE;
3586 	}
3587 
3588 	if (out_store != NULL)
3589 		*out_store = g_object_ref (store);
3590 
3591 	if (out_folder_name != NULL)
3592 		*out_folder_name = folder_name;
3593 	else
3594 		g_free (folder_name);
3595 
3596 	g_clear_object (&store);
3597 
3598 	return TRUE;
3599 }
3600 
3601 gboolean
em_folder_tree_store_root_selected(EMFolderTree * folder_tree,CamelStore ** out_store)3602 em_folder_tree_store_root_selected (EMFolderTree *folder_tree,
3603                                     CamelStore **out_store)
3604 {
3605 	GtkTreeView *tree_view;
3606 	GtkTreeSelection *selection;
3607 	GtkTreeModel *model;
3608 	GtkTreeIter iter;
3609 	CamelStore *store = NULL;
3610 	gboolean is_store = FALSE;
3611 
3612 	g_return_val_if_fail (folder_tree != NULL, FALSE);
3613 	g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), FALSE);
3614 
3615 	tree_view = GTK_TREE_VIEW (folder_tree);
3616 	selection = gtk_tree_view_get_selection (tree_view);
3617 
3618 	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
3619 		return FALSE;
3620 
3621 	gtk_tree_model_get (
3622 		model, &iter,
3623 		COL_OBJECT_CAMEL_STORE, &store,
3624 		COL_BOOL_IS_STORE, &is_store, -1);
3625 
3626 	/* We should always get a valid store. */
3627 	g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
3628 
3629 	if (!is_store) {
3630 		g_clear_object (&store);
3631 		return FALSE;
3632 	}
3633 
3634 	if (out_store != NULL)
3635 		*out_store = g_object_ref (store);
3636 
3637 	g_clear_object (&store);
3638 
3639 	return TRUE;
3640 }
3641 
3642 gchar *
em_folder_tree_get_selected_uri(EMFolderTree * folder_tree)3643 em_folder_tree_get_selected_uri (EMFolderTree *folder_tree)
3644 {
3645 	GtkTreeView *tree_view;
3646 	GtkTreeSelection *selection;
3647 	GtkTreeModel *model;
3648 	GtkTreeIter iter;
3649 	CamelStore *store;
3650 	gchar *folder_name;
3651 	gchar *folder_uri = NULL;
3652 
3653 	g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
3654 
3655 	tree_view = GTK_TREE_VIEW (folder_tree);
3656 	selection = gtk_tree_view_get_selection (tree_view);
3657 
3658 	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
3659 		return NULL;
3660 
3661 	gtk_tree_model_get (
3662 		model, &iter,
3663 		COL_OBJECT_CAMEL_STORE, &store,
3664 		COL_STRING_FULL_NAME, &folder_name, -1);
3665 
3666 	/* We should always get a valid store. */
3667 	g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
3668 
3669 	if (folder_name != NULL)
3670 		folder_uri = e_mail_folder_uri_build (store, folder_name);
3671 	else
3672 		folder_uri = e_mail_folder_uri_build (store, "");
3673 
3674 	g_free (folder_name);
3675 	g_clear_object (&store);
3676 
3677 	return folder_uri;
3678 }
3679 
3680 /**
3681  * em_folder_tree_ref_selected_store:
3682  * @folder_tree: an #EMFolderTree
3683  *
3684  * Returns the #CamelStore for the selected row in @folder_tree, or %NULL
3685  * if no row is selected.
3686  *
3687  * The returned #CamelStore is referenced for thread-safety and must be
3688  * unreferenced with g_object_unref() when finished with it.
3689  *
3690  * Returns: a #CamelStore, or %NULL
3691  **/
3692 CamelStore *
em_folder_tree_ref_selected_store(EMFolderTree * folder_tree)3693 em_folder_tree_ref_selected_store (EMFolderTree *folder_tree)
3694 {
3695 	GtkTreeView *tree_view;
3696 	GtkTreeSelection *selection;
3697 	GtkTreeModel *model;
3698 	GtkTreeIter iter;
3699 	CamelStore *store = NULL;
3700 
3701 	g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
3702 
3703 	/* Don't use em_folder_tree_get_selected() here because we
3704 	 * want this to work whether a folder or store is selected. */
3705 
3706 	tree_view = GTK_TREE_VIEW (folder_tree);
3707 	selection = gtk_tree_view_get_selection (tree_view);
3708 
3709 	if (gtk_tree_selection_get_selected (selection, &model, &iter))
3710 		gtk_tree_model_get (
3711 			model, &iter,
3712 			COL_OBJECT_CAMEL_STORE, &store, -1);
3713 
3714 	return store;
3715 }
3716 
3717 void
em_folder_tree_set_skip_double_click(EMFolderTree * folder_tree,gboolean skip)3718 em_folder_tree_set_skip_double_click (EMFolderTree *folder_tree,
3719                                       gboolean skip)
3720 {
3721 	folder_tree->priv->skip_double_click = skip;
3722 }
3723 
3724 /* stores come first, then by uri */
3725 static gint
sort_by_store_and_uri(gconstpointer name1,gconstpointer name2)3726 sort_by_store_and_uri (gconstpointer name1,
3727                        gconstpointer name2)
3728 {
3729 	const gchar *n1 = name1, *n2 = name2;
3730 	gboolean is_store1, is_store2;
3731 
3732 	if (n1 == NULL || n2 == NULL) {
3733 		if (n1 == n2)
3734 			return 0;
3735 		else
3736 			return n1 ? -1 : 1;
3737 	}
3738 
3739 	is_store1 = g_str_has_prefix (n1, "Store ");
3740 	is_store2 = g_str_has_prefix (n2, "Store ");
3741 
3742 	if ((is_store1 || is_store2) && (!is_store1 || !is_store2)) {
3743 		return is_store1 ? -1 : 1;
3744 	}
3745 
3746 	return strcmp (n1, n2);
3747 }
3748 
3749 /* restores state of a tree (collapsed/expanded) as stores in the given key_file */
3750 void
em_folder_tree_restore_state(EMFolderTree * folder_tree,GKeyFile * key_file)3751 em_folder_tree_restore_state (EMFolderTree *folder_tree,
3752                               GKeyFile *key_file)
3753 {
3754 	EMFolderTreeModel *folder_tree_model;
3755 	EMailSession *session;
3756 	GtkTreeModel *tree_model;
3757 	GtkTreeView *tree_view;
3758 	GtkTreeIter iter;
3759 	gboolean valid;
3760 	gchar **groups_arr;
3761 	GSList *groups, *group;
3762 	gint ii;
3763 
3764 	/* Make sure we have a key file to restore state from. */
3765 	if (key_file == NULL)
3766 		return;
3767 
3768 	tree_view = GTK_TREE_VIEW (folder_tree);
3769 	tree_model = gtk_tree_view_get_model (tree_view);
3770 
3771 	folder_tree_model = EM_FOLDER_TREE_MODEL (tree_model);
3772 	session = em_folder_tree_model_get_session (folder_tree_model);
3773 	g_return_if_fail (E_IS_MAIL_SESSION (session));
3774 
3775 	/* Set the initial folder tree expanded state in two stages:
3776 	 *
3777 	 * 1) Iterate over the "Store" and "Folder" state file groups
3778 	 *    and apply the "Expanded" keys where possible.
3779 	 *
3780 	 * 2) Iterate over the top-level nodes in the folder tree
3781 	 *    (these are all stores) and expand those that have no
3782 	 *    corresponding "Expanded" key in the state file.  This
3783 	 *    ensures that new stores are expanded by default.
3784 	 */
3785 
3786 	/* Stage 1 */
3787 
3788 	/* Collapse all so we have a clean slate. */
3789 	gtk_tree_view_collapse_all (tree_view);
3790 
3791 	groups_arr = g_key_file_get_groups (key_file, NULL);
3792 	groups = NULL;
3793 
3794 	for (ii = 0; groups_arr[ii] != NULL; ii++) {
3795 		groups = g_slist_prepend (groups, groups_arr[ii]);
3796 	}
3797 
3798 	groups = g_slist_sort (groups, sort_by_store_and_uri);
3799 
3800 	for (group = groups; group != NULL; group = group->next) {
3801 		GtkTreeRowReference *reference = NULL;
3802 		CamelStore *store = NULL;
3803 		const gchar *group_name = group->data;
3804 		const gchar *key = STATE_KEY_EXPANDED;
3805 		gchar *folder_name = NULL;
3806 		gboolean expanded = FALSE;
3807 		gboolean success = FALSE;
3808 
3809 		if (g_str_has_prefix (group_name, "Store ")) {
3810 			CamelService *service;
3811 			const gchar *uid = group_name + 6;
3812 
3813 			service = camel_session_ref_service (
3814 				CAMEL_SESSION (session), uid);
3815 			if (CAMEL_IS_STORE (service)) {
3816 				store = CAMEL_STORE (g_object_ref (service));
3817 				success = TRUE;
3818 			}
3819 			if (service != NULL)
3820 				g_object_unref (service);
3821 			expanded = TRUE;
3822 
3823 		} else if (g_str_has_prefix (group_name, "Folder ")) {
3824 			const gchar *uri = group_name + 7;
3825 
3826 			success = e_mail_folder_uri_parse (
3827 				CAMEL_SESSION (session), uri,
3828 				&store, &folder_name, NULL);
3829 			expanded = FALSE;
3830 		}
3831 
3832 		if (g_key_file_has_key (key_file, group_name, key, NULL))
3833 			expanded = g_key_file_get_boolean (
3834 				key_file, group_name, key, NULL);
3835 
3836 		if (expanded && success) {
3837 			reference = em_folder_tree_model_get_row_reference (
3838 				folder_tree_model, store, folder_name);
3839 		}
3840 
3841 		if (gtk_tree_row_reference_valid (reference)) {
3842 			GtkTreePath *path;
3843 			GtkTreeIter iter;
3844 
3845 			path = gtk_tree_row_reference_get_path (reference);
3846 			gtk_tree_model_get_iter (tree_model, &iter, path);
3847 			gtk_tree_view_expand_row (tree_view, path, FALSE);
3848 			gtk_tree_path_free (path);
3849 		}
3850 
3851 		if (store != NULL)
3852 			g_object_unref (store);
3853 		g_free (folder_name);
3854 	}
3855 
3856 	g_slist_free (groups);
3857 	g_strfreev (groups_arr);
3858 
3859 	/* Stage 2 */
3860 
3861 	valid = gtk_tree_model_get_iter_first (tree_model, &iter);
3862 
3863 	while (valid) {
3864 		CamelStore *store;
3865 		CamelService *service;
3866 		const gchar *key = STATE_KEY_EXPANDED;
3867 		const gchar *uid;
3868 		gchar *group_name;
3869 
3870 		gtk_tree_model_get (
3871 			tree_model, &iter,
3872 			COL_OBJECT_CAMEL_STORE, &store, -1);
3873 
3874 		if (store == NULL)
3875 			goto next;
3876 
3877 		service = CAMEL_SERVICE (store);
3878 		uid = camel_service_get_uid (service);
3879 		group_name = g_strdup_printf ("Store %s", uid);
3880 
3881 		/* Expand stores that have no "Expanded" key. */
3882 		if (!g_key_file_has_key (key_file, group_name, key, NULL)) {
3883 			GtkTreePath *path;
3884 
3885 			path = gtk_tree_model_get_path (tree_model, &iter);
3886 			gtk_tree_view_expand_row (tree_view, path, FALSE);
3887 			gtk_tree_path_free (path);
3888 		}
3889 
3890 		g_free (group_name);
3891 		g_clear_object (&store);
3892 
3893 	next:
3894 		valid = gtk_tree_model_iter_next (tree_model, &iter);
3895 	}
3896 }
3897 
3898 /**
3899  * em_folder_tree_select_store_when_added:
3900  * @folder_tree: an #EMFolderTree
3901  * @store_uid: UID of a CamelStore to remember to select
3902  *
3903  * Instruct @folder_tree to select a CamelStore with UID @store_uid,
3904  * if/when it is added to the tree. This is necessary, because
3905  * the addition is done asynchronously.
3906  *
3907  * Since: 3.12
3908  **/
3909 void
em_folder_tree_select_store_when_added(EMFolderTree * folder_tree,const gchar * store_uid)3910 em_folder_tree_select_store_when_added (EMFolderTree *folder_tree,
3911 					const gchar *store_uid)
3912 {
3913 	g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
3914 
3915 	if (g_strcmp0 (store_uid, folder_tree->priv->select_store_uid_when_added) == 0)
3916 		return;
3917 
3918 	g_free (folder_tree->priv->select_store_uid_when_added);
3919 	folder_tree->priv->select_store_uid_when_added = g_strdup (store_uid);
3920 }
3921