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