1 /*
2 * This program is free software; you can redistribute it and/or modify it
3 * under the terms of the GNU Lesser General Public License as published by
4 * the Free Software Foundation.
5 *
6 * This program is distributed in the hope that it will be useful, but
7 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
9 * for more details.
10 *
11 * You should have received a copy of the GNU Lesser General Public License
12 * along with this program; if not, see <http://www.gnu.org/licenses/>.
13 *
14 *
15 * Authors:
16 * Miguel de Icaza (miguel@ximian.com)
17 * Bertrand Guiheneuf (bg@aful.org)
18 * And just about everyone else in evolution ...
19 *
20 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
21 *
22 */
23
24 #include "evolution-config.h"
25
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <unistd.h>
29 #include <string.h>
30 #include <ctype.h>
31
32 #include <glib/gi18n.h>
33 #include <glib/gstdio.h>
34
35 #include "e-mail-label-list-store.h"
36 #include "e-mail-notes.h"
37 #include "e-mail-ui-session.h"
38 #include "em-utils.h"
39
40 /*#define TIMEIT */
41
42 #ifdef TIMEIT
43 #include <sys/time.h>
44 #include <unistd.h>
45 #endif
46
47 #ifdef G_OS_WIN32
48 #ifdef gmtime_r
49 #undef gmtime_r
50 #endif
51 #ifdef localtime_r
52 #undef localtime_r
53 #endif
54
55 /* The gmtime() and localtime() in Microsoft's C library are MT-safe */
56 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
57 #define localtime_r(tp,tmp) (localtime(tp)?(*(tmp)=*localtime(tp),(tmp)):0)
58 #endif
59
60 #include "message-list.h"
61
62 #define d(x)
63 #define t(x)
64 #define dd(x) G_STMT_START { if (camel_debug ("message-list")) { x; } } G_STMT_END
65
66 #define MESSAGE_LIST_GET_PRIVATE(obj) \
67 (G_TYPE_INSTANCE_GET_PRIVATE \
68 ((obj), MESSAGE_LIST_TYPE, MessageListPrivate))
69
70 /* Common search expression segments. */
71 #define EXCLUDE_DELETED_MESSAGES_EXPR "(not (system-flag \"deleted\"))"
72 #define EXCLUDE_JUNK_MESSAGES_EXPR "(not (system-flag \"junk\"))"
73
74 typedef struct _ExtendedGNode ExtendedGNode;
75 typedef struct _RegenData RegenData;
76
77 struct _MLSelection {
78 GPtrArray *uids;
79 CamelFolder *folder;
80 };
81
82 struct _MessageListPrivate {
83 GtkWidget *invisible; /* 4 selection */
84
85 EMailSession *session;
86
87 CamelFolder *folder;
88 gulong folder_changed_handler_id;
89
90 /* For message list regeneration. */
91 GMutex regen_lock;
92 RegenData *regen_data;
93 guint regen_idle_id;
94
95 gboolean thaw_needs_regen;
96
97 GMutex thread_tree_lock;
98 CamelFolderThread *thread_tree;
99
100 struct _MLSelection clipboard;
101 gboolean destroyed;
102
103 gboolean expanded_default;
104 gboolean group_by_threads;
105 gboolean show_deleted;
106 gboolean show_junk;
107 gboolean thread_latest;
108 gboolean thread_subject;
109 gboolean thread_compress;
110 gboolean any_row_changed; /* save state before regen list when this is set to true */
111 gboolean show_subject_above_sender;
112 gboolean regen_selects_unread;
113
114 GtkTargetList *copy_target_list;
115 GtkTargetList *paste_target_list;
116
117 /* XXX Not sure if we really need a separate frozen counter
118 * for the tree model but the old ETreeMemory class had
119 * its own frozen counter so we preserve it here. */
120 GNode *tree_model_root;
121 gint tree_model_frozen;
122
123 /* This aids in automatic message selection. */
124 time_t newest_read_date;
125 const gchar *newest_read_uid;
126 time_t oldest_unread_date;
127 const gchar *oldest_unread_uid;
128
129 GSettings *mail_settings;
130 gchar **re_prefixes;
131 gchar **re_separators;
132 GMutex re_prefixes_lock;
133
134 GdkRGBA *new_mail_bg_color;
135 gchar *new_mail_fg_color;
136
137 guint update_actions_idle_id;
138
139 volatile gint setting_up_search_folder;
140
141 GSettings *eds_settings; /* references org.gnome.evolution-data-server schema */
142 gchar *user_headers[CAMEL_UTILS_MAX_USER_HEADERS + 1];
143 guint user_headers_count; /* how many are set */
144 };
145
146 /* XXX Plain GNode suffers from O(N) tail insertions, and that won't
147 * do for large mail folders. This structure extends GNode with
148 * a pointer to its last child, so we get O(1) tail insertions. */
149 struct _ExtendedGNode {
150 GNode gnode;
151 GNode *last_child;
152 };
153
154 struct _RegenData {
155 volatile gint ref_count;
156
157 EActivity *activity;
158 MessageList *message_list;
159 ETableSortInfo *sort_info;
160 ETableHeader *full_header;
161
162 gchar *search;
163
164 gboolean group_by_threads;
165 gboolean thread_subject;
166 gboolean select_unread;
167
168 CamelFolderThread *thread_tree;
169
170 /* This indicates we're regenerating the message list because
171 * we received a "folder-changed" signal from our CamelFolder. */
172 gboolean folder_changed;
173 GHashTable *removed_uids; /* gchar *~>NULL */
174
175 CamelFolder *folder;
176 GPtrArray *summary;
177
178 gint last_row; /* last selected (cursor) row */
179
180 xmlDoc *expand_state; /* expanded state to be restored */
181
182 /* These may be set during a regen operation. Use the
183 * select_lock to ensure consistency and thread-safety.
184 * These are applied after the operation is finished. */
185 GMutex select_lock;
186 gchar *select_uid;
187 gboolean select_all;
188 gboolean select_use_fallback;
189 };
190
191 enum {
192 PROP_0,
193 PROP_COPY_TARGET_LIST,
194 PROP_FOLDER,
195 PROP_GROUP_BY_THREADS,
196 PROP_PASTE_TARGET_LIST,
197 PROP_SESSION,
198 PROP_SHOW_DELETED,
199 PROP_SHOW_JUNK,
200 PROP_SHOW_SUBJECT_ABOVE_SENDER,
201 PROP_THREAD_LATEST,
202 PROP_THREAD_SUBJECT,
203 PROP_THREAD_COMPRESS
204 };
205
206 /* Forward Declarations */
207 static void message_list_selectable_init
208 (ESelectableInterface *iface);
209 static void message_list_tree_model_init
210 (ETreeModelInterface *iface);
211 static gboolean message_list_get_hide_deleted
212 (MessageList *message_list,
213 CamelFolder *folder);
214
215 G_DEFINE_TYPE_WITH_CODE (
216 MessageList,
217 message_list,
218 E_TYPE_TREE,
219 G_IMPLEMENT_INTERFACE (
220 E_TYPE_EXTENSIBLE, NULL)
221 G_IMPLEMENT_INTERFACE (
222 E_TYPE_SELECTABLE,
223 message_list_selectable_init)
224 G_IMPLEMENT_INTERFACE (
225 E_TYPE_TREE_MODEL,
226 message_list_tree_model_init))
227
228 static struct {
229 const gchar *target;
230 GdkAtom atom;
231 guint32 actions;
232 } ml_drag_info[] = {
233 { "x-uid-list", NULL, GDK_ACTION_MOVE | GDK_ACTION_COPY },
234 { "message/rfc822", NULL, GDK_ACTION_COPY },
235 { "text/uri-list", NULL, GDK_ACTION_COPY },
236 };
237
238 enum {
239 DND_X_UID_LIST, /* x-uid-list */
240 DND_MESSAGE_RFC822, /* message/rfc822 */
241 DND_TEXT_URI_LIST /* text/uri-list */
242 };
243
244 /* What we send */
245 static GtkTargetEntry ml_drag_types[] = {
246 { (gchar *) "x-uid-list", 0, DND_X_UID_LIST },
247 { (gchar *) "text/uri-list", 0, DND_TEXT_URI_LIST },
248 };
249
250 /* What we accept */
251 static GtkTargetEntry ml_drop_types[] = {
252 { (gchar *) "x-uid-list", 0, DND_X_UID_LIST },
253 { (gchar *) "message/rfc822", 0, DND_MESSAGE_RFC822 },
254 { (gchar *) "text/uri-list", 0, DND_TEXT_URI_LIST },
255 };
256
257 /*
258 * Default sizes for the ETable display
259 *
260 */
261 #define N_CHARS(x) (CHAR_WIDTH * (x))
262
263 #define COL_ICON_WIDTH (16)
264 #define COL_ATTACH_WIDTH (16)
265 #define COL_CHECK_BOX_WIDTH (16)
266 #define COL_FROM_EXPANSION (24.0)
267 #define COL_FROM_WIDTH_MIN (32)
268 #define COL_SUBJECT_EXPANSION (30.0)
269 #define COL_SUBJECT_WIDTH_MIN (32)
270 #define COL_SENT_EXPANSION (24.0)
271 #define COL_SENT_WIDTH_MIN (32)
272 #define COL_RECEIVED_EXPANSION (20.0)
273 #define COL_RECEIVED_WIDTH_MIN (32)
274 #define COL_TO_EXPANSION (24.0)
275 #define COL_TO_WIDTH_MIN (32)
276 #define COL_SIZE_EXPANSION (6.0)
277 #define COL_SIZE_WIDTH_MIN (32)
278 #define COL_SENDER_EXPANSION (24.0)
279 #define COL_SENDER_WIDTH_MIN (32)
280
281 enum {
282 NORMALISED_SUBJECT,
283 NORMALISED_FROM,
284 NORMALISED_TO,
285 NORMALISED_LAST
286 };
287
288 static void on_cursor_activated_cmd (ETree *tree,
289 gint row,
290 GNode *node,
291 gpointer user_data);
292 static void on_selection_changed_cmd (ETree *tree,
293 MessageList *message_list);
294 static gint on_click (ETree *tree,
295 gint row,
296 GNode *node,
297 gint col,
298 GdkEvent *event,
299 MessageList *message_list);
300
301 static void mail_regen_list (MessageList *message_list,
302 const gchar *search,
303 CamelFolderChangeInfo *folder_changes);
304 static void mail_regen_cancel (MessageList *message_list);
305
306 static void clear_info (gchar *key,
307 GNode *node,
308 MessageList *message_list);
309
310 enum {
311 MESSAGE_SELECTED,
312 MESSAGE_LIST_BUILT,
313 UPDATE_ACTIONS,
314 LAST_SIGNAL
315 };
316
317 static guint signals[LAST_SIGNAL] = {0, };
318
319 static const gchar *status_map[] = {
320 N_("Unseen"),
321 N_("Seen"),
322 N_("Answered"),
323 N_("Forwarded"),
324 N_("Answered"), /* and unread */
325 N_("Forwarded") /* and unread */
326 };
327
328 static const gchar *status_icons[] = {
329 "mail-unread",
330 "mail-read",
331 "mail-replied",
332 "mail-forward",
333 "mail-replied", /* and unread */
334 "mail-forward" /* and unread */
335 };
336
337 static const gchar *score_map[] = {
338 N_("Lowest"),
339 N_("Lower"),
340 N_("Low"),
341 N_("Normal"),
342 N_("High"),
343 N_("Higher"),
344 N_("Highest"),
345 };
346
347 static const gchar *score_icons[] = {
348 "stock_score-lowest",
349 "stock_score-lower",
350 "stock_score-low",
351 "stock_score-normal",
352 "stock_score-high",
353 "stock_score-higher",
354 "stock_score-highest"
355 };
356
357 static const gchar *attachment_icons[] = {
358 NULL, /* empty icon */
359 "mail-attachment",
360 "stock_people",
361 "evolution-memos",
362 "mail-mark-junk"
363 };
364
365 static const gchar *flagged_icons[] = {
366 NULL, /* empty icon */
367 "emblem-important"
368 };
369
370 static const gchar *followup_icons[] = {
371 NULL, /* empty icon */
372 "stock_mail-flag-for-followup",
373 "stock_mail-flag-for-followup-done"
374 };
375
376 static GNode *
extended_g_node_new(gpointer data)377 extended_g_node_new (gpointer data)
378 {
379 GNode *node;
380
381 node = (GNode *) g_slice_new0 (ExtendedGNode);
382 node->data = data;
383
384 return node;
385 }
386
387 static void
extended_g_node_unlink(GNode * node)388 extended_g_node_unlink (GNode *node)
389 {
390 g_return_if_fail (node != NULL);
391
392 /* Update the last_child pointer before we unlink. */
393 if (node->parent != NULL) {
394 ExtendedGNode *ext_parent;
395
396 ext_parent = (ExtendedGNode *) node->parent;
397 if (ext_parent->last_child == node) {
398 g_warn_if_fail (node->next == NULL);
399 ext_parent->last_child = node->prev;
400 }
401 }
402
403 g_node_unlink (node);
404 }
405
406 static void
extended_g_nodes_free(GNode * node)407 extended_g_nodes_free (GNode *node)
408 {
409 while (node != NULL) {
410 GNode *next = node->next;
411 if (node->children != NULL)
412 extended_g_nodes_free (node->children);
413 g_slice_free (ExtendedGNode, (ExtendedGNode *) node);
414 node = next;
415 }
416 }
417
418 static void
extended_g_node_destroy(GNode * root)419 extended_g_node_destroy (GNode *root)
420 {
421 g_return_if_fail (root != NULL);
422
423 if (!G_NODE_IS_ROOT (root))
424 extended_g_node_unlink (root);
425
426 extended_g_nodes_free (root);
427 }
428
429 static GNode *
extended_g_node_insert_before(GNode * parent,GNode * sibling,GNode * node)430 extended_g_node_insert_before (GNode *parent,
431 GNode *sibling,
432 GNode *node)
433 {
434 ExtendedGNode *ext_parent;
435
436 g_return_val_if_fail (parent != NULL, node);
437 g_return_val_if_fail (node != NULL, node);
438 g_return_val_if_fail (G_NODE_IS_ROOT (node), node);
439 if (sibling != NULL)
440 g_return_val_if_fail (sibling->parent == parent, node);
441
442 ext_parent = (ExtendedGNode *) parent;
443
444 /* This is where tracking the last child pays off. */
445 if (sibling == NULL && ext_parent->last_child != NULL) {
446 node->parent = parent;
447 node->prev = ext_parent->last_child;
448 ext_parent->last_child->next = node;
449 } else {
450 g_node_insert_before (parent, sibling, node);
451 }
452
453 if (sibling == NULL)
454 ext_parent->last_child = node;
455
456 return node;
457 }
458
459 static GNode *
extended_g_node_insert(GNode * parent,gint position,GNode * node)460 extended_g_node_insert (GNode *parent,
461 gint position,
462 GNode *node)
463 {
464 GNode *sibling;
465
466 g_return_val_if_fail (parent != NULL, node);
467 g_return_val_if_fail (node != NULL, node);
468 g_return_val_if_fail (G_NODE_IS_ROOT (node), node);
469
470 if (position > 0)
471 sibling = g_node_nth_child (parent, position);
472 else if (position == 0)
473 sibling = parent->children;
474 else /* if (position < 0) */
475 sibling = NULL;
476
477 return extended_g_node_insert_before (parent, sibling, node);
478 }
479
480 static RegenData *
regen_data_new(MessageList * message_list,GCancellable * cancellable)481 regen_data_new (MessageList *message_list,
482 GCancellable *cancellable)
483 {
484 RegenData *regen_data;
485 EActivity *activity;
486 EMailSession *session;
487 ETreeTableAdapter *adapter;
488
489 adapter = e_tree_get_table_adapter (E_TREE (message_list));
490
491 activity = e_activity_new ();
492 e_activity_set_cancellable (activity, cancellable);
493 e_activity_set_text (activity, _("Generating message list"));
494
495 regen_data = g_slice_new0 (RegenData);
496 regen_data->ref_count = 1;
497 regen_data->activity = g_object_ref (activity);
498 regen_data->message_list = g_object_ref (message_list);
499 regen_data->folder = message_list_ref_folder (message_list);
500 regen_data->last_row = -1;
501
502 if (adapter) {
503 regen_data->sort_info = e_tree_table_adapter_get_sort_info (adapter);
504 regen_data->full_header = e_tree_table_adapter_get_header (adapter);
505
506 if (regen_data->sort_info)
507 g_object_ref (regen_data->sort_info);
508 if (regen_data->full_header)
509 g_object_ref (regen_data->full_header);
510 }
511
512 if (message_list->just_set_folder)
513 regen_data->select_uid = g_strdup (message_list->cursor_uid);
514
515 g_mutex_init (®en_data->select_lock);
516
517 session = message_list_get_session (message_list);
518 e_mail_ui_session_add_activity (E_MAIL_UI_SESSION (session), activity);
519
520 g_object_unref (activity);
521
522 return regen_data;
523 }
524
525 static RegenData *
regen_data_ref(RegenData * regen_data)526 regen_data_ref (RegenData *regen_data)
527 {
528 g_return_val_if_fail (regen_data != NULL, NULL);
529 g_return_val_if_fail (regen_data->ref_count > 0, NULL);
530
531 g_atomic_int_inc (®en_data->ref_count);
532
533 return regen_data;
534 }
535
536 static void
regen_data_unref(RegenData * regen_data)537 regen_data_unref (RegenData *regen_data)
538 {
539 g_return_if_fail (regen_data != NULL);
540 g_return_if_fail (regen_data->ref_count > 0);
541
542 if (g_atomic_int_dec_and_test (®en_data->ref_count)) {
543
544 g_clear_object (®en_data->activity);
545 g_clear_object (®en_data->message_list);
546 g_clear_object (®en_data->sort_info);
547 g_clear_object (®en_data->full_header);
548
549 g_free (regen_data->search);
550
551 if (regen_data->thread_tree != NULL)
552 camel_folder_thread_messages_unref (
553 regen_data->thread_tree);
554
555 if (regen_data->summary != NULL) {
556 guint ii, length;
557
558 length = regen_data->summary->len;
559
560 for (ii = 0; ii < length; ii++)
561 g_clear_object (®en_data->summary->pdata[ii]);
562
563 g_ptr_array_free (regen_data->summary, TRUE);
564 }
565
566 if (regen_data->removed_uids)
567 g_hash_table_destroy (regen_data->removed_uids);
568 g_clear_object (®en_data->folder);
569
570 if (regen_data->expand_state != NULL)
571 xmlFreeDoc (regen_data->expand_state);
572
573 g_mutex_clear (®en_data->select_lock);
574 g_free (regen_data->select_uid);
575
576 g_slice_free (RegenData, regen_data);
577 }
578 }
579
580 static void
message_list_set_thread_tree(MessageList * message_list,CamelFolderThread * thread_tree)581 message_list_set_thread_tree (MessageList *message_list,
582 CamelFolderThread *thread_tree)
583 {
584 g_return_if_fail (IS_MESSAGE_LIST (message_list));
585
586 g_mutex_lock (&message_list->priv->thread_tree_lock);
587
588 if (thread_tree != NULL)
589 camel_folder_thread_messages_ref (thread_tree);
590
591 if (message_list->priv->thread_tree != NULL)
592 camel_folder_thread_messages_unref (
593 message_list->priv->thread_tree);
594
595 message_list->priv->thread_tree = thread_tree;
596
597 g_mutex_unlock (&message_list->priv->thread_tree_lock);
598 }
599
600 static RegenData *
message_list_ref_regen_data(MessageList * message_list)601 message_list_ref_regen_data (MessageList *message_list)
602 {
603 RegenData *regen_data = NULL;
604
605 g_mutex_lock (&message_list->priv->regen_lock);
606
607 if (message_list->priv->regen_data != NULL)
608 regen_data = regen_data_ref (message_list->priv->regen_data);
609
610 g_mutex_unlock (&message_list->priv->regen_lock);
611
612 return regen_data;
613 }
614
615 static void
message_list_tree_model_freeze(MessageList * message_list)616 message_list_tree_model_freeze (MessageList *message_list)
617 {
618 if (message_list->priv->tree_model_frozen == 0)
619 e_tree_model_pre_change (E_TREE_MODEL (message_list));
620
621 message_list->priv->tree_model_frozen++;
622 }
623
624 static void
message_list_tree_model_thaw(MessageList * message_list)625 message_list_tree_model_thaw (MessageList *message_list)
626 {
627 if (message_list->priv->tree_model_frozen > 0)
628 message_list->priv->tree_model_frozen--;
629
630 if (message_list->priv->tree_model_frozen == 0)
631 e_tree_model_node_changed (
632 E_TREE_MODEL (message_list),
633 message_list->priv->tree_model_root);
634 }
635
636 static GNode *
message_list_tree_model_insert(MessageList * message_list,GNode * parent,gint position,gpointer data)637 message_list_tree_model_insert (MessageList *message_list,
638 GNode *parent,
639 gint position,
640 gpointer data)
641 {
642 ETreeModel *tree_model;
643 GNode *node;
644 gboolean tree_model_frozen;
645
646 if (parent == NULL)
647 g_return_val_if_fail (
648 message_list->priv->tree_model_root == NULL, NULL);
649
650 tree_model = E_TREE_MODEL (message_list);
651 tree_model_frozen = (message_list->priv->tree_model_frozen > 0);
652
653 if (!tree_model_frozen)
654 e_tree_model_pre_change (tree_model);
655
656 node = extended_g_node_new (data);
657
658 if (parent != NULL) {
659 extended_g_node_insert (parent, position, node);
660 if (!tree_model_frozen)
661 e_tree_model_node_inserted (tree_model, parent, node);
662 } else {
663 message_list->priv->tree_model_root = node;
664 if (!tree_model_frozen)
665 e_tree_model_node_changed (tree_model, node);
666 }
667
668 return node;
669 }
670
671 static void
message_list_tree_model_remove(MessageList * message_list,GNode * node)672 message_list_tree_model_remove (MessageList *message_list,
673 GNode *node)
674 {
675 ETreeModel *tree_model;
676 GNode *parent = node->parent;
677 gboolean tree_model_frozen;
678 gint old_position = 0;
679
680 g_return_if_fail (node != NULL);
681
682 tree_model = E_TREE_MODEL (message_list);
683 tree_model_frozen = (message_list->priv->tree_model_frozen > 0);
684
685 if (!tree_model_frozen) {
686 e_tree_model_pre_change (tree_model);
687 old_position = g_node_child_position (parent, node);
688 }
689
690 extended_g_node_unlink (node);
691
692 if (!tree_model_frozen)
693 e_tree_model_node_removed (
694 tree_model, parent, node, old_position);
695
696 extended_g_node_destroy (node);
697
698 if (node == message_list->priv->tree_model_root)
699 message_list->priv->tree_model_root = NULL;
700
701 if (!tree_model_frozen)
702 e_tree_model_node_deleted (tree_model, node);
703 }
704
705 static gint
address_compare(gconstpointer address1,gconstpointer address2,gpointer cmp_cache)706 address_compare (gconstpointer address1,
707 gconstpointer address2,
708 gpointer cmp_cache)
709 {
710 gint retval;
711
712 g_return_val_if_fail (address1 != NULL, 1);
713 g_return_val_if_fail (address2 != NULL, -1);
714
715 retval = g_ascii_strcasecmp ((gchar *) address1, (gchar *) address2);
716
717 return retval;
718 }
719
720 static gint
mail_status_compare(gconstpointer pstatus1,gconstpointer pstatus2,gpointer cmp_cache)721 mail_status_compare (gconstpointer pstatus1,
722 gconstpointer pstatus2,
723 gpointer cmp_cache)
724 {
725 gint status1 = GPOINTER_TO_INT (pstatus1);
726 gint status2 = GPOINTER_TO_INT (pstatus2);
727 gboolean is_unread1 = status1 == 0 || status1 == 4 || status1 == 5;
728 gboolean is_unread2 = status2 == 0 || status2 == 4 || status2 == 5;
729
730 if ((is_unread1 ? 1 : 0) == (is_unread2 ? 1 : 0))
731 return e_int_compare (pstatus1, pstatus2);
732
733 return is_unread1 ? -1 : 1;
734 }
735
736 static gchar *
filter_size(gint size)737 filter_size (gint size)
738 {
739 gfloat fsize;
740
741 if (size < 1024) {
742 return g_strdup_printf ("%d", size);
743 } else {
744 fsize = ((gfloat) size) / 1024.0;
745 if (fsize < 1024.0) {
746 return g_strdup_printf ("%.2f K", fsize);
747 } else {
748 fsize /= 1024.0;
749 return g_strdup_printf ("%.2f M", fsize);
750 }
751 }
752 }
753
754 /* Gets the uid of the message displayed at a given view row */
755 static const gchar *
get_message_uid(MessageList * message_list,GNode * node)756 get_message_uid (MessageList *message_list,
757 GNode *node)
758 {
759 g_return_val_if_fail (node != NULL, NULL);
760 g_return_val_if_fail (node->data != NULL, NULL);
761
762 return camel_message_info_get_uid (node->data);
763 }
764
765 /* Gets the CamelMessageInfo for the message displayed at the given
766 * view row.
767 */
768 static CamelMessageInfo *
get_message_info(MessageList * message_list,GNode * node)769 get_message_info (MessageList *message_list,
770 GNode *node)
771 {
772 g_return_val_if_fail (node != NULL, NULL);
773 g_return_val_if_fail (node->data != NULL, NULL);
774
775 return node->data;
776 }
777
778 static const gchar *
get_normalised_string(MessageList * message_list,CamelMessageInfo * info,gint col)779 get_normalised_string (MessageList *message_list,
780 CamelMessageInfo *info,
781 gint col)
782 {
783 const gchar *string, *str;
784 gchar *normalised;
785 EPoolv *poolv;
786 gint index;
787
788 switch (col) {
789 case COL_SUBJECT_NORM:
790 string = camel_message_info_get_subject (info);
791 index = NORMALISED_SUBJECT;
792 break;
793 case COL_FROM_NORM:
794 string = camel_message_info_get_from (info);
795 index = NORMALISED_FROM;
796 break;
797 case COL_TO_NORM:
798 string = camel_message_info_get_to (info);
799 index = NORMALISED_TO;
800 break;
801 default:
802 string = NULL;
803 index = NORMALISED_LAST;
804 g_warning ("Should not be reached\n");
805 }
806
807 /* slight optimisation */
808 if (string == NULL || string[0] == '\0')
809 return "";
810
811 poolv = g_hash_table_lookup (message_list->normalised_hash, camel_message_info_get_uid (info));
812 if (poolv == NULL) {
813 poolv = e_poolv_new (NORMALISED_LAST);
814 g_hash_table_insert (message_list->normalised_hash, (gchar *) camel_message_info_get_uid (info), poolv);
815 } else {
816 str = e_poolv_get (poolv, index);
817 if (*str)
818 return str;
819 }
820
821 if (col == COL_SUBJECT_NORM) {
822 gint skip_len;
823 const gchar *subject;
824 gboolean found_re = TRUE;
825
826 subject = string;
827 while (found_re) {
828 g_mutex_lock (&message_list->priv->re_prefixes_lock);
829 found_re = em_utils_is_re_in_subject (
830 subject, &skip_len, (const gchar * const *) message_list->priv->re_prefixes,
831 (const gchar * const *) message_list->priv->re_separators) && skip_len > 0;
832 g_mutex_unlock (&message_list->priv->re_prefixes_lock);
833
834 if (found_re)
835 subject += skip_len;
836
837 /* jump over any spaces */
838 while (*subject && isspace ((gint) *subject))
839 subject++;
840 }
841
842 /* jump over any spaces */
843 while (*subject && isspace ((gint) *subject))
844 subject++;
845
846 string = subject;
847 normalised = g_utf8_collate_key (string, -1);
848 } else {
849 /* because addresses require strings, not collate keys */
850 normalised = g_strdup (string);
851 }
852
853 e_poolv_set (poolv, index, normalised, TRUE);
854
855 return e_poolv_get (poolv, index);
856 }
857
858 static void
clear_selection(MessageList * message_list,struct _MLSelection * selection)859 clear_selection (MessageList *message_list,
860 struct _MLSelection *selection)
861 {
862 g_clear_pointer (&selection->uids, g_ptr_array_unref);
863 g_clear_object (&selection->folder);
864 }
865
866 static GNode *
ml_get_next_node(GNode * node,GNode * subroot)867 ml_get_next_node (GNode *node,
868 GNode *subroot)
869 {
870 GNode *next;
871
872 if (!node)
873 return NULL;
874
875 next = g_node_first_child (node);
876
877 if (!next && node != subroot) {
878 next = g_node_next_sibling (node);
879 }
880
881 if (!next && node != subroot) {
882 next = node->parent;
883 while (next) {
884 GNode *sibl = g_node_next_sibling (next);
885
886 if (next == subroot)
887 return NULL;
888
889 if (sibl) {
890 next = sibl;
891 break;
892 } else {
893 next = next->parent;
894 }
895 }
896 }
897
898 return next;
899 }
900
901 static GNode *
ml_get_prev_node(GNode * node,GNode * subroot)902 ml_get_prev_node (GNode *node,
903 GNode *subroot)
904 {
905 GNode *prev;
906
907 if (!node)
908 return NULL;
909
910 if (node == subroot)
911 prev = NULL;
912 else
913 prev = g_node_prev_sibling (node);
914
915 if (!prev) {
916 prev = node->parent;
917
918 if (prev == subroot)
919 return NULL;
920
921 if (prev)
922 return prev;
923 }
924
925 if (prev) {
926 GNode *child = g_node_last_child (prev);
927 while (child) {
928 prev = child;
929 child = g_node_last_child (child);
930 }
931 }
932
933 return prev;
934 }
935
936 static GNode *
ml_get_last_tree_node(GNode * node,GNode * subroot)937 ml_get_last_tree_node (GNode *node,
938 GNode *subroot)
939 {
940 GNode *child;
941
942 if (!node)
943 return NULL;
944
945 while (node->parent && node->parent != subroot)
946 node = node->parent;
947
948 if (node == subroot)
949 child = node;
950 else
951 child = g_node_last_sibling (node);
952
953 if (!child)
954 child = node;
955
956 while (child = g_node_last_child (child), child) {
957 node = child;
958 }
959
960 return node;
961 }
962
963 static GNode *
ml_search_forward(MessageList * message_list,gint start,gint end,guint32 flags,guint32 mask,gboolean include_collapsed,gboolean skip_first)964 ml_search_forward (MessageList *message_list,
965 gint start,
966 gint end,
967 guint32 flags,
968 guint32 mask,
969 gboolean include_collapsed,
970 gboolean skip_first)
971 {
972 GNode *node;
973 gint row;
974 CamelMessageInfo *info;
975 ETreeTableAdapter *etta;
976
977 etta = e_tree_get_table_adapter (E_TREE (message_list));
978
979 for (row = start; row <= end; row++) {
980 node = e_tree_table_adapter_node_at_row (etta, row);
981 if (node != NULL && !skip_first
982 && (info = get_message_info (message_list, node))
983 && (camel_message_info_get_flags (info) & mask) == flags)
984 return node;
985
986 skip_first = FALSE;
987
988 if (node && include_collapsed && !e_tree_table_adapter_node_is_expanded (etta, node) && g_node_first_child (node)) {
989 GNode *subnode = node;
990
991 while (subnode = ml_get_next_node (subnode, node), subnode && subnode != node) {
992 if ((info = get_message_info (message_list, subnode)) &&
993 (camel_message_info_get_flags (info) & mask) == flags)
994 return subnode;
995 }
996 }
997 }
998
999 return NULL;
1000 }
1001
1002 static GNode *
ml_search_backward(MessageList * message_list,gint start,gint end,guint32 flags,guint32 mask,gboolean include_collapsed,gboolean skip_first)1003 ml_search_backward (MessageList *message_list,
1004 gint start,
1005 gint end,
1006 guint32 flags,
1007 guint32 mask,
1008 gboolean include_collapsed,
1009 gboolean skip_first)
1010 {
1011 GNode *node;
1012 gint row;
1013 CamelMessageInfo *info;
1014 ETreeTableAdapter *etta;
1015
1016 etta = e_tree_get_table_adapter (E_TREE (message_list));
1017
1018 for (row = start; row >= end; row--) {
1019 node = e_tree_table_adapter_node_at_row (etta, row);
1020 if (node != NULL && !skip_first
1021 && (info = get_message_info (message_list, node))
1022 && (camel_message_info_get_flags (info) & mask) == flags) {
1023 if (include_collapsed && !e_tree_table_adapter_node_is_expanded (etta, node) && g_node_first_child (node)) {
1024 GNode *subnode = ml_get_last_tree_node (g_node_first_child (node), node);
1025
1026 while (subnode && subnode != node) {
1027 if ((info = get_message_info (message_list, subnode)) &&
1028 (camel_message_info_get_flags (info) & mask) == flags)
1029 return subnode;
1030
1031 subnode = ml_get_prev_node (subnode, node);
1032 }
1033 }
1034
1035 return node;
1036 }
1037
1038 if (node && include_collapsed && !skip_first && !e_tree_table_adapter_node_is_expanded (etta, node) && g_node_first_child (node)) {
1039 GNode *subnode = ml_get_last_tree_node (g_node_first_child (node), node);
1040
1041 while (subnode && subnode != node) {
1042 if ((info = get_message_info (message_list, subnode)) &&
1043 (camel_message_info_get_flags (info) & mask) == flags)
1044 return subnode;
1045
1046 subnode = ml_get_prev_node (subnode, node);
1047 }
1048 }
1049
1050 skip_first = FALSE;
1051 }
1052
1053 return NULL;
1054 }
1055
1056 static GNode *
ml_search_path(MessageList * message_list,MessageListSelectDirection direction,guint32 flags,guint32 mask)1057 ml_search_path (MessageList *message_list,
1058 MessageListSelectDirection direction,
1059 guint32 flags,
1060 guint32 mask)
1061 {
1062 ETreeTableAdapter *adapter;
1063 gboolean include_collapsed;
1064 GNode *node;
1065 gint row_count;
1066 gint row;
1067
1068 if (message_list->cursor_uid == NULL)
1069 return NULL;
1070
1071 node = g_hash_table_lookup (
1072 message_list->uid_nodemap,
1073 message_list->cursor_uid);
1074 if (node == NULL)
1075 return NULL;
1076
1077 adapter = e_tree_get_table_adapter (E_TREE (message_list));
1078 row_count = e_table_model_row_count (E_TABLE_MODEL (adapter));
1079
1080 row = e_tree_table_adapter_row_of_node (adapter, node);
1081 if (row == -1)
1082 return NULL;
1083
1084 include_collapsed = (direction & MESSAGE_LIST_SELECT_INCLUDE_COLLAPSED) != 0;
1085
1086 if ((direction & MESSAGE_LIST_SELECT_DIRECTION) == MESSAGE_LIST_SELECT_NEXT)
1087 node = ml_search_forward (
1088 message_list, row, row_count - 1, flags, mask, include_collapsed, TRUE);
1089 else
1090 node = ml_search_backward (
1091 message_list, row, 0, flags, mask, include_collapsed, TRUE);
1092
1093 if (node == NULL && (direction & MESSAGE_LIST_SELECT_WRAP)) {
1094 if ((direction & MESSAGE_LIST_SELECT_DIRECTION) == MESSAGE_LIST_SELECT_NEXT)
1095 node = ml_search_forward (
1096 message_list, 0, row, flags, mask, include_collapsed, FALSE);
1097 else
1098 node = ml_search_backward (
1099 message_list, row_count - 1, row, flags, mask, include_collapsed, FALSE);
1100 }
1101
1102 return node;
1103 }
1104
1105 static void
select_node(MessageList * message_list,GNode * node)1106 select_node (MessageList *message_list,
1107 GNode *node)
1108 {
1109 ETree *tree;
1110 ETreeTableAdapter *etta;
1111 ETreeSelectionModel *etsm;
1112
1113 tree = E_TREE (message_list);
1114 etta = e_tree_get_table_adapter (tree);
1115 etsm = (ETreeSelectionModel *) e_tree_get_selection_model (tree);
1116
1117 g_free (message_list->cursor_uid);
1118 message_list->cursor_uid = NULL;
1119
1120 e_tree_table_adapter_show_node (etta, node);
1121 e_tree_set_cursor (tree, node);
1122 e_tree_selection_model_select_single_path (etsm, node);
1123 }
1124
1125 /**
1126 * message_list_select:
1127 * @message_list: a MessageList
1128 * @direction: the direction to search in
1129 * @flags: a set of flag values
1130 * @mask: a mask for comparing against @flags
1131 *
1132 * This moves the message list selection to a suitable row. @flags and
1133 * @mask combine to specify what constitutes a suitable row. @direction is
1134 * %MESSAGE_LIST_SELECT_NEXT if it should find the next matching
1135 * message, or %MESSAGE_LIST_SELECT_PREVIOUS if it should find the
1136 * previous. %MESSAGE_LIST_SELECT_WRAP is an option bit which specifies the
1137 * search should wrap.
1138 *
1139 * If no suitable row is found, the selection will be
1140 * unchanged.
1141 *
1142 * Returns %TRUE if a new message has been selected or %FALSE otherwise.
1143 **/
1144 gboolean
message_list_select(MessageList * message_list,MessageListSelectDirection direction,guint32 flags,guint32 mask)1145 message_list_select (MessageList *message_list,
1146 MessageListSelectDirection direction,
1147 guint32 flags,
1148 guint32 mask)
1149 {
1150 GNode *node;
1151
1152 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
1153
1154 node = ml_search_path (message_list, direction, flags, mask);
1155 if (node != NULL) {
1156 select_node (message_list, node);
1157
1158 /* This function is usually called in response to a key
1159 * press, so grab focus if the message list is visible. */
1160 if (gtk_widget_get_visible (GTK_WIDGET (message_list)))
1161 gtk_widget_grab_focus (GTK_WIDGET (message_list));
1162
1163 return TRUE;
1164 } else
1165 return FALSE;
1166 }
1167
1168 /**
1169 * message_list_can_select:
1170 * @message_list:
1171 * @direction:
1172 * @flags:
1173 * @mask:
1174 *
1175 * Returns true if the selection specified is possible with the current view.
1176 *
1177 * Return value:
1178 **/
1179 gboolean
message_list_can_select(MessageList * message_list,MessageListSelectDirection direction,guint32 flags,guint32 mask)1180 message_list_can_select (MessageList *message_list,
1181 MessageListSelectDirection direction,
1182 guint32 flags,
1183 guint32 mask)
1184 {
1185 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
1186
1187 return ml_search_path (message_list, direction, flags, mask) != NULL;
1188 }
1189
1190 /**
1191 * message_list_select_uid:
1192 * @message_list:
1193 * @uid:
1194 *
1195 * Selects the message with the given UID.
1196 **/
1197 void
message_list_select_uid(MessageList * message_list,const gchar * uid,gboolean with_fallback)1198 message_list_select_uid (MessageList *message_list,
1199 const gchar *uid,
1200 gboolean with_fallback)
1201 {
1202 MessageListPrivate *priv;
1203 GHashTable *uid_nodemap;
1204 GNode *node = NULL;
1205 RegenData *regen_data = NULL;
1206
1207 g_return_if_fail (IS_MESSAGE_LIST (message_list));
1208
1209 priv = message_list->priv;
1210 uid_nodemap = message_list->uid_nodemap;
1211
1212 if (message_list->priv->folder == NULL)
1213 return;
1214
1215 /* Try to find the requested message UID. */
1216 if (uid != NULL)
1217 node = g_hash_table_lookup (uid_nodemap, uid);
1218
1219 regen_data = message_list_ref_regen_data (message_list);
1220
1221 /* If we're busy or waiting to regenerate the message list, cache
1222 * the UID so we can try again when we're done. Otherwise if the
1223 * requested message UID was not found and 'with_fallback' is set,
1224 * try a couple fallbacks:
1225 *
1226 * 1) Oldest unread message in the list, by date received.
1227 * 2) Newest read message in the list, by date received.
1228 */
1229 if (regen_data != NULL) {
1230 g_mutex_lock (®en_data->select_lock);
1231 g_free (regen_data->select_uid);
1232 regen_data->select_uid = g_strdup (uid);
1233 regen_data->select_use_fallback = with_fallback;
1234 g_mutex_unlock (®en_data->select_lock);
1235
1236 regen_data_unref (regen_data);
1237
1238 } else if (with_fallback) {
1239 if (node == NULL && priv->oldest_unread_uid != NULL)
1240 node = g_hash_table_lookup (
1241 uid_nodemap, priv->oldest_unread_uid);
1242 if (node == NULL && priv->newest_read_uid != NULL)
1243 node = g_hash_table_lookup (
1244 uid_nodemap, priv->newest_read_uid);
1245 }
1246
1247 if (node) {
1248 ETree *tree;
1249 GNode *old_cur;
1250
1251 tree = E_TREE (message_list);
1252 old_cur = e_tree_get_cursor (tree);
1253
1254 /* This will emit a changed signal that we'll pick up */
1255 e_tree_set_cursor (tree, node);
1256
1257 if (old_cur == node)
1258 g_signal_emit (
1259 message_list,
1260 signals[MESSAGE_SELECTED],
1261 0, message_list->cursor_uid);
1262 } else if (message_list->just_set_folder) {
1263 g_free (message_list->cursor_uid);
1264 message_list->cursor_uid = g_strdup (uid);
1265 g_signal_emit (
1266 message_list,
1267 signals[MESSAGE_SELECTED], 0, message_list->cursor_uid);
1268 } else {
1269 g_free (message_list->cursor_uid);
1270 message_list->cursor_uid = NULL;
1271 g_signal_emit (
1272 message_list,
1273 signals[MESSAGE_SELECTED],
1274 0, NULL);
1275 }
1276 }
1277
1278 void
message_list_select_next_thread(MessageList * message_list)1279 message_list_select_next_thread (MessageList *message_list)
1280 {
1281 ETreeTableAdapter *adapter;
1282 GNode *node;
1283 gint row_count;
1284 gint row;
1285 gint ii;
1286
1287 g_return_if_fail (IS_MESSAGE_LIST (message_list));
1288
1289 if (message_list->cursor_uid == NULL)
1290 return;
1291
1292 node = g_hash_table_lookup (
1293 message_list->uid_nodemap,
1294 message_list->cursor_uid);
1295 if (node == NULL)
1296 return;
1297
1298 adapter = e_tree_get_table_adapter (E_TREE (message_list));
1299 row_count = e_table_model_row_count ((ETableModel *) adapter);
1300
1301 row = e_tree_table_adapter_row_of_node (adapter, node);
1302 if (row == -1)
1303 return;
1304
1305 /* find the next node which has a root parent (i.e. toplevel node) */
1306 for (ii = row + 1; ii < row_count - 1; ii++) {
1307 node = e_tree_table_adapter_node_at_row (adapter, ii);
1308 if (node != NULL && G_NODE_IS_ROOT (node->parent)) {
1309 select_node (message_list, node);
1310 return;
1311 }
1312 }
1313 }
1314
1315 void
message_list_select_prev_thread(MessageList * message_list)1316 message_list_select_prev_thread (MessageList *message_list)
1317 {
1318 ETreeTableAdapter *adapter;
1319 GNode *node;
1320 gboolean skip_first;
1321 gint row;
1322 gint ii;
1323
1324 g_return_if_fail (IS_MESSAGE_LIST (message_list));
1325
1326 if (message_list->cursor_uid == NULL)
1327 return;
1328
1329 node = g_hash_table_lookup (
1330 message_list->uid_nodemap,
1331 message_list->cursor_uid);
1332 if (node == NULL)
1333 return;
1334
1335 adapter = e_tree_get_table_adapter (E_TREE (message_list));
1336
1337 row = e_tree_table_adapter_row_of_node (adapter, node);
1338 if (row == -1)
1339 return;
1340
1341 /* skip first found if in the middle of the thread */
1342 skip_first = !G_NODE_IS_ROOT (node->parent);
1343
1344 /* find the previous node which has a root parent (i.e. toplevel node) */
1345 for (ii = row - 1; ii >= 0; ii--) {
1346 node = e_tree_table_adapter_node_at_row (adapter, ii);
1347 if (node != NULL && G_NODE_IS_ROOT (node->parent)) {
1348 if (skip_first) {
1349 skip_first = FALSE;
1350 continue;
1351 }
1352
1353 select_node (message_list, node);
1354 return;
1355 }
1356 }
1357 }
1358
1359 /**
1360 * message_list_select_all:
1361 * @message_list: Message List widget
1362 *
1363 * Selects all messages in the message list.
1364 **/
1365 void
message_list_select_all(MessageList * message_list)1366 message_list_select_all (MessageList *message_list)
1367 {
1368 RegenData *regen_data = NULL;
1369
1370 g_return_if_fail (IS_MESSAGE_LIST (message_list));
1371
1372 regen_data = message_list_ref_regen_data (message_list);
1373
1374 if (regen_data != NULL && regen_data->group_by_threads) {
1375 regen_data->select_all = TRUE;
1376 } else {
1377 ETree *tree;
1378 ESelectionModel *selection_model;
1379
1380 tree = E_TREE (message_list);
1381 selection_model = e_tree_get_selection_model (tree);
1382 e_selection_model_select_all (selection_model);
1383 }
1384
1385 if (regen_data != NULL)
1386 regen_data_unref (regen_data);
1387 }
1388
1389 typedef struct thread_select_info {
1390 MessageList *message_list;
1391 GPtrArray *paths;
1392 } thread_select_info_t;
1393
1394 static gboolean
select_thread_node(ETreeModel * model,GNode * node,gpointer user_data)1395 select_thread_node (ETreeModel *model,
1396 GNode *node,
1397 gpointer user_data)
1398 {
1399 thread_select_info_t *tsi = (thread_select_info_t *) user_data;
1400
1401 g_ptr_array_add (tsi->paths, node);
1402
1403 return FALSE; /*not done yet */
1404 }
1405
1406 static void
select_thread(MessageList * message_list,ETreeForeachFunc selector)1407 select_thread (MessageList *message_list,
1408 ETreeForeachFunc selector)
1409 {
1410 ETree *tree;
1411 ETreeSelectionModel *etsm;
1412 thread_select_info_t tsi;
1413
1414 tsi.message_list = message_list;
1415 tsi.paths = g_ptr_array_new ();
1416
1417 tree = E_TREE (message_list);
1418 etsm = (ETreeSelectionModel *) e_tree_get_selection_model (tree);
1419
1420 e_tree_selection_model_foreach (etsm, selector, &tsi);
1421
1422 e_tree_selection_model_select_paths (etsm, tsi.paths);
1423
1424 g_ptr_array_free (tsi.paths, TRUE);
1425 }
1426
1427 static void
thread_select_foreach(ETreePath path,gpointer user_data)1428 thread_select_foreach (ETreePath path,
1429 gpointer user_data)
1430 {
1431 thread_select_info_t *tsi = (thread_select_info_t *) user_data;
1432 ETreeModel *tree_model;
1433 GNode *last, *node = path;
1434
1435 tree_model = E_TREE_MODEL (tsi->message_list);
1436
1437 do {
1438 last = node;
1439 node = node->parent;
1440 } while (node && !G_NODE_IS_ROOT (node));
1441
1442 g_ptr_array_add (tsi->paths, last);
1443
1444 e_tree_model_node_traverse (
1445 tree_model, last,
1446 (ETreePathFunc) select_thread_node, tsi);
1447 }
1448
1449 /**
1450 * message_list_select_thread:
1451 * @message_list: Message List widget
1452 *
1453 * Selects all messages in the current thread (based on cursor).
1454 **/
1455 void
message_list_select_thread(MessageList * message_list)1456 message_list_select_thread (MessageList *message_list)
1457 {
1458 g_return_if_fail (IS_MESSAGE_LIST (message_list));
1459
1460 select_thread (message_list, thread_select_foreach);
1461 }
1462
1463 static void
subthread_select_foreach(ETreePath path,gpointer user_data)1464 subthread_select_foreach (ETreePath path,
1465 gpointer user_data)
1466 {
1467 thread_select_info_t *tsi = (thread_select_info_t *) user_data;
1468 ETreeModel *tree_model;
1469
1470 tree_model = E_TREE_MODEL (tsi->message_list);
1471
1472 e_tree_model_node_traverse (
1473 tree_model, path,
1474 (ETreePathFunc) select_thread_node, tsi);
1475 }
1476
1477 /**
1478 * message_list_select_subthread:
1479 * @message_list: Message List widget
1480 *
1481 * Selects all messages in the current subthread (based on cursor).
1482 **/
1483 void
message_list_select_subthread(MessageList * message_list)1484 message_list_select_subthread (MessageList *message_list)
1485 {
1486 g_return_if_fail (IS_MESSAGE_LIST (message_list));
1487
1488 select_thread (message_list, subthread_select_foreach);
1489 }
1490
1491 void
message_list_copy(MessageList * message_list,gboolean cut)1492 message_list_copy (MessageList *message_list,
1493 gboolean cut)
1494 {
1495 MessageListPrivate *priv;
1496 GPtrArray *uids;
1497
1498 g_return_if_fail (IS_MESSAGE_LIST (message_list));
1499
1500 priv = message_list->priv;
1501
1502 clear_selection (message_list, &priv->clipboard);
1503
1504 uids = message_list_get_selected_with_collapsed_threads (message_list);
1505
1506 if (uids->len > 0) {
1507 if (cut) {
1508 CamelFolder *folder;
1509 gint i;
1510
1511 folder = message_list_ref_folder (message_list);
1512
1513 camel_folder_freeze (folder);
1514
1515 for (i = 0; i < uids->len; i++)
1516 camel_folder_set_message_flags (
1517 folder, uids->pdata[i],
1518 CAMEL_MESSAGE_SEEN |
1519 CAMEL_MESSAGE_DELETED,
1520 CAMEL_MESSAGE_SEEN |
1521 CAMEL_MESSAGE_DELETED);
1522
1523 camel_folder_thaw (folder);
1524
1525 g_object_unref (folder);
1526 }
1527
1528 priv->clipboard.uids = g_ptr_array_ref (uids);
1529 priv->clipboard.folder = message_list_ref_folder (message_list);
1530
1531 gtk_selection_owner_set (
1532 priv->invisible,
1533 GDK_SELECTION_CLIPBOARD,
1534 gtk_get_current_event_time ());
1535 } else {
1536 gtk_selection_owner_set (
1537 NULL, GDK_SELECTION_CLIPBOARD,
1538 gtk_get_current_event_time ());
1539 }
1540
1541 g_ptr_array_unref (uids);
1542 }
1543
1544 void
message_list_paste(MessageList * message_list)1545 message_list_paste (MessageList *message_list)
1546 {
1547 g_return_if_fail (IS_MESSAGE_LIST (message_list));
1548
1549 gtk_selection_convert (
1550 message_list->priv->invisible,
1551 GDK_SELECTION_CLIPBOARD,
1552 gdk_atom_intern ("x-uid-list", FALSE),
1553 GDK_CURRENT_TIME);
1554 }
1555
1556 static void
for_node_and_subtree_if_collapsed(MessageList * message_list,GNode * node,CamelMessageInfo * mi,ETreePathFunc func,gpointer data)1557 for_node_and_subtree_if_collapsed (MessageList *message_list,
1558 GNode *node,
1559 CamelMessageInfo *mi,
1560 ETreePathFunc func,
1561 gpointer data)
1562 {
1563 ETreeModel *tree_model;
1564 ETreeTableAdapter *adapter;
1565 GNode *child = NULL;
1566
1567 tree_model = E_TREE_MODEL (message_list);
1568 adapter = e_tree_get_table_adapter (E_TREE (message_list));
1569
1570 func (NULL, (ETreePath) mi, data);
1571
1572 if (node != NULL)
1573 child = g_node_first_child (node);
1574
1575 if (child && !e_tree_table_adapter_node_is_expanded (adapter, node))
1576 e_tree_model_node_traverse (tree_model, node, func, data);
1577 }
1578
1579 static gboolean
unread_foreach(ETreeModel * etm,ETreePath path,gpointer data)1580 unread_foreach (ETreeModel *etm,
1581 ETreePath path,
1582 gpointer data)
1583 {
1584 gboolean *saw_unread = data;
1585 CamelMessageInfo *info;
1586
1587 if (!etm)
1588 info = (CamelMessageInfo *) path;
1589 else
1590 info = ((GNode *) path)->data;
1591 g_return_val_if_fail (info != NULL, FALSE);
1592
1593 if (!(camel_message_info_get_flags (info) & CAMEL_MESSAGE_SEEN))
1594 *saw_unread = TRUE;
1595
1596 return FALSE;
1597 }
1598
1599 struct LatestData {
1600 gboolean sent;
1601 time_t latest;
1602 };
1603
1604 static gboolean
latest_foreach(ETreeModel * etm,ETreePath path,gpointer data)1605 latest_foreach (ETreeModel *etm,
1606 ETreePath path,
1607 gpointer data)
1608 {
1609 struct LatestData *ld = data;
1610 CamelMessageInfo *info;
1611 time_t date;
1612
1613 if (!etm)
1614 info = (CamelMessageInfo *) path;
1615 else
1616 info = ((GNode *) path)->data;
1617 g_return_val_if_fail (info != NULL, FALSE);
1618
1619 date = ld->sent ? camel_message_info_get_date_sent (info)
1620 : camel_message_info_get_date_received (info);
1621
1622 if (ld->latest == 0 || date > ld->latest)
1623 ld->latest = date;
1624
1625 return FALSE;
1626 }
1627
1628 static void
ml_add_name_or_email(GString * addresses,const gchar * address,gint addr_start,gboolean use_name)1629 ml_add_name_or_email (GString *addresses,
1630 const gchar *address,
1631 gint addr_start,
1632 gboolean use_name)
1633 {
1634 g_return_if_fail (addresses != NULL);
1635
1636 if (!address || !*address)
1637 return;
1638
1639 while (*address == ' ') {
1640 if (addr_start >= 0)
1641 addr_start--;
1642
1643 address++;
1644 }
1645
1646 if (addresses->len)
1647 g_string_append_c (addresses, ' ');
1648
1649 if (addr_start < 0) {
1650 g_string_append (addresses, address);
1651 } else if (use_name) {
1652 g_string_append_len (addresses, address, addr_start - 1);
1653 } else {
1654 const gchar *addr_end = strrchr (address + addr_start, '>');
1655
1656 if (addr_end)
1657 g_string_append_len (addresses, address + addr_start, addr_end - address - addr_start);
1658 else
1659 g_string_append (addresses, address + addr_start);
1660 }
1661 }
1662
1663 static gchar *
sanitize_addresses(const gchar * string,gboolean return_names)1664 sanitize_addresses (const gchar *string,
1665 gboolean return_names)
1666 {
1667 GString *gstring;
1668 gboolean quoted = FALSE;
1669 const gchar *p;
1670 gint addr_start = -1;
1671 GString *addresses = g_string_new ("");
1672
1673 if (!string || !*string)
1674 return g_string_free (addresses, FALSE);
1675
1676 gstring = g_string_new ("");
1677
1678 for (p = string; *p; p = g_utf8_next_char (p)) {
1679 gunichar c = g_utf8_get_char (p);
1680
1681 if (c == '"') {
1682 quoted = ~quoted;
1683 } else if (c == '<' && !quoted && addr_start == -1) {
1684 addr_start = gstring->len + 1;
1685 } else if (c == ',' && !quoted) {
1686 ml_add_name_or_email (addresses, gstring->str, addr_start, return_names);
1687 g_string_append_c (addresses, ',');
1688 g_string_truncate (gstring, 0);
1689 addr_start = -1;
1690 continue;
1691 }
1692
1693 g_string_append_unichar (gstring, c);
1694 }
1695
1696 ml_add_name_or_email (addresses, gstring->str, addr_start, return_names);
1697 g_string_free (gstring, TRUE);
1698
1699 return g_string_free (addresses, FALSE);
1700 }
1701
1702 struct LabelsData {
1703 EMailLabelListStore *store;
1704 GHashTable *labels_tag2iter;
1705 };
1706
1707 static void
add_label_if_known(struct LabelsData * ld,const gchar * tag)1708 add_label_if_known (struct LabelsData *ld,
1709 const gchar *tag)
1710 {
1711 GtkTreeIter label_defn;
1712
1713 if (e_mail_label_list_store_lookup (ld->store, tag, &label_defn)) {
1714 g_hash_table_insert (
1715 ld->labels_tag2iter,
1716 /* Should be the same as the "tag" arg */
1717 e_mail_label_list_store_get_tag (ld->store, &label_defn),
1718 gtk_tree_iter_copy (&label_defn));
1719 }
1720 }
1721
1722 static gboolean
add_all_labels_foreach(ETreeModel * etm,ETreePath path,gpointer data)1723 add_all_labels_foreach (ETreeModel *etm,
1724 ETreePath path,
1725 gpointer data)
1726 {
1727 struct LabelsData *ld = data;
1728 CamelMessageInfo *msg_info;
1729 const gchar *old_label;
1730 gchar *new_label;
1731 const CamelNamedFlags *flags;
1732 guint ii, len;
1733
1734 if (!etm)
1735 msg_info = (CamelMessageInfo *) path;
1736 else
1737 msg_info = ((GNode *) path)->data;
1738 g_return_val_if_fail (msg_info != NULL, FALSE);
1739
1740 camel_message_info_property_lock (msg_info);
1741 flags = camel_message_info_get_user_flags (msg_info);
1742 len = camel_named_flags_get_length (flags);
1743
1744 for (ii = 0; ii < len; ii++)
1745 add_label_if_known (ld, camel_named_flags_get (flags, ii));
1746
1747 old_label = camel_message_info_get_user_tag (msg_info, "label");
1748 if (old_label != NULL) {
1749 /* Convert old-style labels ("<name>") to "$Label<name>". */
1750 new_label = g_alloca (strlen (old_label) + 10);
1751 g_stpcpy (g_stpcpy (new_label, "$Label"), old_label);
1752
1753 add_label_if_known (ld, new_label);
1754 }
1755
1756 camel_message_info_property_unlock (msg_info);
1757
1758 return FALSE;
1759 }
1760
1761 static const gchar *
get_trimmed_subject(CamelMessageInfo * info,MessageList * message_list)1762 get_trimmed_subject (CamelMessageInfo *info,
1763 MessageList *message_list)
1764 {
1765 const gchar *subject;
1766 const gchar *mlist;
1767 gint mlist_len = 0;
1768 gboolean found_mlist;
1769
1770 subject = camel_message_info_get_subject (info);
1771 if (!subject || !*subject)
1772 return subject;
1773
1774 mlist = camel_message_info_get_mlist (info);
1775
1776 if (mlist && *mlist) {
1777 const gchar *mlist_end;
1778
1779 mlist_end = strchr (mlist, '@');
1780 if (mlist_end)
1781 mlist_len = mlist_end - mlist;
1782 else
1783 mlist_len = strlen (mlist);
1784 }
1785
1786 do {
1787 gint skip_len;
1788 gboolean found_re = TRUE;
1789
1790 found_mlist = FALSE;
1791
1792 while (found_re) {
1793 found_re = FALSE;
1794
1795 g_mutex_lock (&message_list->priv->re_prefixes_lock);
1796 found_re = em_utils_is_re_in_subject (
1797 subject, &skip_len, (const gchar * const *) message_list->priv->re_prefixes,
1798 (const gchar * const *) message_list->priv->re_separators) && skip_len > 0;
1799 g_mutex_unlock (&message_list->priv->re_prefixes_lock);
1800 if (found_re)
1801 subject += skip_len;
1802
1803 /* jump over any spaces */
1804 while (*subject && isspace ((gint) *subject))
1805 subject++;
1806 }
1807
1808 if (mlist_len &&
1809 *subject == '[' &&
1810 !g_ascii_strncasecmp ((gchar *) subject + 1, mlist, mlist_len) &&
1811 subject[1 + mlist_len] == ']') {
1812 subject += 1 + mlist_len + 1; /* jump over "[mailing-list]" */
1813 found_mlist = TRUE;
1814
1815 /* jump over any spaces */
1816 while (*subject && isspace ((gint) *subject))
1817 subject++;
1818 }
1819 } while (found_mlist);
1820
1821 /* jump over any spaces */
1822 while (*subject && isspace ((gint) *subject))
1823 subject++;
1824
1825 return subject;
1826 }
1827
1828 static gpointer
ml_tree_value_at_ex(ETreeModel * etm,GNode * node,gint col,CamelMessageInfo * msg_info,MessageList * message_list)1829 ml_tree_value_at_ex (ETreeModel *etm,
1830 GNode *node,
1831 gint col,
1832 CamelMessageInfo *msg_info,
1833 MessageList *message_list)
1834 {
1835 EMailSession *session;
1836 const gchar *str;
1837 guint32 flags;
1838
1839 session = message_list_get_session (message_list);
1840
1841 g_return_val_if_fail (msg_info != NULL, NULL);
1842
1843 switch (col) {
1844 case COL_MESSAGE_STATUS:
1845 flags = camel_message_info_get_flags (msg_info);
1846 if (!(flags & CAMEL_MESSAGE_SEEN) && (flags & CAMEL_MESSAGE_ANSWERED) != 0)
1847 return GINT_TO_POINTER (4);
1848 else if (!(flags & CAMEL_MESSAGE_SEEN) && (flags & CAMEL_MESSAGE_FORWARDED) != 0)
1849 return GINT_TO_POINTER (5);
1850 else if (flags & CAMEL_MESSAGE_ANSWERED)
1851 return GINT_TO_POINTER (2);
1852 else if (flags & CAMEL_MESSAGE_FORWARDED)
1853 return GINT_TO_POINTER (3);
1854 else if (flags & CAMEL_MESSAGE_SEEN)
1855 return GINT_TO_POINTER (1);
1856 else
1857 return GINT_TO_POINTER (0);
1858 case COL_FLAGGED:
1859 return GINT_TO_POINTER ((camel_message_info_get_flags (msg_info) & CAMEL_MESSAGE_FLAGGED) != 0);
1860 case COL_SCORE: {
1861 const gchar *tag;
1862 gint score = 0;
1863
1864 tag = camel_message_info_get_user_tag (msg_info, "score");
1865 if (tag)
1866 score = atoi (tag);
1867
1868 return GINT_TO_POINTER (score);
1869 }
1870 case COL_FOLLOWUP_FLAG_STATUS: {
1871 const gchar *tag, *cmp;
1872
1873 /* FIXME: this all should be methods off of message-tag-followup class,
1874 * FIXME: the tag names should be namespaced :( */
1875 tag = camel_message_info_get_user_tag (msg_info, "follow-up");
1876 cmp = camel_message_info_get_user_tag (msg_info, "completed-on");
1877 if (tag && tag[0]) {
1878 if (cmp && cmp[0])
1879 return GINT_TO_POINTER (2);
1880 else
1881 return GINT_TO_POINTER (1);
1882 } else
1883 return GINT_TO_POINTER (0);
1884 }
1885 case COL_FOLLOWUP_DUE_BY: {
1886 const gchar *tag;
1887 time_t due_by;
1888
1889 tag = camel_message_info_get_user_tag (msg_info, "due-by");
1890 if (tag && *tag) {
1891 gint64 *res;
1892
1893 due_by = camel_header_decode_date (tag, NULL);
1894 res = g_new0 (gint64, 1);
1895 *res = (gint64) due_by;
1896
1897 return res;
1898 } else {
1899 return NULL;
1900 }
1901 }
1902 case COL_FOLLOWUP_FLAG:
1903 str = camel_message_info_get_user_tag (msg_info, "follow-up");
1904 return (gpointer)(str ? str : "");
1905 case COL_ATTACHMENT:
1906 if (camel_message_info_get_flags (msg_info) & CAMEL_MESSAGE_JUNK)
1907 return GINT_TO_POINTER (4);
1908 if (camel_message_info_get_user_flag (msg_info, E_MAIL_NOTES_USER_FLAG))
1909 return GINT_TO_POINTER (3);
1910 if (camel_message_info_get_user_flag (msg_info, "$has_cal"))
1911 return GINT_TO_POINTER (2);
1912 return GINT_TO_POINTER ((camel_message_info_get_flags (msg_info) & CAMEL_MESSAGE_ATTACHMENTS) != 0);
1913 case COL_FROM:
1914 str = camel_message_info_get_from (msg_info);
1915 return (gpointer)(str ? str : "");
1916 case COL_FROM_NORM:
1917 return (gpointer) get_normalised_string (message_list, msg_info, col);
1918 case COL_SUBJECT:
1919 str = camel_message_info_get_subject (msg_info);
1920 return (gpointer)(str ? str : "");
1921 case COL_SUBJECT_TRIMMED:
1922 str = get_trimmed_subject (msg_info, message_list);
1923 return (gpointer)(str ? str : "");
1924 case COL_SUBJECT_NORM:
1925 return (gpointer) get_normalised_string (message_list, msg_info, col);
1926 case COL_SENT: {
1927 struct LatestData ld;
1928 gint64 *res;
1929
1930 ld.sent = TRUE;
1931 ld.latest = 0;
1932
1933 for_node_and_subtree_if_collapsed (message_list, node, msg_info, latest_foreach, &ld);
1934
1935 res = g_new0 (gint64, 1);
1936 *res = (gint64) ld.latest;
1937
1938 return res;
1939 }
1940 case COL_RECEIVED: {
1941 struct LatestData ld;
1942 gint64 *res;
1943
1944 ld.sent = FALSE;
1945 ld.latest = 0;
1946
1947 for_node_and_subtree_if_collapsed (message_list, node, msg_info, latest_foreach, &ld);
1948
1949 res = g_new0 (gint64, 1);
1950 *res = (gint64) ld.latest;
1951
1952 return res;
1953 }
1954 case COL_TO:
1955 str = camel_message_info_get_to (msg_info);
1956 return (gpointer)(str ? str : "");
1957 case COL_TO_NORM:
1958 return (gpointer) get_normalised_string (message_list, msg_info, col);
1959 case COL_SIZE:
1960 return GINT_TO_POINTER (camel_message_info_get_size (msg_info));
1961 case COL_DELETED:
1962 return GINT_TO_POINTER ((camel_message_info_get_flags (msg_info) & CAMEL_MESSAGE_DELETED) != 0);
1963 case COL_DELETED_OR_JUNK:
1964 return GINT_TO_POINTER ((camel_message_info_get_flags (msg_info) & (CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_JUNK)) != 0);
1965 case COL_JUNK:
1966 return GINT_TO_POINTER ((camel_message_info_get_flags (msg_info) & CAMEL_MESSAGE_JUNK) != 0);
1967 case COL_JUNK_STRIKEOUT_COLOR:
1968 return GUINT_TO_POINTER (((camel_message_info_get_flags (msg_info) & CAMEL_MESSAGE_JUNK) != 0) ? 0xFF0000 : 0x0);
1969 case COL_UNREAD: {
1970 gboolean saw_unread = FALSE;
1971
1972 for_node_and_subtree_if_collapsed (message_list, node, msg_info, unread_foreach, &saw_unread);
1973
1974 return GINT_TO_POINTER (saw_unread);
1975 }
1976 case COL_COLOUR: {
1977 const gchar *colour, *due_by, *completed, *followup;
1978
1979 /* Priority: colour tag; label tag; important flag; due-by tag */
1980
1981 /* This is astonisngly poorly written code */
1982
1983 /* To add to the woes, what color to show when the user choose multiple labels ?
1984 Don't say that I need to have the new labels[with subject] column visible always */
1985
1986 colour = NULL;
1987 due_by = camel_message_info_get_user_tag (msg_info, "due-by");
1988 completed = camel_message_info_get_user_tag (msg_info, "completed-on");
1989 followup = camel_message_info_get_user_tag (msg_info, "follow-up");
1990 if (colour == NULL) {
1991 /* Get all applicable labels. */
1992 struct LabelsData ld;
1993
1994 ld.store = e_mail_ui_session_get_label_store (
1995 E_MAIL_UI_SESSION (session));
1996 ld.labels_tag2iter = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gtk_tree_iter_free);
1997 for_node_and_subtree_if_collapsed (message_list, node, msg_info, add_all_labels_foreach, &ld);
1998
1999 if (g_hash_table_size (ld.labels_tag2iter) == 1) {
2000 GHashTableIter iter;
2001 GtkTreeIter *label_defn;
2002 GdkColor colour_val;
2003 gchar *colour_alloced;
2004
2005 /* Extract the single label from the hashtable. */
2006 g_hash_table_iter_init (&iter, ld.labels_tag2iter);
2007 if (g_hash_table_iter_next (&iter, NULL, (gpointer *) &label_defn)) {
2008 e_mail_label_list_store_get_color (ld.store, label_defn, &colour_val);
2009
2010 /* XXX Hack to avoid returning an allocated string. */
2011 colour_alloced = gdk_color_to_string (&colour_val);
2012 colour = g_intern_string (colour_alloced);
2013 g_free (colour_alloced);
2014 }
2015 } else if (g_hash_table_size (ld.labels_tag2iter) > 1) {
2016 /* When there is more than one label set, then pick the color of the first
2017 found, in order of the EMailLabelListStore */
2018 GtkTreeIter titer;
2019 GtkTreeModel *model = GTK_TREE_MODEL (ld.store);
2020
2021 if (gtk_tree_model_get_iter_first (model, &titer)) {
2022 do {
2023 gchar *tag;
2024
2025 tag = e_mail_label_list_store_get_tag (ld.store, &titer);
2026 if (tag && g_hash_table_contains (ld.labels_tag2iter, tag)) {
2027 GdkColor colour_val;
2028
2029 g_free (tag);
2030
2031 if (e_mail_label_list_store_get_color (ld.store, &titer, &colour_val)) {
2032 gchar *colour_alloced;
2033
2034 /* XXX Hack to avoid returning an allocated string. */
2035 colour_alloced = gdk_color_to_string (&colour_val);
2036 colour = g_intern_string (colour_alloced);
2037 g_free (colour_alloced);
2038 }
2039 break;
2040 }
2041
2042 g_free (tag);
2043 } while (gtk_tree_model_iter_next (model, &titer));
2044 }
2045 }
2046
2047 g_hash_table_destroy (ld.labels_tag2iter);
2048 }
2049
2050 if (!colour) {
2051 if (camel_message_info_get_flags (msg_info) & CAMEL_MESSAGE_FLAGGED) {
2052 /* FIXME: extract from the important.xpm somehow. */
2053 colour = "#A7453E";
2054 } else if (((followup && *followup) || (due_by && *due_by)) && !(completed && *completed)) {
2055 time_t now = time (NULL);
2056
2057 if ((followup && *followup) || now >= camel_header_decode_date (due_by, NULL))
2058 colour = "#A7453E";
2059 }
2060 }
2061
2062 if (!colour)
2063 colour = camel_message_info_get_user_tag (msg_info, "color");
2064
2065 /*
2066 * No flags/tags/user color on mail, check for unread status and
2067 * "new-mail-fg-color" CSS attribute.
2068 */
2069 if (!colour && message_list->priv->new_mail_fg_color) {
2070 gboolean saw_unread = FALSE;
2071
2072 for_node_and_subtree_if_collapsed (message_list, node, msg_info, unread_foreach, &saw_unread);
2073
2074 if (saw_unread)
2075 colour = message_list->priv->new_mail_fg_color;
2076 }
2077
2078 return (gpointer) colour;
2079 }
2080 case COL_ITALIC: {
2081 return GINT_TO_POINTER (camel_message_info_get_user_flag (msg_info, "ignore-thread") ? 1 : 0);
2082 }
2083 case COL_LOCATION: {
2084 /* Fixme : freeing memory stuff (mem leaks) */
2085 CamelStore *store;
2086 CamelFolder *folder;
2087 CamelService *service;
2088 const gchar *store_name;
2089 const gchar *folder_name;
2090
2091 folder = message_list->priv->folder;
2092
2093 if (CAMEL_IS_VEE_FOLDER (folder))
2094 folder = camel_vee_folder_get_location (
2095 CAMEL_VEE_FOLDER (folder),
2096 (CamelVeeMessageInfo *) msg_info, NULL);
2097
2098 store = camel_folder_get_parent_store (folder);
2099 folder_name = camel_folder_get_full_name (folder);
2100
2101 service = CAMEL_SERVICE (store);
2102 store_name = camel_service_get_display_name (service);
2103
2104 return g_strdup_printf ("%s : %s", store_name, folder_name);
2105 }
2106 case COL_MIXED_RECIPIENTS:
2107 case COL_RECIPIENTS:
2108 case COL_RECIPIENTS_MAIL: {
2109 str = camel_message_info_get_to (msg_info);
2110 return sanitize_addresses (str, col != COL_RECIPIENTS_MAIL);
2111 }
2112 case COL_MIXED_SENDER:
2113 case COL_SENDER:
2114 case COL_SENDER_MAIL: {
2115 str = camel_message_info_get_from (msg_info);
2116 return sanitize_addresses (str, col != COL_SENDER_MAIL);
2117 }
2118 case COL_LABELS:{
2119 struct LabelsData ld;
2120 GString *result = g_string_new ("");
2121
2122 ld.store = e_mail_ui_session_get_label_store (
2123 E_MAIL_UI_SESSION (session));
2124 ld.labels_tag2iter = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gtk_tree_iter_free);
2125 for_node_and_subtree_if_collapsed (message_list, node, msg_info, add_all_labels_foreach, &ld);
2126
2127 if (g_hash_table_size (ld.labels_tag2iter) > 0) {
2128 GHashTableIter iter;
2129 GtkTreeIter *label_defn;
2130
2131 g_hash_table_iter_init (&iter, ld.labels_tag2iter);
2132 while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &label_defn)) {
2133 gchar *label_name, *label_name_clean;
2134
2135 if (result->len > 0)
2136 g_string_append (result, ", ");
2137
2138 label_name = e_mail_label_list_store_get_name (ld.store, label_defn);
2139 label_name_clean = e_str_without_underscores (label_name);
2140
2141 g_string_append (result, label_name_clean);
2142
2143 g_free (label_name_clean);
2144 g_free (label_name);
2145 }
2146 }
2147
2148 g_hash_table_destroy (ld.labels_tag2iter);
2149 return (gpointer) g_string_free (result, FALSE);
2150 }
2151 case COL_UID: {
2152 return (gpointer) camel_pstring_strdup (camel_message_info_get_uid (msg_info));
2153 }
2154 case COL_USER_HEADER_1:
2155 case COL_USER_HEADER_2:
2156 case COL_USER_HEADER_3: {
2157 const gchar *name = NULL;
2158 guint index = col - COL_USER_HEADER_1;
2159 if (message_list->priv->user_headers && index < message_list->priv->user_headers_count)
2160 name = message_list->priv->user_headers[index];
2161 if (name && *name)
2162 return (gpointer) camel_message_info_dup_user_header (msg_info, name);
2163 return NULL;
2164 }
2165 default:
2166 g_warning ("%s: This shouldn't be reached (col:%d)", G_STRFUNC, col);
2167 return NULL;
2168 }
2169 }
2170
2171 static gchar *
filter_date(const gint64 * pdate)2172 filter_date (const gint64 *pdate)
2173 {
2174 time_t nowdate = time (NULL);
2175 time_t yesdate, date;
2176 struct tm then, now, yesterday;
2177 gchar buf[26];
2178 gboolean done = FALSE;
2179
2180 if (!pdate || *pdate == 0)
2181 return g_strdup (_("?"));
2182
2183 date = (time_t) *pdate;
2184 localtime_r (&date, &then);
2185 localtime_r (&nowdate, &now);
2186 if (then.tm_mday == now.tm_mday &&
2187 then.tm_mon == now.tm_mon &&
2188 then.tm_year == now.tm_year) {
2189 e_utf8_strftime_fix_am_pm (buf, 26, _("Today %l:%M %p"), &then);
2190 done = TRUE;
2191 }
2192 if (!done) {
2193 yesdate = nowdate - 60 * 60 * 24;
2194 localtime_r (&yesdate, &yesterday);
2195 if (then.tm_mday == yesterday.tm_mday &&
2196 then.tm_mon == yesterday.tm_mon &&
2197 then.tm_year == yesterday.tm_year) {
2198 e_utf8_strftime_fix_am_pm (buf, 26, _("Yesterday %l:%M %p"), &then);
2199 done = TRUE;
2200 }
2201 }
2202 if (!done) {
2203 gint i;
2204 for (i = 2; i < 7; i++) {
2205 yesdate = nowdate - 60 * 60 * 24 * i;
2206 localtime_r (&yesdate, &yesterday);
2207 if (then.tm_mday == yesterday.tm_mday &&
2208 then.tm_mon == yesterday.tm_mon &&
2209 then.tm_year == yesterday.tm_year) {
2210 e_utf8_strftime_fix_am_pm (buf, 26, _("%a %l:%M %p"), &then);
2211 done = TRUE;
2212 break;
2213 }
2214 }
2215 }
2216 if (!done) {
2217 if (then.tm_year == now.tm_year) {
2218 e_utf8_strftime_fix_am_pm (buf, 26, _("%b %d %l:%M %p"), &then);
2219 } else {
2220 e_utf8_strftime_fix_am_pm (buf, 26, _("%b %d %Y"), &then);
2221 }
2222 }
2223
2224 return g_strdup (buf);
2225 }
2226
2227 static ECell *
create_composite_cell(GSettings * mail_settings,gint col)2228 create_composite_cell (GSettings *mail_settings,
2229 gint col)
2230 {
2231 ECell *cell_vbox, *cell_hbox, *cell_sub, *cell_date, *cell_from, *top_cell_tree, *bottom_cell_tree, *cell_attach;
2232 gboolean show_email;
2233 gboolean show_subject_above_sender;
2234
2235 show_email = g_settings_get_boolean (mail_settings, "show-email");
2236 show_subject_above_sender = g_settings_get_boolean (mail_settings, "show-subject-above-sender");
2237
2238 if (!show_email)
2239 col = (col == COL_FROM) ? COL_SENDER : COL_RECIPIENTS;
2240
2241 cell_vbox = e_cell_vbox_new ();
2242
2243 cell_hbox = e_cell_hbox_new ();
2244
2245 /* Exclude the meeting icon. */
2246 cell_attach = e_cell_toggle_new (attachment_icons, G_N_ELEMENTS (attachment_icons));
2247
2248 cell_date = e_cell_date_new (NULL, GTK_JUSTIFY_RIGHT);
2249 e_cell_date_set_format_component (E_CELL_DATE (cell_date), "mail");
2250 g_object_set (
2251 cell_date,
2252 "bold_column", COL_UNREAD,
2253 "italic-column", COL_ITALIC,
2254 "color_column", COL_COLOUR,
2255 NULL);
2256
2257 cell_from = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT);
2258 g_object_set (
2259 cell_from,
2260 "bold_column", COL_UNREAD,
2261 "italic-column", COL_ITALIC,
2262 "color_column", COL_COLOUR,
2263 NULL);
2264
2265 e_cell_hbox_append (E_CELL_HBOX (cell_hbox), cell_from, show_subject_above_sender ? COL_SUBJECT : col, 68);
2266 e_cell_hbox_append (E_CELL_HBOX (cell_hbox), cell_attach, COL_ATTACHMENT, 5);
2267 e_cell_hbox_append (E_CELL_HBOX (cell_hbox), cell_date, COL_SENT, 27);
2268 g_object_unref (cell_from);
2269 g_object_unref (cell_attach);
2270 g_object_unref (cell_date);
2271
2272 cell_sub = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT);
2273 g_object_set (
2274 cell_sub,
2275 "color_column", COL_COLOUR,
2276 NULL);
2277 top_cell_tree = e_cell_tree_new (TRUE, FALSE, cell_hbox);
2278 bottom_cell_tree = e_cell_tree_new (TRUE, TRUE, cell_sub);
2279 e_cell_vbox_append (E_CELL_VBOX (cell_vbox), top_cell_tree, show_subject_above_sender ? COL_SUBJECT : col);
2280 e_cell_vbox_append (E_CELL_VBOX (cell_vbox), bottom_cell_tree, show_subject_above_sender ? col : COL_SUBJECT);
2281 g_object_unref (cell_sub);
2282 g_object_unref (cell_hbox);
2283 g_object_unref (top_cell_tree);
2284 g_object_unref (bottom_cell_tree);
2285
2286 g_object_set_data (G_OBJECT (cell_vbox), "cell_date", cell_date);
2287 g_object_set_data (G_OBJECT (cell_vbox), "cell_sub", cell_sub);
2288 g_object_set_data (G_OBJECT (cell_vbox), "cell_from", cell_from);
2289 g_object_set_data (G_OBJECT (cell_vbox), "cell_hbox", cell_hbox);
2290 g_object_set_data (G_OBJECT (cell_vbox), "address_model_col", GINT_TO_POINTER (col));
2291
2292 return cell_vbox;
2293 }
2294
2295 static void
composite_cell_set_show_subject_above_sender(ECell * cell,gboolean show_subject_above_sender)2296 composite_cell_set_show_subject_above_sender (ECell *cell,
2297 gboolean show_subject_above_sender)
2298 {
2299 ECellVbox *cell_vbox;
2300 ECellHbox *cell_hbox;
2301 ECell *cell_from;
2302 GObject *cell_obj;
2303 gint address_model_col, cell_from_index;
2304
2305 g_return_if_fail (E_IS_CELL_VBOX (cell));
2306
2307 cell_obj = G_OBJECT (cell);
2308 address_model_col = GPOINTER_TO_INT (g_object_get_data (cell_obj, "address_model_col"));
2309
2310 cell_vbox = E_CELL_VBOX (cell);
2311 g_return_if_fail (cell_vbox->subcell_count == 2);
2312 g_return_if_fail (cell_vbox->model_cols != NULL);
2313
2314 cell_from = g_object_get_data (cell_obj, "cell_from");
2315 g_return_if_fail (E_IS_CELL (cell_from));
2316
2317 cell_hbox = g_object_get_data (cell_obj, "cell_hbox");
2318 g_return_if_fail (E_IS_CELL_HBOX (cell_hbox));
2319
2320 for (cell_from_index = 0; cell_from_index < cell_hbox->subcell_count; cell_from_index++) {
2321 if (cell_hbox->subcells[cell_from_index] == cell_from)
2322 break;
2323 }
2324
2325 g_return_if_fail (cell_from_index < cell_hbox->subcell_count);
2326
2327 cell_hbox->model_cols[cell_from_index] = show_subject_above_sender ? COL_SUBJECT : address_model_col;
2328 cell_vbox->model_cols[0] = show_subject_above_sender ? COL_SUBJECT : address_model_col;
2329 cell_vbox->model_cols[1] = show_subject_above_sender ? address_model_col : COL_SUBJECT;
2330 }
2331
2332 static void
composite_cell_set_strike_col(ECell * cell,gint strikeout_col,gint strikeout_color_col)2333 composite_cell_set_strike_col (ECell *cell,
2334 gint strikeout_col,
2335 gint strikeout_color_col)
2336 {
2337 g_object_set (g_object_get_data (G_OBJECT (cell), "cell_date"),
2338 "strikeout-column", strikeout_col,
2339 "strikeout-color-column", strikeout_color_col,
2340 NULL);
2341
2342 g_object_set (g_object_get_data (G_OBJECT (cell), "cell_from"),
2343 "strikeout-column", strikeout_col,
2344 "strikeout-color-column", strikeout_color_col,
2345 NULL);
2346 }
2347
2348 static ETableExtras *
message_list_create_extras(GSettings * mail_settings)2349 message_list_create_extras (GSettings *mail_settings)
2350 {
2351 ETableExtras *extras;
2352 ECell *cell;
2353
2354 extras = e_table_extras_new ();
2355 e_table_extras_add_icon_name (extras, "status", "mail-unread");
2356 e_table_extras_add_icon_name (extras, "score", "stock_score-higher");
2357 e_table_extras_add_icon_name (extras, "attachment", "mail-attachment");
2358 e_table_extras_add_icon_name (extras, "flagged", "emblem-important");
2359 e_table_extras_add_icon_name (extras, "followup", "stock_mail-flag-for-followup");
2360
2361 e_table_extras_add_compare (extras, "address_compare", address_compare);
2362 e_table_extras_add_compare (extras, "mail-status", mail_status_compare);
2363
2364 cell = e_cell_toggle_new (status_icons, G_N_ELEMENTS (status_icons));
2365 e_cell_toggle_set_icon_descriptions (E_CELL_TOGGLE (cell), status_map, G_N_ELEMENTS (status_map));
2366 e_table_extras_add_cell (extras, "render_message_status", cell);
2367 g_object_unref (cell);
2368
2369 cell = e_cell_toggle_new (
2370 attachment_icons, G_N_ELEMENTS (attachment_icons));
2371 e_table_extras_add_cell (extras, "render_attachment", cell);
2372 g_object_unref (cell);
2373
2374 cell = e_cell_toggle_new (
2375 flagged_icons, G_N_ELEMENTS (flagged_icons));
2376 e_table_extras_add_cell (extras, "render_flagged", cell);
2377 g_object_unref (cell);
2378
2379 cell = e_cell_toggle_new (
2380 followup_icons, G_N_ELEMENTS (followup_icons));
2381 e_table_extras_add_cell (extras, "render_flag_status", cell);
2382 g_object_unref (cell);
2383
2384 cell = e_cell_toggle_new (
2385 score_icons, G_N_ELEMENTS (score_icons));
2386 e_table_extras_add_cell (extras, "render_score", cell);
2387 g_object_unref (cell);
2388
2389 /* date cell */
2390 cell = e_cell_date_new (NULL, GTK_JUSTIFY_LEFT);
2391 e_cell_date_set_format_component (E_CELL_DATE (cell), "mail");
2392 g_object_set (
2393 cell,
2394 "bold_column", COL_UNREAD,
2395 "italic-column", COL_ITALIC,
2396 "color_column", COL_COLOUR,
2397 NULL);
2398 e_table_extras_add_cell (extras, "render_date", cell);
2399 g_object_unref (cell);
2400
2401 /* text cell */
2402 cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT);
2403 g_object_set (
2404 cell,
2405 "bold_column", COL_UNREAD,
2406 "italic-column", COL_ITALIC,
2407 "color_column", COL_COLOUR,
2408 NULL);
2409 e_table_extras_add_cell (extras, "render_text", cell);
2410 g_object_unref (cell);
2411
2412 cell = e_cell_tree_new (TRUE, TRUE, cell);
2413 e_table_extras_add_cell (extras, "render_tree", cell);
2414 g_object_unref (cell);
2415
2416 /* size cell */
2417 cell = e_cell_size_new (NULL, GTK_JUSTIFY_RIGHT);
2418 g_object_set (
2419 cell,
2420 "bold_column", COL_UNREAD,
2421 "italic-column", COL_ITALIC,
2422 "color_column", COL_COLOUR,
2423 NULL);
2424 e_table_extras_add_cell (extras, "render_size", cell);
2425 g_object_unref (cell);
2426
2427 /* Composite cell for wide view */
2428 cell = create_composite_cell (mail_settings, COL_FROM);
2429 e_table_extras_add_cell (extras, "render_composite_from", cell);
2430 g_object_unref (cell);
2431
2432 cell = create_composite_cell (mail_settings, COL_TO);
2433 e_table_extras_add_cell (extras, "render_composite_to", cell);
2434 g_object_unref (cell);
2435
2436 /* set proper format component for a default 'date' cell renderer */
2437 cell = e_table_extras_get_cell (extras, "date");
2438 e_cell_date_set_format_component (E_CELL_DATE (cell), "mail");
2439
2440 return extras;
2441 }
2442
2443 static gboolean
message_list_is_searching(MessageList * message_list)2444 message_list_is_searching (MessageList *message_list)
2445 {
2446 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
2447
2448 return message_list->search && *message_list->search;
2449 }
2450
2451 static void
save_tree_state(MessageList * message_list,CamelFolder * folder)2452 save_tree_state (MessageList *message_list,
2453 CamelFolder *folder)
2454 {
2455 ETreeTableAdapter *adapter;
2456 gchar *filename;
2457
2458 if (folder == NULL)
2459 return;
2460
2461 if (message_list_is_searching (message_list))
2462 return;
2463
2464 adapter = e_tree_get_table_adapter (E_TREE (message_list));
2465
2466 filename = mail_config_folder_to_cachename (folder, "et-expanded-");
2467 e_tree_table_adapter_save_expanded_state (adapter, filename);
2468 g_free (filename);
2469
2470 message_list->priv->any_row_changed = FALSE;
2471 }
2472
2473 static void
load_tree_state(MessageList * message_list,CamelFolder * folder,xmlDoc * expand_state)2474 load_tree_state (MessageList *message_list,
2475 CamelFolder *folder,
2476 xmlDoc *expand_state)
2477 {
2478 ETreeTableAdapter *adapter;
2479
2480 if (folder == NULL)
2481 return;
2482
2483 adapter = e_tree_get_table_adapter (E_TREE (message_list));
2484
2485 if (expand_state != NULL) {
2486 e_tree_table_adapter_load_expanded_state_xml (
2487 adapter, expand_state);
2488 } else {
2489 gchar *filename;
2490
2491 filename = mail_config_folder_to_cachename (
2492 folder, "et-expanded-");
2493 e_tree_table_adapter_load_expanded_state (adapter, filename);
2494 g_free (filename);
2495 }
2496
2497 message_list->priv->any_row_changed = FALSE;
2498 }
2499
2500 void
message_list_save_state(MessageList * message_list)2501 message_list_save_state (MessageList *message_list)
2502 {
2503 CamelFolder *folder;
2504
2505 g_return_if_fail (IS_MESSAGE_LIST (message_list));
2506
2507 folder = message_list_ref_folder (message_list);
2508
2509 if (folder != NULL) {
2510 save_tree_state (message_list, folder);
2511 g_object_unref (folder);
2512 }
2513 }
2514
2515 static void
message_list_setup_etree(MessageList * message_list)2516 message_list_setup_etree (MessageList *message_list)
2517 {
2518 CamelFolder *folder;
2519
2520 /* Build the spec based on the folder, and possibly
2521 * from a saved file. Otherwise, leave default. */
2522
2523 folder = message_list_ref_folder (message_list);
2524
2525 if (folder != NULL) {
2526 gint data = 1;
2527 ETableItem *item;
2528
2529 item = e_tree_get_item (E_TREE (message_list));
2530
2531 g_object_set (message_list, "uniform_row_height", TRUE, NULL);
2532 g_object_set_data (
2533 G_OBJECT (((GnomeCanvasItem *) item)->canvas),
2534 "freeze-cursor", &data);
2535
2536 /* build based on saved file */
2537 load_tree_state (message_list, folder, NULL);
2538
2539 g_object_unref (folder);
2540 }
2541 }
2542
2543 static void
ml_selection_get(GtkWidget * widget,GtkSelectionData * data,guint info,guint time_stamp,MessageList * message_list)2544 ml_selection_get (GtkWidget *widget,
2545 GtkSelectionData *data,
2546 guint info,
2547 guint time_stamp,
2548 MessageList *message_list)
2549 {
2550 struct _MLSelection *selection;
2551
2552 selection = &message_list->priv->clipboard;
2553
2554 if (selection->uids == NULL)
2555 return;
2556
2557 if (info & 2) {
2558 /* text/plain */
2559 d (printf ("setting text/plain selection for uids\n"));
2560 em_utils_selection_set_mailbox (data, selection->folder, selection->uids);
2561 } else {
2562 /* x-uid-list */
2563 d (printf ("setting x-uid-list selection for uids\n"));
2564 em_utils_selection_set_uidlist (data, selection->folder, selection->uids);
2565 }
2566 }
2567
2568 static gboolean
ml_selection_clear_event(GtkWidget * widget,GdkEventSelection * event,MessageList * message_list)2569 ml_selection_clear_event (GtkWidget *widget,
2570 GdkEventSelection *event,
2571 MessageList *message_list)
2572 {
2573 MessageListPrivate *p = message_list->priv;
2574
2575 clear_selection (message_list, &p->clipboard);
2576
2577 return TRUE;
2578 }
2579
2580 static void
ml_selection_received(GtkWidget * widget,GtkSelectionData * selection_data,guint time,MessageList * message_list)2581 ml_selection_received (GtkWidget *widget,
2582 GtkSelectionData *selection_data,
2583 guint time,
2584 MessageList *message_list)
2585 {
2586 EMailSession *session;
2587 CamelFolder *folder;
2588 GdkAtom target;
2589
2590 target = gtk_selection_data_get_target (selection_data);
2591
2592 if (target != gdk_atom_intern ("x-uid-list", FALSE)) {
2593 d (printf ("Unknown selection received by message-list\n"));
2594 return;
2595 }
2596
2597 folder = message_list_ref_folder (message_list);
2598 session = message_list_get_session (message_list);
2599
2600 /* FIXME Not passing a GCancellable or GError here. */
2601 em_utils_selection_get_uidlist (
2602 selection_data, session, folder, FALSE, NULL, NULL);
2603
2604 g_clear_object (&folder);
2605 }
2606
2607 static void
ml_tree_drag_data_get(ETree * tree,gint row,GNode * node,gint col,GdkDragContext * context,GtkSelectionData * data,guint info,guint time,MessageList * message_list)2608 ml_tree_drag_data_get (ETree *tree,
2609 gint row,
2610 GNode *node,
2611 gint col,
2612 GdkDragContext *context,
2613 GtkSelectionData *data,
2614 guint info,
2615 guint time,
2616 MessageList *message_list)
2617 {
2618 CamelFolder *folder;
2619 GPtrArray *uids;
2620
2621 folder = message_list_ref_folder (message_list);
2622 uids = message_list_get_selected_with_collapsed_threads (message_list);
2623
2624 if (uids->len > 0) {
2625 switch (info) {
2626 case DND_X_UID_LIST:
2627 em_utils_selection_set_uidlist (data, folder, uids);
2628 break;
2629 case DND_TEXT_URI_LIST:
2630 em_utils_selection_set_urilist (data, folder, uids);
2631 break;
2632 }
2633 }
2634
2635 g_clear_object (&folder);
2636 g_ptr_array_unref (uids);
2637 }
2638
2639 /* TODO: merge this with the folder tree stuff via empopup targets */
2640 /* Drop handling */
2641 struct _drop_msg {
2642 MailMsg base;
2643
2644 GdkDragContext *context;
2645
2646 /* Only selection->data and selection->length are valid */
2647 GtkSelectionData *selection;
2648
2649 CamelFolder *folder;
2650 MessageList *message_list;
2651
2652 guint32 action;
2653 guint info;
2654
2655 guint move : 1;
2656 guint moved : 1;
2657 guint aborted : 1;
2658 };
2659
2660 static gchar *
ml_drop_async_desc(struct _drop_msg * m)2661 ml_drop_async_desc (struct _drop_msg *m)
2662 {
2663 const gchar *full_name;
2664
2665 full_name = camel_folder_get_full_name (m->folder);
2666
2667 if (m->move)
2668 return g_strdup_printf (_("Moving messages into folder %s"), full_name);
2669 else
2670 return g_strdup_printf (_("Copying messages into folder %s"), full_name);
2671 }
2672
2673 static void
ml_drop_async_exec(struct _drop_msg * m,GCancellable * cancellable,GError ** error)2674 ml_drop_async_exec (struct _drop_msg *m,
2675 GCancellable *cancellable,
2676 GError **error)
2677 {
2678 EMailSession *session;
2679
2680 session = message_list_get_session (m->message_list);
2681
2682 switch (m->info) {
2683 case DND_X_UID_LIST:
2684 em_utils_selection_get_uidlist (
2685 m->selection, session, m->folder,
2686 m->action == GDK_ACTION_MOVE,
2687 cancellable, error);
2688 break;
2689 case DND_MESSAGE_RFC822:
2690 em_utils_selection_get_message (m->selection, m->folder);
2691 break;
2692 case DND_TEXT_URI_LIST:
2693 em_utils_selection_get_urilist (m->selection, m->folder);
2694 break;
2695 }
2696 }
2697
2698 static void
ml_drop_async_done(struct _drop_msg * m)2699 ml_drop_async_done (struct _drop_msg *m)
2700 {
2701 gboolean success, delete;
2702
2703 /* ?? */
2704 if (m->aborted) {
2705 success = FALSE;
2706 delete = FALSE;
2707 } else {
2708 success = (m->base.error == NULL);
2709 delete = success && m->move && !m->moved;
2710 }
2711
2712 gtk_drag_finish (m->context, success, delete, GDK_CURRENT_TIME);
2713 }
2714
2715 static void
ml_drop_async_free(struct _drop_msg * m)2716 ml_drop_async_free (struct _drop_msg *m)
2717 {
2718 g_object_unref (m->context);
2719 g_object_unref (m->folder);
2720 g_object_unref (m->message_list);
2721 gtk_selection_data_free (m->selection);
2722 }
2723
2724 static MailMsgInfo ml_drop_async_info = {
2725 sizeof (struct _drop_msg),
2726 (MailMsgDescFunc) ml_drop_async_desc,
2727 (MailMsgExecFunc) ml_drop_async_exec,
2728 (MailMsgDoneFunc) ml_drop_async_done,
2729 (MailMsgFreeFunc) ml_drop_async_free
2730 };
2731
2732 static void
ml_drop_action(struct _drop_msg * m)2733 ml_drop_action (struct _drop_msg *m)
2734 {
2735 m->move = m->action == GDK_ACTION_MOVE;
2736 mail_msg_unordered_push (m);
2737 }
2738
2739 static void
ml_tree_drag_data_received(ETree * tree,gint row,GNode * node,gint col,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint info,guint time,MessageList * message_list)2740 ml_tree_drag_data_received (ETree *tree,
2741 gint row,
2742 GNode *node,
2743 gint col,
2744 GdkDragContext *context,
2745 gint x,
2746 gint y,
2747 GtkSelectionData *selection_data,
2748 guint info,
2749 guint time,
2750 MessageList *message_list)
2751 {
2752 CamelFolder *folder;
2753 struct _drop_msg *m;
2754
2755 if (gtk_selection_data_get_data (selection_data) == NULL)
2756 return;
2757
2758 if (gtk_selection_data_get_length (selection_data) == -1)
2759 return;
2760
2761 folder = message_list_ref_folder (message_list);
2762 if (folder == NULL)
2763 return;
2764
2765 m = mail_msg_new (&ml_drop_async_info);
2766 m->context = g_object_ref (context);
2767 m->folder = g_object_ref (folder);
2768 m->message_list = g_object_ref (message_list);
2769 m->action = gdk_drag_context_get_selected_action (context);
2770 m->info = info;
2771
2772 /* need to copy, goes away once we exit */
2773 m->selection = gtk_selection_data_copy (selection_data);
2774
2775 ml_drop_action (m);
2776
2777 g_object_unref (folder);
2778 }
2779
2780 struct search_child_struct {
2781 gboolean found;
2782 gconstpointer looking_for;
2783 };
2784
2785 static void
search_child_cb(GtkWidget * widget,gpointer data)2786 search_child_cb (GtkWidget *widget,
2787 gpointer data)
2788 {
2789 struct search_child_struct *search = (struct search_child_struct *) data;
2790
2791 search->found = search->found || g_direct_equal (widget, search->looking_for);
2792 }
2793
2794 static gboolean
is_tree_widget_children(ETree * tree,gconstpointer widget)2795 is_tree_widget_children (ETree *tree,
2796 gconstpointer widget)
2797 {
2798 struct search_child_struct search;
2799
2800 search.found = FALSE;
2801 search.looking_for = widget;
2802
2803 gtk_container_foreach (GTK_CONTAINER (tree), search_child_cb, &search);
2804
2805 return search.found;
2806 }
2807
2808 static gboolean
ml_tree_drag_motion(ETree * tree,GdkDragContext * context,gint x,gint y,guint time,MessageList * message_list)2809 ml_tree_drag_motion (ETree *tree,
2810 GdkDragContext *context,
2811 gint x,
2812 gint y,
2813 guint time,
2814 MessageList *message_list)
2815 {
2816 GList *targets;
2817 GdkDragAction action, actions = 0;
2818 GtkWidget *source_widget;
2819
2820 /* If drop target is name of the account/store
2821 * and not actual folder, don't allow any action. */
2822 if (message_list->priv->folder == NULL) {
2823 gdk_drag_status (context, 0, time);
2824 return TRUE;
2825 }
2826
2827 source_widget = gtk_drag_get_source_widget (context);
2828
2829 /* If source widget is packed under 'tree', don't allow any action */
2830 if (is_tree_widget_children (tree, source_widget)) {
2831 gdk_drag_status (context, 0, time);
2832 return TRUE;
2833 }
2834
2835 if (EM_IS_FOLDER_TREE (source_widget)) {
2836 EMFolderTree *folder_tree;
2837 CamelFolder *selected_folder = NULL;
2838 CamelStore *selected_store;
2839 gchar *selected_folder_name;
2840 gboolean has_selection;
2841
2842 folder_tree = EM_FOLDER_TREE (source_widget);
2843
2844 has_selection = em_folder_tree_get_selected (
2845 folder_tree, &selected_store, &selected_folder_name);
2846
2847 /* Sanity checks */
2848 g_warn_if_fail (
2849 (has_selection && selected_store != NULL) ||
2850 (!has_selection && selected_store == NULL));
2851 g_warn_if_fail (
2852 (has_selection && selected_folder_name != NULL) ||
2853 (!has_selection && selected_folder_name == NULL));
2854
2855 if (has_selection) {
2856 selected_folder = camel_store_get_folder_sync (
2857 selected_store, selected_folder_name,
2858 0, NULL, NULL);
2859 g_object_unref (selected_store);
2860 g_free (selected_folder_name);
2861 }
2862
2863 if (selected_folder == message_list->priv->folder) {
2864 gdk_drag_status (context, 0, time);
2865 return TRUE;
2866 }
2867 }
2868
2869 targets = gdk_drag_context_list_targets (context);
2870 while (targets != NULL) {
2871 gint i;
2872
2873 d (printf ("atom drop '%s'\n", gdk_atom_name (targets->data)));
2874 for (i = 0; i < G_N_ELEMENTS (ml_drag_info); i++)
2875 if (targets->data == (gpointer) ml_drag_info[i].atom)
2876 actions |= ml_drag_info[i].actions;
2877
2878 targets = g_list_next (targets);
2879 }
2880 d (printf ("\n"));
2881
2882 actions &= gdk_drag_context_get_actions (context);
2883 action = gdk_drag_context_get_suggested_action (context);
2884 if (action == GDK_ACTION_COPY && (actions & GDK_ACTION_MOVE))
2885 action = GDK_ACTION_MOVE;
2886
2887 gdk_drag_status (context, action, time);
2888
2889 return action != 0;
2890 }
2891
2892 static gboolean
message_list_update_actions_idle_cb(gpointer user_data)2893 message_list_update_actions_idle_cb (gpointer user_data)
2894 {
2895 GWeakRef *weak_ref = user_data;
2896 MessageList *message_list;
2897
2898 g_return_val_if_fail (weak_ref != NULL, FALSE);
2899
2900 message_list = g_weak_ref_get (weak_ref);
2901 if (message_list) {
2902 message_list->priv->update_actions_idle_id = 0;
2903
2904 if (!message_list->priv->destroyed)
2905 g_signal_emit (message_list, signals[UPDATE_ACTIONS], 0, NULL);
2906
2907 g_object_unref (message_list);
2908 }
2909
2910 return FALSE;
2911 }
2912
2913 static void
message_list_schedule_update_actions(MessageList * message_list)2914 message_list_schedule_update_actions (MessageList *message_list)
2915 {
2916 g_return_if_fail (IS_MESSAGE_LIST (message_list));
2917
2918 if (!message_list->priv->update_actions_idle_id) {
2919 message_list->priv->update_actions_idle_id =
2920 g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, message_list_update_actions_idle_cb,
2921 e_weak_ref_new (message_list), (GDestroyNotify) e_weak_ref_free);
2922 }
2923 }
2924
2925 static void
on_model_row_changed(ETableModel * model,gint row,MessageList * message_list)2926 on_model_row_changed (ETableModel *model,
2927 gint row,
2928 MessageList *message_list)
2929 {
2930 message_list->priv->any_row_changed = TRUE;
2931
2932 if (e_selection_model_is_row_selected (e_tree_get_selection_model (E_TREE (message_list)), row))
2933 message_list_schedule_update_actions (message_list);
2934 }
2935
2936 static gboolean
ml_tree_sorting_changed(ETreeTableAdapter * adapter,MessageList * message_list)2937 ml_tree_sorting_changed (ETreeTableAdapter *adapter,
2938 MessageList *message_list)
2939 {
2940 gboolean group_by_threads;
2941
2942 g_return_val_if_fail (message_list != NULL, FALSE);
2943
2944 group_by_threads = message_list_get_group_by_threads (message_list);
2945
2946 if (group_by_threads && message_list->frozen == 0) {
2947
2948 /* Invalidate the thread tree. */
2949 message_list_set_thread_tree (message_list, NULL);
2950
2951 mail_regen_list (message_list, NULL, NULL);
2952
2953 return TRUE;
2954 } else if (group_by_threads) {
2955 message_list->priv->thaw_needs_regen = TRUE;
2956 }
2957
2958 return FALSE;
2959 }
2960
2961 static gboolean
ml_get_new_mail_bg_color(ETableItem * item,gint row,gint col,GdkRGBA * inout_background,MessageList * message_list)2962 ml_get_new_mail_bg_color (ETableItem *item,
2963 gint row,
2964 gint col,
2965 GdkRGBA *inout_background,
2966 MessageList *message_list)
2967 {
2968 CamelMessageInfo *msg_info;
2969 ETreePath path;
2970
2971 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
2972 g_return_val_if_fail (inout_background != NULL, FALSE);
2973
2974 if (!message_list->priv->new_mail_bg_color || row < 0)
2975 return FALSE;
2976
2977 path = e_tree_table_adapter_node_at_row (e_tree_get_table_adapter (E_TREE (message_list)), row);
2978 if (!path || G_NODE_IS_ROOT ((GNode *) path))
2979 return FALSE;
2980
2981 /* retrieve the message information array */
2982 msg_info = ((GNode *) path)->data;
2983 g_return_val_if_fail (msg_info != NULL, FALSE);
2984
2985 if (!(camel_message_info_get_flags (msg_info) & CAMEL_MESSAGE_SEEN)) {
2986 *inout_background = *(message_list->priv->new_mail_bg_color);
2987 return TRUE;
2988 }
2989
2990 return FALSE;
2991 }
2992
2993 static gboolean
ml_get_bg_color_cb(ETableItem * item,gint row,gint col,GdkRGBA * inout_background,MessageList * message_list)2994 ml_get_bg_color_cb (ETableItem *item,
2995 gint row,
2996 gint col,
2997 GdkRGBA *inout_background,
2998 MessageList *message_list)
2999 {
3000 gboolean was_set = FALSE;
3001
3002 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
3003 g_return_val_if_fail (inout_background != NULL, FALSE);
3004
3005 if (row < 0)
3006 return FALSE;
3007
3008 if (e_selection_model_is_row_selected (e_tree_get_selection_model (E_TREE (message_list)), row)) {
3009 ETableModel *table_model;
3010 gchar *color_spec;
3011
3012 table_model = E_TABLE_MODEL (e_tree_get_table_adapter (E_TREE (message_list)));
3013
3014 color_spec = e_table_model_value_at (table_model, COL_COLOUR, row);
3015 if (color_spec && gdk_rgba_parse (inout_background, color_spec)) {
3016 was_set = TRUE;
3017 }
3018
3019 if (color_spec)
3020 e_table_model_free_value (table_model, COL_COLOUR, color_spec);
3021 }
3022
3023 if (!was_set)
3024 was_set = ml_get_new_mail_bg_color (item, row, col, inout_background, message_list);
3025
3026 return was_set;
3027 }
3028
3029 static void
ml_style_updated_cb(MessageList * message_list)3030 ml_style_updated_cb (MessageList *message_list)
3031 {
3032 GdkRGBA *new_mail_fg_color = NULL;
3033
3034 g_return_if_fail (IS_MESSAGE_LIST (message_list));
3035
3036 g_clear_pointer (&message_list->priv->new_mail_bg_color, gdk_rgba_free);
3037 g_clear_pointer (&message_list->priv->new_mail_fg_color, g_free);
3038
3039 gtk_widget_style_get (GTK_WIDGET (message_list),
3040 "new-mail-bg-color", &message_list->priv->new_mail_bg_color,
3041 "new-mail-fg-color", &new_mail_fg_color,
3042 NULL);
3043
3044 if (new_mail_fg_color) {
3045 message_list->priv->new_mail_fg_color = gdk_rgba_to_string (new_mail_fg_color);
3046
3047 gdk_rgba_free (new_mail_fg_color);
3048 }
3049 }
3050
3051 static void
message_list_get_preferred_width(GtkWidget * widget,gint * out_minimum_width,gint * out_natural_width)3052 message_list_get_preferred_width (GtkWidget *widget,
3053 gint *out_minimum_width,
3054 gint *out_natural_width)
3055 {
3056 /* Chain up to parent's method. */
3057 GTK_WIDGET_CLASS (message_list_parent_class)->get_preferred_width (widget, out_minimum_width, out_natural_width);
3058
3059 if (out_minimum_width && *out_minimum_width < 50)
3060 *out_minimum_width = 50;
3061
3062 if (out_natural_width && out_minimum_width &&
3063 *out_natural_width < *out_minimum_width)
3064 *out_natural_width = *out_minimum_width;
3065 }
3066
3067 static void
message_list_localized_re_changed_cb(GSettings * settings,const gchar * key,gpointer user_data)3068 message_list_localized_re_changed_cb (GSettings *settings,
3069 const gchar *key,
3070 gpointer user_data)
3071 {
3072 MessageList *message_list = user_data;
3073 gchar *prefixes;
3074
3075 g_return_if_fail (IS_MESSAGE_LIST (message_list));
3076
3077 g_mutex_lock (&message_list->priv->re_prefixes_lock);
3078
3079 g_strfreev (message_list->priv->re_prefixes);
3080 prefixes = g_settings_get_string (settings, "composer-localized-re");
3081 message_list->priv->re_prefixes = g_strsplit (prefixes ? prefixes : "", ",", -1);
3082 g_free (prefixes);
3083
3084 g_mutex_unlock (&message_list->priv->re_prefixes_lock);
3085 }
3086
3087 static void
message_list_localized_re_separators_changed_cb(GSettings * settings,const gchar * key,gpointer user_data)3088 message_list_localized_re_separators_changed_cb (GSettings *settings,
3089 const gchar *key,
3090 gpointer user_data)
3091 {
3092 MessageList *message_list = user_data;
3093
3094 g_return_if_fail (IS_MESSAGE_LIST (message_list));
3095
3096 g_mutex_lock (&message_list->priv->re_prefixes_lock);
3097
3098 g_strfreev (message_list->priv->re_separators);
3099 message_list->priv->re_separators = g_settings_get_strv (settings, "composer-localized-re-separators");
3100
3101 if (message_list->priv->re_separators && !*message_list->priv->re_separators) {
3102 g_strfreev (message_list->priv->re_separators);
3103 message_list->priv->re_separators = NULL;
3104 }
3105
3106 g_mutex_unlock (&message_list->priv->re_prefixes_lock);
3107 }
3108
3109 static void
message_list_user_headers_changed_cb(GSettings * settings,const gchar * key,gpointer user_data)3110 message_list_user_headers_changed_cb (GSettings *settings,
3111 const gchar *key,
3112 gpointer user_data)
3113 {
3114 /* Do it this way, to reuse the localized strings from the message-list.etspec */
3115 const gchar *default_titles[] = {
3116 N_("User Header 1"),
3117 N_("User Header 2"),
3118 N_("User Header 3")
3119 };
3120 MessageList *message_list = user_data;
3121 ETableSpecification *spec;
3122 GnomeCanvasItem *header_item;
3123 ETableHeader *header;
3124 gchar **user_headers;
3125 gboolean changed = FALSE;
3126 guint ii, jj;
3127
3128 g_return_if_fail (IS_MESSAGE_LIST (message_list));
3129 #ifdef ENABLE_MAINTAINER_MODE
3130 g_warn_if_fail (G_N_ELEMENTS (default_titles) == CAMEL_UTILS_MAX_USER_HEADERS);
3131 #endif
3132
3133 spec = e_tree_get_spec (E_TREE (message_list));
3134 header_item = e_tree_get_header_item (E_TREE (message_list));
3135 if (header_item)
3136 g_object_get (header_item, "full-header", &header, NULL);
3137 else
3138 header = NULL;
3139
3140 user_headers = g_settings_get_strv (settings, "camel-message-info-user-headers");
3141
3142 for (ii = 0, jj = 0; user_headers && user_headers[ii] && jj < CAMEL_UTILS_MAX_USER_HEADERS; ii++) {
3143 const gchar *header_name = NULL;
3144 gchar *display_name = NULL;
3145
3146 camel_util_decode_user_header_setting (user_headers[ii], &display_name, &header_name);
3147
3148 if (header_name && *header_name) {
3149 ETableColumnSpecification *col_spec;
3150
3151 if (g_strcmp0 (message_list->priv->user_headers[jj], header_name) != 0) {
3152 g_free (message_list->priv->user_headers[jj]);
3153 message_list->priv->user_headers[jj] = g_strdup (header_name);
3154 changed = TRUE;
3155 }
3156
3157 col_spec = spec ? e_table_specification_get_column_by_model_col (spec, COL_USER_HEADER_1 + jj) : NULL;
3158 if (col_spec && g_strcmp0 (col_spec->title, display_name && *display_name ? display_name : header_name) != 0) {
3159 ETableCol *col;
3160
3161 changed = TRUE;
3162 g_free (col_spec->title);
3163 if (display_name && *display_name) {
3164 col_spec->title = display_name;
3165 display_name = NULL;
3166 } else {
3167 col_spec->title = g_strdup (header_name);
3168 }
3169
3170 col = header ? e_table_header_get_column_by_col_idx (header, COL_USER_HEADER_1 + jj) : NULL;
3171 if (col && g_strcmp0 (col->text, col_spec->title) != 0) {
3172 g_free (col->text);
3173 col->text = g_strdup (col_spec->title);
3174 }
3175 }
3176
3177 jj++;
3178 }
3179
3180 g_free (display_name);
3181 }
3182
3183 message_list->priv->user_headers_count = jj;
3184
3185 for (ii = jj; ii < CAMEL_UTILS_MAX_USER_HEADERS; ii++) {
3186 if (message_list->priv->user_headers[ii]) {
3187 ETableColumnSpecification *col_spec;
3188 ETableCol *col;
3189 const gchar *title;
3190
3191 title = _(default_titles[ii]);
3192
3193 col_spec = spec ? e_table_specification_get_column_by_model_col (spec, COL_USER_HEADER_1 + jj) : NULL;
3194 if (col_spec && g_strcmp0 (col_spec->title, title) != 0) {
3195 g_free (col_spec->title);
3196 col_spec->title = g_strdup (title);
3197 }
3198
3199 col = header ? e_table_header_get_column_by_col_idx (header, COL_USER_HEADER_1 + ii) : NULL;
3200 if (col && g_strcmp0 (col->text, title) != 0) {
3201 g_free (col->text);
3202 col->text = g_strdup (title);
3203 }
3204
3205 changed = TRUE;
3206 }
3207
3208 g_free (message_list->priv->user_headers[ii]);
3209 message_list->priv->user_headers[ii] = NULL;
3210 }
3211
3212 message_list->priv->user_headers[jj] = NULL;
3213
3214 g_strfreev (user_headers);
3215
3216 if (changed)
3217 gtk_widget_queue_draw (GTK_WIDGET (message_list));
3218 }
3219
3220 static void
message_list_set_session(MessageList * message_list,EMailSession * session)3221 message_list_set_session (MessageList *message_list,
3222 EMailSession *session)
3223 {
3224 g_return_if_fail (E_IS_MAIL_SESSION (session));
3225 g_return_if_fail (message_list->priv->session == NULL);
3226
3227 message_list->priv->session = g_object_ref (session);
3228 }
3229
3230 static void
message_list_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)3231 message_list_set_property (GObject *object,
3232 guint property_id,
3233 const GValue *value,
3234 GParamSpec *pspec)
3235 {
3236 switch (property_id) {
3237 case PROP_FOLDER:
3238 message_list_set_folder (
3239 MESSAGE_LIST (object),
3240 g_value_get_object (value));
3241 return;
3242
3243 case PROP_GROUP_BY_THREADS:
3244 message_list_set_group_by_threads (
3245 MESSAGE_LIST (object),
3246 g_value_get_boolean (value));
3247 return;
3248
3249 case PROP_SESSION:
3250 message_list_set_session (
3251 MESSAGE_LIST (object),
3252 g_value_get_object (value));
3253 return;
3254
3255 case PROP_SHOW_DELETED:
3256 message_list_set_show_deleted (
3257 MESSAGE_LIST (object),
3258 g_value_get_boolean (value));
3259 return;
3260
3261 case PROP_SHOW_JUNK:
3262 message_list_set_show_junk (
3263 MESSAGE_LIST (object),
3264 g_value_get_boolean (value));
3265 return;
3266
3267 case PROP_SHOW_SUBJECT_ABOVE_SENDER:
3268 message_list_set_show_subject_above_sender (
3269 MESSAGE_LIST (object),
3270 g_value_get_boolean (value));
3271 return;
3272
3273 case PROP_THREAD_LATEST:
3274 message_list_set_thread_latest (
3275 MESSAGE_LIST (object),
3276 g_value_get_boolean (value));
3277 return;
3278
3279 case PROP_THREAD_SUBJECT:
3280 message_list_set_thread_subject (
3281 MESSAGE_LIST (object),
3282 g_value_get_boolean (value));
3283 return;
3284
3285 case PROP_THREAD_COMPRESS:
3286 message_list_set_thread_compress (
3287 MESSAGE_LIST (object),
3288 g_value_get_boolean (value));
3289 return;
3290 }
3291
3292 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
3293 }
3294
3295 static void
message_list_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)3296 message_list_get_property (GObject *object,
3297 guint property_id,
3298 GValue *value,
3299 GParamSpec *pspec)
3300 {
3301 switch (property_id) {
3302 case PROP_COPY_TARGET_LIST:
3303 g_value_set_boxed (
3304 value,
3305 message_list_get_copy_target_list (
3306 MESSAGE_LIST (object)));
3307 return;
3308
3309 case PROP_FOLDER:
3310 g_value_take_object (
3311 value,
3312 message_list_ref_folder (
3313 MESSAGE_LIST (object)));
3314 return;
3315
3316 case PROP_GROUP_BY_THREADS:
3317 g_value_set_boolean (
3318 value,
3319 message_list_get_group_by_threads (
3320 MESSAGE_LIST (object)));
3321 return;
3322
3323 case PROP_PASTE_TARGET_LIST:
3324 g_value_set_boxed (
3325 value,
3326 message_list_get_paste_target_list (
3327 MESSAGE_LIST (object)));
3328 return;
3329
3330 case PROP_SESSION:
3331 g_value_set_object (
3332 value,
3333 message_list_get_session (
3334 MESSAGE_LIST (object)));
3335 return;
3336
3337 case PROP_SHOW_DELETED:
3338 g_value_set_boolean (
3339 value,
3340 message_list_get_show_deleted (
3341 MESSAGE_LIST (object)));
3342 return;
3343
3344 case PROP_SHOW_JUNK:
3345 g_value_set_boolean (
3346 value,
3347 message_list_get_show_junk (
3348 MESSAGE_LIST (object)));
3349 return;
3350
3351 case PROP_SHOW_SUBJECT_ABOVE_SENDER:
3352 g_value_set_boolean (
3353 value,
3354 message_list_get_show_subject_above_sender (
3355 MESSAGE_LIST (object)));
3356 return;
3357
3358 case PROP_THREAD_LATEST:
3359 g_value_set_boolean (
3360 value,
3361 message_list_get_thread_latest (
3362 MESSAGE_LIST (object)));
3363 return;
3364
3365 case PROP_THREAD_SUBJECT:
3366 g_value_set_boolean (
3367 value,
3368 message_list_get_thread_subject (
3369 MESSAGE_LIST (object)));
3370 return;
3371
3372 case PROP_THREAD_COMPRESS:
3373 g_value_set_boolean (
3374 value,
3375 message_list_get_thread_compress (
3376 MESSAGE_LIST (object)));
3377 return;
3378 }
3379
3380 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
3381 }
3382
3383 static void
message_list_dispose(GObject * object)3384 message_list_dispose (GObject *object)
3385 {
3386 MessageList *message_list = MESSAGE_LIST (object);
3387 MessageListPrivate *priv;
3388
3389 priv = message_list->priv;
3390
3391 if (priv->folder_changed_handler_id > 0) {
3392 g_signal_handler_disconnect (
3393 priv->folder,
3394 priv->folder_changed_handler_id);
3395 priv->folder_changed_handler_id = 0;
3396 }
3397
3398 g_clear_pointer (&priv->copy_target_list, gtk_target_list_unref);
3399 g_clear_pointer (&priv->paste_target_list, gtk_target_list_unref);
3400
3401 priv->destroyed = TRUE;
3402
3403 if (message_list->priv->folder != NULL)
3404 mail_regen_cancel (message_list);
3405
3406 g_mutex_lock (&message_list->priv->regen_lock);
3407
3408 /* This can happen when the regen_idle_id is removed before it's invoked */
3409 g_clear_pointer (&message_list->priv->regen_data, regen_data_unref);
3410
3411 g_mutex_unlock (&message_list->priv->regen_lock);
3412
3413 if (message_list->uid_nodemap) {
3414 g_hash_table_foreach (
3415 message_list->uid_nodemap,
3416 (GHFunc) clear_info, message_list);
3417 g_hash_table_destroy (message_list->uid_nodemap);
3418 message_list->uid_nodemap = NULL;
3419 }
3420
3421 if (priv->mail_settings) {
3422 g_signal_handlers_disconnect_by_func (priv->mail_settings,
3423 G_CALLBACK (message_list_localized_re_changed_cb), message_list);
3424
3425 g_signal_handlers_disconnect_by_func (priv->mail_settings,
3426 G_CALLBACK (message_list_localized_re_separators_changed_cb), message_list);
3427 }
3428
3429 if (priv->eds_settings) {
3430 g_signal_handlers_disconnect_by_func (priv->eds_settings,
3431 G_CALLBACK (message_list_user_headers_changed_cb), message_list);
3432 }
3433
3434 g_clear_object (&priv->session);
3435 g_clear_object (&priv->folder);
3436 g_clear_object (&priv->invisible);
3437 g_clear_object (&priv->mail_settings);
3438 g_clear_object (&priv->eds_settings);
3439
3440 g_clear_object (&message_list->extras);
3441
3442 if (message_list->idle_id > 0) {
3443 g_source_remove (message_list->idle_id);
3444 message_list->idle_id = 0;
3445 }
3446
3447 if (message_list->seen_id > 0) {
3448 g_source_remove (message_list->seen_id);
3449 message_list->seen_id = 0;
3450 }
3451
3452 if (priv->update_actions_idle_id) {
3453 g_source_remove (priv->update_actions_idle_id);
3454 priv->update_actions_idle_id = 0;
3455 }
3456
3457 /* Chain up to parent's dispose() method. */
3458 G_OBJECT_CLASS (message_list_parent_class)->dispose (object);
3459 }
3460
3461 static void
message_list_finalize(GObject * object)3462 message_list_finalize (GObject *object)
3463 {
3464 MessageList *message_list = MESSAGE_LIST (object);
3465 guint ii;
3466
3467 g_hash_table_destroy (message_list->normalised_hash);
3468
3469 if (message_list->priv->thread_tree != NULL)
3470 camel_folder_thread_messages_unref (
3471 message_list->priv->thread_tree);
3472
3473 g_free (message_list->search);
3474 g_free (message_list->frozen_search);
3475 g_free (message_list->cursor_uid);
3476 g_strfreev (message_list->priv->re_prefixes);
3477 g_strfreev (message_list->priv->re_separators);
3478
3479 g_mutex_clear (&message_list->priv->regen_lock);
3480 g_mutex_clear (&message_list->priv->thread_tree_lock);
3481 g_mutex_clear (&message_list->priv->re_prefixes_lock);
3482
3483 clear_selection (message_list, &message_list->priv->clipboard);
3484
3485 if (message_list->priv->tree_model_root != NULL)
3486 extended_g_node_destroy (message_list->priv->tree_model_root);
3487
3488 g_clear_pointer (&message_list->priv->new_mail_bg_color, gdk_rgba_free);
3489 g_clear_pointer (&message_list->priv->new_mail_fg_color, g_free);
3490
3491 for (ii = 0; ii < CAMEL_UTILS_MAX_USER_HEADERS; ii++) {
3492 g_free (message_list->priv->user_headers[ii]);
3493 message_list->priv->user_headers[ii] = NULL;
3494 }
3495
3496 /* Chain up to parent's finalize() method. */
3497 G_OBJECT_CLASS (message_list_parent_class)->finalize (object);
3498 }
3499
3500 static void
message_list_constructed(GObject * object)3501 message_list_constructed (GObject *object)
3502 {
3503 /* Chain up to parent's constructed() method. */
3504 G_OBJECT_CLASS (message_list_parent_class)->constructed (object);
3505
3506 e_extensible_load_extensions (E_EXTENSIBLE (object));
3507 }
3508
3509 static void
message_list_selectable_update_actions(ESelectable * selectable,EFocusTracker * focus_tracker,GdkAtom * clipboard_targets,gint n_clipboard_targets)3510 message_list_selectable_update_actions (ESelectable *selectable,
3511 EFocusTracker *focus_tracker,
3512 GdkAtom *clipboard_targets,
3513 gint n_clipboard_targets)
3514 {
3515 ETreeTableAdapter *adapter;
3516 GtkAction *action;
3517 gint row_count;
3518
3519 adapter = e_tree_get_table_adapter (E_TREE (selectable));
3520 row_count = e_table_model_row_count (E_TABLE_MODEL (adapter));
3521
3522 action = e_focus_tracker_get_select_all_action (focus_tracker);
3523 gtk_action_set_tooltip (action, _("Select all visible messages"));
3524 gtk_action_set_sensitive (action, row_count > 0);
3525 }
3526
3527 static void
message_list_selectable_select_all(ESelectable * selectable)3528 message_list_selectable_select_all (ESelectable *selectable)
3529 {
3530 message_list_select_all (MESSAGE_LIST (selectable));
3531 }
3532
3533 static ETreePath
message_list_get_root(ETreeModel * tree_model)3534 message_list_get_root (ETreeModel *tree_model)
3535 {
3536 MessageList *message_list = MESSAGE_LIST (tree_model);
3537
3538 return message_list->priv->tree_model_root;
3539 }
3540
3541 static ETreePath
message_list_get_parent(ETreeModel * tree_model,ETreePath path)3542 message_list_get_parent (ETreeModel *tree_model,
3543 ETreePath path)
3544 {
3545 return ((GNode *) path)->parent;
3546 }
3547
3548 static ETreePath
message_list_get_first_child(ETreeModel * tree_model,ETreePath path)3549 message_list_get_first_child (ETreeModel *tree_model,
3550 ETreePath path)
3551 {
3552 return g_node_first_child ((GNode *) path);
3553 }
3554
3555 static ETreePath
message_list_get_next(ETreeModel * tree_model,ETreePath path)3556 message_list_get_next (ETreeModel *tree_model,
3557 ETreePath path)
3558 {
3559 return g_node_next_sibling ((GNode *) path);
3560 }
3561
3562 static gboolean
message_list_is_root(ETreeModel * tree_model,ETreePath path)3563 message_list_is_root (ETreeModel *tree_model,
3564 ETreePath path)
3565 {
3566 return G_NODE_IS_ROOT ((GNode *) path);
3567 }
3568
3569 static gboolean
message_list_is_expandable(ETreeModel * tree_model,ETreePath path)3570 message_list_is_expandable (ETreeModel *tree_model,
3571 ETreePath path)
3572 {
3573 return (g_node_first_child ((GNode *) path) != NULL);
3574 }
3575
3576 static guint
message_list_get_n_nodes(ETreeModel * tree_model)3577 message_list_get_n_nodes (ETreeModel *tree_model)
3578 {
3579 ETreePath root;
3580
3581 root = e_tree_model_get_root (tree_model);
3582
3583 if (root == NULL)
3584 return 0;
3585
3586 /* The root node is an empty placeholder, so
3587 * subtract one from the count to exclude it. */
3588
3589 return g_node_n_nodes ((GNode *) root, G_TRAVERSE_ALL) - 1;
3590 }
3591
3592 static guint
message_list_get_n_children(ETreeModel * tree_model,ETreePath path)3593 message_list_get_n_children (ETreeModel *tree_model,
3594 ETreePath path)
3595 {
3596 return g_node_n_children ((GNode *) path);
3597 }
3598
3599 static guint
message_list_depth(ETreeModel * tree_model,ETreePath path)3600 message_list_depth (ETreeModel *tree_model,
3601 ETreePath path)
3602 {
3603 guint depth;
3604
3605 if (message_list_get_thread_compress (MESSAGE_LIST (tree_model))) {
3606 GNode *node = ((GNode *) path);
3607
3608 depth = 1;
3609
3610 while (node && !G_NODE_IS_ROOT (node)) {
3611 if (!node->children || node->prev || node->next || G_NODE_IS_ROOT (node->parent) ||
3612 (node->parent && (node->parent->prev || node->parent->next || G_NODE_IS_ROOT (node->parent->parent))))
3613 depth++;
3614
3615 node = node->parent;
3616 }
3617 } else {
3618 depth = g_node_depth ((GNode *) path);
3619 }
3620
3621 return depth;
3622 }
3623
3624 static gboolean
message_list_get_expanded_default(ETreeModel * tree_model)3625 message_list_get_expanded_default (ETreeModel *tree_model)
3626 {
3627 MessageList *message_list = MESSAGE_LIST (tree_model);
3628
3629 return message_list->priv->expanded_default;
3630 }
3631
3632 static gint
message_list_column_count(ETreeModel * tree_model)3633 message_list_column_count (ETreeModel *tree_model)
3634 {
3635 return COL_LAST;
3636 }
3637
3638 static gchar *
message_list_get_save_id(ETreeModel * tree_model,ETreePath path)3639 message_list_get_save_id (ETreeModel *tree_model,
3640 ETreePath path)
3641 {
3642 CamelMessageInfo *info;
3643
3644 if (G_NODE_IS_ROOT ((GNode *) path))
3645 return g_strdup ("root");
3646
3647 /* Note: ETable can ask for the save_id while we're clearing
3648 * it, which is the only time info should be NULL. */
3649 info = ((GNode *) path)->data;
3650 if (info == NULL)
3651 return NULL;
3652
3653 return g_strdup (camel_message_info_get_uid (info));
3654 }
3655
3656 static ETreePath
message_list_get_node_by_id(ETreeModel * tree_model,const gchar * save_id)3657 message_list_get_node_by_id (ETreeModel *tree_model,
3658 const gchar *save_id)
3659 {
3660 MessageList *message_list;
3661
3662 message_list = MESSAGE_LIST (tree_model);
3663
3664 if (!strcmp (save_id, "root"))
3665 return e_tree_model_get_root (tree_model);
3666
3667 return g_hash_table_lookup (message_list->uid_nodemap, save_id);
3668 }
3669
3670 static gpointer
message_list_sort_value_at(ETreeModel * tree_model,ETreePath path,gint col)3671 message_list_sort_value_at (ETreeModel *tree_model,
3672 ETreePath path,
3673 gint col)
3674 {
3675 MessageList *message_list;
3676 GNode *path_node;
3677 struct LatestData ld;
3678 gint64 *res;
3679
3680 message_list = MESSAGE_LIST (tree_model);
3681
3682 if (!(col == COL_SENT || col == COL_RECEIVED))
3683 return e_tree_model_value_at (tree_model, path, col);
3684
3685 path_node = (GNode *) path;
3686
3687 if (G_NODE_IS_ROOT (path_node))
3688 return NULL;
3689
3690 ld.sent = (col == COL_SENT);
3691 ld.latest = 0;
3692
3693 latest_foreach (tree_model, path, &ld);
3694 if (message_list->priv->thread_latest && (!e_tree_get_sort_children_ascending (E_TREE (message_list)) ||
3695 !path_node || !path_node->parent || !path_node->parent->parent))
3696 e_tree_model_node_traverse (
3697 tree_model, path, latest_foreach, &ld);
3698
3699 res = g_new0 (gint64, 1);
3700 *res = (gint64) ld.latest;
3701
3702 return res;
3703 }
3704
3705 static gpointer
message_list_value_at(ETreeModel * tree_model,ETreePath path,gint col)3706 message_list_value_at (ETreeModel *tree_model,
3707 ETreePath path,
3708 gint col)
3709 {
3710 MessageList *message_list;
3711 CamelMessageInfo *msg_info;
3712 gpointer result;
3713
3714 message_list = MESSAGE_LIST (tree_model);
3715
3716 if (!path || G_NODE_IS_ROOT ((GNode *) path))
3717 return NULL;
3718
3719 /* retrieve the message information array */
3720 msg_info = ((GNode *) path)->data;
3721 g_return_val_if_fail (msg_info != NULL, NULL);
3722
3723 camel_message_info_property_lock (msg_info);
3724 result = ml_tree_value_at_ex (tree_model, path, col, msg_info, message_list);
3725 camel_message_info_property_unlock (msg_info);
3726
3727 return result;
3728 }
3729
3730 static gpointer
message_list_duplicate_value(ETreeModel * tree_model,gint col,gconstpointer value)3731 message_list_duplicate_value (ETreeModel *tree_model,
3732 gint col,
3733 gconstpointer value)
3734 {
3735 switch (col) {
3736 case COL_MESSAGE_STATUS:
3737 case COL_FLAGGED:
3738 case COL_SCORE:
3739 case COL_ATTACHMENT:
3740 case COL_DELETED:
3741 case COL_DELETED_OR_JUNK:
3742 case COL_JUNK:
3743 case COL_JUNK_STRIKEOUT_COLOR:
3744 case COL_UNREAD:
3745 case COL_SIZE:
3746 case COL_FOLLOWUP_FLAG:
3747 case COL_FOLLOWUP_FLAG_STATUS:
3748 return (gpointer) value;
3749
3750 case COL_UID:
3751 return (gpointer) camel_pstring_strdup (value);
3752
3753 case COL_FROM:
3754 case COL_SUBJECT:
3755 case COL_TO:
3756 case COL_SENDER:
3757 case COL_SENDER_MAIL:
3758 case COL_RECIPIENTS:
3759 case COL_RECIPIENTS_MAIL:
3760 case COL_MIXED_SENDER:
3761 case COL_MIXED_RECIPIENTS:
3762 case COL_LOCATION:
3763 case COL_LABELS:
3764 case COL_USER_HEADER_1:
3765 case COL_USER_HEADER_2:
3766 case COL_USER_HEADER_3:
3767 return g_strdup (value);
3768
3769 case COL_SENT:
3770 case COL_RECEIVED:
3771 case COL_FOLLOWUP_DUE_BY:
3772 if (value) {
3773 gint64 *res;
3774 const gint64 *pvalue = value;
3775
3776 res = g_new0 (gint64, 1);
3777 *res = *pvalue;
3778
3779 return res;
3780 } else
3781 return NULL;
3782
3783 default:
3784 g_return_val_if_reached (NULL);
3785 }
3786 }
3787
3788 static void
message_list_free_value(ETreeModel * tree_model,gint col,gpointer value)3789 message_list_free_value (ETreeModel *tree_model,
3790 gint col,
3791 gpointer value)
3792 {
3793 switch (col) {
3794 case COL_MESSAGE_STATUS:
3795 case COL_FLAGGED:
3796 case COL_SCORE:
3797 case COL_ATTACHMENT:
3798 case COL_DELETED:
3799 case COL_DELETED_OR_JUNK:
3800 case COL_JUNK:
3801 case COL_JUNK_STRIKEOUT_COLOR:
3802 case COL_UNREAD:
3803 case COL_SIZE:
3804 case COL_FOLLOWUP_FLAG:
3805 case COL_FOLLOWUP_FLAG_STATUS:
3806 case COL_FROM:
3807 case COL_FROM_NORM:
3808 case COL_TO:
3809 case COL_TO_NORM:
3810 case COL_SUBJECT:
3811 case COL_SUBJECT_NORM:
3812 case COL_SUBJECT_TRIMMED:
3813 case COL_COLOUR:
3814 case COL_ITALIC:
3815 break;
3816
3817 case COL_UID:
3818 camel_pstring_free (value);
3819 break;
3820
3821 case COL_LOCATION:
3822 case COL_SENDER:
3823 case COL_SENDER_MAIL:
3824 case COL_RECIPIENTS:
3825 case COL_RECIPIENTS_MAIL:
3826 case COL_MIXED_SENDER:
3827 case COL_MIXED_RECIPIENTS:
3828 case COL_LABELS:
3829 case COL_SENT:
3830 case COL_RECEIVED:
3831 case COL_FOLLOWUP_DUE_BY:
3832 case COL_USER_HEADER_1:
3833 case COL_USER_HEADER_2:
3834 case COL_USER_HEADER_3:
3835 g_free (value);
3836 break;
3837
3838 default:
3839 g_warn_if_reached ();
3840 }
3841 }
3842
3843 static gpointer
message_list_initialize_value(ETreeModel * tree_model,gint col)3844 message_list_initialize_value (ETreeModel *tree_model,
3845 gint col)
3846 {
3847 switch (col) {
3848 case COL_MESSAGE_STATUS:
3849 case COL_FLAGGED:
3850 case COL_SCORE:
3851 case COL_ATTACHMENT:
3852 case COL_DELETED:
3853 case COL_DELETED_OR_JUNK:
3854 case COL_JUNK:
3855 case COL_JUNK_STRIKEOUT_COLOR:
3856 case COL_UNREAD:
3857 case COL_SENT:
3858 case COL_RECEIVED:
3859 case COL_SIZE:
3860 case COL_FROM:
3861 case COL_SUBJECT:
3862 case COL_TO:
3863 case COL_FOLLOWUP_FLAG:
3864 case COL_FOLLOWUP_FLAG_STATUS:
3865 case COL_FOLLOWUP_DUE_BY:
3866 case COL_UID:
3867 case COL_USER_HEADER_1:
3868 case COL_USER_HEADER_2:
3869 case COL_USER_HEADER_3:
3870 return NULL;
3871
3872 case COL_LOCATION:
3873 case COL_SENDER:
3874 case COL_SENDER_MAIL:
3875 case COL_RECIPIENTS:
3876 case COL_RECIPIENTS_MAIL:
3877 case COL_MIXED_SENDER:
3878 case COL_MIXED_RECIPIENTS:
3879 case COL_LABELS:
3880 return g_strdup ("");
3881
3882 default:
3883 g_return_val_if_reached (NULL);
3884 }
3885 }
3886
3887 static gboolean
message_list_value_is_empty(ETreeModel * tree_model,gint col,gconstpointer value)3888 message_list_value_is_empty (ETreeModel *tree_model,
3889 gint col,
3890 gconstpointer value)
3891 {
3892 switch (col) {
3893 case COL_MESSAGE_STATUS:
3894 case COL_FLAGGED:
3895 case COL_SCORE:
3896 case COL_ATTACHMENT:
3897 case COL_DELETED:
3898 case COL_DELETED_OR_JUNK:
3899 case COL_JUNK:
3900 case COL_JUNK_STRIKEOUT_COLOR:
3901 case COL_UNREAD:
3902 case COL_SENT:
3903 case COL_RECEIVED:
3904 case COL_SIZE:
3905 case COL_FOLLOWUP_FLAG_STATUS:
3906 case COL_FOLLOWUP_DUE_BY:
3907 return value == NULL;
3908
3909 case COL_FROM:
3910 case COL_SUBJECT:
3911 case COL_TO:
3912 case COL_FOLLOWUP_FLAG:
3913 case COL_LOCATION:
3914 case COL_SENDER:
3915 case COL_SENDER_MAIL:
3916 case COL_RECIPIENTS:
3917 case COL_RECIPIENTS_MAIL:
3918 case COL_MIXED_SENDER:
3919 case COL_MIXED_RECIPIENTS:
3920 case COL_LABELS:
3921 case COL_UID:
3922 case COL_USER_HEADER_1:
3923 case COL_USER_HEADER_2:
3924 case COL_USER_HEADER_3:
3925 return !(value && *(gchar *) value);
3926
3927 default:
3928 g_return_val_if_reached (FALSE);
3929 }
3930 }
3931
3932 static gchar *
message_list_value_to_string(ETreeModel * tree_model,gint col,gconstpointer value)3933 message_list_value_to_string (ETreeModel *tree_model,
3934 gint col,
3935 gconstpointer value)
3936 {
3937 guint ii;
3938
3939 switch (col) {
3940 case COL_MESSAGE_STATUS:
3941 ii = GPOINTER_TO_UINT (value);
3942 if (ii > 5)
3943 return g_strdup ("");
3944 return g_strdup (status_map[ii]);
3945
3946 case COL_SCORE:
3947 ii = GPOINTER_TO_UINT (value) + 3;
3948 if (ii > 6)
3949 ii = 3;
3950 return g_strdup (score_map[ii]);
3951
3952 case COL_ATTACHMENT:
3953 case COL_FLAGGED:
3954 case COL_DELETED:
3955 case COL_DELETED_OR_JUNK:
3956 case COL_JUNK:
3957 case COL_JUNK_STRIKEOUT_COLOR:
3958 case COL_UNREAD:
3959 case COL_FOLLOWUP_FLAG_STATUS:
3960 ii = GPOINTER_TO_UINT (value);
3961 return g_strdup_printf ("%u", ii);
3962
3963 case COL_SENT:
3964 case COL_RECEIVED:
3965 case COL_FOLLOWUP_DUE_BY:
3966 return filter_date (value);
3967
3968 case COL_SIZE:
3969 return filter_size (GPOINTER_TO_INT (value));
3970
3971 case COL_FROM:
3972 case COL_SUBJECT:
3973 case COL_TO:
3974 case COL_FOLLOWUP_FLAG:
3975 case COL_LOCATION:
3976 case COL_SENDER:
3977 case COL_SENDER_MAIL:
3978 case COL_RECIPIENTS:
3979 case COL_RECIPIENTS_MAIL:
3980 case COL_MIXED_SENDER:
3981 case COL_MIXED_RECIPIENTS:
3982 case COL_LABELS:
3983 case COL_UID:
3984 case COL_USER_HEADER_1:
3985 case COL_USER_HEADER_2:
3986 case COL_USER_HEADER_3:
3987 return g_strdup (value);
3988
3989 default:
3990 g_return_val_if_reached (NULL);
3991 }
3992
3993 }
3994
3995 static void
message_list_class_init(MessageListClass * class)3996 message_list_class_init (MessageListClass *class)
3997 {
3998 GObjectClass *object_class;
3999 GtkWidgetClass *widget_class;
4000
4001 if (!ml_drag_info[0].atom) {
4002 gint ii;
4003
4004 for (ii = 0; ii < G_N_ELEMENTS (ml_drag_info); ii++) {
4005 ml_drag_info[ii].atom = gdk_atom_intern (ml_drag_info[ii].target, FALSE);
4006 }
4007
4008 for (ii = 0; ii < G_N_ELEMENTS (status_map); ii++) {
4009 status_map[ii] = _(status_map[ii]);
4010 }
4011
4012 for (ii = 0; ii < G_N_ELEMENTS (score_map); ii++) {
4013 score_map[ii] = _(score_map[ii]);
4014 }
4015 }
4016
4017 g_type_class_add_private (class, sizeof (MessageListPrivate));
4018
4019 widget_class = GTK_WIDGET_CLASS (class);
4020 widget_class->get_preferred_width = message_list_get_preferred_width;
4021
4022 object_class = G_OBJECT_CLASS (class);
4023 object_class->set_property = message_list_set_property;
4024 object_class->get_property = message_list_get_property;
4025 object_class->dispose = message_list_dispose;
4026 object_class->finalize = message_list_finalize;
4027 object_class->constructed = message_list_constructed;
4028
4029 class->message_list_built = NULL;
4030
4031 /* Inherited from ESelectableInterface */
4032 g_object_class_override_property (
4033 object_class,
4034 PROP_COPY_TARGET_LIST,
4035 "copy-target-list");
4036
4037 g_object_class_install_property (
4038 object_class,
4039 PROP_FOLDER,
4040 g_param_spec_object (
4041 "folder",
4042 "Folder",
4043 "The source folder",
4044 CAMEL_TYPE_FOLDER,
4045 G_PARAM_READWRITE |
4046 G_PARAM_STATIC_STRINGS));
4047
4048 g_object_class_install_property (
4049 object_class,
4050 PROP_GROUP_BY_THREADS,
4051 g_param_spec_boolean (
4052 "group-by-threads",
4053 "Group By Threads",
4054 "Group messages into conversation threads",
4055 FALSE,
4056 G_PARAM_READWRITE |
4057 G_PARAM_CONSTRUCT |
4058 G_PARAM_STATIC_STRINGS));
4059
4060 /* Inherited from ESelectableInterface */
4061 g_object_class_override_property (
4062 object_class,
4063 PROP_PASTE_TARGET_LIST,
4064 "paste-target-list");
4065
4066 g_object_class_install_property (
4067 object_class,
4068 PROP_SESSION,
4069 g_param_spec_object (
4070 "session",
4071 "Mail Session",
4072 "The mail session",
4073 E_TYPE_MAIL_SESSION,
4074 G_PARAM_READWRITE |
4075 G_PARAM_CONSTRUCT_ONLY |
4076 G_PARAM_STATIC_STRINGS));
4077
4078 g_object_class_install_property (
4079 object_class,
4080 PROP_SHOW_DELETED,
4081 g_param_spec_boolean (
4082 "show-deleted",
4083 "Show Deleted",
4084 "Show messages marked for deletion",
4085 FALSE,
4086 G_PARAM_READWRITE |
4087 G_PARAM_CONSTRUCT |
4088 G_PARAM_STATIC_STRINGS));
4089
4090 g_object_class_install_property (
4091 object_class,
4092 PROP_SHOW_JUNK,
4093 g_param_spec_boolean (
4094 "show-junk",
4095 "Show Junk",
4096 "Show messages marked as junk",
4097 FALSE,
4098 G_PARAM_READWRITE |
4099 G_PARAM_CONSTRUCT |
4100 G_PARAM_STATIC_STRINGS));
4101
4102 g_object_class_install_property (
4103 object_class,
4104 PROP_SHOW_SUBJECT_ABOVE_SENDER,
4105 g_param_spec_boolean (
4106 "show-subject-above-sender",
4107 "Show Subject Above Sender",
4108 NULL,
4109 FALSE,
4110 G_PARAM_READWRITE |
4111 G_PARAM_CONSTRUCT |
4112 G_PARAM_STATIC_STRINGS));
4113
4114 g_object_class_install_property (
4115 object_class,
4116 PROP_THREAD_LATEST,
4117 g_param_spec_boolean (
4118 "thread-latest",
4119 "Thread Latest",
4120 "Sort threads by latest message",
4121 TRUE,
4122 G_PARAM_READWRITE |
4123 G_PARAM_CONSTRUCT |
4124 G_PARAM_STATIC_STRINGS));
4125
4126 g_object_class_install_property (
4127 object_class,
4128 PROP_THREAD_SUBJECT,
4129 g_param_spec_boolean (
4130 "thread-subject",
4131 "Thread Subject",
4132 "Thread messages by Subject headers",
4133 FALSE,
4134 G_PARAM_READWRITE |
4135 G_PARAM_CONSTRUCT |
4136 G_PARAM_STATIC_STRINGS));
4137
4138 g_object_class_install_property (
4139 object_class,
4140 PROP_THREAD_COMPRESS,
4141 g_param_spec_boolean (
4142 "thread-compress",
4143 "Thread Compress",
4144 "Compress flat threads",
4145 TRUE,
4146 G_PARAM_READWRITE |
4147 G_PARAM_CONSTRUCT |
4148 G_PARAM_STATIC_STRINGS));
4149
4150 gtk_widget_class_install_style_property (
4151 GTK_WIDGET_CLASS (class),
4152 g_param_spec_boxed (
4153 "new-mail-bg-color",
4154 "New Mail Background Color",
4155 "Background color to use for new mails",
4156 GDK_TYPE_RGBA,
4157 G_PARAM_READABLE));
4158
4159 gtk_widget_class_install_style_property (
4160 GTK_WIDGET_CLASS (class),
4161 g_param_spec_boxed (
4162 "new-mail-fg-color",
4163 "New Mail Foreground Color",
4164 "Foreground color to use for new mails",
4165 GDK_TYPE_RGBA,
4166 G_PARAM_READABLE));
4167
4168 signals[MESSAGE_SELECTED] = g_signal_new (
4169 "message_selected",
4170 MESSAGE_LIST_TYPE,
4171 G_SIGNAL_RUN_LAST,
4172 G_STRUCT_OFFSET (MessageListClass, message_selected),
4173 NULL,
4174 NULL,
4175 g_cclosure_marshal_VOID__STRING,
4176 G_TYPE_NONE, 1,
4177 G_TYPE_STRING);
4178
4179 signals[MESSAGE_LIST_BUILT] = g_signal_new (
4180 "message_list_built",
4181 MESSAGE_LIST_TYPE,
4182 G_SIGNAL_RUN_LAST,
4183 G_STRUCT_OFFSET (MessageListClass, message_list_built),
4184 NULL,
4185 NULL,
4186 g_cclosure_marshal_VOID__VOID,
4187 G_TYPE_NONE, 0);
4188
4189 signals[UPDATE_ACTIONS] = g_signal_new (
4190 "update-actions",
4191 MESSAGE_LIST_TYPE,
4192 G_SIGNAL_RUN_LAST,
4193 0, /* G_STRUCT_OFFSET (MessageListClass, update_actions), */
4194 NULL,
4195 NULL,
4196 g_cclosure_marshal_VOID__VOID,
4197 G_TYPE_NONE, 0);
4198 }
4199
4200 static void
message_list_selectable_init(ESelectableInterface * iface)4201 message_list_selectable_init (ESelectableInterface *iface)
4202 {
4203 iface->update_actions = message_list_selectable_update_actions;
4204 iface->select_all = message_list_selectable_select_all;
4205 }
4206
4207 static void
message_list_tree_model_init(ETreeModelInterface * iface)4208 message_list_tree_model_init (ETreeModelInterface *iface)
4209 {
4210 iface->get_root = message_list_get_root;
4211 iface->get_parent = message_list_get_parent;
4212 iface->get_first_child = message_list_get_first_child;
4213 iface->get_next = message_list_get_next;
4214 iface->is_root = message_list_is_root;
4215 iface->is_expandable = message_list_is_expandable;
4216 iface->get_n_nodes = message_list_get_n_nodes;
4217 iface->get_n_children = message_list_get_n_children;
4218 iface->depth = message_list_depth;
4219 iface->get_expanded_default = message_list_get_expanded_default;
4220 iface->column_count = message_list_column_count;
4221 iface->get_save_id = message_list_get_save_id;
4222 iface->get_node_by_id = message_list_get_node_by_id;
4223 iface->sort_value_at = message_list_sort_value_at;
4224 iface->value_at = message_list_value_at;
4225 iface->duplicate_value = message_list_duplicate_value;
4226 iface->free_value = message_list_free_value;
4227 iface->initialize_value = message_list_initialize_value;
4228 iface->value_is_empty = message_list_value_is_empty;
4229 iface->value_to_string = message_list_value_to_string;
4230 }
4231
4232 static void
message_list_init(MessageList * message_list)4233 message_list_init (MessageList *message_list)
4234 {
4235 MessageListPrivate *p;
4236 GtkTargetList *target_list;
4237 GdkAtom matom;
4238
4239 message_list->priv = MESSAGE_LIST_GET_PRIVATE (message_list);
4240
4241 message_list->normalised_hash = g_hash_table_new_full (
4242 g_str_hash, g_str_equal,
4243 (GDestroyNotify) NULL,
4244 (GDestroyNotify) e_poolv_destroy);
4245
4246 message_list->uid_nodemap = g_hash_table_new (g_str_hash, g_str_equal);
4247
4248 message_list->cursor_uid = NULL;
4249 message_list->last_sel_single = FALSE;
4250
4251 g_mutex_init (&message_list->priv->regen_lock);
4252 g_mutex_init (&message_list->priv->thread_tree_lock);
4253 g_mutex_init (&message_list->priv->re_prefixes_lock);
4254
4255 /* TODO: Should this only get the selection if we're realised? */
4256 p = message_list->priv;
4257 p->invisible = gtk_invisible_new ();
4258 p->destroyed = FALSE;
4259 g_object_ref_sink (p->invisible);
4260 p->any_row_changed = FALSE;
4261
4262 matom = gdk_atom_intern ("x-uid-list", FALSE);
4263 gtk_selection_add_target (p->invisible, GDK_SELECTION_CLIPBOARD, matom, 0);
4264 gtk_selection_add_target (p->invisible, GDK_SELECTION_CLIPBOARD, GDK_SELECTION_TYPE_STRING, 2);
4265
4266 g_signal_connect (
4267 p->invisible, "selection_get",
4268 G_CALLBACK (ml_selection_get), message_list);
4269 g_signal_connect (
4270 p->invisible, "selection_clear_event",
4271 G_CALLBACK (ml_selection_clear_event), message_list);
4272 g_signal_connect (
4273 p->invisible, "selection_received",
4274 G_CALLBACK (ml_selection_received), message_list);
4275
4276 /* FIXME This is currently unused. */
4277 target_list = gtk_target_list_new (NULL, 0);
4278 message_list->priv->copy_target_list = target_list;
4279
4280 /* FIXME This is currently unused. */
4281 target_list = gtk_target_list_new (NULL, 0);
4282 message_list->priv->paste_target_list = target_list;
4283
4284 message_list->priv->mail_settings = e_util_ref_settings ("org.gnome.evolution.mail");
4285 message_list->priv->eds_settings = e_util_ref_settings ("org.gnome.evolution-data-server");
4286 message_list->priv->re_prefixes = NULL;
4287 message_list->priv->re_separators = NULL;
4288 message_list->priv->group_by_threads = TRUE;
4289 message_list->priv->new_mail_bg_color = NULL;
4290 message_list->priv->new_mail_fg_color = NULL;
4291
4292 g_signal_connect (message_list->priv->mail_settings, "changed::composer-localized-re",
4293 G_CALLBACK (message_list_localized_re_changed_cb), message_list);
4294
4295 g_signal_connect (message_list->priv->mail_settings, "changed::composer-localized-re-separators",
4296 G_CALLBACK (message_list_localized_re_separators_changed_cb), message_list);
4297
4298 message_list_localized_re_changed_cb (message_list->priv->mail_settings, NULL, message_list);
4299 message_list_localized_re_separators_changed_cb (message_list->priv->mail_settings, NULL, message_list);
4300
4301 g_signal_connect (message_list->priv->eds_settings, "changed::camel-message-info-user-headers",
4302 G_CALLBACK (message_list_user_headers_changed_cb), message_list);
4303 }
4304
4305 static void
message_list_construct(MessageList * message_list)4306 message_list_construct (MessageList *message_list)
4307 {
4308 ETreeTableAdapter *adapter;
4309 ETableSpecification *specification;
4310 ETableItem *item;
4311 AtkObject *a11y;
4312 gboolean constructed;
4313 gchar *etspecfile;
4314 GError *local_error = NULL;
4315
4316 /*
4317 * The etree
4318 */
4319 message_list->extras = message_list_create_extras (message_list->priv->mail_settings);
4320
4321 etspecfile = g_build_filename (
4322 EVOLUTION_ETSPECDIR, "message-list.etspec", NULL);
4323 specification = e_table_specification_new (etspecfile, &local_error);
4324
4325 /* Failure here is fatal. */
4326 if (local_error != NULL) {
4327 g_error ("%s: %s", etspecfile, local_error->message);
4328 g_return_if_reached ();
4329 }
4330
4331 constructed = e_tree_construct (
4332 E_TREE (message_list),
4333 E_TREE_MODEL (message_list),
4334 message_list->extras, specification);
4335
4336 g_object_unref (specification);
4337 g_free (etspecfile);
4338
4339 adapter = e_tree_get_table_adapter (E_TREE (message_list));
4340
4341 if (constructed)
4342 e_tree_table_adapter_root_node_set_visible (adapter, FALSE);
4343
4344 if (atk_get_root () != NULL) {
4345 a11y = gtk_widget_get_accessible (GTK_WIDGET (message_list));
4346 atk_object_set_name (a11y, _("Messages"));
4347 }
4348
4349 g_signal_connect (
4350 adapter, "model_row_changed",
4351 G_CALLBACK (on_model_row_changed), message_list);
4352
4353 g_signal_connect (
4354 message_list, "cursor_activated",
4355 G_CALLBACK (on_cursor_activated_cmd), message_list);
4356
4357 g_signal_connect (
4358 message_list, "click",
4359 G_CALLBACK (on_click), message_list);
4360
4361 g_signal_connect (
4362 message_list, "selection_change",
4363 G_CALLBACK (on_selection_changed_cmd), message_list);
4364
4365 e_tree_drag_source_set (
4366 E_TREE (message_list), GDK_BUTTON1_MASK,
4367 ml_drag_types, G_N_ELEMENTS (ml_drag_types),
4368 GDK_ACTION_MOVE | GDK_ACTION_COPY);
4369
4370 g_signal_connect (
4371 message_list, "tree_drag_data_get",
4372 G_CALLBACK (ml_tree_drag_data_get), message_list);
4373
4374 gtk_drag_dest_set (
4375 GTK_WIDGET (message_list),
4376 GTK_DEST_DEFAULT_ALL,
4377 ml_drop_types,
4378 G_N_ELEMENTS (ml_drop_types),
4379 GDK_ACTION_MOVE |
4380 GDK_ACTION_COPY);
4381
4382 g_signal_connect (
4383 message_list, "tree_drag_data_received",
4384 G_CALLBACK (ml_tree_drag_data_received), message_list);
4385
4386 g_signal_connect (
4387 message_list, "drag-motion",
4388 G_CALLBACK (ml_tree_drag_motion), message_list);
4389
4390 g_signal_connect (
4391 adapter, "sorting_changed",
4392 G_CALLBACK (ml_tree_sorting_changed), message_list);
4393
4394 item = e_tree_get_item (E_TREE (message_list));
4395 g_signal_connect (item, "get-bg-color",
4396 G_CALLBACK (ml_get_bg_color_cb), message_list);
4397
4398 g_signal_connect (message_list, "realize",
4399 G_CALLBACK (ml_style_updated_cb), NULL);
4400
4401 g_signal_connect (message_list, "style-updated",
4402 G_CALLBACK (ml_style_updated_cb), NULL);
4403
4404 message_list_user_headers_changed_cb (message_list->priv->eds_settings, NULL, message_list);
4405 }
4406
4407 /**
4408 * message_list_new:
4409 *
4410 * Creates a new message-list widget.
4411 *
4412 * Returns a new message-list widget.
4413 **/
4414 GtkWidget *
message_list_new(EMailSession * session)4415 message_list_new (EMailSession *session)
4416 {
4417 GtkWidget *message_list;
4418
4419 g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
4420
4421 message_list = g_object_new (
4422 message_list_get_type (),
4423 "session", session, NULL);
4424
4425 message_list_construct (MESSAGE_LIST (message_list));
4426
4427 return message_list;
4428 }
4429
4430 EMailSession *
message_list_get_session(MessageList * message_list)4431 message_list_get_session (MessageList *message_list)
4432 {
4433 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
4434
4435 return message_list->priv->session;
4436 }
4437
4438 static void
clear_info(gchar * key,GNode * node,MessageList * message_list)4439 clear_info (gchar *key,
4440 GNode *node,
4441 MessageList *message_list)
4442 {
4443 g_clear_object (&node->data);
4444 }
4445
4446 static void
clear_tree(MessageList * message_list,gboolean tfree)4447 clear_tree (MessageList *message_list,
4448 gboolean tfree)
4449 {
4450 ETreeModel *tree_model;
4451 CamelFolder *folder;
4452
4453 #ifdef TIMEIT
4454 struct timeval start, end;
4455 gulong diff;
4456
4457 printf ("Clearing tree\n");
4458 gettimeofday (&start, NULL);
4459 #endif
4460
4461 tree_model = E_TREE_MODEL (message_list);
4462
4463 /* we also reset the uid_rowmap since it is no longer useful/valid anyway */
4464 folder = message_list_ref_folder (message_list);
4465 if (folder != NULL)
4466 g_hash_table_foreach (
4467 message_list->uid_nodemap,
4468 (GHFunc) clear_info, message_list);
4469 g_hash_table_destroy (message_list->uid_nodemap);
4470 message_list->uid_nodemap = g_hash_table_new (g_str_hash, g_str_equal);
4471 g_clear_object (&folder);
4472
4473 message_list->priv->newest_read_date = 0;
4474 message_list->priv->newest_read_uid = NULL;
4475 message_list->priv->oldest_unread_date = 0;
4476 message_list->priv->oldest_unread_uid = NULL;
4477
4478 if (message_list->priv->tree_model_root != NULL) {
4479 /* we should be frozen already */
4480 message_list_tree_model_remove (
4481 message_list, message_list->priv->tree_model_root);
4482 }
4483
4484 e_tree_table_adapter_clear_nodes_silent (e_tree_get_table_adapter (E_TREE (message_list)));
4485
4486 /* Create a new placeholder root node. */
4487 message_list_tree_model_insert (message_list, NULL, 0, NULL);
4488 g_warn_if_fail (message_list->priv->tree_model_root != NULL);
4489
4490 /* Also reset cursor node, it had been just erased */
4491 e_tree_set_cursor (E_TREE (message_list), message_list->priv->tree_model_root);
4492
4493 if (tfree)
4494 e_tree_model_rebuilt (tree_model);
4495 #ifdef TIMEIT
4496 gettimeofday (&end, NULL);
4497 diff = end.tv_sec * 1000 + end.tv_usec / 1000;
4498 diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
4499 printf ("Clearing tree took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
4500 #endif
4501 }
4502
4503 static gboolean
message_list_folder_filters_system_flag(const gchar * expr,const gchar * flag)4504 message_list_folder_filters_system_flag (const gchar *expr,
4505 const gchar *flag)
4506 {
4507 const gchar *pos;
4508
4509 if (!expr || !*expr)
4510 return FALSE;
4511
4512 g_return_val_if_fail (flag && *flag, FALSE);
4513
4514 while (pos = strstr (expr, flag), pos) {
4515 /* This is searching for something like 'system-flag "' + flag + '"'
4516 in the expression, without fully parsing it. */
4517 if (pos > expr && pos[-1] == '\"' && pos[strlen(flag)] == '\"') {
4518 const gchar *system_flag = "system-flag";
4519 gint ii = 2, jj = strlen (system_flag) - 1;
4520
4521 while (pos - ii >= expr && g_ascii_isspace (pos[-ii]))
4522 ii++;
4523
4524 while (pos - ii >= expr && jj >= 0 && system_flag[jj] == pos[-ii]) {
4525 ii++;
4526 jj--;
4527 }
4528
4529 if (jj == -1)
4530 return TRUE;
4531 }
4532
4533 expr = pos + 1;
4534 }
4535
4536 return FALSE;
4537 }
4538
4539 static gboolean
folder_store_supports_vjunk_folder(CamelFolder * folder)4540 folder_store_supports_vjunk_folder (CamelFolder *folder)
4541 {
4542 CamelStore *store;
4543
4544 g_return_val_if_fail (folder != NULL, FALSE);
4545
4546 store = camel_folder_get_parent_store (folder);
4547 if (store == NULL)
4548 return FALSE;
4549
4550 if (CAMEL_IS_VEE_FOLDER (folder))
4551 return TRUE;
4552
4553 if (camel_store_get_flags (store) & CAMEL_STORE_VJUNK)
4554 return TRUE;
4555
4556 if (camel_store_get_flags (store) & CAMEL_STORE_REAL_JUNK_FOLDER)
4557 return TRUE;
4558
4559 return FALSE;
4560 }
4561
4562 static gboolean
message_list_get_hide_junk(MessageList * message_list,CamelFolder * folder)4563 message_list_get_hide_junk (MessageList *message_list,
4564 CamelFolder *folder)
4565 {
4566 guint32 folder_flags;
4567
4568 if (folder == NULL)
4569 return FALSE;
4570
4571 if (message_list_get_show_junk (message_list))
4572 return FALSE;
4573
4574 if (!folder_store_supports_vjunk_folder (folder))
4575 return FALSE;
4576
4577 folder_flags = camel_folder_get_flags (folder);
4578
4579 if (folder_flags & CAMEL_FOLDER_IS_JUNK)
4580 return FALSE;
4581
4582 if (folder_flags & CAMEL_FOLDER_IS_TRASH)
4583 return FALSE;
4584
4585 if (CAMEL_IS_VEE_FOLDER (folder)) {
4586 const gchar *expr = camel_vee_folder_get_expression (CAMEL_VEE_FOLDER (folder));
4587 if (message_list_folder_filters_system_flag (expr, "Junk"))
4588 return FALSE;
4589 }
4590
4591 return TRUE;
4592 }
4593
4594 static gboolean
message_list_get_hide_deleted(MessageList * message_list,CamelFolder * folder)4595 message_list_get_hide_deleted (MessageList *message_list,
4596 CamelFolder *folder)
4597 {
4598 CamelStore *store;
4599 gboolean non_trash_folder;
4600
4601 if (folder == NULL)
4602 return FALSE;
4603
4604 if (message_list_get_show_deleted (message_list))
4605 return FALSE;
4606
4607 store = camel_folder_get_parent_store (folder);
4608 g_return_val_if_fail (store != NULL, FALSE);
4609
4610 non_trash_folder =
4611 ((camel_store_get_flags (store) & CAMEL_STORE_VTRASH) == 0) ||
4612 ((camel_folder_get_flags (folder) & CAMEL_FOLDER_IS_TRASH) == 0);
4613
4614 if (non_trash_folder && CAMEL_IS_VEE_FOLDER (folder)) {
4615 const gchar *expr = camel_vee_folder_get_expression (CAMEL_VEE_FOLDER (folder));
4616 if (message_list_folder_filters_system_flag (expr, "Deleted"))
4617 return FALSE;
4618 }
4619
4620 return non_trash_folder;
4621 }
4622
4623 /* Check if the given node is selectable in the current message list,
4624 * which depends on the type of the folder (normal, junk, trash). */
4625 static gboolean
is_node_selectable(MessageList * message_list,CamelMessageInfo * info,GHashTable * removed_uids)4626 is_node_selectable (MessageList *message_list,
4627 CamelMessageInfo *info,
4628 GHashTable *removed_uids)
4629 {
4630 CamelFolder *folder;
4631 gboolean is_junk_folder;
4632 gboolean is_trash_folder;
4633 guint32 flags, folder_flags;
4634 gboolean flag_junk;
4635 gboolean flag_deleted;
4636 gboolean hide_junk;
4637 gboolean hide_deleted;
4638 gboolean store_has_vjunk;
4639 gboolean selectable = FALSE;
4640
4641 g_return_val_if_fail (info != NULL, FALSE);
4642
4643 if (removed_uids && g_hash_table_contains (removed_uids, camel_message_info_get_uid (info)))
4644 return FALSE;
4645
4646 folder = message_list_ref_folder (message_list);
4647 g_return_val_if_fail (folder != NULL, FALSE);
4648
4649 store_has_vjunk = folder_store_supports_vjunk_folder (folder);
4650 folder_flags = camel_folder_get_flags (folder);
4651
4652 /* check folder type */
4653 is_junk_folder = store_has_vjunk && (folder_flags & CAMEL_FOLDER_IS_JUNK) != 0;
4654 is_trash_folder = folder_flags & CAMEL_FOLDER_IS_TRASH;
4655
4656 hide_junk = message_list_get_hide_junk (message_list, folder);
4657 hide_deleted = message_list_get_hide_deleted (message_list, folder);
4658
4659 g_object_unref (folder);
4660
4661 /* check flags set on current message */
4662 flags = camel_message_info_get_flags (info);
4663 flag_junk = store_has_vjunk && (flags & CAMEL_MESSAGE_JUNK) != 0;
4664 flag_deleted = flags & CAMEL_MESSAGE_DELETED;
4665
4666 /* perform actions depending on folder type */
4667 if (is_junk_folder) {
4668 /* messages in a junk folder are selectable only if
4669 * the message is marked as junk and if not deleted
4670 * when hide_deleted is set */
4671 if (flag_junk && !(flag_deleted && hide_deleted))
4672 selectable = TRUE;
4673
4674 } else if (is_trash_folder) {
4675 /* messages in a trash folder are selectable unless
4676 * not deleted any more */
4677 if (flag_deleted)
4678 selectable = TRUE;
4679 } else {
4680 /* in normal folders it depends on hide_deleted,
4681 * hide_junk and the message flags */
4682 if (!(flag_junk && hide_junk)
4683 && !(flag_deleted && hide_deleted))
4684 selectable = TRUE;
4685 }
4686
4687 return selectable;
4688 }
4689
4690 /* We try and find something that is selectable in our tree. There is
4691 * actually no assurance that we'll find something that will still be
4692 * there next time, but its probably going to work most of the time. */
4693 static gchar *
find_next_selectable(MessageList * message_list,GHashTable * removed_uids)4694 find_next_selectable (MessageList *message_list,
4695 GHashTable *removed_uids)
4696 {
4697 ETreeTableAdapter *adapter;
4698 GNode *node;
4699 gint vrow_orig;
4700 gint vrow;
4701 gint row_count;
4702 CamelMessageInfo *info;
4703
4704 node = g_hash_table_lookup (
4705 message_list->uid_nodemap,
4706 message_list->cursor_uid);
4707 if (node == NULL)
4708 return NULL;
4709
4710 info = get_message_info (message_list, node);
4711 if (info && is_node_selectable (message_list, info, removed_uids))
4712 return NULL;
4713
4714 adapter = e_tree_get_table_adapter (E_TREE (message_list));
4715 row_count = e_table_model_row_count (E_TABLE_MODEL (adapter));
4716
4717 /* model_to_view_row etc simply dont work for sorted views. Sigh. */
4718 vrow_orig = e_tree_table_adapter_row_of_node (adapter, node);
4719
4720 /* We already checked this node. */
4721 vrow = vrow_orig + 1;
4722
4723 while (vrow < row_count) {
4724 node = e_tree_table_adapter_node_at_row (adapter, vrow);
4725 info = get_message_info (message_list, node);
4726 if (info && is_node_selectable (message_list, info, removed_uids))
4727 return g_strdup (camel_message_info_get_uid (info));
4728 vrow++;
4729 }
4730
4731 /* We didn't find any undeleted entries _below_ the currently selected one
4732 * * so let's try to find one _above_ */
4733 vrow = vrow_orig - 1;
4734
4735 while (vrow >= 0) {
4736 node = e_tree_table_adapter_node_at_row (adapter, vrow);
4737 info = get_message_info (message_list, node);
4738 if (info && is_node_selectable (message_list, info, removed_uids))
4739 return g_strdup (camel_message_info_get_uid (info));
4740 vrow--;
4741 }
4742
4743 return NULL;
4744 }
4745
4746 static GNode *
ml_uid_nodemap_insert(MessageList * message_list,CamelMessageInfo * info,GNode * parent,gint row)4747 ml_uid_nodemap_insert (MessageList *message_list,
4748 CamelMessageInfo *info,
4749 GNode *parent,
4750 gint row)
4751 {
4752 GNode *node;
4753 const gchar *uid;
4754 time_t date;
4755 guint flags;
4756
4757 if (parent == NULL)
4758 parent = message_list->priv->tree_model_root;
4759
4760 node = message_list_tree_model_insert (
4761 message_list, parent, row, info);
4762
4763 uid = camel_message_info_get_uid (info);
4764 flags = camel_message_info_get_flags (info);
4765 date = camel_message_info_get_date_received (info);
4766
4767 g_object_ref (info);
4768 g_hash_table_insert (message_list->uid_nodemap, (gpointer) uid, node);
4769
4770 /* Track the latest seen and unseen messages shown, used in
4771 * fallback heuristics for automatic message selection. */
4772 if (flags & CAMEL_MESSAGE_SEEN) {
4773 if (date > message_list->priv->newest_read_date) {
4774 message_list->priv->newest_read_date = date;
4775 message_list->priv->newest_read_uid = uid;
4776 }
4777 } else {
4778 if (message_list->priv->oldest_unread_date == 0) {
4779 message_list->priv->oldest_unread_date = date;
4780 message_list->priv->oldest_unread_uid = uid;
4781 } else if (date < message_list->priv->oldest_unread_date) {
4782 message_list->priv->oldest_unread_date = date;
4783 message_list->priv->oldest_unread_uid = uid;
4784 }
4785 }
4786
4787 return node;
4788 }
4789
4790 static void
ml_uid_nodemap_remove(MessageList * message_list,CamelMessageInfo * info)4791 ml_uid_nodemap_remove (MessageList *message_list,
4792 CamelMessageInfo *info)
4793 {
4794 const gchar *uid;
4795
4796 uid = camel_message_info_get_uid (info);
4797
4798 if (uid == message_list->priv->newest_read_uid) {
4799 message_list->priv->newest_read_date = 0;
4800 message_list->priv->newest_read_uid = NULL;
4801 }
4802
4803 if (uid == message_list->priv->oldest_unread_uid) {
4804 message_list->priv->oldest_unread_date = 0;
4805 message_list->priv->oldest_unread_uid = NULL;
4806 }
4807
4808 g_hash_table_remove (message_list->uid_nodemap, uid);
4809 g_clear_object (&info);
4810 }
4811
4812 /* only call if we have a tree model */
4813 /* builds the tree structure */
4814
4815 static void build_subtree (MessageList *message_list,
4816 GNode *parent,
4817 CamelFolderThreadNode *c,
4818 gint *row);
4819
4820 static void build_subtree_diff (MessageList *message_list,
4821 GNode *parent,
4822 GNode *node,
4823 CamelFolderThreadNode *c,
4824 gint *row);
4825
4826 static void
build_tree(MessageList * message_list,CamelFolderThread * thread,gboolean folder_changed)4827 build_tree (MessageList *message_list,
4828 CamelFolderThread *thread,
4829 gboolean folder_changed)
4830 {
4831 gint row = 0;
4832 ETableItem *table_item = e_tree_get_item (E_TREE (message_list));
4833 #ifdef TIMEIT
4834 struct timeval start, end;
4835 gulong diff;
4836
4837 printf ("Building tree\n");
4838 gettimeofday (&start, NULL);
4839 #endif
4840
4841 #ifdef TIMEIT
4842 gettimeofday (&end, NULL);
4843 diff = end.tv_sec * 1000 + end.tv_usec / 1000;
4844 diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
4845 printf ("Loading tree state took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
4846 #endif
4847
4848 if (message_list->priv->tree_model_root == NULL) {
4849 message_list_tree_model_insert (message_list, NULL, 0, NULL);
4850 g_warn_if_fail (message_list->priv->tree_model_root != NULL);
4851 }
4852
4853 if (table_item)
4854 e_table_item_freeze (table_item);
4855
4856 message_list_tree_model_freeze (message_list);
4857
4858 clear_tree (message_list, FALSE);
4859
4860 build_subtree (
4861 message_list,
4862 message_list->priv->tree_model_root,
4863 thread ? thread->tree : NULL, &row);
4864
4865 message_list_tree_model_thaw (message_list);
4866
4867 if (table_item) {
4868 /* Show the cursor unless we're responding to a
4869 * "folder-changed" signal from our CamelFolder. */
4870 if (folder_changed)
4871 table_item->queue_show_cursor = FALSE;
4872 e_table_item_thaw (table_item);
4873 }
4874
4875 #ifdef TIMEIT
4876 gettimeofday (&end, NULL);
4877 diff = end.tv_sec * 1000 + end.tv_usec / 1000;
4878 diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
4879 printf ("Building tree took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
4880 #endif
4881 }
4882
4883 /* this is about 20% faster than build_subtree_diff,
4884 * entirely because e_tree_model_node_insert (xx, -1 xx)
4885 * is faster than inserting to the right row :( */
4886 /* Otherwise, this code would probably go as it does the same thing essentially */
4887 static void
build_subtree(MessageList * message_list,GNode * parent,CamelFolderThreadNode * c,gint * row)4888 build_subtree (MessageList *message_list,
4889 GNode *parent,
4890 CamelFolderThreadNode *c,
4891 gint *row)
4892 {
4893 GNode *node;
4894
4895 while (c) {
4896 /* phantom nodes no longer allowed */
4897 if (!c->message) {
4898 g_warning ("c->message shouldn't be NULL\n");
4899 c = c->next;
4900 continue;
4901 }
4902
4903 node = ml_uid_nodemap_insert (
4904 message_list,
4905 (CamelMessageInfo *) c->message, parent, -1);
4906
4907 if (c->child) {
4908 build_subtree (message_list, node, c->child, row);
4909 }
4910 c = c->next;
4911 }
4912 }
4913
4914 /* compares a thread tree node with the etable tree node to see if they point to
4915 * the same object */
4916 static gint
node_equal(ETreeModel * etm,GNode * ap,CamelFolderThreadNode * bp)4917 node_equal (ETreeModel *etm,
4918 GNode *ap,
4919 CamelFolderThreadNode *bp)
4920 {
4921 if (bp->message && strcmp (camel_message_info_get_uid (ap->data), camel_message_info_get_uid (bp->message)) == 0)
4922 return 1;
4923
4924 return 0;
4925 }
4926
4927 /* adds a single node, retains save state, and handles adding children if required */
4928 static void
add_node_diff(MessageList * message_list,GNode * parent,GNode * node,CamelFolderThreadNode * c,gint * row,gint myrow)4929 add_node_diff (MessageList *message_list,
4930 GNode *parent,
4931 GNode *node,
4932 CamelFolderThreadNode *c,
4933 gint *row,
4934 gint myrow)
4935 {
4936 CamelMessageInfo *info;
4937 GNode *new_node;
4938
4939 g_return_if_fail (c->message != NULL);
4940
4941 /* XXX Casting away constness. */
4942 info = (CamelMessageInfo *) c->message;
4943
4944 /* we just update the hashtable key */
4945 ml_uid_nodemap_remove (message_list, info);
4946 new_node = ml_uid_nodemap_insert (message_list, info, parent, myrow);
4947 (*row)++;
4948
4949 if (c->child) {
4950 build_subtree_diff (
4951 message_list, new_node, NULL, c->child, row);
4952 }
4953 }
4954
4955 /* removes node, children recursively and all associated data */
4956 static void
remove_node_diff(MessageList * message_list,GNode * node,gint depth)4957 remove_node_diff (MessageList *message_list,
4958 GNode *node,
4959 gint depth)
4960 {
4961 ETreePath cp, cn;
4962 CamelMessageInfo *info;
4963
4964 t (printf ("Removing node: %s\n", (gchar *) node->data));
4965
4966 /* we depth-first remove all node data's ... */
4967 cp = g_node_first_child (node);
4968 while (cp) {
4969 cn = g_node_next_sibling (cp);
4970 remove_node_diff (message_list, cp, depth + 1);
4971 cp = cn;
4972 }
4973
4974 /* and the rowid entry - if and only if it is referencing this node */
4975 info = node->data;
4976
4977 /* and only at the toplevel, remove the node (etree should optimise this remove somewhat) */
4978 if (depth == 0)
4979 message_list_tree_model_remove (message_list, node);
4980
4981 g_return_if_fail (info);
4982 ml_uid_nodemap_remove (message_list, info);
4983 }
4984
4985 /* applies a new tree structure to an existing tree, but only by changing things
4986 * that have changed */
4987 static void
build_subtree_diff(MessageList * message_list,GNode * parent,GNode * node,CamelFolderThreadNode * c,gint * row)4988 build_subtree_diff (MessageList *message_list,
4989 GNode *parent,
4990 GNode *node,
4991 CamelFolderThreadNode *c,
4992 gint *row)
4993 {
4994 ETreeModel *tree_model;
4995 GNode *ap, *ai, *at, *tmp;
4996 CamelFolderThreadNode *bp, *bi, *bt;
4997 gint i, j, myrow = 0;
4998
4999 tree_model = E_TREE_MODEL (message_list);
5000
5001 ap = node;
5002 bp = c;
5003
5004 while (ap || bp) {
5005 t (printf ("Processing row: %d (subtree row %d)\n", *row, myrow));
5006 if (ap == NULL) {
5007 t (printf ("out of old nodes\n"));
5008 /* ran out of old nodes - remaining nodes are added */
5009 add_node_diff (
5010 message_list, parent, ap, bp, row, myrow);
5011 myrow++;
5012 bp = bp->next;
5013 } else if (bp == NULL) {
5014 t (printf ("out of new nodes\n"));
5015 /* ran out of new nodes - remaining nodes are removed */
5016 tmp = g_node_next_sibling (ap);
5017 remove_node_diff (message_list, ap, 0);
5018 ap = tmp;
5019 } else if (node_equal (tree_model, ap, bp)) {
5020 *row = (*row)+1;
5021 myrow++;
5022 tmp = g_node_first_child (ap);
5023 /* make child lists match (if either has one) */
5024 if (bp->child || tmp) {
5025 build_subtree_diff (
5026 message_list, ap, tmp, bp->child, row);
5027 }
5028 ap = g_node_next_sibling (ap);
5029 bp = bp->next;
5030 } else {
5031 t (printf ("searching for matches\n"));
5032 /* we have to scan each side for a match */
5033 bi = bp->next;
5034 ai = g_node_next_sibling (ap);
5035 for (i = 1; bi != NULL; i++,bi = bi->next) {
5036 if (node_equal (tree_model, ap, bi))
5037 break;
5038 }
5039 for (j = 1; ai != NULL; j++,ai = g_node_next_sibling (ai)) {
5040 if (node_equal (tree_model, ai, bp))
5041 break;
5042 }
5043 if (i < j) {
5044 /* smaller run of new nodes - must be nodes to add */
5045 if (bi) {
5046 bt = bp;
5047 while (bt != bi) {
5048 t (printf ("adding new node 0\n"));
5049 add_node_diff (
5050 message_list, parent, NULL, bt, row, myrow);
5051 myrow++;
5052 bt = bt->next;
5053 }
5054 bp = bi;
5055 } else {
5056 t (printf ("adding new node 1\n"));
5057 /* no match in new nodes, add one, try next */
5058 add_node_diff (
5059 message_list, parent, NULL, bp, row, myrow);
5060 myrow++;
5061 bp = bp->next;
5062 }
5063 } else {
5064 /* bigger run of old nodes - must be nodes to remove */
5065 if (ai) {
5066 at = ap;
5067 while (at != NULL && at != ai) {
5068 t (printf ("removing old node 0\n"));
5069 tmp = g_node_next_sibling (at);
5070 remove_node_diff (message_list, at, 0);
5071 at = tmp;
5072 }
5073 ap = ai;
5074 } else {
5075 t (printf ("adding new node 2\n"));
5076 /* didn't find match in old nodes, must be new node? */
5077 add_node_diff (
5078 message_list, parent, NULL, bp, row, myrow);
5079 myrow++;
5080 bp = bp->next;
5081 }
5082 }
5083 }
5084 }
5085 }
5086
5087 static void
build_flat(MessageList * message_list,GPtrArray * summary,gboolean folder_changed,GHashTable * removed_uids)5088 build_flat (MessageList *message_list,
5089 GPtrArray *summary,
5090 gboolean folder_changed,
5091 GHashTable *removed_uids)
5092 {
5093 gchar *saveuid = NULL;
5094 gint i;
5095 GPtrArray *selected;
5096 #ifdef TIMEIT
5097 struct timeval start, end;
5098 gulong diff;
5099
5100 printf ("Building flat\n");
5101 gettimeofday (&start, NULL);
5102 #endif
5103
5104 if (message_list->cursor_uid != NULL)
5105 saveuid = find_next_selectable (message_list, removed_uids);
5106
5107 selected = message_list_get_selected (message_list);
5108
5109 message_list_tree_model_freeze (message_list);
5110
5111 clear_tree (message_list, FALSE);
5112
5113 for (i = 0; i < summary->len; i++) {
5114 CamelMessageInfo *info = summary->pdata[i];
5115
5116 ml_uid_nodemap_insert (message_list, info, NULL, -1);
5117 }
5118
5119 message_list_tree_model_thaw (message_list);
5120
5121 message_list_set_selected (message_list, selected);
5122
5123 g_ptr_array_unref (selected);
5124
5125 if (saveuid) {
5126 GNode *node;
5127
5128 node = g_hash_table_lookup (
5129 message_list->uid_nodemap, saveuid);
5130 if (node == NULL) {
5131 g_free (message_list->cursor_uid);
5132 message_list->cursor_uid = NULL;
5133 g_signal_emit (
5134 message_list,
5135 signals[MESSAGE_SELECTED], 0, NULL);
5136 } else if (!folder_changed || !e_tree_get_item (E_TREE (message_list))) {
5137 e_tree_set_cursor (E_TREE (message_list), node);
5138 }
5139 g_free (saveuid);
5140 }
5141
5142 #ifdef TIMEIT
5143 gettimeofday (&end, NULL);
5144 diff = end.tv_sec * 1000 + end.tv_usec / 1000;
5145 diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
5146 printf ("Building flat took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
5147 #endif
5148
5149 }
5150
5151 static void
message_list_change_first_visible_parent(MessageList * message_list,GNode * node)5152 message_list_change_first_visible_parent (MessageList *message_list,
5153 GNode *node)
5154 {
5155 ETreeModel *tree_model;
5156 ETreeTableAdapter *adapter;
5157 GNode *first_visible = NULL;
5158
5159 tree_model = E_TREE_MODEL (message_list);
5160 adapter = e_tree_get_table_adapter (E_TREE (message_list));
5161
5162 while (node != NULL && (node = node->parent) != NULL) {
5163 if (!e_tree_table_adapter_node_is_expanded (adapter, node))
5164 first_visible = node;
5165 }
5166
5167 if (first_visible != NULL) {
5168 e_tree_model_pre_change (tree_model);
5169 e_tree_model_node_data_changed (tree_model, first_visible);
5170 }
5171 }
5172
5173 static CamelFolderChangeInfo *
mail_folder_hide_by_flag(CamelFolder * folder,MessageList * message_list,CamelFolderChangeInfo * changes,gint flag)5174 mail_folder_hide_by_flag (CamelFolder *folder,
5175 MessageList *message_list,
5176 CamelFolderChangeInfo *changes,
5177 gint flag)
5178 {
5179 CamelFolderChangeInfo *newchanges;
5180 CamelMessageInfo *info;
5181 gint i;
5182
5183 newchanges = camel_folder_change_info_new ();
5184
5185 for (i = 0; i < changes->uid_changed->len; i++) {
5186 GNode *node;
5187 guint32 flags;
5188
5189 node = g_hash_table_lookup (
5190 message_list->uid_nodemap,
5191 changes->uid_changed->pdata[i]);
5192 info = camel_folder_get_message_info (
5193 folder, changes->uid_changed->pdata[i]);
5194 if (info)
5195 flags = camel_message_info_get_flags (info);
5196
5197 if (node != NULL && info != NULL && (flags & flag) != 0)
5198 camel_folder_change_info_remove_uid (
5199 newchanges, changes->uid_changed->pdata[i]);
5200 else if (node == NULL && info != NULL && (flags & flag) == 0)
5201 camel_folder_change_info_add_uid (
5202 newchanges, changes->uid_changed->pdata[i]);
5203 else
5204 camel_folder_change_info_change_uid (
5205 newchanges, changes->uid_changed->pdata[i]);
5206
5207 g_clear_object (&info);
5208 }
5209
5210 if (newchanges->uid_added->len > 0 || newchanges->uid_removed->len > 0) {
5211 for (i = 0; i < changes->uid_added->len; i++)
5212 camel_folder_change_info_add_uid (
5213 newchanges, changes->uid_added->pdata[i]);
5214 for (i = 0; i < changes->uid_removed->len; i++)
5215 camel_folder_change_info_remove_uid (
5216 newchanges, changes->uid_removed->pdata[i]);
5217 } else {
5218 camel_folder_change_info_clear (newchanges);
5219 camel_folder_change_info_cat (newchanges, changes);
5220 }
5221
5222 return newchanges;
5223 }
5224
5225 static void
message_list_folder_changed(CamelFolder * folder,CamelFolderChangeInfo * changes,MessageList * message_list)5226 message_list_folder_changed (CamelFolder *folder,
5227 CamelFolderChangeInfo *changes,
5228 MessageList *message_list)
5229 {
5230 CamelFolderChangeInfo *altered_changes = NULL;
5231 RegenData *regen_data;
5232 gboolean need_list_regen = TRUE;
5233
5234 g_return_if_fail (CAMEL_IS_FOLDER (folder));
5235 g_return_if_fail (changes != NULL);
5236 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5237
5238 if (message_list->priv->destroyed)
5239 return;
5240
5241 regen_data = message_list_ref_regen_data (message_list);
5242
5243 d (
5244 printf ("%s: regen_data:%p changes:%p added:%d removed:%d changed:%d recent:%d for '%s'\n",
5245 G_STRFUNC, regen_data, changes,
5246 changes ? changes->uid_added->len : -1,
5247 changes ? changes->uid_removed->len : -1,
5248 changes ? changes->uid_changed->len : -1,
5249 changes ? changes->uid_recent->len : -1,
5250 camel_folder_get_full_name (folder)));
5251
5252 /* Skip the quick update when the message list is being regenerated */
5253 if (changes && !regen_data) {
5254 ETreeModel *tree_model;
5255 gboolean hide_junk;
5256 gboolean hide_deleted;
5257 gint i;
5258
5259 tree_model = E_TREE_MODEL (message_list);
5260
5261 hide_junk = message_list_get_hide_junk (message_list, folder);
5262 hide_deleted = message_list_get_hide_deleted (message_list, folder);
5263
5264 for (i = 0; i < changes->uid_removed->len; i++)
5265 g_hash_table_remove (
5266 message_list->normalised_hash,
5267 changes->uid_removed->pdata[i]);
5268
5269 /* Check if the hidden state has changed.
5270 * If so, modify accordingly and regenerate. */
5271 if (hide_junk || hide_deleted)
5272 altered_changes = mail_folder_hide_by_flag (
5273 folder, message_list, changes,
5274 (hide_junk ? CAMEL_MESSAGE_JUNK : 0) |
5275 (hide_deleted ? CAMEL_MESSAGE_DELETED : 0));
5276 else {
5277 altered_changes = camel_folder_change_info_new ();
5278 camel_folder_change_info_cat (altered_changes, changes);
5279 }
5280
5281 if (altered_changes->uid_added->len == 0 && altered_changes->uid_removed->len == 0 && altered_changes->uid_changed->len < 100) {
5282 for (i = 0; i < altered_changes->uid_changed->len; i++) {
5283 GNode *node;
5284
5285 node = g_hash_table_lookup (
5286 message_list->uid_nodemap,
5287 altered_changes->uid_changed->pdata[i]);
5288 if (node) {
5289 e_tree_model_pre_change (tree_model);
5290 e_tree_model_node_data_changed (tree_model, node);
5291
5292 message_list_change_first_visible_parent (message_list, node);
5293 }
5294 }
5295
5296 g_signal_emit (
5297 message_list,
5298 signals[MESSAGE_LIST_BUILT], 0);
5299
5300 need_list_regen = FALSE;
5301 }
5302 }
5303
5304 if (need_list_regen) {
5305 /* Use 'changes' only if this is not the first change after the folder
5306 had been set. There could happen a race condition on folder enter which prevented
5307 the message list to scroll to the cursor position due to the folder_changed = TRUE,
5308 by cancelling the full rebuild request. */
5309 mail_regen_list (message_list, NULL, message_list->just_set_folder ? NULL : changes);
5310 }
5311
5312 if (altered_changes != NULL)
5313 camel_folder_change_info_free (altered_changes);
5314
5315 if (regen_data)
5316 regen_data_unref (regen_data);
5317 }
5318
5319 typedef struct _FolderChangedData {
5320 GWeakRef *folder; /* CamelFolder * */
5321 CamelFolderChangeInfo *changes;
5322 GWeakRef *message_list; /* MessageList * */
5323 } FolderChangedData;
5324
5325 static void
folder_changed_data_free(gpointer ptr)5326 folder_changed_data_free (gpointer ptr)
5327 {
5328 FolderChangedData *fcd = ptr;
5329
5330 if (fcd) {
5331 e_weak_ref_free (fcd->folder);
5332 e_weak_ref_free (fcd->message_list);
5333 camel_folder_change_info_free (fcd->changes);
5334 g_slice_free (FolderChangedData, fcd);
5335 }
5336 }
5337
5338 static gboolean
message_list_folder_changed_timeout_cb(gpointer user_data)5339 message_list_folder_changed_timeout_cb (gpointer user_data)
5340 {
5341 FolderChangedData *fcd = user_data;
5342 CamelFolder *folder;
5343 MessageList *message_list;
5344
5345 g_return_val_if_fail (fcd != NULL, FALSE);
5346
5347 folder = g_weak_ref_get (fcd->folder);
5348 message_list = g_weak_ref_get (fcd->message_list);
5349
5350 if (folder && message_list)
5351 message_list_folder_changed (folder, fcd->changes, message_list);
5352
5353 g_clear_object (&message_list);
5354 g_clear_object (&folder);
5355
5356 return FALSE;
5357 }
5358
5359 static void
message_list_folder_changed_cb(CamelFolder * folder,CamelFolderChangeInfo * changes,MessageList * message_list)5360 message_list_folder_changed_cb (CamelFolder *folder,
5361 CamelFolderChangeInfo *changes,
5362 MessageList *message_list)
5363 {
5364 if (message_list->priv->destroyed)
5365 return;
5366
5367 if (e_util_is_main_thread (g_thread_self ())) {
5368 message_list_folder_changed (folder, changes, message_list);
5369 } else {
5370 FolderChangedData *fcd;
5371
5372 fcd = g_slice_new0 (FolderChangedData);
5373 fcd->folder = e_weak_ref_new (folder);
5374 fcd->changes = camel_folder_change_info_copy (changes);
5375 fcd->message_list = e_weak_ref_new (message_list);
5376
5377 /* Just to have it called in the main/UI thread */
5378 g_timeout_add_full (G_PRIORITY_DEFAULT, 1,
5379 message_list_folder_changed_timeout_cb,
5380 fcd, folder_changed_data_free);
5381 }
5382 }
5383
5384 CamelFolder *
message_list_ref_folder(MessageList * message_list)5385 message_list_ref_folder (MessageList *message_list)
5386 {
5387 CamelFolder *folder = NULL;
5388
5389 /* XXX Do we need a property lock to guard this? */
5390
5391 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
5392
5393 if (message_list->priv->folder != NULL)
5394 folder = g_object_ref (message_list->priv->folder);
5395
5396 return folder;
5397 }
5398
5399 /**
5400 * message_list_set_folder:
5401 * @message_list: Message List widget
5402 * @folder: folder backend to be set
5403 *
5404 * Sets @folder to be the backend folder for @message_list.
5405 **/
5406 void
message_list_set_folder(MessageList * message_list,CamelFolder * folder)5407 message_list_set_folder (MessageList *message_list,
5408 CamelFolder *folder)
5409 {
5410 /* XXX Do we need a property lock to guard this? */
5411
5412 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5413
5414 if (folder == message_list->priv->folder)
5415 return;
5416
5417 if (folder != NULL) {
5418 g_return_if_fail (CAMEL_IS_FOLDER (folder));
5419 g_object_ref (folder);
5420 }
5421
5422 mail_regen_cancel (message_list);
5423
5424 g_free (message_list->search);
5425 message_list->search = NULL;
5426
5427 g_free (message_list->frozen_search);
5428 message_list->frozen_search = NULL;
5429
5430 if (message_list->seen_id) {
5431 g_source_remove (message_list->seen_id);
5432 message_list->seen_id = 0;
5433 }
5434
5435 /* reset the normalised sort performance hack */
5436 g_hash_table_remove_all (message_list->normalised_hash);
5437
5438 if (message_list->priv->folder != NULL)
5439 save_tree_state (message_list, message_list->priv->folder);
5440
5441 message_list_tree_model_freeze (message_list);
5442 clear_tree (message_list, TRUE);
5443 message_list_tree_model_thaw (message_list);
5444
5445 /* remove the cursor activate idle handler */
5446 if (message_list->idle_id != 0) {
5447 g_source_remove (message_list->idle_id);
5448 message_list->idle_id = 0;
5449 }
5450
5451 if (message_list->priv->folder != NULL) {
5452 g_signal_handler_disconnect (
5453 message_list->priv->folder,
5454 message_list->priv->folder_changed_handler_id);
5455 message_list->priv->folder_changed_handler_id = 0;
5456
5457 if (message_list->uid_nodemap != NULL)
5458 g_hash_table_foreach (
5459 message_list->uid_nodemap,
5460 (GHFunc) clear_info, message_list);
5461
5462 g_clear_object (&message_list->priv->folder);
5463 }
5464
5465 /* Invalidate the thread tree. */
5466 message_list_set_thread_tree (message_list, NULL);
5467
5468 g_free (message_list->cursor_uid);
5469 message_list->cursor_uid = NULL;
5470
5471 /* Always emit message-selected, event when an account node
5472 * (folder == NULL) is selected, so that views know what happened and
5473 * can stop all running operations etc. */
5474 g_signal_emit (message_list, signals[MESSAGE_SELECTED], 0, NULL);
5475
5476 if (folder != NULL) {
5477 gboolean non_trash_folder;
5478 gboolean non_junk_folder;
5479 gint strikeout_col, strikeout_color_col;
5480 ECell *cell;
5481 gulong handler_id;
5482
5483 message_list->priv->folder = folder;
5484 message_list->just_set_folder = TRUE;
5485
5486 non_trash_folder = !(camel_folder_get_flags (folder) & CAMEL_FOLDER_IS_TRASH);
5487 non_junk_folder = !(camel_folder_get_flags (folder) & CAMEL_FOLDER_IS_JUNK);
5488
5489 strikeout_col = -1;
5490 strikeout_color_col = -1;
5491
5492 /* Setup the strikeout effect for non-trash or non-junk folders */
5493 if (non_trash_folder && non_junk_folder) {
5494 strikeout_col = COL_DELETED_OR_JUNK;
5495 strikeout_color_col = COL_JUNK_STRIKEOUT_COLOR;
5496 } else if (non_trash_folder) {
5497 strikeout_col = COL_DELETED;
5498 } else if (non_junk_folder) {
5499 strikeout_col = COL_JUNK;
5500 strikeout_color_col = COL_JUNK_STRIKEOUT_COLOR;
5501 }
5502
5503 cell = e_table_extras_get_cell (message_list->extras, "render_date");
5504 g_object_set (cell, "strikeout-column", strikeout_col, "strikeout-color-column", strikeout_color_col, NULL);
5505
5506 cell = e_table_extras_get_cell (message_list->extras, "render_text");
5507 g_object_set (cell, "strikeout-column", strikeout_col, "strikeout-color-column", strikeout_color_col, NULL);
5508
5509 cell = e_table_extras_get_cell (message_list->extras, "render_size");
5510 g_object_set (cell, "strikeout-column", strikeout_col, "strikeout-color-column", strikeout_color_col, NULL);
5511
5512 cell = e_table_extras_get_cell (message_list->extras, "render_composite_from");
5513 composite_cell_set_strike_col (cell, strikeout_col, strikeout_color_col);
5514
5515 cell = e_table_extras_get_cell (message_list->extras, "render_composite_to");
5516 composite_cell_set_strike_col (cell, strikeout_col, strikeout_color_col);
5517
5518 /* Build the etree suitable for this folder */
5519 message_list_setup_etree (message_list);
5520
5521 handler_id = g_signal_connect (
5522 folder, "changed",
5523 G_CALLBACK (message_list_folder_changed_cb),
5524 message_list);
5525 message_list->priv->folder_changed_handler_id = handler_id;
5526
5527 if (message_list->frozen == 0)
5528 mail_regen_list (message_list, NULL, NULL);
5529 else
5530 message_list->priv->thaw_needs_regen = TRUE;
5531 }
5532 }
5533
5534 GtkTargetList *
message_list_get_copy_target_list(MessageList * message_list)5535 message_list_get_copy_target_list (MessageList *message_list)
5536 {
5537 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
5538
5539 return message_list->priv->copy_target_list;
5540 }
5541
5542 GtkTargetList *
message_list_get_paste_target_list(MessageList * message_list)5543 message_list_get_paste_target_list (MessageList *message_list)
5544 {
5545 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
5546
5547 return message_list->priv->paste_target_list;
5548 }
5549
5550 void
message_list_set_expanded_default(MessageList * message_list,gboolean expanded_default)5551 message_list_set_expanded_default (MessageList *message_list,
5552 gboolean expanded_default)
5553 {
5554 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5555
5556 message_list->priv->expanded_default = expanded_default;
5557 }
5558
5559 gboolean
message_list_get_group_by_threads(MessageList * message_list)5560 message_list_get_group_by_threads (MessageList *message_list)
5561 {
5562 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
5563
5564 return message_list->priv->group_by_threads;
5565 }
5566
5567 void
message_list_set_group_by_threads(MessageList * message_list,gboolean group_by_threads)5568 message_list_set_group_by_threads (MessageList *message_list,
5569 gboolean group_by_threads)
5570 {
5571 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5572
5573 if (group_by_threads == message_list->priv->group_by_threads)
5574 return;
5575
5576 message_list->priv->group_by_threads = group_by_threads;
5577 e_tree_set_grouped_view (E_TREE (message_list), group_by_threads);
5578
5579 g_object_notify (G_OBJECT (message_list), "group-by-threads");
5580
5581 /* Changing this property triggers a message list regen. */
5582 if (message_list->frozen == 0)
5583 mail_regen_list (message_list, NULL, NULL);
5584 else
5585 message_list->priv->thaw_needs_regen = TRUE;
5586 }
5587
5588 gboolean
message_list_get_show_deleted(MessageList * message_list)5589 message_list_get_show_deleted (MessageList *message_list)
5590 {
5591 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
5592
5593 return message_list->priv->show_deleted;
5594 }
5595
5596 void
message_list_set_show_deleted(MessageList * message_list,gboolean show_deleted)5597 message_list_set_show_deleted (MessageList *message_list,
5598 gboolean show_deleted)
5599 {
5600 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5601
5602 if (show_deleted == message_list->priv->show_deleted)
5603 return;
5604
5605 message_list->priv->show_deleted = show_deleted;
5606
5607 g_object_notify (G_OBJECT (message_list), "show-deleted");
5608
5609 /* Invalidate the thread tree. */
5610 message_list_set_thread_tree (message_list, NULL);
5611
5612 /* Changing this property triggers a message list regen. */
5613 if (message_list->frozen == 0)
5614 mail_regen_list (message_list, NULL, NULL);
5615 else
5616 message_list->priv->thaw_needs_regen = TRUE;
5617 }
5618
5619 gboolean
message_list_get_show_junk(MessageList * message_list)5620 message_list_get_show_junk (MessageList *message_list)
5621 {
5622 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
5623
5624 return message_list->priv->show_junk;
5625 }
5626
5627 void
message_list_set_show_junk(MessageList * message_list,gboolean show_junk)5628 message_list_set_show_junk (MessageList *message_list,
5629 gboolean show_junk)
5630 {
5631 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5632
5633 if (show_junk == message_list->priv->show_junk)
5634 return;
5635
5636 message_list->priv->show_junk = show_junk;
5637
5638 g_object_notify (G_OBJECT (message_list), "show-junk");
5639
5640 /* Invalidate the thread tree. */
5641 message_list_set_thread_tree (message_list, NULL);
5642
5643 /* Changing this property triggers a message list regen. */
5644 if (message_list->frozen == 0)
5645 mail_regen_list (message_list, NULL, NULL);
5646 else
5647 message_list->priv->thaw_needs_regen = TRUE;
5648 }
5649
5650 gboolean
message_list_get_show_subject_above_sender(MessageList * message_list)5651 message_list_get_show_subject_above_sender (MessageList *message_list)
5652 {
5653 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
5654
5655 return message_list->priv->show_subject_above_sender;
5656 }
5657
5658 void
message_list_set_show_subject_above_sender(MessageList * message_list,gboolean show_subject_above_sender)5659 message_list_set_show_subject_above_sender (MessageList *message_list,
5660 gboolean show_subject_above_sender)
5661 {
5662 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5663
5664 if (show_subject_above_sender == message_list->priv->show_subject_above_sender)
5665 return;
5666
5667 message_list->priv->show_subject_above_sender = show_subject_above_sender;
5668
5669 if (message_list->extras) {
5670 ECell *cell;
5671
5672 cell = e_table_extras_get_cell (message_list->extras, "render_composite_from");
5673 if (cell)
5674 composite_cell_set_show_subject_above_sender (cell, show_subject_above_sender);
5675
5676 cell = e_table_extras_get_cell (message_list->extras, "render_composite_to");
5677 if (cell)
5678 composite_cell_set_show_subject_above_sender (cell, show_subject_above_sender);
5679
5680 if (message_list->priv->folder &&
5681 gtk_widget_get_realized (GTK_WIDGET (message_list)) &&
5682 gtk_widget_get_visible (GTK_WIDGET (message_list)))
5683 mail_regen_list (message_list, NULL, NULL);
5684 }
5685
5686 g_object_notify (G_OBJECT (message_list), "show-subject-above-sender");
5687 }
5688
5689 gboolean
message_list_get_thread_latest(MessageList * message_list)5690 message_list_get_thread_latest (MessageList *message_list)
5691 {
5692 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
5693
5694 return message_list->priv->thread_latest;
5695 }
5696
5697 void
message_list_set_thread_latest(MessageList * message_list,gboolean thread_latest)5698 message_list_set_thread_latest (MessageList *message_list,
5699 gboolean thread_latest)
5700 {
5701 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5702
5703 if (thread_latest == message_list->priv->thread_latest)
5704 return;
5705
5706 message_list->priv->thread_latest = thread_latest;
5707
5708 g_object_notify (G_OBJECT (message_list), "thread-latest");
5709 }
5710
5711 gboolean
message_list_get_thread_subject(MessageList * message_list)5712 message_list_get_thread_subject (MessageList *message_list)
5713 {
5714 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
5715
5716 return message_list->priv->thread_subject;
5717 }
5718
5719 void
message_list_set_thread_subject(MessageList * message_list,gboolean thread_subject)5720 message_list_set_thread_subject (MessageList *message_list,
5721 gboolean thread_subject)
5722 {
5723 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5724
5725 if (thread_subject == message_list->priv->thread_subject)
5726 return;
5727
5728 message_list->priv->thread_subject = thread_subject;
5729
5730 g_object_notify (G_OBJECT (message_list), "thread-subject");
5731 }
5732
5733 gboolean
message_list_get_thread_compress(MessageList * message_list)5734 message_list_get_thread_compress (MessageList *message_list)
5735 {
5736 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
5737
5738 return message_list->priv->thread_compress;
5739 }
5740
5741 void
message_list_set_thread_compress(MessageList * message_list,gboolean thread_compress)5742 message_list_set_thread_compress (MessageList *message_list,
5743 gboolean thread_compress)
5744 {
5745 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5746
5747 if ((thread_compress ? 1 : 0) == (message_list->priv->thread_compress ? 1 : 0))
5748 return;
5749
5750 message_list->priv->thread_compress = thread_compress;
5751
5752 g_object_notify (G_OBJECT (message_list), "thread-compress");
5753
5754 gtk_widget_queue_draw (GTK_WIDGET (message_list));
5755 }
5756
5757 gboolean
message_list_get_regen_selects_unread(MessageList * message_list)5758 message_list_get_regen_selects_unread (MessageList *message_list)
5759 {
5760 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
5761
5762 return message_list->priv->regen_selects_unread;
5763 }
5764
5765 void
message_list_set_regen_selects_unread(MessageList * message_list,gboolean regen_selects_unread)5766 message_list_set_regen_selects_unread (MessageList *message_list,
5767 gboolean regen_selects_unread)
5768 {
5769 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5770
5771 if ((regen_selects_unread ? 1 : 0) == (message_list->priv->regen_selects_unread ? 1 : 0))
5772 return;
5773
5774 message_list->priv->regen_selects_unread = regen_selects_unread;
5775 }
5776
5777 static gboolean
on_cursor_activated_idle(gpointer data)5778 on_cursor_activated_idle (gpointer data)
5779 {
5780 MessageList *message_list = data;
5781 ESelectionModel *esm;
5782 gint selected;
5783
5784 esm = e_tree_get_selection_model (E_TREE (message_list));
5785 selected = e_selection_model_selected_count (esm);
5786
5787 if (selected == 1 && message_list->cursor_uid) {
5788 d (printf ("emitting cursor changed signal, for uid %s\n", message_list->cursor_uid));
5789 g_signal_emit (
5790 message_list,
5791 signals[MESSAGE_SELECTED], 0,
5792 message_list->cursor_uid);
5793 } else {
5794 g_signal_emit (
5795 message_list,
5796 signals[MESSAGE_SELECTED], 0,
5797 NULL);
5798 }
5799
5800 message_list->idle_id = 0;
5801 return FALSE;
5802 }
5803
5804 static void
on_cursor_activated_cmd(ETree * tree,gint row,GNode * node,gpointer user_data)5805 on_cursor_activated_cmd (ETree *tree,
5806 gint row,
5807 GNode *node,
5808 gpointer user_data)
5809 {
5810 MessageList *message_list = MESSAGE_LIST (user_data);
5811 const gchar *new_uid;
5812
5813 if (node == NULL || G_NODE_IS_ROOT (node))
5814 new_uid = NULL;
5815 else
5816 new_uid = get_message_uid (message_list, node);
5817
5818 /* Do not check the cursor_uid and the new_uid values, because the
5819 * selected item (set in on_selection_changed_cmd) can be different
5820 * from the one with a cursor (when selecting with Ctrl, for example).
5821 * This has a little side-effect, when keeping list it that state,
5822 * then changing folders forth and back will select and move cursor
5823 * to that selected item. Does anybody consider it as a bug? */
5824 if ((message_list->cursor_uid == NULL && new_uid == NULL)
5825 || (message_list->last_sel_single && message_list->cursor_uid != NULL && new_uid != NULL))
5826 return;
5827
5828 g_free (message_list->cursor_uid);
5829 message_list->cursor_uid = g_strdup (new_uid);
5830
5831 if (!message_list->idle_id) {
5832 message_list->idle_id =
5833 g_idle_add_full (
5834 G_PRIORITY_LOW, on_cursor_activated_idle,
5835 message_list, NULL);
5836 }
5837 }
5838
5839 static void
on_selection_changed_cmd(ETree * tree,MessageList * message_list)5840 on_selection_changed_cmd (ETree *tree,
5841 MessageList *message_list)
5842 {
5843 GPtrArray *uids = NULL;
5844 const gchar *newuid;
5845 guint selected_count;
5846 GNode *cursor;
5847
5848 selected_count = message_list_selected_count (message_list);
5849 if (selected_count == 1) {
5850 uids = message_list_get_selected (message_list);
5851
5852 if (uids->len == 1)
5853 newuid = g_ptr_array_index (uids, 0);
5854 else
5855 newuid = NULL;
5856 } else if ((cursor = e_tree_get_cursor (tree)))
5857 newuid = (gchar *) camel_message_info_get_uid (cursor->data);
5858 else
5859 newuid = NULL;
5860
5861 /* If the selection isn't empty, then we ignore the no-uid check, since this event
5862 * is also used for other updating. If it is empty, it might just be a setup event
5863 * from etree which we do need to ignore */
5864 if ((newuid == NULL && message_list->cursor_uid == NULL && selected_count == 0) ||
5865 (message_list->last_sel_single && selected_count == 1 && message_list->cursor_uid != NULL && (newuid == NULL || !strcmp (message_list->cursor_uid, newuid)))) {
5866 /* noop */
5867 } else {
5868 g_free (message_list->cursor_uid);
5869 message_list->cursor_uid = g_strdup (newuid);
5870 if (message_list->idle_id == 0)
5871 message_list->idle_id = g_idle_add_full (
5872 G_PRIORITY_LOW,
5873 on_cursor_activated_idle,
5874 message_list, NULL);
5875 }
5876
5877 message_list->last_sel_single = selected_count == 1;
5878
5879 if (uids)
5880 g_ptr_array_unref (uids);
5881 }
5882
5883 static gint
on_click(ETree * tree,gint row,GNode * node,gint col,GdkEvent * event,MessageList * list)5884 on_click (ETree *tree,
5885 gint row,
5886 GNode *node,
5887 gint col,
5888 GdkEvent *event,
5889 MessageList *list)
5890 {
5891 CamelFolder *folder;
5892 CamelMessageInfo *info;
5893 gboolean folder_is_trash;
5894 gint flag = 0;
5895 guint32 flags;
5896
5897 if (col == COL_MESSAGE_STATUS)
5898 flag = CAMEL_MESSAGE_SEEN;
5899 else if (col == COL_FLAGGED)
5900 flag = CAMEL_MESSAGE_FLAGGED;
5901 else if (col != COL_FOLLOWUP_FLAG_STATUS)
5902 return FALSE;
5903
5904 if (!(info = get_message_info (list, node)))
5905 return FALSE;
5906
5907 folder = message_list_ref_folder (list);
5908 g_return_val_if_fail (folder != NULL, FALSE);
5909
5910 if (col == COL_FOLLOWUP_FLAG_STATUS) {
5911 const gchar *tag, *cmp;
5912
5913 tag = camel_message_info_get_user_tag (info, "follow-up");
5914 cmp = camel_message_info_get_user_tag (info, "completed-on");
5915 if (tag && tag[0]) {
5916 if (cmp && cmp[0]) {
5917 camel_message_info_set_user_tag (info, "follow-up", NULL);
5918 camel_message_info_set_user_tag (info, "due-by", NULL);
5919 camel_message_info_set_user_tag (info, "completed-on", NULL);
5920 } else {
5921 gchar *text;
5922
5923 text = camel_header_format_date (time (NULL), 0);
5924 camel_message_info_set_user_tag (info, "completed-on", text);
5925 g_free (text);
5926 }
5927 } else {
5928 /* default follow-up flag name to use when clicked in the message list column */
5929 camel_message_info_set_user_tag (info, "follow-up", _("Follow-up"));
5930 camel_message_info_set_user_tag (info, "completed-on", NULL);
5931 }
5932
5933 g_object_unref (folder);
5934
5935 return TRUE;
5936 }
5937
5938 flags = camel_message_info_get_flags (info);
5939
5940 folder_is_trash =
5941 ((camel_folder_get_flags (folder) & CAMEL_FOLDER_IS_TRASH) != 0);
5942
5943 /* If a message was marked as deleted and the user flags it as
5944 * important or unread in a non-Trash folder, then undelete the
5945 * message. We avoid automatically undeleting messages while
5946 * viewing a Trash folder because it would cause the message to
5947 * suddenly disappear from the message list, which is confusing
5948 * and alarming to the user. */
5949 if (!folder_is_trash && flags & CAMEL_MESSAGE_DELETED) {
5950 if (col == COL_FLAGGED && !(flags & CAMEL_MESSAGE_FLAGGED))
5951 flag |= CAMEL_MESSAGE_DELETED;
5952
5953 if (col == COL_MESSAGE_STATUS && (flags & CAMEL_MESSAGE_SEEN))
5954 flag |= CAMEL_MESSAGE_DELETED;
5955 }
5956
5957 camel_message_info_set_flags (info, flag, ~flags);
5958
5959 /* Notify the folder tree model that the user has marked a message
5960 * as unread so it doesn't mistake the event as new mail arriving. */
5961 if (col == COL_MESSAGE_STATUS && (flags & CAMEL_MESSAGE_SEEN)) {
5962 EMFolderTreeModel *model;
5963
5964 model = em_folder_tree_model_get_default ();
5965 em_folder_tree_model_user_marked_unread (model, folder, 1);
5966 }
5967
5968 if (flag == CAMEL_MESSAGE_SEEN && list->seen_id &&
5969 g_strcmp0 (list->cursor_uid, camel_message_info_get_uid (info)) == 0) {
5970 g_source_remove (list->seen_id);
5971 list->seen_id = 0;
5972 }
5973
5974 g_object_unref (folder);
5975
5976 return TRUE;
5977 }
5978
5979 struct _ml_selected_data {
5980 MessageList *message_list;
5981 ETreeTableAdapter *adapter;
5982 gboolean with_collapsed_threads;
5983 GPtrArray *uids;
5984 };
5985
5986 static gboolean
ml_getselected_collapsed_cb(ETreeModel * tree_model,ETreePath path,gpointer user_data)5987 ml_getselected_collapsed_cb (ETreeModel *tree_model,
5988 ETreePath path,
5989 gpointer user_data)
5990 {
5991 struct _ml_selected_data *data = user_data;
5992 const gchar *uid;
5993 GNode *node = (GNode *) path;
5994
5995 uid = get_message_uid (data->message_list, node);
5996 g_return_val_if_fail (uid != NULL, FALSE);
5997 g_ptr_array_add (data->uids, g_strdup (uid));
5998
5999 return FALSE;
6000 }
6001
6002 static void
ml_getselected_cb(GNode * node,gpointer user_data)6003 ml_getselected_cb (GNode *node,
6004 gpointer user_data)
6005 {
6006 struct _ml_selected_data *data = user_data;
6007 const gchar *uid;
6008
6009 if (G_NODE_IS_ROOT (node))
6010 return;
6011
6012 uid = get_message_uid (data->message_list, node);
6013 g_return_if_fail (uid != NULL);
6014 g_ptr_array_add (data->uids, g_strdup (uid));
6015
6016 if (data->with_collapsed_threads && g_node_first_child (node) &&
6017 !e_tree_table_adapter_node_is_expanded (data->adapter, node)) {
6018 e_tree_model_node_traverse (E_TREE_MODEL (data->message_list), node, ml_getselected_collapsed_cb, data);
6019 }
6020 }
6021
6022 static GPtrArray *
message_list_get_selected_full(MessageList * message_list,gboolean with_collapsed_threads)6023 message_list_get_selected_full (MessageList *message_list,
6024 gboolean with_collapsed_threads)
6025 {
6026 CamelFolder *folder;
6027 ESelectionModel *selection;
6028
6029 struct _ml_selected_data data = {
6030 message_list,
6031 NULL
6032 };
6033
6034 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
6035
6036 data.adapter = e_tree_get_table_adapter (E_TREE (message_list));
6037 data.with_collapsed_threads = with_collapsed_threads;
6038 data.uids = g_ptr_array_new ();
6039 g_ptr_array_set_free_func (data.uids, (GDestroyNotify) g_free);
6040
6041 selection = e_tree_get_selection_model (E_TREE (message_list));
6042
6043 e_tree_selection_model_foreach (
6044 E_TREE_SELECTION_MODEL (selection),
6045 (ETreeForeachFunc) ml_getselected_cb, &data);
6046
6047 folder = message_list_ref_folder (message_list);
6048
6049 if (folder != NULL && data.uids->len > 0)
6050 camel_folder_sort_uids (folder, data.uids);
6051
6052 g_clear_object (&folder);
6053
6054 return data.uids;
6055 }
6056
6057 GPtrArray *
message_list_get_selected(MessageList * message_list)6058 message_list_get_selected (MessageList *message_list)
6059 {
6060 return message_list_get_selected_full (message_list, FALSE);
6061 }
6062
6063 GPtrArray *
message_list_get_selected_with_collapsed_threads(MessageList * message_list)6064 message_list_get_selected_with_collapsed_threads (MessageList *message_list)
6065 {
6066 return message_list_get_selected_full (message_list, TRUE);
6067 }
6068
6069 void
message_list_set_selected(MessageList * message_list,GPtrArray * uids)6070 message_list_set_selected (MessageList *message_list,
6071 GPtrArray *uids)
6072 {
6073 gint i;
6074 ETreeSelectionModel *etsm;
6075 GNode *node;
6076 GPtrArray *paths;
6077
6078 g_return_if_fail (IS_MESSAGE_LIST (message_list));
6079
6080 paths = g_ptr_array_new ();
6081 etsm = (ETreeSelectionModel *)
6082 e_tree_get_selection_model (E_TREE (message_list));
6083 for (i = 0; i < uids->len; i++) {
6084 node = g_hash_table_lookup (
6085 message_list->uid_nodemap, uids->pdata[i]);
6086 if (node != NULL)
6087 g_ptr_array_add (paths, node);
6088 }
6089
6090 e_tree_selection_model_select_paths (etsm, paths);
6091 g_ptr_array_free (paths, TRUE);
6092 }
6093
6094 struct ml_sort_uids_data {
6095 gchar *uid;
6096 gint row;
6097 };
6098
6099 static gint
ml_sort_uids_cb(gconstpointer a,gconstpointer b)6100 ml_sort_uids_cb (gconstpointer a,
6101 gconstpointer b)
6102 {
6103 struct ml_sort_uids_data * const *pdataA = a;
6104 struct ml_sort_uids_data * const *pdataB = b;
6105
6106 return (* pdataA)->row - (* pdataB)->row;
6107 }
6108
6109 void
message_list_sort_uids(MessageList * message_list,GPtrArray * uids)6110 message_list_sort_uids (MessageList *message_list,
6111 GPtrArray *uids)
6112 {
6113 struct ml_sort_uids_data *data;
6114 GPtrArray *array;
6115 GNode *node;
6116 ETreeTableAdapter *adapter;
6117 gint ii;
6118
6119 g_return_if_fail (message_list != NULL);
6120 g_return_if_fail (IS_MESSAGE_LIST (message_list));
6121 g_return_if_fail (uids != NULL);
6122
6123 if (uids->len <= 1)
6124 return;
6125
6126 adapter = e_tree_get_table_adapter (E_TREE (message_list));
6127
6128 array = g_ptr_array_new_full (uids->len, g_free);
6129
6130 for (ii = 0; ii < uids->len; ii++) {
6131 data = g_new0 (struct ml_sort_uids_data, 1);
6132 data->uid = g_ptr_array_index (uids, ii);
6133
6134 node = g_hash_table_lookup (message_list->uid_nodemap, data->uid);
6135 if (node != NULL)
6136 data->row = e_tree_table_adapter_row_of_node (adapter, node);
6137 else
6138 data->row = ii;
6139
6140 g_ptr_array_add (array, data);
6141 }
6142
6143 g_ptr_array_sort (array, ml_sort_uids_cb);
6144
6145 for (ii = 0; ii < uids->len; ii++) {
6146 data = g_ptr_array_index (array, ii);
6147
6148 uids->pdata[ii] = data->uid;
6149 }
6150
6151 g_ptr_array_free (array, TRUE);
6152 }
6153
6154 struct ml_count_data {
6155 MessageList *message_list;
6156 guint count;
6157 };
6158
6159 static void
ml_getcount_cb(GNode * node,gpointer user_data)6160 ml_getcount_cb (GNode *node,
6161 gpointer user_data)
6162 {
6163 struct ml_count_data *data = user_data;
6164
6165 if (!G_NODE_IS_ROOT (node))
6166 data->count++;
6167 }
6168
6169 guint
message_list_count(MessageList * message_list)6170 message_list_count (MessageList *message_list)
6171 {
6172 struct ml_count_data data = { message_list, 0 };
6173
6174 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), 0);
6175
6176 e_tree_path_foreach (
6177 E_TREE (message_list),
6178 (ETreeForeachFunc) ml_getcount_cb, &data);
6179
6180 return data.count;
6181 }
6182
6183 guint
message_list_selected_count(MessageList * message_list)6184 message_list_selected_count (MessageList *message_list)
6185 {
6186 ESelectionModel *selection;
6187
6188 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), 0);
6189
6190 selection = e_tree_get_selection_model (E_TREE (message_list));
6191 return e_selection_model_selected_count (selection);
6192 }
6193
6194 void
message_list_freeze(MessageList * message_list)6195 message_list_freeze (MessageList *message_list)
6196 {
6197 g_return_if_fail (IS_MESSAGE_LIST (message_list));
6198 message_list->frozen++;
6199 }
6200
6201 void
message_list_thaw(MessageList * message_list)6202 message_list_thaw (MessageList *message_list)
6203 {
6204 g_return_if_fail (IS_MESSAGE_LIST (message_list));
6205 g_return_if_fail (message_list->frozen != 0);
6206
6207 message_list->frozen--;
6208 if (message_list->frozen == 0 && message_list->priv->thaw_needs_regen) {
6209 const gchar *search;
6210
6211 if (message_list->frozen_search != NULL)
6212 search = message_list->frozen_search;
6213 else
6214 search = NULL;
6215
6216 mail_regen_list (message_list, search, NULL);
6217
6218 g_free (message_list->frozen_search);
6219 message_list->frozen_search = NULL;
6220 message_list->priv->thaw_needs_regen = FALSE;
6221 }
6222 }
6223
6224 /* set whether we are in threaded view or flat view */
6225 void
message_list_set_threaded_expand_all(MessageList * message_list)6226 message_list_set_threaded_expand_all (MessageList *message_list)
6227 {
6228 g_return_if_fail (IS_MESSAGE_LIST (message_list));
6229
6230 if (message_list_get_group_by_threads (message_list)) {
6231 message_list->expand_all = 1;
6232
6233 if (message_list->frozen == 0)
6234 mail_regen_list (message_list, NULL, NULL);
6235 else
6236 message_list->priv->thaw_needs_regen = TRUE;
6237 }
6238 }
6239
6240 void
message_list_set_threaded_collapse_all(MessageList * message_list)6241 message_list_set_threaded_collapse_all (MessageList *message_list)
6242 {
6243 g_return_if_fail (IS_MESSAGE_LIST (message_list));
6244
6245 if (message_list_get_group_by_threads (message_list)) {
6246 message_list->collapse_all = 1;
6247
6248 if (message_list->frozen == 0)
6249 mail_regen_list (message_list, NULL, NULL);
6250 else
6251 message_list->priv->thaw_needs_regen = TRUE;
6252 }
6253 }
6254
6255 void
message_list_set_search(MessageList * message_list,const gchar * search)6256 message_list_set_search (MessageList *message_list,
6257 const gchar *search)
6258 {
6259 RegenData *current_regen_data;
6260
6261 g_return_if_fail (IS_MESSAGE_LIST (message_list));
6262
6263 current_regen_data = message_list_ref_regen_data (message_list);
6264
6265 if (!current_regen_data && (search == NULL || search[0] == '\0'))
6266 if (message_list->search == NULL || message_list->search[0] == '\0')
6267 return;
6268
6269 if (!current_regen_data && search != NULL && message_list->search != NULL &&
6270 strcmp (search, message_list->search) == 0) {
6271 return;
6272 }
6273
6274 if (current_regen_data)
6275 regen_data_unref (current_regen_data);
6276
6277 /* Invalidate the thread tree. */
6278 message_list_set_thread_tree (message_list, NULL);
6279
6280 if (message_list->frozen == 0)
6281 mail_regen_list (message_list, search ? search : "", NULL);
6282 else {
6283 g_free (message_list->frozen_search);
6284 message_list->frozen_search = g_strdup (search);
6285 message_list->priv->thaw_needs_regen = TRUE;
6286 }
6287 }
6288
6289 static void
message_list_regen_tweak_search_results(MessageList * message_list,GPtrArray * search_results,CamelFolder * folder,gboolean folder_changed,gboolean show_deleted,gboolean show_junk)6290 message_list_regen_tweak_search_results (MessageList *message_list,
6291 GPtrArray *search_results,
6292 CamelFolder *folder,
6293 gboolean folder_changed,
6294 gboolean show_deleted,
6295 gboolean show_junk)
6296 {
6297 CamelMessageInfo *info;
6298 CamelMessageFlags flags;
6299 const gchar *uid;
6300 gboolean needs_tweaking;
6301 gboolean uid_is_deleted;
6302 gboolean uid_is_junk;
6303 gboolean add_uid;
6304 guint ii;
6305
6306 /* If we're responding to a "folder-changed" signal, then the
6307 * displayed message may not be included in the search results.
6308 * Include the displayed message anyway so it doesn't suddenly
6309 * disappear while the user is reading it. */
6310 needs_tweaking =
6311 ((folder_changed || message_list->just_set_folder) && message_list->cursor_uid != NULL);
6312
6313 if (!needs_tweaking)
6314 return;
6315
6316 uid = message_list->cursor_uid;
6317
6318 /* Scan the search results for a particular UID.
6319 * If found then the results don't need tweaked. */
6320 for (ii = 0; ii < search_results->len; ii++) {
6321 if (g_str_equal (uid, search_results->pdata[ii]))
6322 return;
6323 }
6324
6325 info = camel_folder_get_message_info (folder, uid);
6326
6327 /* XXX Should we emit a runtime warning here? */
6328 if (info == NULL)
6329 return;
6330
6331 flags = camel_message_info_get_flags (info);
6332 uid_is_deleted = ((flags & CAMEL_MESSAGE_DELETED) != 0);
6333 uid_is_junk = ((flags & CAMEL_MESSAGE_JUNK) != 0);
6334
6335 if (!folder_store_supports_vjunk_folder (folder))
6336 uid_is_junk = FALSE;
6337
6338 add_uid =
6339 (!uid_is_junk || show_junk) &&
6340 (!uid_is_deleted || show_deleted);
6341
6342 if (add_uid)
6343 g_ptr_array_add (
6344 search_results,
6345 (gpointer) camel_pstring_strdup (uid));
6346
6347 g_clear_object (&info);
6348 }
6349
6350 static void
message_list_regen_thread(GSimpleAsyncResult * simple,GObject * source_object,GCancellable * cancellable)6351 message_list_regen_thread (GSimpleAsyncResult *simple,
6352 GObject *source_object,
6353 GCancellable *cancellable)
6354 {
6355 MessageList *message_list;
6356 RegenData *regen_data;
6357 GPtrArray *uids, *searchuids = NULL;
6358 CamelMessageInfo *info;
6359 CamelFolder *folder;
6360 GNode *cursor;
6361 ETree *tree;
6362 GString *expr;
6363 gboolean hide_deleted;
6364 gboolean hide_junk;
6365 GError *local_error = NULL;
6366
6367 message_list = MESSAGE_LIST (source_object);
6368 regen_data = g_simple_async_result_get_op_res_gpointer (simple);
6369
6370 if (g_cancellable_is_cancelled (cancellable))
6371 return;
6372
6373 /* Just for convenience. */
6374 folder = g_object_ref (regen_data->folder);
6375
6376 hide_junk = message_list_get_hide_junk (message_list, folder);
6377 hide_deleted = message_list_get_hide_deleted (message_list, folder);
6378
6379 tree = E_TREE (message_list);
6380 cursor = e_tree_get_cursor (tree);
6381 if (cursor != NULL)
6382 regen_data->last_row =
6383 e_tree_table_adapter_row_of_node (
6384 e_tree_get_table_adapter (tree), cursor);
6385
6386 /* Construct the search expression. */
6387
6388 expr = g_string_new ("");
6389
6390 if (hide_deleted && hide_junk) {
6391 g_string_append_printf (
6392 expr, "(and %s %s)",
6393 EXCLUDE_DELETED_MESSAGES_EXPR,
6394 EXCLUDE_JUNK_MESSAGES_EXPR);
6395 } else if (hide_deleted) {
6396 g_string_append (expr, EXCLUDE_DELETED_MESSAGES_EXPR);
6397 } else if (hide_junk) {
6398 g_string_append (expr, EXCLUDE_JUNK_MESSAGES_EXPR);
6399 }
6400
6401 /* The 'expr' should be enclosed in "(match-all ...)", thus the search traverses
6402 folder content, but also try to not repeat it, to avoid unnecessary performance hits. */
6403 if (regen_data->search != NULL) {
6404 gboolean is_match_all = g_str_has_prefix (regen_data->search, "(match-all ") && !strstr (regen_data->search, "(body-contains ");
6405 gboolean is_match_threads = strstr (regen_data->search, "(match-threads ") != NULL;
6406
6407 if (expr->len == 0) {
6408 g_string_assign (expr, regen_data->search);
6409
6410 if (!is_match_all && !is_match_threads && expr->len) {
6411 g_string_prepend (expr, "(match-all ");
6412 g_string_append_c (expr, ')');
6413 }
6414 } else if (is_match_threads || !is_match_all) {
6415 /* The "match-threads" cannot be below "match-all". */
6416 g_string_prepend (expr, "(and (match-all ");
6417 g_string_append (expr, ") ");
6418 g_string_append (expr, regen_data->search);
6419 g_string_append_c (expr, ')');
6420 } else {
6421 const gchar *stripped_search = regen_data->search + 11; /* strlen ("(match-all ") */
6422 gint len = strlen (stripped_search);
6423
6424 g_string_prepend (expr, "(match-all (and ");
6425 g_string_append_c (expr, ' ');
6426
6427 if (len > 0 && stripped_search[len - 1] == ')') {
6428 g_string_append_len (expr, stripped_search, len - 1);
6429 } else {
6430 g_string_append (expr, regen_data->search);
6431 }
6432
6433 g_string_append (expr, "))");
6434 }
6435 } else if (expr->len) {
6436 g_string_prepend (expr, "(match-all ");
6437 g_string_append_c (expr, ')');
6438 }
6439
6440 /* Execute the search. */
6441
6442 if (expr->len == 0) {
6443 uids = camel_folder_get_uids (folder);
6444 dd (g_print ("%s: got %d uids in folder %p (%s : %s)\n", G_STRFUNC, uids ? uids->len : -1, folder,
6445 camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder))),
6446 camel_folder_get_full_name (folder)));
6447 } else {
6448 uids = camel_folder_search_by_expression (
6449 folder, expr->str, cancellable, &local_error);
6450
6451 dd (g_print ("%s: got %d uids in folder %p (%s : %s) for expression:---%s---\n", G_STRFUNC,
6452 uids ? uids->len : -1, folder,
6453 camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder))),
6454 camel_folder_get_full_name (folder), expr->str));
6455
6456 /* XXX This indicates we need to use a different
6457 * "free UID" function for some dumb reason. */
6458 searchuids = uids;
6459
6460 if (uids != NULL) {
6461 message_list_regen_tweak_search_results (
6462 message_list,
6463 uids, folder,
6464 regen_data->folder_changed,
6465 !hide_deleted,
6466 !hide_junk);
6467
6468 dd (g_print (" %s: got %d uids in folder %p (%s : %s) after tweak, hide_deleted:%d, hide_junk:%d\n", G_STRFUNC,
6469 uids ? uids->len : -1, folder,
6470 camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder))),
6471 camel_folder_get_full_name (folder), hide_deleted, hide_junk));
6472 }
6473 }
6474
6475 g_string_free (expr, TRUE);
6476
6477 /* Handle search error or cancellation. */
6478
6479 if (local_error == NULL) {
6480 /* coverity[unchecked_value] */
6481 if (g_cancellable_set_error_if_cancelled (cancellable, &local_error)) {
6482 ;
6483 }
6484 }
6485
6486 if (local_error != NULL) {
6487 g_simple_async_result_take_error (simple, local_error);
6488 goto exit;
6489 }
6490
6491 /* XXX This check might not be necessary. A successfully completed
6492 * search with no results should return an empty UID array, but
6493 * still need to verify that. */
6494 if (uids == NULL)
6495 goto exit;
6496
6497 camel_folder_sort_uids (folder, uids);
6498
6499 /* update/build a new tree */
6500 if (regen_data->group_by_threads) {
6501 CamelFolderThread *thread_tree;
6502
6503 /* Always build a new thread_tree, to avoid race condition
6504 when accessing it here and in the build_tree() call
6505 from multiple threads. */
6506 thread_tree = camel_folder_thread_messages_new (folder, uids, regen_data->thread_subject);
6507
6508 /* We will build the ETreeModel content from this
6509 * CamelFolderThread during regen post-processing.
6510 *
6511 * We're committed at this point so keep our own
6512 * reference in case the MessageList's reference
6513 * gets invalidated before regen post-processing. */
6514 regen_data->thread_tree = thread_tree;
6515
6516 } else {
6517 guint ii;
6518
6519 regen_data->summary = g_ptr_array_new ();
6520
6521 camel_folder_summary_prepare_fetch_all (camel_folder_get_folder_summary (folder), NULL);
6522
6523 for (ii = 0; ii < uids->len; ii++) {
6524 const gchar *uid;
6525
6526 uid = g_ptr_array_index (uids, ii);
6527 info = camel_folder_get_message_info (folder, uid);
6528 if (info != NULL)
6529 g_ptr_array_add (regen_data->summary, info);
6530 }
6531 }
6532
6533 exit:
6534 if (searchuids != NULL)
6535 camel_folder_search_free (folder, searchuids);
6536 else if (uids != NULL)
6537 camel_folder_free_uids (folder, uids);
6538
6539 g_object_unref (folder);
6540 }
6541
6542 static gint
message_list_correct_row_for_remove(MessageList * message_list,gint row,GHashTable * removed_uids)6543 message_list_correct_row_for_remove (MessageList *message_list,
6544 gint row,
6545 GHashTable *removed_uids)
6546 {
6547 ETreeTableAdapter *adapter;
6548 gint orig_row = row, row_count;
6549 gboolean delete_selects_previous;
6550 GSettings *settings;
6551 gboolean done = FALSE;
6552 gint round;
6553
6554 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), row);
6555
6556 if (!removed_uids)
6557 return row;
6558
6559 settings = e_util_ref_settings ("org.gnome.evolution.mail");
6560 delete_selects_previous = g_settings_get_boolean (settings, "delete-selects-previous");
6561 g_clear_object (&settings);
6562
6563 adapter = e_tree_get_table_adapter (E_TREE (message_list));
6564 row_count = e_table_model_row_count (E_TABLE_MODEL (adapter));
6565
6566 for (round = 0; round < 2 && !done && row_count; round++) {
6567 row = orig_row;
6568
6569 /* The first round tries to find the next/previous not-deleted message in the list;
6570 the second round does the same in the opposite direction. */
6571 if (round)
6572 delete_selects_previous = !delete_selects_previous;
6573
6574 while (!done && row >= 0 && row < row_count) {
6575 GNode *node;
6576
6577 node = e_tree_table_adapter_node_at_row (adapter, row);
6578 if (!node)
6579 break;
6580
6581 done = !g_hash_table_contains (removed_uids, get_message_uid (message_list, node));
6582
6583 if (!done) {
6584 if (delete_selects_previous)
6585 row--;
6586 else
6587 row++;
6588 }
6589 }
6590 }
6591
6592 if (!done) {
6593 /* This is flipped due to the second round */
6594 if (delete_selects_previous)
6595 row = row_count - 1;
6596 else
6597 row = row_count ? 0 : -1;
6598 }
6599
6600 return row;
6601 }
6602
6603 static gint
message_list_correct_row_for_remove_in_selection(MessageList * message_list,gint row,GHashTable * removed_uids)6604 message_list_correct_row_for_remove_in_selection (MessageList *message_list,
6605 gint row,
6606 GHashTable *removed_uids)
6607 {
6608 ETreeTableAdapter *adapter;
6609 GNode *node;
6610 GPtrArray *selected;
6611 guint ii;
6612 gint best_row = row, best_dist = -1;
6613
6614 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), row);
6615
6616 if (!removed_uids)
6617 return row;
6618
6619 adapter = e_tree_get_table_adapter (E_TREE (message_list));
6620 node = e_tree_table_adapter_node_at_row (adapter, row);
6621 if (!node || !g_hash_table_contains (removed_uids, get_message_uid (message_list, node)))
6622 return row;
6623
6624 selected = message_list_get_selected (message_list);
6625 if (!selected)
6626 return row;
6627
6628 for (ii = 0; ii < selected->len; ii++) {
6629 gint sel_row, sel_dist;
6630
6631 node = g_hash_table_lookup (message_list->uid_nodemap, g_ptr_array_index (selected, ii));
6632 if (!node || g_hash_table_contains (removed_uids, get_message_uid (message_list, node)))
6633 continue;
6634
6635 sel_row = e_tree_table_adapter_row_of_node (adapter, node);
6636 sel_dist = ABS (sel_row - row);
6637
6638 /* No good guess between selection, just find the nearest not deleted selected row */
6639 if (sel_dist < best_dist || best_dist == -1) {
6640 best_row = sel_row;
6641 best_dist = sel_dist;
6642 }
6643 }
6644
6645 g_ptr_array_unref (selected);
6646
6647 return best_row;
6648 }
6649
6650 static void
message_list_update_tree_text(MessageList * message_list)6651 message_list_update_tree_text (MessageList *message_list)
6652 {
6653 ETreeTableAdapter *adapter;
6654 ETree *tree;
6655 const gchar *info_message;
6656 gboolean have_search_expr;
6657 gint row_count;
6658
6659 g_return_if_fail (IS_MESSAGE_LIST (message_list));
6660 g_return_if_fail (e_util_is_main_thread (g_thread_self ()));
6661
6662 if (!gtk_widget_get_visible (GTK_WIDGET (message_list)))
6663 return;
6664
6665 tree = E_TREE (message_list);
6666 adapter = e_tree_get_table_adapter (tree);
6667 row_count = e_table_model_row_count (E_TABLE_MODEL (adapter));
6668
6669 /* space is used to indicate no search too */
6670 have_search_expr =
6671 (message_list->search != NULL) &&
6672 (*message_list->search != '\0') &&
6673 (strcmp (message_list->search, " ") != 0);
6674
6675 if (row_count > 0) {
6676 info_message = NULL;
6677 } else if (message_list_is_setting_up_search_folder (message_list)) {
6678 info_message = _("Generating message list…");
6679 } else if (have_search_expr) {
6680 info_message =
6681 _("No message satisfies your search criteria. "
6682 "Change search criteria by selecting a new "
6683 "Show message filter from the drop down list "
6684 "above or by running a new search either by "
6685 "clearing it with Search→Clear menu item or "
6686 "by changing the query above.");
6687 } else {
6688 info_message = _("There are no messages in this folder.");
6689 }
6690
6691 e_tree_set_info_message (tree, info_message);
6692 }
6693
6694 static void
message_list_regen_done_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)6695 message_list_regen_done_cb (GObject *source_object,
6696 GAsyncResult *result,
6697 gpointer user_data)
6698 {
6699 MessageList *message_list;
6700 GSimpleAsyncResult *simple;
6701 RegenData *regen_data;
6702 EActivity *activity;
6703 ETree *tree;
6704 ETreeTableAdapter *adapter;
6705 gboolean was_searching, is_searching;
6706 gint row_count;
6707 const gchar *start_selection_uid = NULL, *last_row_uid = NULL; /* These are in Camel's string pool */
6708 GError *local_error = NULL;
6709
6710 message_list = MESSAGE_LIST (source_object);
6711 simple = G_SIMPLE_ASYNC_RESULT (result);
6712 regen_data = g_simple_async_result_get_op_res_gpointer (simple);
6713
6714 /* Withdraw our RegenData from the private struct, if it hasn't
6715 * already been replaced. We have exclusive access to it now. */
6716 g_mutex_lock (&message_list->priv->regen_lock);
6717 if (message_list->priv->regen_data == regen_data) {
6718 regen_data_unref (message_list->priv->regen_data);
6719 message_list->priv->regen_data = NULL;
6720 e_tree_set_info_message (E_TREE (message_list), NULL);
6721 }
6722 g_mutex_unlock (&message_list->priv->regen_lock);
6723
6724 activity = regen_data->activity;
6725
6726 if (g_simple_async_result_propagate_error (simple, &local_error) &&
6727 e_activity_handle_cancellation (activity, local_error)) {
6728 g_error_free (local_error);
6729 return;
6730
6731 /* FIXME This should be handed off to an EAlertSink. */
6732 } else if (local_error != NULL) {
6733 g_warning ("%s: %s", G_STRFUNC, local_error->message);
6734 g_error_free (local_error);
6735 return;
6736 }
6737
6738 e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
6739
6740 tree = E_TREE (message_list);
6741 adapter = e_tree_get_table_adapter (tree);
6742
6743 /* Show the cursor unless we're responding to a
6744 * "folder-changed" signal from our CamelFolder. */
6745 if (!regen_data->folder_changed)
6746 e_tree_show_cursor_after_reflow (tree);
6747
6748 g_signal_handlers_block_by_func (
6749 adapter, ml_tree_sorting_changed, message_list);
6750
6751 was_searching = message_list_is_searching (message_list);
6752
6753 g_free (message_list->search);
6754 message_list->search = g_strdup (regen_data->search);
6755
6756 is_searching = message_list_is_searching (message_list);
6757
6758 if (!message_list->just_set_folder) {
6759 gint row;
6760
6761 row = e_tree_selection_model_get_selection_start_row (E_TREE_SELECTION_MODEL (e_tree_get_selection_model (tree)));
6762
6763 if (row != -1)
6764 row = message_list_correct_row_for_remove_in_selection (message_list, row, regen_data->removed_uids);
6765
6766 if (row != -1) {
6767 GNode *node;
6768
6769 node = e_tree_table_adapter_node_at_row (adapter, row);
6770 if (node)
6771 start_selection_uid = camel_pstring_strdup (get_message_uid (message_list, node));
6772 }
6773 }
6774
6775 if (!regen_data->select_all && !regen_data->select_uid && regen_data->last_row != -1) {
6776 regen_data->last_row = message_list_correct_row_for_remove (message_list, regen_data->last_row, regen_data->removed_uids);
6777
6778 if (regen_data->last_row != -1) {
6779 GNode *node;
6780
6781 node = e_tree_table_adapter_node_at_row (adapter, regen_data->last_row);
6782 if (node)
6783 last_row_uid = camel_pstring_strdup (get_message_uid (message_list, node));
6784 }
6785 }
6786
6787 if (regen_data->group_by_threads) {
6788 ETableItem *table_item = e_tree_get_item (E_TREE (message_list));
6789 GPtrArray *selected;
6790 gchar *saveuid = NULL;
6791 gboolean forcing_expand_state;
6792
6793 forcing_expand_state =
6794 message_list->expand_all ||
6795 message_list->collapse_all;
6796
6797 if (message_list->just_set_folder) {
6798 message_list->just_set_folder = FALSE;
6799 /* Load state from disk rather than use
6800 * the memory data when changing folders. */
6801 g_clear_pointer (®en_data->expand_state, xmlFreeDoc);
6802 }
6803
6804 if (forcing_expand_state) {
6805 gint state;
6806
6807 if (message_list->expand_all)
6808 state = 1; /* force expand */
6809 else
6810 state = -1; /* force collapse */
6811
6812 e_tree_table_adapter_force_expanded_state (
6813 adapter, state);
6814 }
6815
6816 if (message_list->cursor_uid != NULL)
6817 saveuid = find_next_selectable (message_list, regen_data->removed_uids);
6818
6819 selected = message_list_get_selected (message_list);
6820
6821 /* Show the cursor unless we're responding to a
6822 * "folder-changed" signal from our CamelFolder. */
6823 build_tree (
6824 message_list,
6825 regen_data->thread_tree,
6826 regen_data->folder_changed);
6827
6828 message_list_set_thread_tree (
6829 message_list, regen_data->thread_tree);
6830
6831 if (forcing_expand_state) {
6832 if (message_list->priv->folder != NULL && tree != NULL)
6833 save_tree_state (message_list, regen_data->folder);
6834
6835 /* Disable forced expand/collapse state. */
6836 e_tree_table_adapter_force_expanded_state (adapter, 0);
6837 } else if (was_searching && !is_searching) {
6838 /* Load expand state from disk */
6839 load_tree_state (
6840 message_list,
6841 regen_data->folder,
6842 NULL);
6843 } else {
6844 /* Load expand state from the previous state or disk */
6845 load_tree_state (
6846 message_list,
6847 regen_data->folder,
6848 regen_data->expand_state);
6849 }
6850
6851 message_list->expand_all = 0;
6852 message_list->collapse_all = 0;
6853
6854 /* restore cursor position only after the expand state is restored,
6855 thus the row numbers will actually match their real rows in UI */
6856
6857 e_table_item_freeze (table_item);
6858
6859 message_list_set_selected (message_list, selected);
6860 g_ptr_array_unref (selected);
6861
6862 /* Show the cursor unless we're responding to a
6863 * "folder-changed" signal from our CamelFolder. */
6864 if (regen_data->folder_changed && table_item != NULL)
6865 table_item->queue_show_cursor = FALSE;
6866
6867 e_table_item_thaw (table_item);
6868
6869 if ((!saveuid || !g_hash_table_lookup (message_list->uid_nodemap, saveuid)) &&
6870 message_list->cursor_uid && g_hash_table_lookup (message_list->uid_nodemap, message_list->cursor_uid)) {
6871 /* this makes sure a visible node is selected, like when
6872 * collapsing all nodes and a children had been selected
6873 */
6874 g_free (saveuid);
6875 saveuid = g_strdup (message_list->cursor_uid);
6876 }
6877
6878 if (message_list_selected_count (message_list) > 1) {
6879 g_free (saveuid);
6880 } else if (saveuid) {
6881 GNode *node;
6882
6883 node = g_hash_table_lookup (
6884 message_list->uid_nodemap, saveuid);
6885 if (node == NULL) {
6886 g_free (message_list->cursor_uid);
6887 message_list->cursor_uid = NULL;
6888 g_signal_emit (
6889 message_list,
6890 signals[MESSAGE_SELECTED], 0, NULL);
6891
6892 } else {
6893 GNode *parent = node;
6894
6895 while ((parent = parent->parent) != NULL) {
6896 if (!e_tree_table_adapter_node_is_expanded (adapter, parent))
6897 node = parent;
6898 }
6899
6900 e_table_item_freeze (table_item);
6901
6902 e_tree_set_cursor (E_TREE (message_list), node);
6903
6904 /* Show the cursor unless we're responding to a
6905 * "folder-changed" signal from our CamelFolder. */
6906 if (regen_data->folder_changed && table_item != NULL)
6907 table_item->queue_show_cursor = FALSE;
6908
6909 e_table_item_thaw (table_item);
6910 }
6911 g_free (saveuid);
6912 } else if (message_list->cursor_uid && !g_hash_table_lookup (message_list->uid_nodemap, message_list->cursor_uid)) {
6913 g_free (message_list->cursor_uid);
6914 message_list->cursor_uid = NULL;
6915 g_signal_emit (
6916 message_list,
6917 signals[MESSAGE_SELECTED], 0, NULL);
6918 }
6919 } else {
6920 build_flat (
6921 message_list,
6922 regen_data->summary,
6923 regen_data->folder_changed,
6924 regen_data->removed_uids);
6925 }
6926
6927 row_count = e_table_model_row_count (E_TABLE_MODEL (adapter));
6928
6929 if (start_selection_uid) {
6930 GNode *node;
6931
6932 node = g_hash_table_lookup (message_list->uid_nodemap, start_selection_uid);
6933 if (node) {
6934 gint row;
6935
6936 row = e_tree_table_adapter_row_of_node (adapter, node);
6937
6938 if (row >= 0 && row < row_count)
6939 e_tree_selection_model_set_selection_start_row (E_TREE_SELECTION_MODEL (e_tree_get_selection_model (tree)), row);
6940 }
6941
6942 camel_pstring_free (start_selection_uid);
6943 }
6944
6945 if (regen_data->select_all) {
6946 message_list_select_all (message_list);
6947
6948 } else if (regen_data->select_uid != NULL) {
6949 message_list_select_uid (
6950 message_list,
6951 regen_data->select_uid,
6952 regen_data->select_use_fallback);
6953
6954 } else if (message_list->cursor_uid == NULL && last_row_uid) {
6955 GNode *node = NULL;
6956 gint sel_count;
6957
6958 sel_count = message_list_selected_count (message_list);
6959
6960 /* It can be that multi-select start and/or end had been removed, in which
6961 case "clamp" the new start/end according to start/end of the restored
6962 selection, even if it is not a consecutive selection (Shift+Arrow can
6963 be broken after this "clamp"). */
6964 if (sel_count > 0) {
6965 GPtrArray *selected;
6966
6967 selected = message_list_get_selected (message_list);
6968
6969 if (selected && selected->len) {
6970 guint ii;
6971 gint min_row = -1, max_row = -1;
6972
6973 for (ii = 0; ii < selected->len; ii++) {
6974 GNode *selected_node;
6975
6976 selected_node = g_hash_table_lookup (message_list->uid_nodemap, g_ptr_array_index (selected, ii));
6977 if (selected_node) {
6978 gint selected_row;
6979
6980 selected_row = e_tree_table_adapter_row_of_node (adapter, selected_node);
6981
6982 if (selected_row >= 0 && selected_row < row_count) {
6983 if (min_row > selected_row || min_row == -1)
6984 min_row = selected_row;
6985
6986 if (max_row < selected_row || max_row == -1)
6987 max_row = selected_row;
6988 }
6989 }
6990 }
6991
6992 if (min_row != -1 && max_row != -1) {
6993 gint start_sel_row, new_last_row = regen_data->last_row;
6994
6995 start_sel_row = e_tree_selection_model_get_selection_start_row (E_TREE_SELECTION_MODEL (e_tree_get_selection_model (tree)));
6996 node = g_hash_table_lookup (message_list->uid_nodemap, last_row_uid);
6997 if (node)
6998 new_last_row = e_tree_table_adapter_row_of_node (adapter, node);
6999
7000 /* Swap them if needed */
7001 if (start_sel_row != -1 && start_sel_row > new_last_row) {
7002 gint tmp = min_row;
7003 min_row = max_row;
7004 max_row = tmp;
7005 }
7006
7007 node = e_tree_table_adapter_node_at_row (adapter, max_row);
7008 if (node) {
7009 /* This also deselects rows */
7010 select_node (message_list, node);
7011
7012 message_list_set_selected (message_list, selected);
7013 e_tree_selection_model_set_selection_start_row (E_TREE_SELECTION_MODEL (e_tree_get_selection_model (tree)), min_row);
7014 }
7015 }
7016 }
7017
7018 if (selected)
7019 g_ptr_array_unref (selected);
7020
7021 if (!node)
7022 sel_count = 0;
7023 }
7024
7025 if (!node)
7026 node = g_hash_table_lookup (message_list->uid_nodemap, last_row_uid);
7027
7028 if (!node) {
7029 if (regen_data->last_row >= row_count)
7030 regen_data->last_row = row_count - 1;
7031
7032 if (regen_data->last_row != -1)
7033 node = e_tree_table_adapter_node_at_row (adapter, regen_data->last_row);
7034 }
7035
7036 if (node && sel_count <= 1)
7037 select_node (message_list, node);
7038 }
7039
7040 if (last_row_uid)
7041 camel_pstring_free (last_row_uid);
7042
7043 message_list_update_tree_text (message_list);
7044
7045 g_signal_handlers_unblock_by_func (
7046 adapter, ml_tree_sorting_changed, message_list);
7047
7048 g_signal_emit (
7049 message_list,
7050 signals[MESSAGE_LIST_BUILT], 0);
7051
7052 message_list->priv->any_row_changed = FALSE;
7053 message_list->just_set_folder = FALSE;
7054
7055 if (!regen_data->select_all && regen_data->select_unread) {
7056 ETreePath cursor_path;
7057 gboolean call_select = TRUE;
7058
7059 cursor_path = e_tree_get_cursor (E_TREE (message_list));
7060
7061 if (cursor_path) {
7062 CamelMessageInfo *info;
7063
7064 info = get_message_info (message_list, cursor_path);
7065 if (info && !(camel_message_info_get_flags (info) & CAMEL_MESSAGE_SEEN))
7066 call_select = FALSE;
7067 }
7068
7069 if (call_select) {
7070 message_list_select (MESSAGE_LIST (message_list), MESSAGE_LIST_SELECT_NEXT |
7071 MESSAGE_LIST_SELECT_WRAP | MESSAGE_LIST_SELECT_INCLUDE_COLLAPSED,
7072 0, CAMEL_MESSAGE_SEEN);
7073 }
7074 }
7075 }
7076
7077 static gboolean
message_list_regen_idle_cb(gpointer user_data)7078 message_list_regen_idle_cb (gpointer user_data)
7079 {
7080 GSimpleAsyncResult *simple;
7081 RegenData *regen_data;
7082 GCancellable *cancellable;
7083 MessageList *message_list;
7084 ETreeTableAdapter *adapter;
7085 gboolean searching;
7086 gint row_count;
7087
7088 simple = G_SIMPLE_ASYNC_RESULT (user_data);
7089 regen_data = g_simple_async_result_get_op_res_gpointer (simple);
7090 cancellable = e_activity_get_cancellable (regen_data->activity);
7091
7092 message_list = regen_data->message_list;
7093
7094 g_mutex_lock (&message_list->priv->regen_lock);
7095
7096 /* Capture MessageList state to use for this regen. */
7097
7098 regen_data->group_by_threads =
7099 message_list_get_group_by_threads (message_list);
7100 regen_data->thread_subject =
7101 message_list_get_thread_subject (message_list);
7102 regen_data->select_unread =
7103 message_list_get_regen_selects_unread (message_list);
7104
7105 if (regen_data->select_unread)
7106 message_list_set_regen_selects_unread (message_list, FALSE);
7107
7108 searching = message_list_is_searching (message_list);
7109
7110 adapter = e_tree_get_table_adapter (E_TREE (message_list));
7111 row_count = e_table_model_row_count (E_TABLE_MODEL (adapter));
7112
7113 if (row_count <= 0) {
7114 if (gtk_widget_get_visible (GTK_WIDGET (message_list)))
7115 e_tree_set_info_message (E_TREE (message_list), _("Generating message list…"));
7116 } else if (regen_data->group_by_threads &&
7117 !message_list->just_set_folder &&
7118 !searching) {
7119 if (message_list->priv->any_row_changed) {
7120 /* Something changed. If it was an expand
7121 * state change, then save the expand state. */
7122 message_list_save_state (message_list);
7123 } else {
7124 /* Remember the expand state and restore it
7125 * after regen. */
7126 regen_data->expand_state =
7127 e_tree_table_adapter_save_expanded_state_xml (
7128 adapter);
7129 }
7130 } else {
7131 /* Remember the expand state and restore it after regen. */
7132 regen_data->expand_state = e_tree_table_adapter_save_expanded_state_xml (adapter);
7133 }
7134
7135 message_list->priv->regen_idle_id = 0;
7136
7137 g_mutex_unlock (&message_list->priv->regen_lock);
7138
7139 if (g_cancellable_is_cancelled (cancellable)) {
7140 g_simple_async_result_complete (simple);
7141 } else {
7142 g_simple_async_result_run_in_thread (
7143 simple,
7144 message_list_regen_thread,
7145 G_PRIORITY_DEFAULT,
7146 cancellable);
7147 }
7148
7149 return FALSE;
7150 }
7151
7152 static void
mail_regen_cancel(MessageList * message_list)7153 mail_regen_cancel (MessageList *message_list)
7154 {
7155 RegenData *regen_data = NULL;
7156
7157 g_mutex_lock (&message_list->priv->regen_lock);
7158
7159 if (message_list->priv->regen_data != NULL)
7160 regen_data = regen_data_ref (message_list->priv->regen_data);
7161
7162 if (message_list->priv->regen_idle_id > 0) {
7163 g_source_remove (message_list->priv->regen_idle_id);
7164 message_list->priv->regen_idle_id = 0;
7165 }
7166
7167 g_mutex_unlock (&message_list->priv->regen_lock);
7168
7169 /* Cancel outside the lock, since this will emit a signal. */
7170 if (regen_data != NULL) {
7171 e_activity_cancel (regen_data->activity);
7172 regen_data_unref (regen_data);
7173 }
7174 }
7175
7176 static void
mail_regen_list(MessageList * message_list,const gchar * search,CamelFolderChangeInfo * folder_changes)7177 mail_regen_list (MessageList *message_list,
7178 const gchar *search,
7179 CamelFolderChangeInfo *folder_changes)
7180 {
7181 GSimpleAsyncResult *simple;
7182 GCancellable *cancellable;
7183 RegenData *new_regen_data;
7184 RegenData *old_regen_data;
7185 gchar *tmp_search_copy = NULL;
7186
7187 if (!search) {
7188 old_regen_data = message_list_ref_regen_data (message_list);
7189
7190 if (old_regen_data && old_regen_data->folder == message_list->priv->folder) {
7191 tmp_search_copy = g_strdup (old_regen_data->search);
7192 search = tmp_search_copy;
7193 } else {
7194 tmp_search_copy = g_strdup (message_list->search);
7195 search = tmp_search_copy;
7196 }
7197
7198 if (old_regen_data)
7199 regen_data_unref (old_regen_data);
7200 } else if (search && !*search) {
7201 search = NULL;
7202 }
7203
7204 /* Report empty search as NULL, not as one/two-space string. */
7205 if (search && (strcmp (search, " ") == 0 || strcmp (search, " ") == 0))
7206 search = NULL;
7207
7208 /* Can't list messages in a folder until we have a folder. */
7209 if (message_list->priv->folder == NULL) {
7210 g_free (message_list->search);
7211 message_list->search = g_strdup (search);
7212 g_free (tmp_search_copy);
7213 return;
7214 }
7215
7216 g_mutex_lock (&message_list->priv->regen_lock);
7217
7218 old_regen_data = message_list->priv->regen_data;
7219
7220 /* If a regen is scheduled but not yet started, just
7221 * apply the argument values without cancelling it. */
7222 if (message_list->priv->regen_idle_id > 0) {
7223 g_return_if_fail (old_regen_data != NULL);
7224
7225 if (g_strcmp0 (search, old_regen_data->search) != 0) {
7226 g_free (old_regen_data->search);
7227 old_regen_data->search = g_strdup (search);
7228 }
7229
7230 /* Only turn off the folder_changed flag, do not turn it on, because otherwise
7231 the view may not scroll to the cursor position, due to claiming that
7232 the regen was done for folder-changed signal, while the initial regen
7233 request would be due to change of the folder in the view (or other similar
7234 reasons). */
7235 if (!folder_changes) {
7236 old_regen_data->folder_changed = FALSE;
7237 } else if (folder_changes->uid_removed) {
7238 guint ii;
7239
7240 if (!old_regen_data->removed_uids)
7241 old_regen_data->removed_uids = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) camel_pstring_free, NULL);
7242
7243 for (ii = 0; ii < folder_changes->uid_removed->len; ii++) {
7244 g_hash_table_insert (old_regen_data->removed_uids, (gpointer) camel_pstring_strdup (folder_changes->uid_removed->pdata[ii]), NULL);
7245 }
7246 }
7247
7248 /* Avoid cancelling on the way out. */
7249 old_regen_data = NULL;
7250
7251 goto exit;
7252 }
7253
7254 cancellable = g_cancellable_new ();
7255
7256 new_regen_data = regen_data_new (message_list, cancellable);
7257 new_regen_data->search = g_strdup (search);
7258 /* Make sure the folder_changes won't reset currently running regen, which would scroll to the selection in the UI */
7259 new_regen_data->folder_changed = folder_changes != NULL && (!old_regen_data || old_regen_data->folder_changed);
7260
7261 if (folder_changes && folder_changes->uid_removed && new_regen_data->folder_changed) {
7262 guint ii;
7263
7264 new_regen_data->removed_uids = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) camel_pstring_free, NULL);
7265
7266 for (ii = 0; ii < folder_changes->uid_removed->len; ii++) {
7267 g_hash_table_insert (new_regen_data->removed_uids, (gpointer) camel_pstring_strdup (folder_changes->uid_removed->pdata[ii]), NULL);
7268 }
7269 }
7270
7271 /* We generate the message list content in a worker thread, and
7272 * then supply our own GAsyncReadyCallback to redraw the widget. */
7273
7274 simple = g_simple_async_result_new (
7275 G_OBJECT (message_list),
7276 message_list_regen_done_cb,
7277 NULL, mail_regen_list);
7278
7279 g_simple_async_result_set_check_cancellable (simple, cancellable);
7280
7281 g_simple_async_result_set_op_res_gpointer (
7282 simple,
7283 regen_data_ref (new_regen_data),
7284 (GDestroyNotify) regen_data_unref);
7285
7286 /* Set the RegenData immediately, but start the actual regen
7287 * operation from an idle callback. That way the caller has
7288 * the remainder of this main loop iteration to make further
7289 * MessageList changes without triggering additional regens. */
7290
7291 message_list->priv->regen_data = regen_data_ref (new_regen_data);
7292
7293 message_list->priv->regen_idle_id =
7294 g_idle_add_full (
7295 G_PRIORITY_DEFAULT_IDLE,
7296 message_list_regen_idle_cb,
7297 g_object_ref (simple),
7298 (GDestroyNotify) g_object_unref);
7299
7300 g_object_unref (simple);
7301
7302 regen_data_unref (new_regen_data);
7303
7304 g_object_unref (cancellable);
7305
7306 exit:
7307 g_mutex_unlock (&message_list->priv->regen_lock);
7308
7309 /* Cancel outside the lock, since this will emit a signal. */
7310 if (old_regen_data != NULL) {
7311 e_activity_cancel (old_regen_data->activity);
7312 regen_data_unref (old_regen_data);
7313 }
7314
7315 g_free (tmp_search_copy);
7316 }
7317
7318 gboolean
message_list_contains_uid(MessageList * message_list,const gchar * uid)7319 message_list_contains_uid (MessageList *message_list,
7320 const gchar *uid)
7321 {
7322 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
7323
7324 if (!uid || !*uid || !message_list->priv->folder)
7325 return FALSE;
7326
7327 return g_hash_table_lookup (message_list->uid_nodemap, uid) != NULL;
7328 }
7329
7330 void
message_list_inc_setting_up_search_folder(MessageList * message_list)7331 message_list_inc_setting_up_search_folder (MessageList *message_list)
7332 {
7333 g_return_if_fail (IS_MESSAGE_LIST (message_list));
7334
7335 g_atomic_int_add (&message_list->priv->setting_up_search_folder, 1);
7336 }
7337
7338 void
message_list_dec_setting_up_search_folder(MessageList * message_list)7339 message_list_dec_setting_up_search_folder (MessageList *message_list)
7340 {
7341 g_return_if_fail (IS_MESSAGE_LIST (message_list));
7342
7343 if (g_atomic_int_dec_and_test (&message_list->priv->setting_up_search_folder))
7344 message_list_update_tree_text (message_list);
7345 }
7346
7347 gboolean
message_list_is_setting_up_search_folder(MessageList * message_list)7348 message_list_is_setting_up_search_folder (MessageList *message_list)
7349 {
7350 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
7351
7352 return g_atomic_int_get (&message_list->priv->setting_up_search_folder) > 0;
7353 }
7354