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