Bug 790291 - Disallow shrinking message list to zero width
[evolution.git] / src / mail / message-list.c
blob0a1e80e8397bb712b97a9b0548115e2f177966bd
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.
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.
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/>.
15 * Authors:
16 * Miguel de Icaza (miguel@ximian.com)
17 * Bertrand Guiheneuf (bg@aful.org)
18 * And just about everyone else in evolution ...
20 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
24 #include "evolution-config.h"
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <unistd.h>
29 #include <string.h>
30 #include <ctype.h>
32 #include <glib/gi18n.h>
33 #include <glib/gstdio.h>
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"
40 /*#define TIMEIT */
42 #ifdef TIMEIT
43 #include <sys/time.h>
44 #include <unistd.h>
45 #endif
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
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
60 #include "message-list.h"
62 #define d(x)
63 #define t(x)
64 #define dd(x) G_STMT_START { if (camel_debug ("message-list")) { x; } } G_STMT_END
66 #define MESSAGE_LIST_GET_PRIVATE(obj) \
67 (G_TYPE_INSTANCE_GET_PRIVATE \
68 ((obj), MESSAGE_LIST_TYPE, MessageListPrivate))
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\"))"
74 typedef struct _ExtendedGNode ExtendedGNode;
75 typedef struct _RegenData RegenData;
77 struct _MLSelection {
78 GPtrArray *uids;
79 CamelFolder *folder;
82 struct _MessageListPrivate {
83 GtkWidget *invisible; /* 4 selection */
85 EMailSession *session;
87 CamelFolder *folder;
88 gulong folder_changed_handler_id;
90 /* For message list regeneration. */
91 GMutex regen_lock;
92 RegenData *regen_data;
93 guint regen_idle_id;
95 gboolean thaw_needs_regen;
97 GMutex thread_tree_lock;
98 CamelFolderThread *thread_tree;
100 struct _MLSelection clipboard;
101 gboolean destroyed;
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 any_row_changed; /* save state before regen list when this is set to true */
110 gboolean show_subject_above_sender;
112 GtkTargetList *copy_target_list;
113 GtkTargetList *paste_target_list;
115 /* XXX Not sure if we really need a separate frozen counter
116 * for the tree model but the old ETreeMemory class had
117 * its own frozen counter so we preserve it here. */
118 GNode *tree_model_root;
119 gint tree_model_frozen;
121 /* This aids in automatic message selection. */
122 time_t newest_read_date;
123 const gchar *newest_read_uid;
124 time_t oldest_unread_date;
125 const gchar *oldest_unread_uid;
127 GSettings *mail_settings;
128 gchar **re_prefixes;
129 gchar **re_separators;
130 GMutex re_prefixes_lock;
132 GdkRGBA *new_mail_bg_color;
135 /* XXX Plain GNode suffers from O(N) tail insertions, and that won't
136 * do for large mail folders. This structure extends GNode with
137 * a pointer to its last child, so we get O(1) tail insertions. */
138 struct _ExtendedGNode {
139 GNode gnode;
140 GNode *last_child;
143 struct _RegenData {
144 volatile gint ref_count;
146 EActivity *activity;
147 MessageList *message_list;
149 gchar *search;
151 gboolean group_by_threads;
152 gboolean thread_subject;
154 CamelFolderThread *thread_tree;
156 /* This indicates we're regenerating the message list because
157 * we received a "folder-changed" signal from our CamelFolder. */
158 gboolean folder_changed;
160 CamelFolder *folder;
161 GPtrArray *summary;
163 gint last_row; /* last selected (cursor) row */
165 xmlDoc *expand_state; /* expanded state to be restored */
167 /* These may be set during a regen operation. Use the
168 * select_lock to ensure consistency and thread-safety.
169 * These are applied after the operation is finished. */
170 GMutex select_lock;
171 gchar *select_uid;
172 gboolean select_all;
173 gboolean select_use_fallback;
176 enum {
177 PROP_0,
178 PROP_COPY_TARGET_LIST,
179 PROP_FOLDER,
180 PROP_GROUP_BY_THREADS,
181 PROP_PASTE_TARGET_LIST,
182 PROP_SESSION,
183 PROP_SHOW_DELETED,
184 PROP_SHOW_JUNK,
185 PROP_SHOW_SUBJECT_ABOVE_SENDER,
186 PROP_THREAD_LATEST,
187 PROP_THREAD_SUBJECT
190 /* Forward Declarations */
191 static void message_list_selectable_init
192 (ESelectableInterface *iface);
193 static void message_list_tree_model_init
194 (ETreeModelInterface *iface);
195 static gboolean message_list_get_hide_deleted
196 (MessageList *message_list,
197 CamelFolder *folder);
199 G_DEFINE_TYPE_WITH_CODE (
200 MessageList,
201 message_list,
202 E_TYPE_TREE,
203 G_IMPLEMENT_INTERFACE (
204 E_TYPE_EXTENSIBLE, NULL)
205 G_IMPLEMENT_INTERFACE (
206 E_TYPE_SELECTABLE,
207 message_list_selectable_init)
208 G_IMPLEMENT_INTERFACE (
209 E_TYPE_TREE_MODEL,
210 message_list_tree_model_init))
212 static struct {
213 const gchar *target;
214 GdkAtom atom;
215 guint32 actions;
216 } ml_drag_info[] = {
217 { "x-uid-list", NULL, GDK_ACTION_MOVE | GDK_ACTION_COPY },
218 { "message/rfc822", NULL, GDK_ACTION_COPY },
219 { "text/uri-list", NULL, GDK_ACTION_COPY },
222 enum {
223 DND_X_UID_LIST, /* x-uid-list */
224 DND_MESSAGE_RFC822, /* message/rfc822 */
225 DND_TEXT_URI_LIST /* text/uri-list */
228 /* What we send */
229 static GtkTargetEntry ml_drag_types[] = {
230 { (gchar *) "x-uid-list", 0, DND_X_UID_LIST },
231 { (gchar *) "text/uri-list", 0, DND_TEXT_URI_LIST },
234 /* What we accept */
235 static GtkTargetEntry ml_drop_types[] = {
236 { (gchar *) "x-uid-list", 0, DND_X_UID_LIST },
237 { (gchar *) "message/rfc822", 0, DND_MESSAGE_RFC822 },
238 { (gchar *) "text/uri-list", 0, DND_TEXT_URI_LIST },
242 * Default sizes for the ETable display
245 #define N_CHARS(x) (CHAR_WIDTH * (x))
247 #define COL_ICON_WIDTH (16)
248 #define COL_ATTACH_WIDTH (16)
249 #define COL_CHECK_BOX_WIDTH (16)
250 #define COL_FROM_EXPANSION (24.0)
251 #define COL_FROM_WIDTH_MIN (32)
252 #define COL_SUBJECT_EXPANSION (30.0)
253 #define COL_SUBJECT_WIDTH_MIN (32)
254 #define COL_SENT_EXPANSION (24.0)
255 #define COL_SENT_WIDTH_MIN (32)
256 #define COL_RECEIVED_EXPANSION (20.0)
257 #define COL_RECEIVED_WIDTH_MIN (32)
258 #define COL_TO_EXPANSION (24.0)
259 #define COL_TO_WIDTH_MIN (32)
260 #define COL_SIZE_EXPANSION (6.0)
261 #define COL_SIZE_WIDTH_MIN (32)
262 #define COL_SENDER_EXPANSION (24.0)
263 #define COL_SENDER_WIDTH_MIN (32)
265 enum {
266 NORMALISED_SUBJECT,
267 NORMALISED_FROM,
268 NORMALISED_TO,
269 NORMALISED_LAST
272 static void on_cursor_activated_cmd (ETree *tree,
273 gint row,
274 GNode *node,
275 gpointer user_data);
276 static void on_selection_changed_cmd (ETree *tree,
277 MessageList *message_list);
278 static gint on_click (ETree *tree,
279 gint row,
280 GNode *node,
281 gint col,
282 GdkEvent *event,
283 MessageList *message_list);
285 static void mail_regen_list (MessageList *message_list,
286 const gchar *search,
287 gboolean folder_changed);
288 static void mail_regen_cancel (MessageList *message_list);
290 static void clear_info (gchar *key,
291 GNode *node,
292 MessageList *message_list);
294 enum {
295 MESSAGE_SELECTED,
296 MESSAGE_LIST_BUILT,
297 LAST_SIGNAL
300 static guint signals[LAST_SIGNAL] = {0, };
302 static const gchar *status_map[] = {
303 N_("Unseen"),
304 N_("Seen"),
305 N_("Answered"),
306 N_("Forwarded"),
307 N_("Multiple Unseen Messages"),
308 N_("Multiple Messages")
311 static const gchar *status_icons[] = {
312 "mail-unread",
313 "mail-read",
314 "mail-replied",
315 "mail-forward",
316 "stock_mail-unread-multiple",
317 "stock_mail-open-multiple"
320 static const gchar *score_map[] = {
321 N_("Lowest"),
322 N_("Lower"),
323 N_("Low"),
324 N_("Normal"),
325 N_("High"),
326 N_("Higher"),
327 N_("Highest"),
330 static const gchar *score_icons[] = {
331 "stock_score-lowest",
332 "stock_score-lower",
333 "stock_score-low",
334 "stock_score-normal",
335 "stock_score-high",
336 "stock_score-higher",
337 "stock_score-highest"
340 static const gchar *attachment_icons[] = {
341 NULL, /* empty icon */
342 "mail-attachment",
343 "stock_people",
344 "evolution-memos",
345 "mail-mark-junk"
348 static const gchar *flagged_icons[] = {
349 NULL, /* empty icon */
350 "emblem-important"
353 static const gchar *followup_icons[] = {
354 NULL, /* empty icon */
355 "stock_mail-flag-for-followup",
356 "stock_mail-flag-for-followup-done"
359 static GNode *
360 extended_g_node_new (gpointer data)
362 GNode *node;
364 node = (GNode *) g_slice_new0 (ExtendedGNode);
365 node->data = data;
367 return node;
370 static void
371 extended_g_node_unlink (GNode *node)
373 g_return_if_fail (node != NULL);
375 /* Update the last_child pointer before we unlink. */
376 if (node->parent != NULL) {
377 ExtendedGNode *ext_parent;
379 ext_parent = (ExtendedGNode *) node->parent;
380 if (ext_parent->last_child == node) {
381 g_warn_if_fail (node->next == NULL);
382 ext_parent->last_child = node->prev;
386 g_node_unlink (node);
389 static void
390 extended_g_nodes_free (GNode *node)
392 while (node != NULL) {
393 GNode *next = node->next;
394 if (node->children != NULL)
395 extended_g_nodes_free (node->children);
396 g_slice_free (ExtendedGNode, (ExtendedGNode *) node);
397 node = next;
401 static void
402 extended_g_node_destroy (GNode *root)
404 g_return_if_fail (root != NULL);
406 if (!G_NODE_IS_ROOT (root))
407 extended_g_node_unlink (root);
409 extended_g_nodes_free (root);
412 static GNode *
413 extended_g_node_insert_before (GNode *parent,
414 GNode *sibling,
415 GNode *node)
417 ExtendedGNode *ext_parent;
419 g_return_val_if_fail (parent != NULL, node);
420 g_return_val_if_fail (node != NULL, node);
421 g_return_val_if_fail (G_NODE_IS_ROOT (node), node);
422 if (sibling != NULL)
423 g_return_val_if_fail (sibling->parent == parent, node);
425 ext_parent = (ExtendedGNode *) parent;
427 /* This is where tracking the last child pays off. */
428 if (sibling == NULL && ext_parent->last_child != NULL) {
429 node->parent = parent;
430 node->prev = ext_parent->last_child;
431 ext_parent->last_child->next = node;
432 } else {
433 g_node_insert_before (parent, sibling, node);
436 if (sibling == NULL)
437 ext_parent->last_child = node;
439 return node;
442 static GNode *
443 extended_g_node_insert (GNode *parent,
444 gint position,
445 GNode *node)
447 GNode *sibling;
449 g_return_val_if_fail (parent != NULL, node);
450 g_return_val_if_fail (node != NULL, node);
451 g_return_val_if_fail (G_NODE_IS_ROOT (node), node);
453 if (position > 0)
454 sibling = g_node_nth_child (parent, position);
455 else if (position == 0)
456 sibling = parent->children;
457 else /* if (position < 0) */
458 sibling = NULL;
460 return extended_g_node_insert_before (parent, sibling, node);
463 static RegenData *
464 regen_data_new (MessageList *message_list,
465 GCancellable *cancellable)
467 RegenData *regen_data;
468 EActivity *activity;
469 EMailSession *session;
471 activity = e_activity_new ();
472 e_activity_set_cancellable (activity, cancellable);
473 e_activity_set_text (activity, _("Generating message list"));
475 regen_data = g_slice_new0 (RegenData);
476 regen_data->ref_count = 1;
477 regen_data->activity = g_object_ref (activity);
478 regen_data->message_list = g_object_ref (message_list);
479 regen_data->folder = message_list_ref_folder (message_list);
480 regen_data->last_row = -1;
482 if (message_list->just_set_folder)
483 regen_data->select_uid = g_strdup (message_list->cursor_uid);
485 g_mutex_init (&regen_data->select_lock);
487 session = message_list_get_session (message_list);
488 e_mail_ui_session_add_activity (E_MAIL_UI_SESSION (session), activity);
490 g_object_unref (activity);
492 return regen_data;
495 static RegenData *
496 regen_data_ref (RegenData *regen_data)
498 g_return_val_if_fail (regen_data != NULL, NULL);
499 g_return_val_if_fail (regen_data->ref_count > 0, NULL);
501 g_atomic_int_inc (&regen_data->ref_count);
503 return regen_data;
506 static void
507 regen_data_unref (RegenData *regen_data)
509 g_return_if_fail (regen_data != NULL);
510 g_return_if_fail (regen_data->ref_count > 0);
512 if (g_atomic_int_dec_and_test (&regen_data->ref_count)) {
514 g_clear_object (&regen_data->activity);
515 g_clear_object (&regen_data->message_list);
517 g_free (regen_data->search);
519 if (regen_data->thread_tree != NULL)
520 camel_folder_thread_messages_unref (
521 regen_data->thread_tree);
523 if (regen_data->summary != NULL) {
524 guint ii, length;
526 length = regen_data->summary->len;
528 for (ii = 0; ii < length; ii++)
529 g_clear_object (&regen_data->summary->pdata[ii]);
531 g_ptr_array_free (regen_data->summary, TRUE);
534 g_clear_object (&regen_data->folder);
536 if (regen_data->expand_state != NULL)
537 xmlFreeDoc (regen_data->expand_state);
539 g_mutex_clear (&regen_data->select_lock);
540 g_free (regen_data->select_uid);
542 g_slice_free (RegenData, regen_data);
546 static CamelFolderThread *
547 message_list_ref_thread_tree (MessageList *message_list)
549 CamelFolderThread *thread_tree = NULL;
551 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
553 g_mutex_lock (&message_list->priv->thread_tree_lock);
555 if (message_list->priv->thread_tree != NULL) {
556 thread_tree = message_list->priv->thread_tree;
557 camel_folder_thread_messages_ref (thread_tree);
560 g_mutex_unlock (&message_list->priv->thread_tree_lock);
562 return thread_tree;
565 static void
566 message_list_set_thread_tree (MessageList *message_list,
567 CamelFolderThread *thread_tree)
569 g_return_if_fail (IS_MESSAGE_LIST (message_list));
571 g_mutex_lock (&message_list->priv->thread_tree_lock);
573 if (thread_tree != NULL)
574 camel_folder_thread_messages_ref (thread_tree);
576 if (message_list->priv->thread_tree != NULL)
577 camel_folder_thread_messages_unref (
578 message_list->priv->thread_tree);
580 message_list->priv->thread_tree = thread_tree;
582 g_mutex_unlock (&message_list->priv->thread_tree_lock);
585 static RegenData *
586 message_list_ref_regen_data (MessageList *message_list)
588 RegenData *regen_data = NULL;
590 g_mutex_lock (&message_list->priv->regen_lock);
592 if (message_list->priv->regen_data != NULL)
593 regen_data = regen_data_ref (message_list->priv->regen_data);
595 g_mutex_unlock (&message_list->priv->regen_lock);
597 return regen_data;
600 static void
601 message_list_tree_model_freeze (MessageList *message_list)
603 if (message_list->priv->tree_model_frozen == 0)
604 e_tree_model_pre_change (E_TREE_MODEL (message_list));
606 message_list->priv->tree_model_frozen++;
609 static void
610 message_list_tree_model_thaw (MessageList *message_list)
612 if (message_list->priv->tree_model_frozen > 0)
613 message_list->priv->tree_model_frozen--;
615 if (message_list->priv->tree_model_frozen == 0)
616 e_tree_model_node_changed (
617 E_TREE_MODEL (message_list),
618 message_list->priv->tree_model_root);
621 static GNode *
622 message_list_tree_model_insert (MessageList *message_list,
623 GNode *parent,
624 gint position,
625 gpointer data)
627 ETreeModel *tree_model;
628 GNode *node;
629 gboolean tree_model_frozen;
631 if (parent == NULL)
632 g_return_val_if_fail (
633 message_list->priv->tree_model_root == NULL, NULL);
635 tree_model = E_TREE_MODEL (message_list);
636 tree_model_frozen = (message_list->priv->tree_model_frozen > 0);
638 if (!tree_model_frozen)
639 e_tree_model_pre_change (tree_model);
641 node = extended_g_node_new (data);
643 if (parent != NULL) {
644 extended_g_node_insert (parent, position, node);
645 if (!tree_model_frozen)
646 e_tree_model_node_inserted (tree_model, parent, node);
647 } else {
648 message_list->priv->tree_model_root = node;
649 if (!tree_model_frozen)
650 e_tree_model_node_changed (tree_model, node);
653 return node;
656 static void
657 message_list_tree_model_remove (MessageList *message_list,
658 GNode *node)
660 ETreeModel *tree_model;
661 GNode *parent = node->parent;
662 gboolean tree_model_frozen;
663 gint old_position = 0;
665 g_return_if_fail (node != NULL);
667 tree_model = E_TREE_MODEL (message_list);
668 tree_model_frozen = (message_list->priv->tree_model_frozen > 0);
670 if (!tree_model_frozen) {
671 e_tree_model_pre_change (tree_model);
672 old_position = g_node_child_position (parent, node);
675 extended_g_node_unlink (node);
677 if (!tree_model_frozen)
678 e_tree_model_node_removed (
679 tree_model, parent, node, old_position);
681 extended_g_node_destroy (node);
683 if (node == message_list->priv->tree_model_root)
684 message_list->priv->tree_model_root = NULL;
686 if (!tree_model_frozen)
687 e_tree_model_node_deleted (tree_model, node);
690 static gint
691 address_compare (gconstpointer address1,
692 gconstpointer address2,
693 gpointer cmp_cache)
695 gint retval;
697 g_return_val_if_fail (address1 != NULL, 1);
698 g_return_val_if_fail (address2 != NULL, -1);
700 retval = g_ascii_strcasecmp ((gchar *) address1, (gchar *) address2);
702 return retval;
705 static gchar *
706 filter_size (gint size)
708 gfloat fsize;
710 if (size < 1024) {
711 return g_strdup_printf ("%d", size);
712 } else {
713 fsize = ((gfloat) size) / 1024.0;
714 if (fsize < 1024.0) {
715 return g_strdup_printf ("%.2f K", fsize);
716 } else {
717 fsize /= 1024.0;
718 return g_strdup_printf ("%.2f M", fsize);
723 /* Gets the uid of the message displayed at a given view row */
724 static const gchar *
725 get_message_uid (MessageList *message_list,
726 GNode *node)
728 g_return_val_if_fail (node != NULL, NULL);
729 g_return_val_if_fail (node->data != NULL, NULL);
731 return camel_message_info_get_uid (node->data);
734 /* Gets the CamelMessageInfo for the message displayed at the given
735 * view row.
737 static CamelMessageInfo *
738 get_message_info (MessageList *message_list,
739 GNode *node)
741 g_return_val_if_fail (node != NULL, NULL);
742 g_return_val_if_fail (node->data != NULL, NULL);
744 return node->data;
747 static const gchar *
748 get_normalised_string (MessageList *message_list,
749 CamelMessageInfo *info,
750 gint col)
752 const gchar *string, *str;
753 gchar *normalised;
754 EPoolv *poolv;
755 gint index;
757 switch (col) {
758 case COL_SUBJECT_NORM:
759 string = camel_message_info_get_subject (info);
760 index = NORMALISED_SUBJECT;
761 break;
762 case COL_FROM_NORM:
763 string = camel_message_info_get_from (info);
764 index = NORMALISED_FROM;
765 break;
766 case COL_TO_NORM:
767 string = camel_message_info_get_to (info);
768 index = NORMALISED_TO;
769 break;
770 default:
771 string = NULL;
772 index = NORMALISED_LAST;
773 g_warning ("Should not be reached\n");
776 /* slight optimisation */
777 if (string == NULL || string[0] == '\0')
778 return "";
780 poolv = g_hash_table_lookup (message_list->normalised_hash, camel_message_info_get_uid (info));
781 if (poolv == NULL) {
782 poolv = e_poolv_new (NORMALISED_LAST);
783 g_hash_table_insert (message_list->normalised_hash, (gchar *) camel_message_info_get_uid (info), poolv);
784 } else {
785 str = e_poolv_get (poolv, index);
786 if (*str)
787 return str;
790 if (col == COL_SUBJECT_NORM) {
791 gint skip_len;
792 const gchar *subject;
793 gboolean found_re = TRUE;
795 subject = string;
796 while (found_re) {
797 g_mutex_lock (&message_list->priv->re_prefixes_lock);
798 found_re = em_utils_is_re_in_subject (
799 subject, &skip_len, (const gchar * const *) message_list->priv->re_prefixes,
800 (const gchar * const *) message_list->priv->re_separators) && skip_len > 0;
801 g_mutex_unlock (&message_list->priv->re_prefixes_lock);
803 if (found_re)
804 subject += skip_len;
806 /* jump over any spaces */
807 while (*subject && isspace ((gint) *subject))
808 subject++;
811 /* jump over any spaces */
812 while (*subject && isspace ((gint) *subject))
813 subject++;
815 string = subject;
816 normalised = g_utf8_collate_key (string, -1);
817 } else {
818 /* because addresses require strings, not collate keys */
819 normalised = g_strdup (string);
822 e_poolv_set (poolv, index, normalised, TRUE);
824 return e_poolv_get (poolv, index);
827 static void
828 clear_selection (MessageList *message_list,
829 struct _MLSelection *selection)
831 if (selection->uids != NULL) {
832 g_ptr_array_unref (selection->uids);
833 selection->uids = NULL;
836 g_clear_object (&selection->folder);
839 static GNode *
840 ml_get_next_node (GNode *node,
841 GNode *subroot)
843 GNode *next;
845 if (!node)
846 return NULL;
848 next = g_node_first_child (node);
850 if (!next && node != subroot) {
851 next = g_node_next_sibling (node);
854 if (!next && node != subroot) {
855 next = node->parent;
856 while (next) {
857 GNode *sibl = g_node_next_sibling (next);
859 if (next == subroot)
860 return NULL;
862 if (sibl) {
863 next = sibl;
864 break;
865 } else {
866 next = next->parent;
871 return next;
874 static GNode *
875 ml_get_prev_node (GNode *node,
876 GNode *subroot)
878 GNode *prev;
880 if (!node)
881 return NULL;
883 if (node == subroot)
884 prev = NULL;
885 else
886 prev = g_node_prev_sibling (node);
888 if (!prev) {
889 prev = node->parent;
891 if (prev == subroot)
892 return NULL;
894 if (prev)
895 return prev;
898 if (prev) {
899 GNode *child = g_node_last_child (prev);
900 while (child) {
901 prev = child;
902 child = g_node_last_child (child);
906 return prev;
909 static GNode *
910 ml_get_last_tree_node (GNode *node,
911 GNode *subroot)
913 GNode *child;
915 if (!node)
916 return NULL;
918 while (node->parent && node->parent != subroot)
919 node = node->parent;
921 if (node == subroot)
922 child = node;
923 else
924 child = g_node_last_sibling (node);
926 if (!child)
927 child = node;
929 while (child = g_node_last_child (child), child) {
930 node = child;
933 return node;
936 static GNode *
937 ml_search_forward (MessageList *message_list,
938 gint start,
939 gint end,
940 guint32 flags,
941 guint32 mask,
942 gboolean include_collapsed,
943 gboolean skip_first)
945 GNode *node;
946 gint row;
947 CamelMessageInfo *info;
948 ETreeTableAdapter *etta;
950 etta = e_tree_get_table_adapter (E_TREE (message_list));
952 for (row = start; row <= end; row++) {
953 node = e_tree_table_adapter_node_at_row (etta, row);
954 if (node != NULL && !skip_first
955 && (info = get_message_info (message_list, node))
956 && (camel_message_info_get_flags (info) & mask) == flags)
957 return node;
959 skip_first = FALSE;
961 if (node && include_collapsed && !e_tree_table_adapter_node_is_expanded (etta, node) && g_node_first_child (node)) {
962 GNode *subnode = node;
964 while (subnode = ml_get_next_node (subnode, node), subnode && subnode != node) {
965 if ((info = get_message_info (message_list, subnode)) &&
966 (camel_message_info_get_flags (info) & mask) == flags)
967 return subnode;
972 return NULL;
975 static GNode *
976 ml_search_backward (MessageList *message_list,
977 gint start,
978 gint end,
979 guint32 flags,
980 guint32 mask,
981 gboolean include_collapsed,
982 gboolean skip_first)
984 GNode *node;
985 gint row;
986 CamelMessageInfo *info;
987 ETreeTableAdapter *etta;
989 etta = e_tree_get_table_adapter (E_TREE (message_list));
991 for (row = start; row >= end; row--) {
992 node = e_tree_table_adapter_node_at_row (etta, row);
993 if (node != NULL && !skip_first
994 && (info = get_message_info (message_list, node))
995 && (camel_message_info_get_flags (info) & mask) == flags) {
996 if (include_collapsed && !e_tree_table_adapter_node_is_expanded (etta, node) && g_node_first_child (node)) {
997 GNode *subnode = ml_get_last_tree_node (g_node_first_child (node), node);
999 while (subnode && subnode != node) {
1000 if ((info = get_message_info (message_list, subnode)) &&
1001 (camel_message_info_get_flags (info) & mask) == flags)
1002 return subnode;
1004 subnode = ml_get_prev_node (subnode, node);
1008 return node;
1011 if (node && include_collapsed && !skip_first && !e_tree_table_adapter_node_is_expanded (etta, node) && g_node_first_child (node)) {
1012 GNode *subnode = ml_get_last_tree_node (g_node_first_child (node), node);
1014 while (subnode && subnode != node) {
1015 if ((info = get_message_info (message_list, subnode)) &&
1016 (camel_message_info_get_flags (info) & mask) == flags)
1017 return subnode;
1019 subnode = ml_get_prev_node (subnode, node);
1023 skip_first = FALSE;
1026 return NULL;
1029 static GNode *
1030 ml_search_path (MessageList *message_list,
1031 MessageListSelectDirection direction,
1032 guint32 flags,
1033 guint32 mask)
1035 ETreeTableAdapter *adapter;
1036 gboolean include_collapsed;
1037 GNode *node;
1038 gint row_count;
1039 gint row;
1041 if (message_list->cursor_uid == NULL)
1042 return NULL;
1044 node = g_hash_table_lookup (
1045 message_list->uid_nodemap,
1046 message_list->cursor_uid);
1047 if (node == NULL)
1048 return NULL;
1050 adapter = e_tree_get_table_adapter (E_TREE (message_list));
1051 row_count = e_table_model_row_count (E_TABLE_MODEL (adapter));
1053 row = e_tree_table_adapter_row_of_node (adapter, node);
1054 if (row == -1)
1055 return NULL;
1057 include_collapsed = (direction & MESSAGE_LIST_SELECT_INCLUDE_COLLAPSED) != 0;
1059 if ((direction & MESSAGE_LIST_SELECT_DIRECTION) == MESSAGE_LIST_SELECT_NEXT)
1060 node = ml_search_forward (
1061 message_list, row, row_count - 1, flags, mask, include_collapsed, TRUE);
1062 else
1063 node = ml_search_backward (
1064 message_list, row, 0, flags, mask, include_collapsed, TRUE);
1066 if (node == NULL && (direction & MESSAGE_LIST_SELECT_WRAP)) {
1067 if ((direction & MESSAGE_LIST_SELECT_DIRECTION) == MESSAGE_LIST_SELECT_NEXT)
1068 node = ml_search_forward (
1069 message_list, 0, row, flags, mask, include_collapsed, FALSE);
1070 else
1071 node = ml_search_backward (
1072 message_list, row_count - 1, row, flags, mask, include_collapsed, FALSE);
1075 return node;
1078 static void
1079 select_node (MessageList *message_list,
1080 GNode *node)
1082 ETree *tree;
1083 ETreeTableAdapter *etta;
1084 ETreeSelectionModel *etsm;
1086 tree = E_TREE (message_list);
1087 etta = e_tree_get_table_adapter (tree);
1088 etsm = (ETreeSelectionModel *) e_tree_get_selection_model (tree);
1090 g_free (message_list->cursor_uid);
1091 message_list->cursor_uid = NULL;
1093 e_tree_table_adapter_show_node (etta, node);
1094 e_tree_set_cursor (tree, node);
1095 e_tree_selection_model_select_single_path (etsm, node);
1099 * message_list_select:
1100 * @message_list: a MessageList
1101 * @direction: the direction to search in
1102 * @flags: a set of flag values
1103 * @mask: a mask for comparing against @flags
1105 * This moves the message list selection to a suitable row. @flags and
1106 * @mask combine to specify what constitutes a suitable row. @direction is
1107 * %MESSAGE_LIST_SELECT_NEXT if it should find the next matching
1108 * message, or %MESSAGE_LIST_SELECT_PREVIOUS if it should find the
1109 * previous. %MESSAGE_LIST_SELECT_WRAP is an option bit which specifies the
1110 * search should wrap.
1112 * If no suitable row is found, the selection will be
1113 * unchanged.
1115 * Returns %TRUE if a new message has been selected or %FALSE otherwise.
1117 gboolean
1118 message_list_select (MessageList *message_list,
1119 MessageListSelectDirection direction,
1120 guint32 flags,
1121 guint32 mask)
1123 GNode *node;
1125 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
1127 node = ml_search_path (message_list, direction, flags, mask);
1128 if (node != NULL) {
1129 select_node (message_list, node);
1131 /* This function is usually called in response to a key
1132 * press, so grab focus if the message list is visible. */
1133 if (gtk_widget_get_visible (GTK_WIDGET (message_list)))
1134 gtk_widget_grab_focus (GTK_WIDGET (message_list));
1136 return TRUE;
1137 } else
1138 return FALSE;
1142 * message_list_can_select:
1143 * @message_list:
1144 * @direction:
1145 * @flags:
1146 * @mask:
1148 * Returns true if the selection specified is possible with the current view.
1150 * Return value:
1152 gboolean
1153 message_list_can_select (MessageList *message_list,
1154 MessageListSelectDirection direction,
1155 guint32 flags,
1156 guint32 mask)
1158 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
1160 return ml_search_path (message_list, direction, flags, mask) != NULL;
1164 * message_list_select_uid:
1165 * @message_list:
1166 * @uid:
1168 * Selects the message with the given UID.
1170 void
1171 message_list_select_uid (MessageList *message_list,
1172 const gchar *uid,
1173 gboolean with_fallback)
1175 MessageListPrivate *priv;
1176 GHashTable *uid_nodemap;
1177 GNode *node = NULL;
1178 RegenData *regen_data = NULL;
1180 g_return_if_fail (IS_MESSAGE_LIST (message_list));
1182 priv = message_list->priv;
1183 uid_nodemap = message_list->uid_nodemap;
1185 if (message_list->priv->folder == NULL)
1186 return;
1188 /* Try to find the requested message UID. */
1189 if (uid != NULL)
1190 node = g_hash_table_lookup (uid_nodemap, uid);
1192 regen_data = message_list_ref_regen_data (message_list);
1194 /* If we're busy or waiting to regenerate the message list, cache
1195 * the UID so we can try again when we're done. Otherwise if the
1196 * requested message UID was not found and 'with_fallback' is set,
1197 * try a couple fallbacks:
1199 * 1) Oldest unread message in the list, by date received.
1200 * 2) Newest read message in the list, by date received.
1202 if (regen_data != NULL) {
1203 g_mutex_lock (&regen_data->select_lock);
1204 g_free (regen_data->select_uid);
1205 regen_data->select_uid = g_strdup (uid);
1206 regen_data->select_use_fallback = with_fallback;
1207 g_mutex_unlock (&regen_data->select_lock);
1209 regen_data_unref (regen_data);
1211 } else if (with_fallback) {
1212 if (node == NULL && priv->oldest_unread_uid != NULL)
1213 node = g_hash_table_lookup (
1214 uid_nodemap, priv->oldest_unread_uid);
1215 if (node == NULL && priv->newest_read_uid != NULL)
1216 node = g_hash_table_lookup (
1217 uid_nodemap, priv->newest_read_uid);
1220 if (node) {
1221 ETree *tree;
1222 GNode *old_cur;
1224 tree = E_TREE (message_list);
1225 old_cur = e_tree_get_cursor (tree);
1227 /* This will emit a changed signal that we'll pick up */
1228 e_tree_set_cursor (tree, node);
1230 if (old_cur == node)
1231 g_signal_emit (
1232 message_list,
1233 signals[MESSAGE_SELECTED],
1234 0, message_list->cursor_uid);
1235 } else if (message_list->just_set_folder) {
1236 g_free (message_list->cursor_uid);
1237 message_list->cursor_uid = g_strdup (uid);
1238 g_signal_emit (
1239 message_list,
1240 signals[MESSAGE_SELECTED], 0, message_list->cursor_uid);
1241 } else {
1242 g_free (message_list->cursor_uid);
1243 message_list->cursor_uid = NULL;
1244 g_signal_emit (
1245 message_list,
1246 signals[MESSAGE_SELECTED],
1247 0, NULL);
1251 void
1252 message_list_select_next_thread (MessageList *message_list)
1254 ETreeTableAdapter *adapter;
1255 GNode *node;
1256 gint row_count;
1257 gint row;
1258 gint ii;
1260 g_return_if_fail (IS_MESSAGE_LIST (message_list));
1262 if (message_list->cursor_uid == NULL)
1263 return;
1265 node = g_hash_table_lookup (
1266 message_list->uid_nodemap,
1267 message_list->cursor_uid);
1268 if (node == NULL)
1269 return;
1271 adapter = e_tree_get_table_adapter (E_TREE (message_list));
1272 row_count = e_table_model_row_count ((ETableModel *) adapter);
1274 row = e_tree_table_adapter_row_of_node (adapter, node);
1275 if (row == -1)
1276 return;
1278 /* find the next node which has a root parent (i.e. toplevel node) */
1279 for (ii = row + 1; ii < row_count - 1; ii++) {
1280 node = e_tree_table_adapter_node_at_row (adapter, ii);
1281 if (node != NULL && G_NODE_IS_ROOT (node->parent)) {
1282 select_node (message_list, node);
1283 return;
1288 void
1289 message_list_select_prev_thread (MessageList *message_list)
1291 ETreeTableAdapter *adapter;
1292 GNode *node;
1293 gboolean skip_first;
1294 gint row;
1295 gint ii;
1297 g_return_if_fail (IS_MESSAGE_LIST (message_list));
1299 if (message_list->cursor_uid == NULL)
1300 return;
1302 node = g_hash_table_lookup (
1303 message_list->uid_nodemap,
1304 message_list->cursor_uid);
1305 if (node == NULL)
1306 return;
1308 adapter = e_tree_get_table_adapter (E_TREE (message_list));
1310 row = e_tree_table_adapter_row_of_node (adapter, node);
1311 if (row == -1)
1312 return;
1314 /* skip first found if in the middle of the thread */
1315 skip_first = !G_NODE_IS_ROOT (node->parent);
1317 /* find the previous node which has a root parent (i.e. toplevel node) */
1318 for (ii = row - 1; ii >= 0; ii--) {
1319 node = e_tree_table_adapter_node_at_row (adapter, ii);
1320 if (node != NULL && G_NODE_IS_ROOT (node->parent)) {
1321 if (skip_first) {
1322 skip_first = FALSE;
1323 continue;
1326 select_node (message_list, node);
1327 return;
1333 * message_list_select_all:
1334 * @message_list: Message List widget
1336 * Selects all messages in the message list.
1338 void
1339 message_list_select_all (MessageList *message_list)
1341 RegenData *regen_data = NULL;
1343 g_return_if_fail (IS_MESSAGE_LIST (message_list));
1345 regen_data = message_list_ref_regen_data (message_list);
1347 if (regen_data != NULL && regen_data->group_by_threads) {
1348 regen_data->select_all = TRUE;
1349 } else {
1350 ETree *tree;
1351 ESelectionModel *selection_model;
1353 tree = E_TREE (message_list);
1354 selection_model = e_tree_get_selection_model (tree);
1355 e_selection_model_select_all (selection_model);
1358 if (regen_data != NULL)
1359 regen_data_unref (regen_data);
1362 typedef struct thread_select_info {
1363 MessageList *message_list;
1364 GPtrArray *paths;
1365 } thread_select_info_t;
1367 static gboolean
1368 select_thread_node (ETreeModel *model,
1369 GNode *node,
1370 gpointer user_data)
1372 thread_select_info_t *tsi = (thread_select_info_t *) user_data;
1374 g_ptr_array_add (tsi->paths, node);
1376 return FALSE; /*not done yet */
1379 static void
1380 select_thread (MessageList *message_list,
1381 ETreeForeachFunc selector)
1383 ETree *tree;
1384 ETreeSelectionModel *etsm;
1385 thread_select_info_t tsi;
1387 tsi.message_list = message_list;
1388 tsi.paths = g_ptr_array_new ();
1390 tree = E_TREE (message_list);
1391 etsm = (ETreeSelectionModel *) e_tree_get_selection_model (tree);
1393 e_tree_selection_model_foreach (etsm, selector, &tsi);
1395 e_tree_selection_model_select_paths (etsm, tsi.paths);
1397 g_ptr_array_free (tsi.paths, TRUE);
1400 static void
1401 thread_select_foreach (ETreePath path,
1402 gpointer user_data)
1404 thread_select_info_t *tsi = (thread_select_info_t *) user_data;
1405 ETreeModel *tree_model;
1406 GNode *last, *node = path;
1408 tree_model = E_TREE_MODEL (tsi->message_list);
1410 do {
1411 last = node;
1412 node = node->parent;
1413 } while (node && !G_NODE_IS_ROOT (node));
1415 g_ptr_array_add (tsi->paths, last);
1417 e_tree_model_node_traverse (
1418 tree_model, last,
1419 (ETreePathFunc) select_thread_node, tsi);
1423 * message_list_select_thread:
1424 * @message_list: Message List widget
1426 * Selects all messages in the current thread (based on cursor).
1428 void
1429 message_list_select_thread (MessageList *message_list)
1431 g_return_if_fail (IS_MESSAGE_LIST (message_list));
1433 select_thread (message_list, thread_select_foreach);
1436 static void
1437 subthread_select_foreach (ETreePath path,
1438 gpointer user_data)
1440 thread_select_info_t *tsi = (thread_select_info_t *) user_data;
1441 ETreeModel *tree_model;
1443 tree_model = E_TREE_MODEL (tsi->message_list);
1445 e_tree_model_node_traverse (
1446 tree_model, path,
1447 (ETreePathFunc) select_thread_node, tsi);
1451 * message_list_select_subthread:
1452 * @message_list: Message List widget
1454 * Selects all messages in the current subthread (based on cursor).
1456 void
1457 message_list_select_subthread (MessageList *message_list)
1459 g_return_if_fail (IS_MESSAGE_LIST (message_list));
1461 select_thread (message_list, subthread_select_foreach);
1464 void
1465 message_list_copy (MessageList *message_list,
1466 gboolean cut)
1468 MessageListPrivate *priv;
1469 GPtrArray *uids;
1471 g_return_if_fail (IS_MESSAGE_LIST (message_list));
1473 priv = message_list->priv;
1475 clear_selection (message_list, &priv->clipboard);
1477 uids = message_list_get_selected (message_list);
1479 if (uids->len > 0) {
1480 if (cut) {
1481 CamelFolder *folder;
1482 gint i;
1484 folder = message_list_ref_folder (message_list);
1486 camel_folder_freeze (folder);
1488 for (i = 0; i < uids->len; i++)
1489 camel_folder_set_message_flags (
1490 folder, uids->pdata[i],
1491 CAMEL_MESSAGE_SEEN |
1492 CAMEL_MESSAGE_DELETED,
1493 CAMEL_MESSAGE_SEEN |
1494 CAMEL_MESSAGE_DELETED);
1496 camel_folder_thaw (folder);
1498 g_object_unref (folder);
1501 priv->clipboard.uids = g_ptr_array_ref (uids);
1502 priv->clipboard.folder = message_list_ref_folder (message_list);
1504 gtk_selection_owner_set (
1505 priv->invisible,
1506 GDK_SELECTION_CLIPBOARD,
1507 gtk_get_current_event_time ());
1508 } else {
1509 gtk_selection_owner_set (
1510 NULL, GDK_SELECTION_CLIPBOARD,
1511 gtk_get_current_event_time ());
1514 g_ptr_array_unref (uids);
1517 void
1518 message_list_paste (MessageList *message_list)
1520 g_return_if_fail (IS_MESSAGE_LIST (message_list));
1522 gtk_selection_convert (
1523 message_list->priv->invisible,
1524 GDK_SELECTION_CLIPBOARD,
1525 gdk_atom_intern ("x-uid-list", FALSE),
1526 GDK_CURRENT_TIME);
1529 static void
1530 for_node_and_subtree_if_collapsed (MessageList *message_list,
1531 GNode *node,
1532 CamelMessageInfo *mi,
1533 ETreePathFunc func,
1534 gpointer data)
1536 ETreeModel *tree_model;
1537 ETreeTableAdapter *adapter;
1538 GNode *child = NULL;
1540 tree_model = E_TREE_MODEL (message_list);
1541 adapter = e_tree_get_table_adapter (E_TREE (message_list));
1543 func (NULL, (ETreePath) mi, data);
1545 if (node != NULL)
1546 child = g_node_first_child (node);
1548 if (child && !e_tree_table_adapter_node_is_expanded (adapter, node))
1549 e_tree_model_node_traverse (tree_model, node, func, data);
1552 static gboolean
1553 unread_foreach (ETreeModel *etm,
1554 ETreePath path,
1555 gpointer data)
1557 gboolean *saw_unread = data;
1558 CamelMessageInfo *info;
1560 if (!etm)
1561 info = (CamelMessageInfo *) path;
1562 else
1563 info = ((GNode *) path)->data;
1564 g_return_val_if_fail (info != NULL, FALSE);
1566 if (!(camel_message_info_get_flags (info) & CAMEL_MESSAGE_SEEN))
1567 *saw_unread = TRUE;
1569 return FALSE;
1572 struct LatestData {
1573 gboolean sent;
1574 time_t latest;
1577 static gboolean
1578 latest_foreach (ETreeModel *etm,
1579 ETreePath path,
1580 gpointer data)
1582 struct LatestData *ld = data;
1583 CamelMessageInfo *info;
1584 time_t date;
1586 if (!etm)
1587 info = (CamelMessageInfo *) path;
1588 else
1589 info = ((GNode *) path)->data;
1590 g_return_val_if_fail (info != NULL, FALSE);
1592 date = ld->sent ? camel_message_info_get_date_sent (info)
1593 : camel_message_info_get_date_received (info);
1595 if (ld->latest == 0 || date > ld->latest)
1596 ld->latest = date;
1598 return FALSE;
1601 static gchar *
1602 sanitize_recipients (const gchar *string)
1604 GString *gstring;
1605 gboolean quoted = FALSE;
1606 const gchar *p;
1607 GString *recipients = g_string_new ("");
1608 gchar *single_add;
1609 gchar **name;
1611 if (!string || !*string)
1612 return g_string_free (recipients, FALSE);
1614 gstring = g_string_new ("");
1616 for (p = string; *p; p = g_utf8_next_char (p)) {
1617 gunichar c = g_utf8_get_char (p);
1619 if (c == '"')
1620 quoted = ~quoted;
1621 else if (c == ',' && !quoted) {
1622 single_add = g_string_free (gstring, FALSE);
1623 name = g_strsplit (single_add,"<",2);
1624 g_string_append (recipients, *name);
1625 g_string_append (recipients, ",");
1626 g_free (single_add);
1627 g_strfreev (name);
1628 gstring = g_string_new ("");
1629 continue;
1632 g_string_append_unichar (gstring, c);
1635 single_add = g_string_free (gstring, FALSE);
1636 name = g_strsplit (single_add,"<",2);
1637 g_string_append (recipients, *name);
1638 g_free (single_add);
1639 g_strfreev (name);
1641 return g_string_free (recipients, FALSE);
1644 struct LabelsData {
1645 EMailLabelListStore *store;
1646 GHashTable *labels_tag2iter;
1649 static void
1650 add_label_if_known (struct LabelsData *ld,
1651 const gchar *tag)
1653 GtkTreeIter label_defn;
1655 if (e_mail_label_list_store_lookup (ld->store, tag, &label_defn)) {
1656 g_hash_table_insert (
1657 ld->labels_tag2iter,
1658 /* Should be the same as the "tag" arg */
1659 e_mail_label_list_store_get_tag (ld->store, &label_defn),
1660 gtk_tree_iter_copy (&label_defn));
1664 static gboolean
1665 add_all_labels_foreach (ETreeModel *etm,
1666 ETreePath path,
1667 gpointer data)
1669 struct LabelsData *ld = data;
1670 CamelMessageInfo *msg_info;
1671 const gchar *old_label;
1672 gchar *new_label;
1673 const CamelNamedFlags *flags;
1674 guint ii, len;
1676 if (!etm)
1677 msg_info = (CamelMessageInfo *) path;
1678 else
1679 msg_info = ((GNode *) path)->data;
1680 g_return_val_if_fail (msg_info != NULL, FALSE);
1682 camel_message_info_property_lock (msg_info);
1683 flags = camel_message_info_get_user_flags (msg_info);
1684 len = camel_named_flags_get_length (flags);
1686 for (ii = 0; ii < len; ii++)
1687 add_label_if_known (ld, camel_named_flags_get (flags, ii));
1689 old_label = camel_message_info_get_user_tag (msg_info, "label");
1690 if (old_label != NULL) {
1691 /* Convert old-style labels ("<name>") to "$Label<name>". */
1692 new_label = g_alloca (strlen (old_label) + 10);
1693 g_stpcpy (g_stpcpy (new_label, "$Label"), old_label);
1695 add_label_if_known (ld, new_label);
1698 camel_message_info_property_unlock (msg_info);
1700 return FALSE;
1703 static const gchar *
1704 get_trimmed_subject (CamelMessageInfo *info,
1705 MessageList *message_list)
1707 const gchar *subject;
1708 const gchar *mlist;
1709 gint mlist_len = 0;
1710 gboolean found_mlist;
1712 subject = camel_message_info_get_subject (info);
1713 if (!subject || !*subject)
1714 return subject;
1716 mlist = camel_message_info_get_mlist (info);
1718 if (mlist && *mlist) {
1719 const gchar *mlist_end;
1721 mlist_end = strchr (mlist, '@');
1722 if (mlist_end)
1723 mlist_len = mlist_end - mlist;
1724 else
1725 mlist_len = strlen (mlist);
1728 do {
1729 gint skip_len;
1730 gboolean found_re = TRUE;
1732 found_mlist = FALSE;
1734 while (found_re) {
1735 found_re = FALSE;
1737 g_mutex_lock (&message_list->priv->re_prefixes_lock);
1738 found_re = em_utils_is_re_in_subject (
1739 subject, &skip_len, (const gchar * const *) message_list->priv->re_prefixes,
1740 (const gchar * const *) message_list->priv->re_separators) && skip_len > 0;
1741 g_mutex_unlock (&message_list->priv->re_prefixes_lock);
1742 if (found_re)
1743 subject += skip_len;
1745 /* jump over any spaces */
1746 while (*subject && isspace ((gint) *subject))
1747 subject++;
1750 if (mlist_len &&
1751 *subject == '[' &&
1752 !g_ascii_strncasecmp ((gchar *) subject + 1, mlist, mlist_len) &&
1753 subject[1 + mlist_len] == ']') {
1754 subject += 1 + mlist_len + 1; /* jump over "[mailing-list]" */
1755 found_mlist = TRUE;
1757 /* jump over any spaces */
1758 while (*subject && isspace ((gint) *subject))
1759 subject++;
1761 } while (found_mlist);
1763 /* jump over any spaces */
1764 while (*subject && isspace ((gint) *subject))
1765 subject++;
1767 return subject;
1770 static gpointer
1771 ml_tree_value_at_ex (ETreeModel *etm,
1772 GNode *node,
1773 gint col,
1774 CamelMessageInfo *msg_info,
1775 MessageList *message_list)
1777 EMailSession *session;
1778 const gchar *str;
1779 guint32 flags;
1781 session = message_list_get_session (message_list);
1783 g_return_val_if_fail (msg_info != NULL, NULL);
1785 switch (col) {
1786 case COL_MESSAGE_STATUS:
1787 flags = camel_message_info_get_flags (msg_info);
1788 if (flags & CAMEL_MESSAGE_ANSWERED)
1789 return GINT_TO_POINTER (2);
1790 else if (flags & CAMEL_MESSAGE_FORWARDED)
1791 return GINT_TO_POINTER (3);
1792 else if (flags & CAMEL_MESSAGE_SEEN)
1793 return GINT_TO_POINTER (1);
1794 else
1795 return GINT_TO_POINTER (0);
1796 case COL_FLAGGED:
1797 return GINT_TO_POINTER ((camel_message_info_get_flags (msg_info) & CAMEL_MESSAGE_FLAGGED) != 0);
1798 case COL_SCORE: {
1799 const gchar *tag;
1800 gint score = 0;
1802 tag = camel_message_info_get_user_tag (msg_info, "score");
1803 if (tag)
1804 score = atoi (tag);
1806 return GINT_TO_POINTER (score);
1808 case COL_FOLLOWUP_FLAG_STATUS: {
1809 const gchar *tag, *cmp;
1811 /* FIXME: this all should be methods off of message-tag-followup class,
1812 * FIXME: the tag names should be namespaced :( */
1813 tag = camel_message_info_get_user_tag (msg_info, "follow-up");
1814 cmp = camel_message_info_get_user_tag (msg_info, "completed-on");
1815 if (tag && tag[0]) {
1816 if (cmp && cmp[0])
1817 return GINT_TO_POINTER (2);
1818 else
1819 return GINT_TO_POINTER (1);
1820 } else
1821 return GINT_TO_POINTER (0);
1823 case COL_FOLLOWUP_DUE_BY: {
1824 const gchar *tag;
1825 time_t due_by;
1827 tag = camel_message_info_get_user_tag (msg_info, "due-by");
1828 if (tag && *tag) {
1829 gint64 *res;
1831 due_by = camel_header_decode_date (tag, NULL);
1832 res = g_new0 (gint64, 1);
1833 *res = (gint64) due_by;
1835 return res;
1836 } else {
1837 return NULL;
1840 case COL_FOLLOWUP_FLAG:
1841 str = camel_message_info_get_user_tag (msg_info, "follow-up");
1842 return (gpointer)(str ? str : "");
1843 case COL_ATTACHMENT:
1844 if (camel_message_info_get_flags (msg_info) & CAMEL_MESSAGE_JUNK)
1845 return GINT_TO_POINTER (4);
1846 if (camel_message_info_get_user_flag (msg_info, E_MAIL_NOTES_USER_FLAG))
1847 return GINT_TO_POINTER (3);
1848 if (camel_message_info_get_user_flag (msg_info, "$has_cal"))
1849 return GINT_TO_POINTER (2);
1850 return GINT_TO_POINTER ((camel_message_info_get_flags (msg_info) & CAMEL_MESSAGE_ATTACHMENTS) != 0);
1851 case COL_FROM:
1852 str = camel_message_info_get_from (msg_info);
1853 return (gpointer)(str ? str : "");
1854 case COL_FROM_NORM:
1855 return (gpointer) get_normalised_string (message_list, msg_info, col);
1856 case COL_SUBJECT:
1857 str = camel_message_info_get_subject (msg_info);
1858 return (gpointer)(str ? str : "");
1859 case COL_SUBJECT_TRIMMED:
1860 str = get_trimmed_subject (msg_info, message_list);
1861 return (gpointer)(str ? str : "");
1862 case COL_SUBJECT_NORM:
1863 return (gpointer) get_normalised_string (message_list, msg_info, col);
1864 case COL_SENT: {
1865 struct LatestData ld;
1866 gint64 *res;
1868 ld.sent = TRUE;
1869 ld.latest = 0;
1871 for_node_and_subtree_if_collapsed (message_list, node, msg_info, latest_foreach, &ld);
1873 res = g_new0 (gint64, 1);
1874 *res = (gint64) ld.latest;
1876 return res;
1878 case COL_RECEIVED: {
1879 struct LatestData ld;
1880 gint64 *res;
1882 ld.sent = FALSE;
1883 ld.latest = 0;
1885 for_node_and_subtree_if_collapsed (message_list, node, msg_info, latest_foreach, &ld);
1887 res = g_new0 (gint64, 1);
1888 *res = (gint64) ld.latest;
1890 return res;
1892 case COL_TO:
1893 str = camel_message_info_get_to (msg_info);
1894 return (gpointer)(str ? str : "");
1895 case COL_TO_NORM:
1896 return (gpointer) get_normalised_string (message_list, msg_info, col);
1897 case COL_SIZE:
1898 return GINT_TO_POINTER (camel_message_info_get_size (msg_info));
1899 case COL_DELETED:
1900 return GINT_TO_POINTER ((camel_message_info_get_flags (msg_info) & CAMEL_MESSAGE_DELETED) != 0);
1901 case COL_DELETED_OR_JUNK:
1902 return GINT_TO_POINTER ((camel_message_info_get_flags (msg_info) & (CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_JUNK)) != 0);
1903 case COL_JUNK:
1904 return GINT_TO_POINTER ((camel_message_info_get_flags (msg_info) & CAMEL_MESSAGE_JUNK) != 0);
1905 case COL_JUNK_STRIKEOUT_COLOR:
1906 return GUINT_TO_POINTER (((camel_message_info_get_flags (msg_info) & CAMEL_MESSAGE_JUNK) != 0) ? 0xFF0000 : 0x0);
1907 case COL_UNREAD: {
1908 gboolean saw_unread = FALSE;
1910 for_node_and_subtree_if_collapsed (message_list, node, msg_info, unread_foreach, &saw_unread);
1912 return GINT_TO_POINTER (saw_unread);
1914 case COL_COLOUR: {
1915 const gchar *colour, *due_by, *completed, *followup;
1917 /* Priority: colour tag; label tag; important flag; due-by tag */
1919 /* This is astonisngly poorly written code */
1921 /* To add to the woes, what color to show when the user choose multiple labels ?
1922 Don't say that I need to have the new labels[with subject] column visible always */
1924 colour = NULL;
1925 due_by = camel_message_info_get_user_tag (msg_info, "due-by");
1926 completed = camel_message_info_get_user_tag (msg_info, "completed-on");
1927 followup = camel_message_info_get_user_tag (msg_info, "follow-up");
1928 if (colour == NULL) {
1929 /* Get all applicable labels. */
1930 struct LabelsData ld;
1932 ld.store = e_mail_ui_session_get_label_store (
1933 E_MAIL_UI_SESSION (session));
1934 ld.labels_tag2iter = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gtk_tree_iter_free);
1935 for_node_and_subtree_if_collapsed (message_list, node, msg_info, add_all_labels_foreach, &ld);
1937 if (g_hash_table_size (ld.labels_tag2iter) == 1) {
1938 GHashTableIter iter;
1939 GtkTreeIter *label_defn;
1940 GdkColor colour_val;
1941 gchar *colour_alloced;
1943 /* Extract the single label from the hashtable. */
1944 g_hash_table_iter_init (&iter, ld.labels_tag2iter);
1945 if (g_hash_table_iter_next (&iter, NULL, (gpointer *) &label_defn)) {
1946 e_mail_label_list_store_get_color (ld.store, label_defn, &colour_val);
1948 /* XXX Hack to avoid returning an allocated string. */
1949 colour_alloced = gdk_color_to_string (&colour_val);
1950 colour = g_intern_string (colour_alloced);
1951 g_free (colour_alloced);
1953 } else if (camel_message_info_get_flags (msg_info) & CAMEL_MESSAGE_FLAGGED) {
1954 /* FIXME: extract from the important.xpm somehow. */
1955 colour = "#A7453E";
1956 } else if (((followup && *followup) || (due_by && *due_by)) && !(completed && *completed)) {
1957 time_t now = time (NULL);
1959 if ((followup && *followup) || now >= camel_header_decode_date (due_by, NULL))
1960 colour = "#A7453E";
1963 g_hash_table_destroy (ld.labels_tag2iter);
1966 if (!colour)
1967 colour = camel_message_info_get_user_tag (msg_info, "color");
1969 return (gpointer) colour;
1971 case COL_ITALIC: {
1972 return GINT_TO_POINTER (camel_message_info_get_user_flag (msg_info, "ignore-thread") ? 1 : 0);
1974 case COL_LOCATION: {
1975 /* Fixme : freeing memory stuff (mem leaks) */
1976 CamelStore *store;
1977 CamelFolder *folder;
1978 CamelService *service;
1979 const gchar *store_name;
1980 const gchar *folder_name;
1982 folder = message_list->priv->folder;
1984 if (CAMEL_IS_VEE_FOLDER (folder))
1985 folder = camel_vee_folder_get_location (
1986 CAMEL_VEE_FOLDER (folder),
1987 (CamelVeeMessageInfo *) msg_info, NULL);
1989 store = camel_folder_get_parent_store (folder);
1990 folder_name = camel_folder_get_full_name (folder);
1992 service = CAMEL_SERVICE (store);
1993 store_name = camel_service_get_display_name (service);
1995 return g_strdup_printf ("%s : %s", store_name, folder_name);
1997 case COL_MIXED_RECIPIENTS:
1998 case COL_RECIPIENTS:{
1999 str = camel_message_info_get_to (msg_info);
2001 return sanitize_recipients (str);
2003 case COL_MIXED_SENDER:
2004 case COL_SENDER:{
2005 gchar **sender_name = NULL;
2006 str = camel_message_info_get_from (msg_info);
2007 if (str && str[0] != '\0') {
2008 gchar *res;
2009 sender_name = g_strsplit (str,"<",2);
2010 res = g_strdup (*sender_name);
2011 g_strfreev (sender_name);
2012 return (gpointer)(res);
2014 else
2015 return (gpointer) g_strdup ("");
2017 case COL_LABELS:{
2018 struct LabelsData ld;
2019 GString *result = g_string_new ("");
2021 ld.store = e_mail_ui_session_get_label_store (
2022 E_MAIL_UI_SESSION (session));
2023 ld.labels_tag2iter = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gtk_tree_iter_free);
2024 for_node_and_subtree_if_collapsed (message_list, node, msg_info, add_all_labels_foreach, &ld);
2026 if (g_hash_table_size (ld.labels_tag2iter) > 0) {
2027 GHashTableIter iter;
2028 GtkTreeIter *label_defn;
2030 g_hash_table_iter_init (&iter, ld.labels_tag2iter);
2031 while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &label_defn)) {
2032 gchar *label_name, *label_name_clean;
2034 if (result->len > 0)
2035 g_string_append (result, ", ");
2037 label_name = e_mail_label_list_store_get_name (ld.store, label_defn);
2038 label_name_clean = e_str_without_underscores (label_name);
2040 g_string_append (result, label_name_clean);
2042 g_free (label_name_clean);
2043 g_free (label_name);
2047 g_hash_table_destroy (ld.labels_tag2iter);
2048 return (gpointer) g_string_free (result, FALSE);
2050 case COL_UID: {
2051 return (gpointer) camel_pstring_strdup (camel_message_info_get_uid (msg_info));
2053 default:
2054 g_warning ("%s: This shouldn't be reached (col:%d)", G_STRFUNC, col);
2055 return NULL;
2059 static gchar *
2060 filter_date (const gint64 *pdate)
2062 time_t nowdate = time (NULL);
2063 time_t yesdate, date;
2064 struct tm then, now, yesterday;
2065 gchar buf[26];
2066 gboolean done = FALSE;
2068 if (!pdate || *pdate == 0)
2069 return g_strdup (_("?"));
2071 date = (time_t) *pdate;
2072 localtime_r (&date, &then);
2073 localtime_r (&nowdate, &now);
2074 if (then.tm_mday == now.tm_mday &&
2075 then.tm_mon == now.tm_mon &&
2076 then.tm_year == now.tm_year) {
2077 e_utf8_strftime_fix_am_pm (buf, 26, _("Today %l:%M %p"), &then);
2078 done = TRUE;
2080 if (!done) {
2081 yesdate = nowdate - 60 * 60 * 24;
2082 localtime_r (&yesdate, &yesterday);
2083 if (then.tm_mday == yesterday.tm_mday &&
2084 then.tm_mon == yesterday.tm_mon &&
2085 then.tm_year == yesterday.tm_year) {
2086 e_utf8_strftime_fix_am_pm (buf, 26, _("Yesterday %l:%M %p"), &then);
2087 done = TRUE;
2090 if (!done) {
2091 gint i;
2092 for (i = 2; i < 7; i++) {
2093 yesdate = nowdate - 60 * 60 * 24 * i;
2094 localtime_r (&yesdate, &yesterday);
2095 if (then.tm_mday == yesterday.tm_mday &&
2096 then.tm_mon == yesterday.tm_mon &&
2097 then.tm_year == yesterday.tm_year) {
2098 e_utf8_strftime_fix_am_pm (buf, 26, _("%a %l:%M %p"), &then);
2099 done = TRUE;
2100 break;
2104 if (!done) {
2105 if (then.tm_year == now.tm_year) {
2106 e_utf8_strftime_fix_am_pm (buf, 26, _("%b %d %l:%M %p"), &then);
2107 } else {
2108 e_utf8_strftime_fix_am_pm (buf, 26, _("%b %d %Y"), &then);
2112 return g_strdup (buf);
2115 static ECell *
2116 create_composite_cell (GSettings *mail_settings,
2117 gint col)
2119 ECell *cell_vbox, *cell_hbox, *cell_sub, *cell_date, *cell_from, *top_cell_tree, *bottom_cell_tree, *cell_attach;
2120 gboolean show_email;
2121 gboolean show_subject_above_sender;
2123 show_email = g_settings_get_boolean (mail_settings, "show-email");
2124 show_subject_above_sender = g_settings_get_boolean (mail_settings, "show-subject-above-sender");
2126 if (!show_email)
2127 col = (col == COL_FROM) ? COL_SENDER : COL_RECIPIENTS;
2129 cell_vbox = e_cell_vbox_new ();
2131 cell_hbox = e_cell_hbox_new ();
2133 /* Exclude the meeting icon. */
2134 cell_attach = e_cell_toggle_new (attachment_icons, G_N_ELEMENTS (attachment_icons));
2136 cell_date = e_cell_date_new (NULL, GTK_JUSTIFY_RIGHT);
2137 e_cell_date_set_format_component (E_CELL_DATE (cell_date), "mail");
2138 g_object_set (
2139 cell_date,
2140 "bold_column", COL_UNREAD,
2141 "italic-column", COL_ITALIC,
2142 "color_column", COL_COLOUR,
2143 NULL);
2145 cell_from = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT);
2146 g_object_set (
2147 cell_from,
2148 "bold_column", COL_UNREAD,
2149 "italic-column", COL_ITALIC,
2150 "color_column", COL_COLOUR,
2151 NULL);
2153 e_cell_hbox_append (E_CELL_HBOX (cell_hbox), cell_from, show_subject_above_sender ? COL_SUBJECT : col, 68);
2154 e_cell_hbox_append (E_CELL_HBOX (cell_hbox), cell_attach, COL_ATTACHMENT, 5);
2155 e_cell_hbox_append (E_CELL_HBOX (cell_hbox), cell_date, COL_SENT, 27);
2156 g_object_unref (cell_from);
2157 g_object_unref (cell_attach);
2158 g_object_unref (cell_date);
2160 cell_sub = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT);
2161 g_object_set (
2162 cell_sub,
2163 "color_column", COL_COLOUR,
2164 NULL);
2165 top_cell_tree = e_cell_tree_new (TRUE, FALSE, cell_hbox);
2166 bottom_cell_tree = e_cell_tree_new (TRUE, TRUE, cell_sub);
2167 e_cell_vbox_append (E_CELL_VBOX (cell_vbox), top_cell_tree, show_subject_above_sender ? COL_SUBJECT : col);
2168 e_cell_vbox_append (E_CELL_VBOX (cell_vbox), bottom_cell_tree, show_subject_above_sender ? col : COL_SUBJECT);
2169 g_object_unref (cell_sub);
2170 g_object_unref (cell_hbox);
2171 g_object_unref (top_cell_tree);
2172 g_object_unref (bottom_cell_tree);
2174 g_object_set_data (G_OBJECT (cell_vbox), "cell_date", cell_date);
2175 g_object_set_data (G_OBJECT (cell_vbox), "cell_sub", cell_sub);
2176 g_object_set_data (G_OBJECT (cell_vbox), "cell_from", cell_from);
2177 g_object_set_data (G_OBJECT (cell_vbox), "cell_hbox", cell_hbox);
2178 g_object_set_data (G_OBJECT (cell_vbox), "address_model_col", GINT_TO_POINTER (col));
2180 return cell_vbox;
2183 static void
2184 composite_cell_set_show_subject_above_sender (ECell *cell,
2185 gboolean show_subject_above_sender)
2187 ECellVbox *cell_vbox;
2188 ECellHbox *cell_hbox;
2189 ECell *cell_from;
2190 GObject *cell_obj;
2191 gint address_model_col, cell_from_index;
2193 g_return_if_fail (E_IS_CELL_VBOX (cell));
2195 cell_obj = G_OBJECT (cell);
2196 address_model_col = GPOINTER_TO_INT (g_object_get_data (cell_obj, "address_model_col"));
2198 cell_vbox = E_CELL_VBOX (cell);
2199 g_return_if_fail (cell_vbox->subcell_count == 2);
2200 g_return_if_fail (cell_vbox->model_cols != NULL);
2202 cell_from = g_object_get_data (cell_obj, "cell_from");
2203 g_return_if_fail (E_IS_CELL (cell_from));
2205 cell_hbox = g_object_get_data (cell_obj, "cell_hbox");
2206 g_return_if_fail (E_IS_CELL_HBOX (cell_hbox));
2208 for (cell_from_index = 0; cell_from_index < cell_hbox->subcell_count; cell_from_index++) {
2209 if (cell_hbox->subcells[cell_from_index] == cell_from)
2210 break;
2213 g_return_if_fail (cell_from_index < cell_hbox->subcell_count);
2215 cell_hbox->model_cols[cell_from_index] = show_subject_above_sender ? COL_SUBJECT : address_model_col;
2216 cell_vbox->model_cols[0] = show_subject_above_sender ? COL_SUBJECT : address_model_col;
2217 cell_vbox->model_cols[1] = show_subject_above_sender ? address_model_col : COL_SUBJECT;
2220 static void
2221 composite_cell_set_strike_col (ECell *cell,
2222 gint strikeout_col,
2223 gint strikeout_color_col)
2225 g_object_set (g_object_get_data (G_OBJECT (cell), "cell_date"),
2226 "strikeout-column", strikeout_col,
2227 "strikeout-color-column", strikeout_color_col,
2228 NULL);
2230 g_object_set (g_object_get_data (G_OBJECT (cell), "cell_from"),
2231 "strikeout-column", strikeout_col,
2232 "strikeout-color-column", strikeout_color_col,
2233 NULL);
2236 static ETableExtras *
2237 message_list_create_extras (GSettings *mail_settings)
2239 ETableExtras *extras;
2240 ECell *cell;
2242 extras = e_table_extras_new ();
2243 e_table_extras_add_icon_name (extras, "status", "mail-unread");
2244 e_table_extras_add_icon_name (extras, "score", "stock_score-higher");
2245 e_table_extras_add_icon_name (extras, "attachment", "mail-attachment");
2246 e_table_extras_add_icon_name (extras, "flagged", "emblem-important");
2247 e_table_extras_add_icon_name (extras, "followup", "stock_mail-flag-for-followup");
2249 e_table_extras_add_compare (extras, "address_compare", address_compare);
2251 cell = e_cell_toggle_new (status_icons, G_N_ELEMENTS (status_icons));
2252 e_cell_toggle_set_icon_descriptions (E_CELL_TOGGLE (cell), status_map, G_N_ELEMENTS (status_map));
2253 e_table_extras_add_cell (extras, "render_message_status", cell);
2254 g_object_unref (cell);
2256 cell = e_cell_toggle_new (
2257 attachment_icons, G_N_ELEMENTS (attachment_icons));
2258 e_table_extras_add_cell (extras, "render_attachment", cell);
2259 g_object_unref (cell);
2261 cell = e_cell_toggle_new (
2262 flagged_icons, G_N_ELEMENTS (flagged_icons));
2263 e_table_extras_add_cell (extras, "render_flagged", cell);
2264 g_object_unref (cell);
2266 cell = e_cell_toggle_new (
2267 followup_icons, G_N_ELEMENTS (followup_icons));
2268 e_table_extras_add_cell (extras, "render_flag_status", cell);
2269 g_object_unref (cell);
2271 cell = e_cell_toggle_new (
2272 score_icons, G_N_ELEMENTS (score_icons));
2273 e_table_extras_add_cell (extras, "render_score", cell);
2274 g_object_unref (cell);
2276 /* date cell */
2277 cell = e_cell_date_new (NULL, GTK_JUSTIFY_LEFT);
2278 e_cell_date_set_format_component (E_CELL_DATE (cell), "mail");
2279 g_object_set (
2280 cell,
2281 "bold_column", COL_UNREAD,
2282 "italic-column", COL_ITALIC,
2283 "color_column", COL_COLOUR,
2284 NULL);
2285 e_table_extras_add_cell (extras, "render_date", cell);
2286 g_object_unref (cell);
2288 /* text cell */
2289 cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT);
2290 g_object_set (
2291 cell,
2292 "bold_column", COL_UNREAD,
2293 "italic-column", COL_ITALIC,
2294 "color_column", COL_COLOUR,
2295 NULL);
2296 e_table_extras_add_cell (extras, "render_text", cell);
2297 g_object_unref (cell);
2299 cell = e_cell_tree_new (TRUE, TRUE, cell);
2300 e_table_extras_add_cell (extras, "render_tree", cell);
2301 g_object_unref (cell);
2303 /* size cell */
2304 cell = e_cell_size_new (NULL, GTK_JUSTIFY_RIGHT);
2305 g_object_set (
2306 cell,
2307 "bold_column", COL_UNREAD,
2308 "italic-column", COL_ITALIC,
2309 "color_column", COL_COLOUR,
2310 NULL);
2311 e_table_extras_add_cell (extras, "render_size", cell);
2312 g_object_unref (cell);
2314 /* Composite cell for wide view */
2315 cell = create_composite_cell (mail_settings, COL_FROM);
2316 e_table_extras_add_cell (extras, "render_composite_from", cell);
2317 g_object_unref (cell);
2319 cell = create_composite_cell (mail_settings, COL_TO);
2320 e_table_extras_add_cell (extras, "render_composite_to", cell);
2321 g_object_unref (cell);
2323 /* set proper format component for a default 'date' cell renderer */
2324 cell = e_table_extras_get_cell (extras, "date");
2325 e_cell_date_set_format_component (E_CELL_DATE (cell), "mail");
2327 return extras;
2330 static gboolean
2331 message_list_is_searching (MessageList *message_list)
2333 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
2335 return message_list->search && *message_list->search;
2338 static void
2339 save_tree_state (MessageList *message_list,
2340 CamelFolder *folder)
2342 ETreeTableAdapter *adapter;
2343 gchar *filename;
2345 if (folder == NULL)
2346 return;
2348 if (message_list_is_searching (message_list))
2349 return;
2351 adapter = e_tree_get_table_adapter (E_TREE (message_list));
2353 filename = mail_config_folder_to_cachename (folder, "et-expanded-");
2354 e_tree_table_adapter_save_expanded_state (adapter, filename);
2355 g_free (filename);
2357 message_list->priv->any_row_changed = FALSE;
2360 static void
2361 load_tree_state (MessageList *message_list,
2362 CamelFolder *folder,
2363 xmlDoc *expand_state)
2365 ETreeTableAdapter *adapter;
2367 if (folder == NULL)
2368 return;
2370 adapter = e_tree_get_table_adapter (E_TREE (message_list));
2372 if (expand_state != NULL) {
2373 e_tree_table_adapter_load_expanded_state_xml (
2374 adapter, expand_state);
2375 } else {
2376 gchar *filename;
2378 filename = mail_config_folder_to_cachename (
2379 folder, "et-expanded-");
2380 e_tree_table_adapter_load_expanded_state (adapter, filename);
2381 g_free (filename);
2384 message_list->priv->any_row_changed = FALSE;
2387 void
2388 message_list_save_state (MessageList *message_list)
2390 CamelFolder *folder;
2392 g_return_if_fail (IS_MESSAGE_LIST (message_list));
2394 folder = message_list_ref_folder (message_list);
2396 if (folder != NULL) {
2397 save_tree_state (message_list, folder);
2398 g_object_unref (folder);
2402 static void
2403 message_list_setup_etree (MessageList *message_list)
2405 CamelFolder *folder;
2407 /* Build the spec based on the folder, and possibly
2408 * from a saved file. Otherwise, leave default. */
2410 folder = message_list_ref_folder (message_list);
2412 if (folder != NULL) {
2413 gint data = 1;
2414 ETableItem *item;
2416 item = e_tree_get_item (E_TREE (message_list));
2418 g_object_set (message_list, "uniform_row_height", TRUE, NULL);
2419 g_object_set_data (
2420 G_OBJECT (((GnomeCanvasItem *) item)->canvas),
2421 "freeze-cursor", &data);
2423 /* build based on saved file */
2424 load_tree_state (message_list, folder, NULL);
2426 g_object_unref (folder);
2430 static void
2431 ml_selection_get (GtkWidget *widget,
2432 GtkSelectionData *data,
2433 guint info,
2434 guint time_stamp,
2435 MessageList *message_list)
2437 struct _MLSelection *selection;
2439 selection = &message_list->priv->clipboard;
2441 if (selection->uids == NULL)
2442 return;
2444 if (info & 2) {
2445 /* text/plain */
2446 d (printf ("setting text/plain selection for uids\n"));
2447 em_utils_selection_set_mailbox (data, selection->folder, selection->uids);
2448 } else {
2449 /* x-uid-list */
2450 d (printf ("setting x-uid-list selection for uids\n"));
2451 em_utils_selection_set_uidlist (data, selection->folder, selection->uids);
2455 static gboolean
2456 ml_selection_clear_event (GtkWidget *widget,
2457 GdkEventSelection *event,
2458 MessageList *message_list)
2460 MessageListPrivate *p = message_list->priv;
2462 clear_selection (message_list, &p->clipboard);
2464 return TRUE;
2467 static void
2468 ml_selection_received (GtkWidget *widget,
2469 GtkSelectionData *selection_data,
2470 guint time,
2471 MessageList *message_list)
2473 EMailSession *session;
2474 CamelFolder *folder;
2475 GdkAtom target;
2477 target = gtk_selection_data_get_target (selection_data);
2479 if (target != gdk_atom_intern ("x-uid-list", FALSE)) {
2480 d (printf ("Unknown selection received by message-list\n"));
2481 return;
2484 folder = message_list_ref_folder (message_list);
2485 session = message_list_get_session (message_list);
2487 /* FIXME Not passing a GCancellable or GError here. */
2488 em_utils_selection_get_uidlist (
2489 selection_data, session, folder, FALSE, NULL, NULL);
2491 g_clear_object (&folder);
2494 static void
2495 ml_tree_drag_data_get (ETree *tree,
2496 gint row,
2497 GNode *node,
2498 gint col,
2499 GdkDragContext *context,
2500 GtkSelectionData *data,
2501 guint info,
2502 guint time,
2503 MessageList *message_list)
2505 CamelFolder *folder;
2506 GPtrArray *uids;
2508 folder = message_list_ref_folder (message_list);
2509 uids = message_list_get_selected (message_list);
2511 if (uids->len > 0) {
2512 switch (info) {
2513 case DND_X_UID_LIST:
2514 em_utils_selection_set_uidlist (data, folder, uids);
2515 break;
2516 case DND_TEXT_URI_LIST:
2517 em_utils_selection_set_urilist (data, folder, uids);
2518 break;
2522 g_clear_object (&folder);
2523 g_ptr_array_unref (uids);
2526 /* TODO: merge this with the folder tree stuff via empopup targets */
2527 /* Drop handling */
2528 struct _drop_msg {
2529 MailMsg base;
2531 GdkDragContext *context;
2533 /* Only selection->data and selection->length are valid */
2534 GtkSelectionData *selection;
2536 CamelFolder *folder;
2537 MessageList *message_list;
2539 guint32 action;
2540 guint info;
2542 guint move : 1;
2543 guint moved : 1;
2544 guint aborted : 1;
2547 static gchar *
2548 ml_drop_async_desc (struct _drop_msg *m)
2550 const gchar *full_name;
2552 full_name = camel_folder_get_full_name (m->folder);
2554 if (m->move)
2555 return g_strdup_printf (_("Moving messages into folder %s"), full_name);
2556 else
2557 return g_strdup_printf (_("Copying messages into folder %s"), full_name);
2560 static void
2561 ml_drop_async_exec (struct _drop_msg *m,
2562 GCancellable *cancellable,
2563 GError **error)
2565 EMailSession *session;
2567 session = message_list_get_session (m->message_list);
2569 switch (m->info) {
2570 case DND_X_UID_LIST:
2571 em_utils_selection_get_uidlist (
2572 m->selection, session, m->folder,
2573 m->action == GDK_ACTION_MOVE,
2574 cancellable, error);
2575 break;
2576 case DND_MESSAGE_RFC822:
2577 em_utils_selection_get_message (m->selection, m->folder);
2578 break;
2579 case DND_TEXT_URI_LIST:
2580 em_utils_selection_get_urilist (m->selection, m->folder);
2581 break;
2585 static void
2586 ml_drop_async_done (struct _drop_msg *m)
2588 gboolean success, delete;
2590 /* ?? */
2591 if (m->aborted) {
2592 success = FALSE;
2593 delete = FALSE;
2594 } else {
2595 success = (m->base.error == NULL);
2596 delete = success && m->move && !m->moved;
2599 gtk_drag_finish (m->context, success, delete, GDK_CURRENT_TIME);
2602 static void
2603 ml_drop_async_free (struct _drop_msg *m)
2605 g_object_unref (m->context);
2606 g_object_unref (m->folder);
2607 g_object_unref (m->message_list);
2608 gtk_selection_data_free (m->selection);
2611 static MailMsgInfo ml_drop_async_info = {
2612 sizeof (struct _drop_msg),
2613 (MailMsgDescFunc) ml_drop_async_desc,
2614 (MailMsgExecFunc) ml_drop_async_exec,
2615 (MailMsgDoneFunc) ml_drop_async_done,
2616 (MailMsgFreeFunc) ml_drop_async_free
2619 static void
2620 ml_drop_action (struct _drop_msg *m)
2622 m->move = m->action == GDK_ACTION_MOVE;
2623 mail_msg_unordered_push (m);
2626 static void
2627 ml_tree_drag_data_received (ETree *tree,
2628 gint row,
2629 GNode *node,
2630 gint col,
2631 GdkDragContext *context,
2632 gint x,
2633 gint y,
2634 GtkSelectionData *selection_data,
2635 guint info,
2636 guint time,
2637 MessageList *message_list)
2639 CamelFolder *folder;
2640 struct _drop_msg *m;
2642 if (gtk_selection_data_get_data (selection_data) == NULL)
2643 return;
2645 if (gtk_selection_data_get_length (selection_data) == -1)
2646 return;
2648 folder = message_list_ref_folder (message_list);
2649 if (folder == NULL)
2650 return;
2652 m = mail_msg_new (&ml_drop_async_info);
2653 m->context = g_object_ref (context);
2654 m->folder = g_object_ref (folder);
2655 m->message_list = g_object_ref (message_list);
2656 m->action = gdk_drag_context_get_selected_action (context);
2657 m->info = info;
2659 /* need to copy, goes away once we exit */
2660 m->selection = gtk_selection_data_copy (selection_data);
2662 ml_drop_action (m);
2664 g_object_unref (folder);
2667 struct search_child_struct {
2668 gboolean found;
2669 gconstpointer looking_for;
2672 static void
2673 search_child_cb (GtkWidget *widget,
2674 gpointer data)
2676 struct search_child_struct *search = (struct search_child_struct *) data;
2678 search->found = search->found || g_direct_equal (widget, search->looking_for);
2681 static gboolean
2682 is_tree_widget_children (ETree *tree,
2683 gconstpointer widget)
2685 struct search_child_struct search;
2687 search.found = FALSE;
2688 search.looking_for = widget;
2690 gtk_container_foreach (GTK_CONTAINER (tree), search_child_cb, &search);
2692 return search.found;
2695 static gboolean
2696 ml_tree_drag_motion (ETree *tree,
2697 GdkDragContext *context,
2698 gint x,
2699 gint y,
2700 guint time,
2701 MessageList *message_list)
2703 GList *targets;
2704 GdkDragAction action, actions = 0;
2705 GtkWidget *source_widget;
2707 /* If drop target is name of the account/store
2708 * and not actual folder, don't allow any action. */
2709 if (message_list->priv->folder == NULL) {
2710 gdk_drag_status (context, 0, time);
2711 return TRUE;
2714 source_widget = gtk_drag_get_source_widget (context);
2716 /* If source widget is packed under 'tree', don't allow any action */
2717 if (is_tree_widget_children (tree, source_widget)) {
2718 gdk_drag_status (context, 0, time);
2719 return TRUE;
2722 if (EM_IS_FOLDER_TREE (source_widget)) {
2723 EMFolderTree *folder_tree;
2724 CamelFolder *selected_folder = NULL;
2725 CamelStore *selected_store;
2726 gchar *selected_folder_name;
2727 gboolean has_selection;
2729 folder_tree = EM_FOLDER_TREE (source_widget);
2731 has_selection = em_folder_tree_get_selected (
2732 folder_tree, &selected_store, &selected_folder_name);
2734 /* Sanity checks */
2735 g_warn_if_fail (
2736 (has_selection && selected_store != NULL) ||
2737 (!has_selection && selected_store == NULL));
2738 g_warn_if_fail (
2739 (has_selection && selected_folder_name != NULL) ||
2740 (!has_selection && selected_folder_name == NULL));
2742 if (has_selection) {
2743 selected_folder = camel_store_get_folder_sync (
2744 selected_store, selected_folder_name,
2745 0, NULL, NULL);
2746 g_object_unref (selected_store);
2747 g_free (selected_folder_name);
2750 if (selected_folder == message_list->priv->folder) {
2751 gdk_drag_status (context, 0, time);
2752 return TRUE;
2756 targets = gdk_drag_context_list_targets (context);
2757 while (targets != NULL) {
2758 gint i;
2760 d (printf ("atom drop '%s'\n", gdk_atom_name (targets->data)));
2761 for (i = 0; i < G_N_ELEMENTS (ml_drag_info); i++)
2762 if (targets->data == (gpointer) ml_drag_info[i].atom)
2763 actions |= ml_drag_info[i].actions;
2765 targets = g_list_next (targets);
2767 d (printf ("\n"));
2769 actions &= gdk_drag_context_get_actions (context);
2770 action = gdk_drag_context_get_suggested_action (context);
2771 if (action == GDK_ACTION_COPY && (actions & GDK_ACTION_MOVE))
2772 action = GDK_ACTION_MOVE;
2774 gdk_drag_status (context, action, time);
2776 return action != 0;
2779 static void
2780 on_model_row_changed (ETableModel *model,
2781 gint row,
2782 MessageList *message_list)
2784 message_list->priv->any_row_changed = TRUE;
2787 static gboolean
2788 ml_tree_sorting_changed (ETreeTableAdapter *adapter,
2789 MessageList *message_list)
2791 gboolean group_by_threads;
2793 g_return_val_if_fail (message_list != NULL, FALSE);
2795 group_by_threads = message_list_get_group_by_threads (message_list);
2797 if (group_by_threads && message_list->frozen == 0) {
2799 /* Invalidate the thread tree. */
2800 message_list_set_thread_tree (message_list, NULL);
2802 mail_regen_list (message_list, NULL, FALSE);
2804 return TRUE;
2805 } else if (group_by_threads) {
2806 message_list->priv->thaw_needs_regen = TRUE;
2809 return FALSE;
2812 static void
2813 ml_get_bg_color_cb (ETableItem *item,
2814 gint row,
2815 gint col,
2816 GdkRGBA *inout_background,
2817 MessageList *message_list)
2819 CamelMessageInfo *msg_info;
2820 ETreePath path;
2822 g_return_if_fail (IS_MESSAGE_LIST (message_list));
2823 g_return_if_fail (inout_background != NULL);
2825 if (!message_list->priv->new_mail_bg_color || row < 0)
2826 return;
2828 path = e_tree_table_adapter_node_at_row (e_tree_get_table_adapter (E_TREE (message_list)), row);
2829 if (!path || G_NODE_IS_ROOT ((GNode *) path))
2830 return;
2832 /* retrieve the message information array */
2833 msg_info = ((GNode *) path)->data;
2834 g_return_if_fail (msg_info != NULL);
2836 if (!(camel_message_info_get_flags (msg_info) & CAMEL_MESSAGE_SEEN))
2837 *inout_background = *(message_list->priv->new_mail_bg_color);
2840 static void
2841 ml_style_updated_cb (MessageList *message_list)
2843 g_return_if_fail (IS_MESSAGE_LIST (message_list));
2845 if (message_list->priv->new_mail_bg_color) {
2846 gdk_rgba_free (message_list->priv->new_mail_bg_color);
2847 message_list->priv->new_mail_bg_color = NULL;
2850 gtk_widget_style_get (GTK_WIDGET (message_list),
2851 "new-mail-bg-color", &message_list->priv->new_mail_bg_color,
2852 NULL);
2855 static void
2856 message_list_get_preferred_width (GtkWidget *widget,
2857 gint *out_minimum_width,
2858 gint *out_natural_width)
2860 /* Chain up to parent's method. */
2861 GTK_WIDGET_CLASS (message_list_parent_class)->get_preferred_width (widget, out_minimum_width, out_natural_width);
2863 if (out_minimum_width && *out_minimum_width < 50)
2864 *out_minimum_width = 50;
2866 if (out_natural_width && out_minimum_width &&
2867 *out_natural_width < *out_minimum_width)
2868 *out_natural_width = *out_minimum_width;
2871 static void
2872 message_list_set_session (MessageList *message_list,
2873 EMailSession *session)
2875 g_return_if_fail (E_IS_MAIL_SESSION (session));
2876 g_return_if_fail (message_list->priv->session == NULL);
2878 message_list->priv->session = g_object_ref (session);
2881 static void
2882 message_list_set_property (GObject *object,
2883 guint property_id,
2884 const GValue *value,
2885 GParamSpec *pspec)
2887 switch (property_id) {
2888 case PROP_FOLDER:
2889 message_list_set_folder (
2890 MESSAGE_LIST (object),
2891 g_value_get_object (value));
2892 return;
2894 case PROP_GROUP_BY_THREADS:
2895 message_list_set_group_by_threads (
2896 MESSAGE_LIST (object),
2897 g_value_get_boolean (value));
2898 return;
2900 case PROP_SESSION:
2901 message_list_set_session (
2902 MESSAGE_LIST (object),
2903 g_value_get_object (value));
2904 return;
2906 case PROP_SHOW_DELETED:
2907 message_list_set_show_deleted (
2908 MESSAGE_LIST (object),
2909 g_value_get_boolean (value));
2910 return;
2912 case PROP_SHOW_JUNK:
2913 message_list_set_show_junk (
2914 MESSAGE_LIST (object),
2915 g_value_get_boolean (value));
2916 return;
2918 case PROP_SHOW_SUBJECT_ABOVE_SENDER:
2919 message_list_set_show_subject_above_sender (
2920 MESSAGE_LIST (object),
2921 g_value_get_boolean (value));
2922 return;
2924 case PROP_THREAD_LATEST:
2925 message_list_set_thread_latest (
2926 MESSAGE_LIST (object),
2927 g_value_get_boolean (value));
2928 return;
2930 case PROP_THREAD_SUBJECT:
2931 message_list_set_thread_subject (
2932 MESSAGE_LIST (object),
2933 g_value_get_boolean (value));
2934 return;
2937 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2940 static void
2941 message_list_get_property (GObject *object,
2942 guint property_id,
2943 GValue *value,
2944 GParamSpec *pspec)
2946 switch (property_id) {
2947 case PROP_COPY_TARGET_LIST:
2948 g_value_set_boxed (
2949 value,
2950 message_list_get_copy_target_list (
2951 MESSAGE_LIST (object)));
2952 return;
2954 case PROP_FOLDER:
2955 g_value_take_object (
2956 value,
2957 message_list_ref_folder (
2958 MESSAGE_LIST (object)));
2959 return;
2961 case PROP_GROUP_BY_THREADS:
2962 g_value_set_boolean (
2963 value,
2964 message_list_get_group_by_threads (
2965 MESSAGE_LIST (object)));
2966 return;
2968 case PROP_PASTE_TARGET_LIST:
2969 g_value_set_boxed (
2970 value,
2971 message_list_get_paste_target_list (
2972 MESSAGE_LIST (object)));
2973 return;
2975 case PROP_SESSION:
2976 g_value_set_object (
2977 value,
2978 message_list_get_session (
2979 MESSAGE_LIST (object)));
2980 return;
2982 case PROP_SHOW_DELETED:
2983 g_value_set_boolean (
2984 value,
2985 message_list_get_show_deleted (
2986 MESSAGE_LIST (object)));
2987 return;
2989 case PROP_SHOW_JUNK:
2990 g_value_set_boolean (
2991 value,
2992 message_list_get_show_junk (
2993 MESSAGE_LIST (object)));
2994 return;
2996 case PROP_SHOW_SUBJECT_ABOVE_SENDER:
2997 g_value_set_boolean (
2998 value,
2999 message_list_get_show_subject_above_sender (
3000 MESSAGE_LIST (object)));
3001 return;
3003 case PROP_THREAD_LATEST:
3004 g_value_set_boolean (
3005 value,
3006 message_list_get_thread_latest (
3007 MESSAGE_LIST (object)));
3008 return;
3010 case PROP_THREAD_SUBJECT:
3011 g_value_set_boolean (
3012 value,
3013 message_list_get_thread_subject (
3014 MESSAGE_LIST (object)));
3015 return;
3018 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
3021 static void
3022 message_list_dispose (GObject *object)
3024 MessageList *message_list = MESSAGE_LIST (object);
3025 MessageListPrivate *priv;
3027 priv = message_list->priv;
3029 if (priv->folder_changed_handler_id > 0) {
3030 g_signal_handler_disconnect (
3031 priv->folder,
3032 priv->folder_changed_handler_id);
3033 priv->folder_changed_handler_id = 0;
3036 if (priv->copy_target_list != NULL) {
3037 gtk_target_list_unref (priv->copy_target_list);
3038 priv->copy_target_list = NULL;
3041 if (priv->paste_target_list != NULL) {
3042 gtk_target_list_unref (priv->paste_target_list);
3043 priv->paste_target_list = NULL;
3046 priv->destroyed = TRUE;
3048 if (message_list->priv->folder != NULL)
3049 mail_regen_cancel (message_list);
3051 if (message_list->uid_nodemap) {
3052 g_hash_table_foreach (
3053 message_list->uid_nodemap,
3054 (GHFunc) clear_info, message_list);
3055 g_hash_table_destroy (message_list->uid_nodemap);
3056 message_list->uid_nodemap = NULL;
3059 g_clear_object (&priv->session);
3060 g_clear_object (&priv->folder);
3061 g_clear_object (&priv->invisible);
3062 g_clear_object (&priv->mail_settings);
3064 g_clear_object (&message_list->extras);
3066 if (message_list->idle_id > 0) {
3067 g_source_remove (message_list->idle_id);
3068 message_list->idle_id = 0;
3071 if (message_list->seen_id > 0) {
3072 g_source_remove (message_list->seen_id);
3073 message_list->seen_id = 0;
3076 /* Chain up to parent's dispose() method. */
3077 G_OBJECT_CLASS (message_list_parent_class)->dispose (object);
3080 static void
3081 message_list_finalize (GObject *object)
3083 MessageList *message_list = MESSAGE_LIST (object);
3085 g_hash_table_destroy (message_list->normalised_hash);
3087 if (message_list->priv->thread_tree != NULL)
3088 camel_folder_thread_messages_unref (
3089 message_list->priv->thread_tree);
3091 g_free (message_list->search);
3092 g_free (message_list->frozen_search);
3093 g_free (message_list->cursor_uid);
3094 g_strfreev (message_list->priv->re_prefixes);
3095 g_strfreev (message_list->priv->re_separators);
3097 g_mutex_clear (&message_list->priv->regen_lock);
3098 g_mutex_clear (&message_list->priv->thread_tree_lock);
3099 g_mutex_clear (&message_list->priv->re_prefixes_lock);
3101 clear_selection (message_list, &message_list->priv->clipboard);
3103 if (message_list->priv->tree_model_root != NULL)
3104 extended_g_node_destroy (message_list->priv->tree_model_root);
3106 if (message_list->priv->new_mail_bg_color) {
3107 gdk_rgba_free (message_list->priv->new_mail_bg_color);
3108 message_list->priv->new_mail_bg_color = NULL;
3111 /* Chain up to parent's finalize() method. */
3112 G_OBJECT_CLASS (message_list_parent_class)->finalize (object);
3115 static void
3116 message_list_constructed (GObject *object)
3118 /* Chain up to parent's constructed() method. */
3119 G_OBJECT_CLASS (message_list_parent_class)->constructed (object);
3121 e_extensible_load_extensions (E_EXTENSIBLE (object));
3124 static void
3125 message_list_selectable_update_actions (ESelectable *selectable,
3126 EFocusTracker *focus_tracker,
3127 GdkAtom *clipboard_targets,
3128 gint n_clipboard_targets)
3130 ETreeTableAdapter *adapter;
3131 GtkAction *action;
3132 gint row_count;
3134 adapter = e_tree_get_table_adapter (E_TREE (selectable));
3135 row_count = e_table_model_row_count (E_TABLE_MODEL (adapter));
3137 action = e_focus_tracker_get_select_all_action (focus_tracker);
3138 gtk_action_set_tooltip (action, _("Select all visible messages"));
3139 gtk_action_set_sensitive (action, row_count > 0);
3142 static void
3143 message_list_selectable_select_all (ESelectable *selectable)
3145 message_list_select_all (MESSAGE_LIST (selectable));
3148 static ETreePath
3149 message_list_get_root (ETreeModel *tree_model)
3151 MessageList *message_list = MESSAGE_LIST (tree_model);
3153 return message_list->priv->tree_model_root;
3156 static ETreePath
3157 message_list_get_parent (ETreeModel *tree_model,
3158 ETreePath path)
3160 return ((GNode *) path)->parent;
3163 static ETreePath
3164 message_list_get_first_child (ETreeModel *tree_model,
3165 ETreePath path)
3167 return g_node_first_child ((GNode *) path);
3170 static ETreePath
3171 message_list_get_next (ETreeModel *tree_model,
3172 ETreePath path)
3174 return g_node_next_sibling ((GNode *) path);
3177 static gboolean
3178 message_list_is_root (ETreeModel *tree_model,
3179 ETreePath path)
3181 return G_NODE_IS_ROOT ((GNode *) path);
3184 static gboolean
3185 message_list_is_expandable (ETreeModel *tree_model,
3186 ETreePath path)
3188 return (g_node_first_child ((GNode *) path) != NULL);
3191 static guint
3192 message_list_get_n_nodes (ETreeModel *tree_model)
3194 ETreePath root;
3196 root = e_tree_model_get_root (tree_model);
3198 if (root == NULL)
3199 return 0;
3201 /* The root node is an empty placeholder, so
3202 * subtract one from the count to exclude it. */
3204 return g_node_n_nodes ((GNode *) root, G_TRAVERSE_ALL) - 1;
3207 static guint
3208 message_list_get_n_children (ETreeModel *tree_model,
3209 ETreePath path)
3211 return g_node_n_children ((GNode *) path);
3214 static guint
3215 message_list_depth (ETreeModel *tree_model,
3216 ETreePath path)
3218 return g_node_depth ((GNode *) path);
3221 static gboolean
3222 message_list_get_expanded_default (ETreeModel *tree_model)
3224 MessageList *message_list = MESSAGE_LIST (tree_model);
3226 return message_list->priv->expanded_default;
3229 static gint
3230 message_list_column_count (ETreeModel *tree_model)
3232 return COL_LAST;
3235 static gchar *
3236 message_list_get_save_id (ETreeModel *tree_model,
3237 ETreePath path)
3239 CamelMessageInfo *info;
3241 if (G_NODE_IS_ROOT ((GNode *) path))
3242 return g_strdup ("root");
3244 /* Note: ETable can ask for the save_id while we're clearing
3245 * it, which is the only time info should be NULL. */
3246 info = ((GNode *) path)->data;
3247 if (info == NULL)
3248 return NULL;
3250 return g_strdup (camel_message_info_get_uid (info));
3253 static ETreePath
3254 message_list_get_node_by_id (ETreeModel *tree_model,
3255 const gchar *save_id)
3257 MessageList *message_list;
3259 message_list = MESSAGE_LIST (tree_model);
3261 if (!strcmp (save_id, "root"))
3262 return e_tree_model_get_root (tree_model);
3264 return g_hash_table_lookup (message_list->uid_nodemap, save_id);
3267 static gpointer
3268 message_list_sort_value_at (ETreeModel *tree_model,
3269 ETreePath path,
3270 gint col)
3272 MessageList *message_list;
3273 GNode *path_node;
3274 struct LatestData ld;
3275 gint64 *res;
3277 message_list = MESSAGE_LIST (tree_model);
3279 if (!(col == COL_SENT || col == COL_RECEIVED))
3280 return e_tree_model_value_at (tree_model, path, col);
3282 path_node = (GNode *) path;
3284 if (G_NODE_IS_ROOT (path_node))
3285 return NULL;
3287 ld.sent = (col == COL_SENT);
3288 ld.latest = 0;
3290 latest_foreach (tree_model, path, &ld);
3291 if (message_list->priv->thread_latest && (!e_tree_get_sort_children_ascending (E_TREE (message_list)) ||
3292 !path_node || !path_node->parent || !path_node->parent->parent))
3293 e_tree_model_node_traverse (
3294 tree_model, path, latest_foreach, &ld);
3296 res = g_new0 (gint64, 1);
3297 *res = (gint64) ld.latest;
3299 return res;
3302 static gpointer
3303 message_list_value_at (ETreeModel *tree_model,
3304 ETreePath path,
3305 gint col)
3307 MessageList *message_list;
3308 CamelMessageInfo *msg_info;
3309 gpointer result;
3311 message_list = MESSAGE_LIST (tree_model);
3313 if (!path || G_NODE_IS_ROOT ((GNode *) path))
3314 return NULL;
3316 /* retrieve the message information array */
3317 msg_info = ((GNode *) path)->data;
3318 g_return_val_if_fail (msg_info != NULL, NULL);
3320 camel_message_info_property_lock (msg_info);
3321 result = ml_tree_value_at_ex (tree_model, path, col, msg_info, message_list);
3322 camel_message_info_property_unlock (msg_info);
3324 return result;
3327 static gpointer
3328 message_list_duplicate_value (ETreeModel *tree_model,
3329 gint col,
3330 gconstpointer value)
3332 switch (col) {
3333 case COL_MESSAGE_STATUS:
3334 case COL_FLAGGED:
3335 case COL_SCORE:
3336 case COL_ATTACHMENT:
3337 case COL_DELETED:
3338 case COL_DELETED_OR_JUNK:
3339 case COL_JUNK:
3340 case COL_JUNK_STRIKEOUT_COLOR:
3341 case COL_UNREAD:
3342 case COL_SIZE:
3343 case COL_FOLLOWUP_FLAG:
3344 case COL_FOLLOWUP_FLAG_STATUS:
3345 return (gpointer) value;
3347 case COL_UID:
3348 return (gpointer) camel_pstring_strdup (value);
3350 case COL_FROM:
3351 case COL_SUBJECT:
3352 case COL_TO:
3353 case COL_SENDER:
3354 case COL_RECIPIENTS:
3355 case COL_MIXED_SENDER:
3356 case COL_MIXED_RECIPIENTS:
3357 case COL_LOCATION:
3358 case COL_LABELS:
3359 return g_strdup (value);
3361 case COL_SENT:
3362 case COL_RECEIVED:
3363 case COL_FOLLOWUP_DUE_BY:
3364 if (value) {
3365 gint64 *res;
3366 const gint64 *pvalue = value;
3368 res = g_new0 (gint64, 1);
3369 *res = *pvalue;
3371 return res;
3372 } else
3373 return NULL;
3375 default:
3376 g_return_val_if_reached (NULL);
3380 static void
3381 message_list_free_value (ETreeModel *tree_model,
3382 gint col,
3383 gpointer value)
3385 switch (col) {
3386 case COL_MESSAGE_STATUS:
3387 case COL_FLAGGED:
3388 case COL_SCORE:
3389 case COL_ATTACHMENT:
3390 case COL_DELETED:
3391 case COL_DELETED_OR_JUNK:
3392 case COL_JUNK:
3393 case COL_JUNK_STRIKEOUT_COLOR:
3394 case COL_UNREAD:
3395 case COL_SIZE:
3396 case COL_FOLLOWUP_FLAG:
3397 case COL_FOLLOWUP_FLAG_STATUS:
3398 case COL_FROM:
3399 case COL_FROM_NORM:
3400 case COL_TO:
3401 case COL_TO_NORM:
3402 case COL_SUBJECT:
3403 case COL_SUBJECT_NORM:
3404 case COL_SUBJECT_TRIMMED:
3405 case COL_COLOUR:
3406 case COL_ITALIC:
3407 break;
3409 case COL_UID:
3410 camel_pstring_free (value);
3411 break;
3413 case COL_LOCATION:
3414 case COL_SENDER:
3415 case COL_RECIPIENTS:
3416 case COL_MIXED_SENDER:
3417 case COL_MIXED_RECIPIENTS:
3418 case COL_LABELS:
3419 case COL_SENT:
3420 case COL_RECEIVED:
3421 case COL_FOLLOWUP_DUE_BY:
3422 g_free (value);
3423 break;
3425 default:
3426 g_warn_if_reached ();
3430 static gpointer
3431 message_list_initialize_value (ETreeModel *tree_model,
3432 gint col)
3434 switch (col) {
3435 case COL_MESSAGE_STATUS:
3436 case COL_FLAGGED:
3437 case COL_SCORE:
3438 case COL_ATTACHMENT:
3439 case COL_DELETED:
3440 case COL_DELETED_OR_JUNK:
3441 case COL_JUNK:
3442 case COL_JUNK_STRIKEOUT_COLOR:
3443 case COL_UNREAD:
3444 case COL_SENT:
3445 case COL_RECEIVED:
3446 case COL_SIZE:
3447 case COL_FROM:
3448 case COL_SUBJECT:
3449 case COL_TO:
3450 case COL_FOLLOWUP_FLAG:
3451 case COL_FOLLOWUP_FLAG_STATUS:
3452 case COL_FOLLOWUP_DUE_BY:
3453 case COL_UID:
3454 return NULL;
3456 case COL_LOCATION:
3457 case COL_SENDER:
3458 case COL_RECIPIENTS:
3459 case COL_MIXED_SENDER:
3460 case COL_MIXED_RECIPIENTS:
3461 case COL_LABELS:
3462 return g_strdup ("");
3464 default:
3465 g_return_val_if_reached (NULL);
3469 static gboolean
3470 message_list_value_is_empty (ETreeModel *tree_model,
3471 gint col,
3472 gconstpointer value)
3474 switch (col) {
3475 case COL_MESSAGE_STATUS:
3476 case COL_FLAGGED:
3477 case COL_SCORE:
3478 case COL_ATTACHMENT:
3479 case COL_DELETED:
3480 case COL_DELETED_OR_JUNK:
3481 case COL_JUNK:
3482 case COL_JUNK_STRIKEOUT_COLOR:
3483 case COL_UNREAD:
3484 case COL_SENT:
3485 case COL_RECEIVED:
3486 case COL_SIZE:
3487 case COL_FOLLOWUP_FLAG_STATUS:
3488 case COL_FOLLOWUP_DUE_BY:
3489 return value == NULL;
3491 case COL_FROM:
3492 case COL_SUBJECT:
3493 case COL_TO:
3494 case COL_FOLLOWUP_FLAG:
3495 case COL_LOCATION:
3496 case COL_SENDER:
3497 case COL_RECIPIENTS:
3498 case COL_MIXED_SENDER:
3499 case COL_MIXED_RECIPIENTS:
3500 case COL_LABELS:
3501 case COL_UID:
3502 return !(value && *(gchar *) value);
3504 default:
3505 g_return_val_if_reached (FALSE);
3509 static gchar *
3510 message_list_value_to_string (ETreeModel *tree_model,
3511 gint col,
3512 gconstpointer value)
3514 guint ii;
3516 switch (col) {
3517 case COL_MESSAGE_STATUS:
3518 ii = GPOINTER_TO_UINT (value);
3519 if (ii > 5)
3520 return g_strdup ("");
3521 return g_strdup (status_map[ii]);
3523 case COL_SCORE:
3524 ii = GPOINTER_TO_UINT (value) + 3;
3525 if (ii > 6)
3526 ii = 3;
3527 return g_strdup (score_map[ii]);
3529 case COL_ATTACHMENT:
3530 case COL_FLAGGED:
3531 case COL_DELETED:
3532 case COL_DELETED_OR_JUNK:
3533 case COL_JUNK:
3534 case COL_JUNK_STRIKEOUT_COLOR:
3535 case COL_UNREAD:
3536 case COL_FOLLOWUP_FLAG_STATUS:
3537 ii = GPOINTER_TO_UINT (value);
3538 return g_strdup_printf ("%u", ii);
3540 case COL_SENT:
3541 case COL_RECEIVED:
3542 case COL_FOLLOWUP_DUE_BY:
3543 return filter_date (value);
3545 case COL_SIZE:
3546 return filter_size (GPOINTER_TO_INT (value));
3548 case COL_FROM:
3549 case COL_SUBJECT:
3550 case COL_TO:
3551 case COL_FOLLOWUP_FLAG:
3552 case COL_LOCATION:
3553 case COL_SENDER:
3554 case COL_RECIPIENTS:
3555 case COL_MIXED_SENDER:
3556 case COL_MIXED_RECIPIENTS:
3557 case COL_LABELS:
3558 case COL_UID:
3559 return g_strdup (value);
3561 default:
3562 g_return_val_if_reached (NULL);
3567 static void
3568 message_list_class_init (MessageListClass *class)
3570 GObjectClass *object_class;
3571 GtkWidgetClass *widget_class;
3573 if (!ml_drag_info[0].atom) {
3574 gint ii;
3576 for (ii = 0; ii < G_N_ELEMENTS (ml_drag_info); ii++) {
3577 ml_drag_info[ii].atom = gdk_atom_intern (ml_drag_info[ii].target, FALSE);
3580 for (ii = 0; ii < G_N_ELEMENTS (status_map); ii++) {
3581 status_map[ii] = _(status_map[ii]);
3584 for (ii = 0; ii < G_N_ELEMENTS (score_map); ii++) {
3585 score_map[ii] = _(score_map[ii]);
3589 g_type_class_add_private (class, sizeof (MessageListPrivate));
3591 widget_class = GTK_WIDGET_CLASS (class);
3592 widget_class->get_preferred_width = message_list_get_preferred_width;
3594 object_class = G_OBJECT_CLASS (class);
3595 object_class->set_property = message_list_set_property;
3596 object_class->get_property = message_list_get_property;
3597 object_class->dispose = message_list_dispose;
3598 object_class->finalize = message_list_finalize;
3599 object_class->constructed = message_list_constructed;
3601 class->message_list_built = NULL;
3603 /* Inherited from ESelectableInterface */
3604 g_object_class_override_property (
3605 object_class,
3606 PROP_COPY_TARGET_LIST,
3607 "copy-target-list");
3609 g_object_class_install_property (
3610 object_class,
3611 PROP_FOLDER,
3612 g_param_spec_object (
3613 "folder",
3614 "Folder",
3615 "The source folder",
3616 CAMEL_TYPE_FOLDER,
3617 G_PARAM_READWRITE |
3618 G_PARAM_STATIC_STRINGS));
3620 g_object_class_install_property (
3621 object_class,
3622 PROP_GROUP_BY_THREADS,
3623 g_param_spec_boolean (
3624 "group-by-threads",
3625 "Group By Threads",
3626 "Group messages into conversation threads",
3627 FALSE,
3628 G_PARAM_READWRITE |
3629 G_PARAM_CONSTRUCT |
3630 G_PARAM_STATIC_STRINGS));
3632 /* Inherited from ESelectableInterface */
3633 g_object_class_override_property (
3634 object_class,
3635 PROP_PASTE_TARGET_LIST,
3636 "paste-target-list");
3638 g_object_class_install_property (
3639 object_class,
3640 PROP_SESSION,
3641 g_param_spec_object (
3642 "session",
3643 "Mail Session",
3644 "The mail session",
3645 E_TYPE_MAIL_SESSION,
3646 G_PARAM_READWRITE |
3647 G_PARAM_CONSTRUCT_ONLY |
3648 G_PARAM_STATIC_STRINGS));
3650 g_object_class_install_property (
3651 object_class,
3652 PROP_SHOW_DELETED,
3653 g_param_spec_boolean (
3654 "show-deleted",
3655 "Show Deleted",
3656 "Show messages marked for deletion",
3657 FALSE,
3658 G_PARAM_READWRITE |
3659 G_PARAM_CONSTRUCT |
3660 G_PARAM_STATIC_STRINGS));
3662 g_object_class_install_property (
3663 object_class,
3664 PROP_SHOW_JUNK,
3665 g_param_spec_boolean (
3666 "show-junk",
3667 "Show Junk",
3668 "Show messages marked as junk",
3669 FALSE,
3670 G_PARAM_READWRITE |
3671 G_PARAM_CONSTRUCT |
3672 G_PARAM_STATIC_STRINGS));
3674 g_object_class_install_property (
3675 object_class,
3676 PROP_SHOW_SUBJECT_ABOVE_SENDER,
3677 g_param_spec_boolean (
3678 "show-subject-above-sender",
3679 "Show Subject Above Sender",
3680 NULL,
3681 FALSE,
3682 G_PARAM_READWRITE |
3683 G_PARAM_CONSTRUCT |
3684 G_PARAM_STATIC_STRINGS));
3686 g_object_class_install_property (
3687 object_class,
3688 PROP_THREAD_LATEST,
3689 g_param_spec_boolean (
3690 "thread-latest",
3691 "Thread Latest",
3692 "Sort threads by latest message",
3693 TRUE,
3694 G_PARAM_READWRITE |
3695 G_PARAM_CONSTRUCT |
3696 G_PARAM_STATIC_STRINGS));
3698 g_object_class_install_property (
3699 object_class,
3700 PROP_THREAD_SUBJECT,
3701 g_param_spec_boolean (
3702 "thread-subject",
3703 "Thread Subject",
3704 "Thread messages by Subject headers",
3705 FALSE,
3706 G_PARAM_READWRITE |
3707 G_PARAM_CONSTRUCT |
3708 G_PARAM_STATIC_STRINGS));
3710 gtk_widget_class_install_style_property (
3711 GTK_WIDGET_CLASS (class),
3712 g_param_spec_boxed (
3713 "new-mail-bg-color",
3714 "New Mail Background Color",
3715 "Background color to use for new mails",
3716 GDK_TYPE_RGBA,
3717 G_PARAM_READABLE));
3719 signals[MESSAGE_SELECTED] = g_signal_new (
3720 "message_selected",
3721 MESSAGE_LIST_TYPE,
3722 G_SIGNAL_RUN_LAST,
3723 G_STRUCT_OFFSET (MessageListClass, message_selected),
3724 NULL,
3725 NULL,
3726 g_cclosure_marshal_VOID__STRING,
3727 G_TYPE_NONE, 1,
3728 G_TYPE_STRING);
3730 signals[MESSAGE_LIST_BUILT] = g_signal_new (
3731 "message_list_built",
3732 MESSAGE_LIST_TYPE,
3733 G_SIGNAL_RUN_LAST,
3734 G_STRUCT_OFFSET (MessageListClass, message_list_built),
3735 NULL,
3736 NULL,
3737 g_cclosure_marshal_VOID__VOID,
3738 G_TYPE_NONE, 0);
3741 static void
3742 message_list_selectable_init (ESelectableInterface *iface)
3744 iface->update_actions = message_list_selectable_update_actions;
3745 iface->select_all = message_list_selectable_select_all;
3748 static void
3749 message_list_tree_model_init (ETreeModelInterface *iface)
3751 iface->get_root = message_list_get_root;
3752 iface->get_parent = message_list_get_parent;
3753 iface->get_first_child = message_list_get_first_child;
3754 iface->get_next = message_list_get_next;
3755 iface->is_root = message_list_is_root;
3756 iface->is_expandable = message_list_is_expandable;
3757 iface->get_n_nodes = message_list_get_n_nodes;
3758 iface->get_n_children = message_list_get_n_children;
3759 iface->depth = message_list_depth;
3760 iface->get_expanded_default = message_list_get_expanded_default;
3761 iface->column_count = message_list_column_count;
3762 iface->get_save_id = message_list_get_save_id;
3763 iface->get_node_by_id = message_list_get_node_by_id;
3764 iface->sort_value_at = message_list_sort_value_at;
3765 iface->value_at = message_list_value_at;
3766 iface->duplicate_value = message_list_duplicate_value;
3767 iface->free_value = message_list_free_value;
3768 iface->initialize_value = message_list_initialize_value;
3769 iface->value_is_empty = message_list_value_is_empty;
3770 iface->value_to_string = message_list_value_to_string;
3773 static void
3774 message_list_init (MessageList *message_list)
3776 MessageListPrivate *p;
3777 GtkTargetList *target_list;
3778 GdkAtom matom;
3780 message_list->priv = MESSAGE_LIST_GET_PRIVATE (message_list);
3782 message_list->normalised_hash = g_hash_table_new_full (
3783 g_str_hash, g_str_equal,
3784 (GDestroyNotify) NULL,
3785 (GDestroyNotify) e_poolv_destroy);
3787 message_list->uid_nodemap = g_hash_table_new (g_str_hash, g_str_equal);
3789 message_list->cursor_uid = NULL;
3790 message_list->last_sel_single = FALSE;
3792 g_mutex_init (&message_list->priv->regen_lock);
3793 g_mutex_init (&message_list->priv->thread_tree_lock);
3794 g_mutex_init (&message_list->priv->re_prefixes_lock);
3796 /* TODO: Should this only get the selection if we're realised? */
3797 p = message_list->priv;
3798 p->invisible = gtk_invisible_new ();
3799 p->destroyed = FALSE;
3800 g_object_ref_sink (p->invisible);
3801 p->any_row_changed = FALSE;
3803 matom = gdk_atom_intern ("x-uid-list", FALSE);
3804 gtk_selection_add_target (p->invisible, GDK_SELECTION_CLIPBOARD, matom, 0);
3805 gtk_selection_add_target (p->invisible, GDK_SELECTION_CLIPBOARD, GDK_SELECTION_TYPE_STRING, 2);
3807 g_signal_connect (
3808 p->invisible, "selection_get",
3809 G_CALLBACK (ml_selection_get), message_list);
3810 g_signal_connect (
3811 p->invisible, "selection_clear_event",
3812 G_CALLBACK (ml_selection_clear_event), message_list);
3813 g_signal_connect (
3814 p->invisible, "selection_received",
3815 G_CALLBACK (ml_selection_received), message_list);
3817 /* FIXME This is currently unused. */
3818 target_list = gtk_target_list_new (NULL, 0);
3819 message_list->priv->copy_target_list = target_list;
3821 /* FIXME This is currently unused. */
3822 target_list = gtk_target_list_new (NULL, 0);
3823 message_list->priv->paste_target_list = target_list;
3825 message_list->priv->mail_settings = e_util_ref_settings ("org.gnome.evolution.mail");
3826 message_list->priv->re_prefixes = NULL;
3827 message_list->priv->re_separators = NULL;
3828 message_list->priv->group_by_threads = TRUE;
3829 message_list->priv->new_mail_bg_color = NULL;
3832 static void
3833 message_list_construct (MessageList *message_list)
3835 ETreeTableAdapter *adapter;
3836 ETableSpecification *specification;
3837 ETableItem *item;
3838 AtkObject *a11y;
3839 gboolean constructed;
3840 gchar *etspecfile;
3841 GError *local_error = NULL;
3844 * The etree
3846 message_list->extras = message_list_create_extras (message_list->priv->mail_settings);
3848 etspecfile = g_build_filename (
3849 EVOLUTION_ETSPECDIR, "message-list.etspec", NULL);
3850 specification = e_table_specification_new (etspecfile, &local_error);
3852 /* Failure here is fatal. */
3853 if (local_error != NULL) {
3854 g_error ("%s: %s", etspecfile, local_error->message);
3855 g_return_if_reached ();
3858 constructed = e_tree_construct (
3859 E_TREE (message_list),
3860 E_TREE_MODEL (message_list),
3861 message_list->extras, specification);
3863 g_object_unref (specification);
3864 g_free (etspecfile);
3866 adapter = e_tree_get_table_adapter (E_TREE (message_list));
3868 if (constructed)
3869 e_tree_table_adapter_root_node_set_visible (adapter, FALSE);
3871 if (atk_get_root () != NULL) {
3872 a11y = gtk_widget_get_accessible (GTK_WIDGET (message_list));
3873 atk_object_set_name (a11y, _("Messages"));
3876 g_signal_connect (
3877 adapter, "model_row_changed",
3878 G_CALLBACK (on_model_row_changed), message_list);
3880 g_signal_connect (
3881 message_list, "cursor_activated",
3882 G_CALLBACK (on_cursor_activated_cmd), message_list);
3884 g_signal_connect (
3885 message_list, "click",
3886 G_CALLBACK (on_click), message_list);
3888 g_signal_connect (
3889 message_list, "selection_change",
3890 G_CALLBACK (on_selection_changed_cmd), message_list);
3892 e_tree_drag_source_set (
3893 E_TREE (message_list), GDK_BUTTON1_MASK,
3894 ml_drag_types, G_N_ELEMENTS (ml_drag_types),
3895 GDK_ACTION_MOVE | GDK_ACTION_COPY);
3897 g_signal_connect (
3898 message_list, "tree_drag_data_get",
3899 G_CALLBACK (ml_tree_drag_data_get), message_list);
3901 gtk_drag_dest_set (
3902 GTK_WIDGET (message_list),
3903 GTK_DEST_DEFAULT_ALL,
3904 ml_drop_types,
3905 G_N_ELEMENTS (ml_drop_types),
3906 GDK_ACTION_MOVE |
3907 GDK_ACTION_COPY);
3909 g_signal_connect (
3910 message_list, "tree_drag_data_received",
3911 G_CALLBACK (ml_tree_drag_data_received), message_list);
3913 g_signal_connect (
3914 message_list, "drag-motion",
3915 G_CALLBACK (ml_tree_drag_motion), message_list);
3917 g_signal_connect (
3918 adapter, "sorting_changed",
3919 G_CALLBACK (ml_tree_sorting_changed), message_list);
3921 item = e_tree_get_item (E_TREE (message_list));
3922 g_signal_connect (item, "get-bg-color",
3923 G_CALLBACK (ml_get_bg_color_cb), message_list);
3925 g_signal_connect (message_list, "realize",
3926 G_CALLBACK (ml_style_updated_cb), NULL);
3928 g_signal_connect (message_list, "style-updated",
3929 G_CALLBACK (ml_style_updated_cb), NULL);
3933 * message_list_new:
3935 * Creates a new message-list widget.
3937 * Returns a new message-list widget.
3939 GtkWidget *
3940 message_list_new (EMailSession *session)
3942 GtkWidget *message_list;
3944 g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
3946 message_list = g_object_new (
3947 message_list_get_type (),
3948 "session", session, NULL);
3950 message_list_construct (MESSAGE_LIST (message_list));
3952 return message_list;
3955 EMailSession *
3956 message_list_get_session (MessageList *message_list)
3958 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
3960 return message_list->priv->session;
3963 static void
3964 clear_info (gchar *key,
3965 GNode *node,
3966 MessageList *message_list)
3968 g_clear_object (&node->data);
3971 static void
3972 clear_tree (MessageList *message_list,
3973 gboolean tfree)
3975 ETreeModel *tree_model;
3976 CamelFolder *folder;
3978 #ifdef TIMEIT
3979 struct timeval start, end;
3980 gulong diff;
3982 printf ("Clearing tree\n");
3983 gettimeofday (&start, NULL);
3984 #endif
3986 tree_model = E_TREE_MODEL (message_list);
3988 /* we also reset the uid_rowmap since it is no longer useful/valid anyway */
3989 folder = message_list_ref_folder (message_list);
3990 if (folder != NULL)
3991 g_hash_table_foreach (
3992 message_list->uid_nodemap,
3993 (GHFunc) clear_info, message_list);
3994 g_hash_table_destroy (message_list->uid_nodemap);
3995 message_list->uid_nodemap = g_hash_table_new (g_str_hash, g_str_equal);
3996 g_clear_object (&folder);
3998 message_list->priv->newest_read_date = 0;
3999 message_list->priv->newest_read_uid = NULL;
4000 message_list->priv->oldest_unread_date = 0;
4001 message_list->priv->oldest_unread_uid = NULL;
4003 if (message_list->priv->tree_model_root != NULL) {
4004 /* we should be frozen already */
4005 message_list_tree_model_remove (
4006 message_list, message_list->priv->tree_model_root);
4009 e_tree_table_adapter_clear_nodes_silent (e_tree_get_table_adapter (E_TREE (message_list)));
4011 /* Create a new placeholder root node. */
4012 message_list_tree_model_insert (message_list, NULL, 0, NULL);
4013 g_warn_if_fail (message_list->priv->tree_model_root != NULL);
4015 /* Also reset cursor node, it had been just erased */
4016 e_tree_set_cursor (E_TREE (message_list), message_list->priv->tree_model_root);
4018 if (tfree)
4019 e_tree_model_rebuilt (tree_model);
4020 #ifdef TIMEIT
4021 gettimeofday (&end, NULL);
4022 diff = end.tv_sec * 1000 + end.tv_usec / 1000;
4023 diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
4024 printf ("Clearing tree took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
4025 #endif
4028 static gboolean
4029 message_list_folder_filters_system_flag (const gchar *expr,
4030 const gchar *flag)
4032 const gchar *pos;
4034 if (!expr || !*expr)
4035 return FALSE;
4037 g_return_val_if_fail (flag && *flag, FALSE);
4039 while (pos = strstr (expr, flag), pos) {
4040 /* This is searching for something like 'system-flag "' + flag + '"'
4041 in the expression, without fully parsing it. */
4042 if (pos > expr && pos[-1] == '\"' && pos[strlen(flag)] == '\"') {
4043 const gchar *system_flag = "system-flag";
4044 gint ii = 2, jj = strlen (system_flag) - 1;
4046 while (pos - ii >= expr && g_ascii_isspace (pos[-ii]))
4047 ii++;
4049 while (pos - ii >= expr && jj >= 0 && system_flag[jj] == pos[-ii]) {
4050 ii++;
4051 jj--;
4054 if (jj == -1)
4055 return TRUE;
4058 expr = pos + 1;
4061 return FALSE;
4064 static gboolean
4065 folder_store_supports_vjunk_folder (CamelFolder *folder)
4067 CamelStore *store;
4069 g_return_val_if_fail (folder != NULL, FALSE);
4071 store = camel_folder_get_parent_store (folder);
4072 if (store == NULL)
4073 return FALSE;
4075 if (CAMEL_IS_VEE_FOLDER (folder))
4076 return TRUE;
4078 if (camel_store_get_flags (store) & CAMEL_STORE_VJUNK)
4079 return TRUE;
4081 if (camel_store_get_flags (store) & CAMEL_STORE_REAL_JUNK_FOLDER)
4082 return TRUE;
4084 return FALSE;
4087 static gboolean
4088 message_list_get_hide_junk (MessageList *message_list,
4089 CamelFolder *folder)
4091 guint32 folder_flags;
4093 if (folder == NULL)
4094 return FALSE;
4096 if (message_list_get_show_junk (message_list))
4097 return FALSE;
4099 if (!folder_store_supports_vjunk_folder (folder))
4100 return FALSE;
4102 folder_flags = camel_folder_get_flags (folder);
4104 if (folder_flags & CAMEL_FOLDER_IS_JUNK)
4105 return FALSE;
4107 if (folder_flags & CAMEL_FOLDER_IS_TRASH)
4108 return FALSE;
4110 if (CAMEL_IS_VEE_FOLDER (folder)) {
4111 const gchar *expr = camel_vee_folder_get_expression (CAMEL_VEE_FOLDER (folder));
4112 if (message_list_folder_filters_system_flag (expr, "Junk"))
4113 return FALSE;
4116 return TRUE;
4119 static gboolean
4120 message_list_get_hide_deleted (MessageList *message_list,
4121 CamelFolder *folder)
4123 CamelStore *store;
4124 gboolean non_trash_folder;
4126 if (folder == NULL)
4127 return FALSE;
4129 if (message_list_get_show_deleted (message_list))
4130 return FALSE;
4132 store = camel_folder_get_parent_store (folder);
4133 g_return_val_if_fail (store != NULL, FALSE);
4135 non_trash_folder =
4136 ((camel_store_get_flags (store) & CAMEL_STORE_VTRASH) == 0) ||
4137 ((camel_folder_get_flags (folder) & CAMEL_FOLDER_IS_TRASH) == 0);
4139 if (non_trash_folder && CAMEL_IS_VEE_FOLDER (folder)) {
4140 const gchar *expr = camel_vee_folder_get_expression (CAMEL_VEE_FOLDER (folder));
4141 if (message_list_folder_filters_system_flag (expr, "Deleted"))
4142 return FALSE;
4145 return non_trash_folder;
4148 /* Check if the given node is selectable in the current message list,
4149 * which depends on the type of the folder (normal, junk, trash). */
4150 static gboolean
4151 is_node_selectable (MessageList *message_list,
4152 CamelMessageInfo *info)
4154 CamelFolder *folder;
4155 gboolean is_junk_folder;
4156 gboolean is_trash_folder;
4157 guint32 flags, folder_flags;
4158 gboolean flag_junk;
4159 gboolean flag_deleted;
4160 gboolean hide_junk;
4161 gboolean hide_deleted;
4162 gboolean store_has_vjunk;
4163 gboolean selectable = FALSE;
4165 g_return_val_if_fail (info != NULL, FALSE);
4167 folder = message_list_ref_folder (message_list);
4168 g_return_val_if_fail (folder != NULL, FALSE);
4170 store_has_vjunk = folder_store_supports_vjunk_folder (folder);
4171 folder_flags = camel_folder_get_flags (folder);
4173 /* check folder type */
4174 is_junk_folder = store_has_vjunk && (folder_flags & CAMEL_FOLDER_IS_JUNK) != 0;
4175 is_trash_folder = folder_flags & CAMEL_FOLDER_IS_TRASH;
4177 hide_junk = message_list_get_hide_junk (message_list, folder);
4178 hide_deleted = message_list_get_hide_deleted (message_list, folder);
4180 g_object_unref (folder);
4182 /* check flags set on current message */
4183 flags = camel_message_info_get_flags (info);
4184 flag_junk = store_has_vjunk && (flags & CAMEL_MESSAGE_JUNK) != 0;
4185 flag_deleted = flags & CAMEL_MESSAGE_DELETED;
4187 /* perform actions depending on folder type */
4188 if (is_junk_folder) {
4189 /* messages in a junk folder are selectable only if
4190 * the message is marked as junk and if not deleted
4191 * when hide_deleted is set */
4192 if (flag_junk && !(flag_deleted && hide_deleted))
4193 selectable = TRUE;
4195 } else if (is_trash_folder) {
4196 /* messages in a trash folder are selectable unless
4197 * not deleted any more */
4198 if (flag_deleted)
4199 selectable = TRUE;
4200 } else {
4201 /* in normal folders it depends on hide_deleted,
4202 * hide_junk and the message flags */
4203 if (!(flag_junk && hide_junk)
4204 && !(flag_deleted && hide_deleted))
4205 selectable = TRUE;
4208 return selectable;
4211 /* We try and find something that is selectable in our tree. There is
4212 * actually no assurance that we'll find something that will still be
4213 * there next time, but its probably going to work most of the time. */
4214 static gchar *
4215 find_next_selectable (MessageList *message_list)
4217 ETreeTableAdapter *adapter;
4218 GNode *node;
4219 gint vrow_orig;
4220 gint vrow;
4221 gint row_count;
4222 CamelMessageInfo *info;
4224 node = g_hash_table_lookup (
4225 message_list->uid_nodemap,
4226 message_list->cursor_uid);
4227 if (node == NULL)
4228 return NULL;
4230 info = get_message_info (message_list, node);
4231 if (info && is_node_selectable (message_list, info))
4232 return NULL;
4234 adapter = e_tree_get_table_adapter (E_TREE (message_list));
4235 row_count = e_table_model_row_count (E_TABLE_MODEL (adapter));
4237 /* model_to_view_row etc simply dont work for sorted views. Sigh. */
4238 vrow_orig = e_tree_table_adapter_row_of_node (adapter, node);
4240 /* We already checked this node. */
4241 vrow = vrow_orig + 1;
4243 while (vrow < row_count) {
4244 node = e_tree_table_adapter_node_at_row (adapter, vrow);
4245 info = get_message_info (message_list, node);
4246 if (info && is_node_selectable (message_list, info))
4247 return g_strdup (camel_message_info_get_uid (info));
4248 vrow++;
4251 /* We didn't find any undeleted entries _below_ the currently selected one
4252 * * so let's try to find one _above_ */
4253 vrow = vrow_orig - 1;
4255 while (vrow >= 0) {
4256 node = e_tree_table_adapter_node_at_row (adapter, vrow);
4257 info = get_message_info (message_list, node);
4258 if (info && is_node_selectable (message_list, info))
4259 return g_strdup (camel_message_info_get_uid (info));
4260 vrow--;
4263 return NULL;
4266 static GNode *
4267 ml_uid_nodemap_insert (MessageList *message_list,
4268 CamelMessageInfo *info,
4269 GNode *parent,
4270 gint row)
4272 CamelFolder *folder;
4273 GNode *node;
4274 const gchar *uid;
4275 time_t date;
4276 guint flags;
4278 folder = message_list_ref_folder (message_list);
4279 g_return_val_if_fail (folder != NULL, NULL);
4281 if (parent == NULL)
4282 parent = message_list->priv->tree_model_root;
4284 node = message_list_tree_model_insert (
4285 message_list, parent, row, info);
4287 uid = camel_message_info_get_uid (info);
4288 flags = camel_message_info_get_flags (info);
4289 date = camel_message_info_get_date_received (info);
4291 g_object_ref (info);
4292 g_hash_table_insert (message_list->uid_nodemap, (gpointer) uid, node);
4294 /* Track the latest seen and unseen messages shown, used in
4295 * fallback heuristics for automatic message selection. */
4296 if (flags & CAMEL_MESSAGE_SEEN) {
4297 if (date > message_list->priv->newest_read_date) {
4298 message_list->priv->newest_read_date = date;
4299 message_list->priv->newest_read_uid = uid;
4301 } else {
4302 if (message_list->priv->oldest_unread_date == 0) {
4303 message_list->priv->oldest_unread_date = date;
4304 message_list->priv->oldest_unread_uid = uid;
4305 } else if (date < message_list->priv->oldest_unread_date) {
4306 message_list->priv->oldest_unread_date = date;
4307 message_list->priv->oldest_unread_uid = uid;
4311 g_object_unref (folder);
4313 return node;
4316 static void
4317 ml_uid_nodemap_remove (MessageList *message_list,
4318 CamelMessageInfo *info)
4320 CamelFolder *folder;
4321 const gchar *uid;
4323 folder = message_list_ref_folder (message_list);
4324 g_return_if_fail (folder != NULL);
4326 uid = camel_message_info_get_uid (info);
4328 if (uid == message_list->priv->newest_read_uid) {
4329 message_list->priv->newest_read_date = 0;
4330 message_list->priv->newest_read_uid = NULL;
4333 if (uid == message_list->priv->oldest_unread_uid) {
4334 message_list->priv->oldest_unread_date = 0;
4335 message_list->priv->oldest_unread_uid = NULL;
4338 g_hash_table_remove (message_list->uid_nodemap, uid);
4339 g_clear_object (&info);
4341 g_object_unref (folder);
4344 /* only call if we have a tree model */
4345 /* builds the tree structure */
4347 static void build_subtree (MessageList *message_list,
4348 GNode *parent,
4349 CamelFolderThreadNode *c,
4350 gint *row);
4352 static void build_subtree_diff (MessageList *message_list,
4353 GNode *parent,
4354 GNode *node,
4355 CamelFolderThreadNode *c,
4356 gint *row);
4358 static void
4359 build_tree (MessageList *message_list,
4360 CamelFolderThread *thread,
4361 gboolean folder_changed)
4363 gint row = 0;
4364 ETableItem *table_item = e_tree_get_item (E_TREE (message_list));
4365 #ifdef TIMEIT
4366 struct timeval start, end;
4367 gulong diff;
4369 printf ("Building tree\n");
4370 gettimeofday (&start, NULL);
4371 #endif
4373 #ifdef TIMEIT
4374 gettimeofday (&end, NULL);
4375 diff = end.tv_sec * 1000 + end.tv_usec / 1000;
4376 diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
4377 printf ("Loading tree state took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
4378 #endif
4380 if (message_list->priv->tree_model_root == NULL) {
4381 message_list_tree_model_insert (message_list, NULL, 0, NULL);
4382 g_warn_if_fail (message_list->priv->tree_model_root != NULL);
4385 if (table_item)
4386 e_table_item_freeze (table_item);
4388 message_list_tree_model_freeze (message_list);
4390 clear_tree (message_list, FALSE);
4392 build_subtree (
4393 message_list,
4394 message_list->priv->tree_model_root,
4395 thread ? thread->tree : NULL, &row);
4397 message_list_tree_model_thaw (message_list);
4399 if (table_item) {
4400 /* Show the cursor unless we're responding to a
4401 * "folder-changed" signal from our CamelFolder. */
4402 if (folder_changed)
4403 table_item->queue_show_cursor = FALSE;
4404 e_table_item_thaw (table_item);
4407 #ifdef TIMEIT
4408 gettimeofday (&end, NULL);
4409 diff = end.tv_sec * 1000 + end.tv_usec / 1000;
4410 diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
4411 printf ("Building tree took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
4412 #endif
4415 /* this is about 20% faster than build_subtree_diff,
4416 * entirely because e_tree_model_node_insert (xx, -1 xx)
4417 * is faster than inserting to the right row :( */
4418 /* Otherwise, this code would probably go as it does the same thing essentially */
4419 static void
4420 build_subtree (MessageList *message_list,
4421 GNode *parent,
4422 CamelFolderThreadNode *c,
4423 gint *row)
4425 GNode *node;
4427 while (c) {
4428 /* phantom nodes no longer allowed */
4429 if (!c->message) {
4430 g_warning ("c->message shouldn't be NULL\n");
4431 c = c->next;
4432 continue;
4435 node = ml_uid_nodemap_insert (
4436 message_list,
4437 (CamelMessageInfo *) c->message, parent, -1);
4439 if (c->child) {
4440 build_subtree (message_list, node, c->child, row);
4442 c = c->next;
4446 /* compares a thread tree node with the etable tree node to see if they point to
4447 * the same object */
4448 static gint
4449 node_equal (ETreeModel *etm,
4450 GNode *ap,
4451 CamelFolderThreadNode *bp)
4453 if (bp->message && strcmp (camel_message_info_get_uid (ap->data), camel_message_info_get_uid (bp->message)) == 0)
4454 return 1;
4456 return 0;
4459 /* adds a single node, retains save state, and handles adding children if required */
4460 static void
4461 add_node_diff (MessageList *message_list,
4462 GNode *parent,
4463 GNode *node,
4464 CamelFolderThreadNode *c,
4465 gint *row,
4466 gint myrow)
4468 CamelMessageInfo *info;
4469 GNode *new_node;
4471 g_return_if_fail (c->message != NULL);
4473 /* XXX Casting away constness. */
4474 info = (CamelMessageInfo *) c->message;
4476 /* we just update the hashtable key */
4477 ml_uid_nodemap_remove (message_list, info);
4478 new_node = ml_uid_nodemap_insert (message_list, info, parent, myrow);
4479 (*row)++;
4481 if (c->child) {
4482 build_subtree_diff (
4483 message_list, new_node, NULL, c->child, row);
4487 /* removes node, children recursively and all associated data */
4488 static void
4489 remove_node_diff (MessageList *message_list,
4490 GNode *node,
4491 gint depth)
4493 ETreePath cp, cn;
4494 CamelMessageInfo *info;
4496 t (printf ("Removing node: %s\n", (gchar *) node->data));
4498 /* we depth-first remove all node data's ... */
4499 cp = g_node_first_child (node);
4500 while (cp) {
4501 cn = g_node_next_sibling (cp);
4502 remove_node_diff (message_list, cp, depth + 1);
4503 cp = cn;
4506 /* and the rowid entry - if and only if it is referencing this node */
4507 info = node->data;
4509 /* and only at the toplevel, remove the node (etree should optimise this remove somewhat) */
4510 if (depth == 0)
4511 message_list_tree_model_remove (message_list, node);
4513 g_return_if_fail (info);
4514 ml_uid_nodemap_remove (message_list, info);
4517 /* applies a new tree structure to an existing tree, but only by changing things
4518 * that have changed */
4519 static void
4520 build_subtree_diff (MessageList *message_list,
4521 GNode *parent,
4522 GNode *node,
4523 CamelFolderThreadNode *c,
4524 gint *row)
4526 ETreeModel *tree_model;
4527 GNode *ap, *ai, *at, *tmp;
4528 CamelFolderThreadNode *bp, *bi, *bt;
4529 gint i, j, myrow = 0;
4531 tree_model = E_TREE_MODEL (message_list);
4533 ap = node;
4534 bp = c;
4536 while (ap || bp) {
4537 t (printf ("Processing row: %d (subtree row %d)\n", *row, myrow));
4538 if (ap == NULL) {
4539 t (printf ("out of old nodes\n"));
4540 /* ran out of old nodes - remaining nodes are added */
4541 add_node_diff (
4542 message_list, parent, ap, bp, row, myrow);
4543 myrow++;
4544 bp = bp->next;
4545 } else if (bp == NULL) {
4546 t (printf ("out of new nodes\n"));
4547 /* ran out of new nodes - remaining nodes are removed */
4548 tmp = g_node_next_sibling (ap);
4549 remove_node_diff (message_list, ap, 0);
4550 ap = tmp;
4551 } else if (node_equal (tree_model, ap, bp)) {
4552 *row = (*row)+1;
4553 myrow++;
4554 tmp = g_node_first_child (ap);
4555 /* make child lists match (if either has one) */
4556 if (bp->child || tmp) {
4557 build_subtree_diff (
4558 message_list, ap, tmp, bp->child, row);
4560 ap = g_node_next_sibling (ap);
4561 bp = bp->next;
4562 } else {
4563 t (printf ("searching for matches\n"));
4564 /* we have to scan each side for a match */
4565 bi = bp->next;
4566 ai = g_node_next_sibling (ap);
4567 for (i = 1; bi != NULL; i++,bi = bi->next) {
4568 if (node_equal (tree_model, ap, bi))
4569 break;
4571 for (j = 1; ai != NULL; j++,ai = g_node_next_sibling (ai)) {
4572 if (node_equal (tree_model, ai, bp))
4573 break;
4575 if (i < j) {
4576 /* smaller run of new nodes - must be nodes to add */
4577 if (bi) {
4578 bt = bp;
4579 while (bt != bi) {
4580 t (printf ("adding new node 0\n"));
4581 add_node_diff (
4582 message_list, parent, NULL, bt, row, myrow);
4583 myrow++;
4584 bt = bt->next;
4586 bp = bi;
4587 } else {
4588 t (printf ("adding new node 1\n"));
4589 /* no match in new nodes, add one, try next */
4590 add_node_diff (
4591 message_list, parent, NULL, bp, row, myrow);
4592 myrow++;
4593 bp = bp->next;
4595 } else {
4596 /* bigger run of old nodes - must be nodes to remove */
4597 if (ai) {
4598 at = ap;
4599 while (at != NULL && at != ai) {
4600 t (printf ("removing old node 0\n"));
4601 tmp = g_node_next_sibling (at);
4602 remove_node_diff (message_list, at, 0);
4603 at = tmp;
4605 ap = ai;
4606 } else {
4607 t (printf ("adding new node 2\n"));
4608 /* didn't find match in old nodes, must be new node? */
4609 add_node_diff (
4610 message_list, parent, NULL, bp, row, myrow);
4611 myrow++;
4612 bp = bp->next;
4619 static void
4620 build_flat (MessageList *message_list,
4621 GPtrArray *summary,
4622 gboolean folder_changed)
4624 gchar *saveuid = NULL;
4625 gint i;
4626 GPtrArray *selected;
4627 #ifdef TIMEIT
4628 struct timeval start, end;
4629 gulong diff;
4631 printf ("Building flat\n");
4632 gettimeofday (&start, NULL);
4633 #endif
4635 if (message_list->cursor_uid != NULL)
4636 saveuid = find_next_selectable (message_list);
4638 selected = message_list_get_selected (message_list);
4640 message_list_tree_model_freeze (message_list);
4642 clear_tree (message_list, FALSE);
4644 for (i = 0; i < summary->len; i++) {
4645 CamelMessageInfo *info = summary->pdata[i];
4647 ml_uid_nodemap_insert (message_list, info, NULL, -1);
4650 message_list_tree_model_thaw (message_list);
4652 message_list_set_selected (message_list, selected);
4654 g_ptr_array_unref (selected);
4656 if (saveuid) {
4657 GNode *node;
4659 node = g_hash_table_lookup (
4660 message_list->uid_nodemap, saveuid);
4661 if (node == NULL) {
4662 g_free (message_list->cursor_uid);
4663 message_list->cursor_uid = NULL;
4664 g_signal_emit (
4665 message_list,
4666 signals[MESSAGE_SELECTED], 0, NULL);
4667 } else if (!folder_changed || !e_tree_get_item (E_TREE (message_list))) {
4668 e_tree_set_cursor (E_TREE (message_list), node);
4670 g_free (saveuid);
4673 #ifdef TIMEIT
4674 gettimeofday (&end, NULL);
4675 diff = end.tv_sec * 1000 + end.tv_usec / 1000;
4676 diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
4677 printf ("Building flat took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
4678 #endif
4682 static void
4683 message_list_change_first_visible_parent (MessageList *message_list,
4684 GNode *node)
4686 ETreeModel *tree_model;
4687 ETreeTableAdapter *adapter;
4688 GNode *first_visible = NULL;
4690 tree_model = E_TREE_MODEL (message_list);
4691 adapter = e_tree_get_table_adapter (E_TREE (message_list));
4693 while (node != NULL && (node = node->parent) != NULL) {
4694 if (!e_tree_table_adapter_node_is_expanded (adapter, node))
4695 first_visible = node;
4698 if (first_visible != NULL) {
4699 e_tree_model_pre_change (tree_model);
4700 e_tree_model_node_data_changed (tree_model, first_visible);
4704 static CamelFolderChangeInfo *
4705 mail_folder_hide_by_flag (CamelFolder *folder,
4706 MessageList *message_list,
4707 CamelFolderChangeInfo *changes,
4708 gint flag)
4710 CamelFolderChangeInfo *newchanges;
4711 CamelMessageInfo *info;
4712 gint i;
4714 newchanges = camel_folder_change_info_new ();
4716 for (i = 0; i < changes->uid_changed->len; i++) {
4717 GNode *node;
4718 guint32 flags;
4720 node = g_hash_table_lookup (
4721 message_list->uid_nodemap,
4722 changes->uid_changed->pdata[i]);
4723 info = camel_folder_get_message_info (
4724 folder, changes->uid_changed->pdata[i]);
4725 if (info)
4726 flags = camel_message_info_get_flags (info);
4728 if (node != NULL && info != NULL && (flags & flag) != 0)
4729 camel_folder_change_info_remove_uid (
4730 newchanges, changes->uid_changed->pdata[i]);
4731 else if (node == NULL && info != NULL && (flags & flag) == 0)
4732 camel_folder_change_info_add_uid (
4733 newchanges, changes->uid_changed->pdata[i]);
4734 else
4735 camel_folder_change_info_change_uid (
4736 newchanges, changes->uid_changed->pdata[i]);
4738 g_clear_object (&info);
4741 if (newchanges->uid_added->len > 0 || newchanges->uid_removed->len > 0) {
4742 for (i = 0; i < changes->uid_added->len; i++)
4743 camel_folder_change_info_add_uid (
4744 newchanges, changes->uid_added->pdata[i]);
4745 for (i = 0; i < changes->uid_removed->len; i++)
4746 camel_folder_change_info_remove_uid (
4747 newchanges, changes->uid_removed->pdata[i]);
4748 } else {
4749 camel_folder_change_info_clear (newchanges);
4750 camel_folder_change_info_cat (newchanges, changes);
4753 return newchanges;
4756 static void
4757 message_list_folder_changed (CamelFolder *folder,
4758 CamelFolderChangeInfo *changes,
4759 MessageList *message_list)
4761 CamelFolderChangeInfo *altered_changes = NULL;
4762 ETreeModel *tree_model;
4763 gboolean need_list_regen = TRUE;
4764 gboolean hide_junk;
4765 gboolean hide_deleted;
4766 gint i;
4768 if (message_list->priv->destroyed)
4769 return;
4771 tree_model = E_TREE_MODEL (message_list);
4773 hide_junk = message_list_get_hide_junk (message_list, folder);
4774 hide_deleted = message_list_get_hide_deleted (message_list, folder);
4777 printf ("%s: changes:%p added:%d removed:%d changed:%d recent:%d for '%s'\n", G_STRFUNC, changes,
4778 changes ? changes->uid_added->len : -1,
4779 changes ? changes->uid_removed->len : -1,
4780 changes ? changes->uid_changed->len : -1,
4781 changes ? changes->uid_recent->len : -1,
4782 camel_folder_get_full_name (folder)));
4783 if (changes != NULL) {
4784 for (i = 0; i < changes->uid_removed->len; i++)
4785 g_hash_table_remove (
4786 message_list->normalised_hash,
4787 changes->uid_removed->pdata[i]);
4789 /* Check if the hidden state has changed.
4790 * If so, modify accordingly and regenerate. */
4791 if (hide_junk || hide_deleted)
4792 altered_changes = mail_folder_hide_by_flag (
4793 folder, message_list, changes,
4794 (hide_junk ? CAMEL_MESSAGE_JUNK : 0) |
4795 (hide_deleted ? CAMEL_MESSAGE_DELETED : 0));
4796 else {
4797 altered_changes = camel_folder_change_info_new ();
4798 camel_folder_change_info_cat (altered_changes, changes);
4801 if (altered_changes->uid_added->len == 0 && altered_changes->uid_removed->len == 0 && altered_changes->uid_changed->len < 100) {
4802 for (i = 0; i < altered_changes->uid_changed->len; i++) {
4803 GNode *node;
4805 node = g_hash_table_lookup (
4806 message_list->uid_nodemap,
4807 altered_changes->uid_changed->pdata[i]);
4808 if (node) {
4809 e_tree_model_pre_change (tree_model);
4810 e_tree_model_node_data_changed (tree_model, node);
4812 message_list_change_first_visible_parent (message_list, node);
4816 g_signal_emit (
4817 message_list,
4818 signals[MESSAGE_LIST_BUILT], 0);
4820 need_list_regen = FALSE;
4824 if (need_list_regen) {
4825 /* Use 'folder_changed = TRUE' only if this is not the first change after the folder
4826 had been set. There could happen a race condition on folder enter which prevented
4827 the message list to scroll to the cursor position due to the folder_changed = TRUE,
4828 by cancelling the full rebuild request. */
4829 mail_regen_list (message_list, NULL, !message_list->just_set_folder);
4832 if (altered_changes != NULL)
4833 camel_folder_change_info_free (altered_changes);
4836 CamelFolder *
4837 message_list_ref_folder (MessageList *message_list)
4839 CamelFolder *folder = NULL;
4841 /* XXX Do we need a property lock to guard this? */
4843 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
4845 if (message_list->priv->folder != NULL)
4846 folder = g_object_ref (message_list->priv->folder);
4848 return folder;
4852 * message_list_set_folder:
4853 * @message_list: Message List widget
4854 * @folder: folder backend to be set
4856 * Sets @folder to be the backend folder for @message_list.
4858 void
4859 message_list_set_folder (MessageList *message_list,
4860 CamelFolder *folder)
4862 /* XXX Do we need a property lock to guard this? */
4864 g_return_if_fail (IS_MESSAGE_LIST (message_list));
4866 if (folder == message_list->priv->folder)
4867 return;
4869 if (folder != NULL) {
4870 g_return_if_fail (CAMEL_IS_FOLDER (folder));
4871 g_object_ref (folder);
4874 g_free (message_list->search);
4875 message_list->search = NULL;
4877 g_free (message_list->frozen_search);
4878 message_list->frozen_search = NULL;
4880 if (message_list->seen_id) {
4881 g_source_remove (message_list->seen_id);
4882 message_list->seen_id = 0;
4885 /* reset the normalised sort performance hack */
4886 g_hash_table_remove_all (message_list->normalised_hash);
4888 mail_regen_cancel (message_list);
4890 if (message_list->priv->folder != NULL)
4891 save_tree_state (message_list, message_list->priv->folder);
4893 message_list_tree_model_freeze (message_list);
4894 clear_tree (message_list, TRUE);
4895 message_list_tree_model_thaw (message_list);
4897 /* remove the cursor activate idle handler */
4898 if (message_list->idle_id != 0) {
4899 g_source_remove (message_list->idle_id);
4900 message_list->idle_id = 0;
4903 if (message_list->priv->folder != NULL) {
4904 g_signal_handler_disconnect (
4905 message_list->priv->folder,
4906 message_list->priv->folder_changed_handler_id);
4907 message_list->priv->folder_changed_handler_id = 0;
4909 if (message_list->uid_nodemap != NULL)
4910 g_hash_table_foreach (
4911 message_list->uid_nodemap,
4912 (GHFunc) clear_info, message_list);
4914 g_clear_object (&message_list->priv->folder);
4917 /* Invalidate the thread tree. */
4918 message_list_set_thread_tree (message_list, NULL);
4920 g_free (message_list->cursor_uid);
4921 message_list->cursor_uid = NULL;
4923 /* Always emit message-selected, event when an account node
4924 * (folder == NULL) is selected, so that views know what happened and
4925 * can stop all running operations etc. */
4926 g_signal_emit (message_list, signals[MESSAGE_SELECTED], 0, NULL);
4928 if (folder != NULL) {
4929 gboolean non_trash_folder;
4930 gboolean non_junk_folder;
4931 gint strikeout_col, strikeout_color_col;
4932 ECell *cell;
4933 gulong handler_id;
4935 message_list->priv->folder = folder;
4936 message_list->just_set_folder = TRUE;
4938 non_trash_folder = !(camel_folder_get_flags (folder) & CAMEL_FOLDER_IS_TRASH);
4939 non_junk_folder = !(camel_folder_get_flags (folder) & CAMEL_FOLDER_IS_JUNK);
4941 strikeout_col = -1;
4942 strikeout_color_col = -1;
4944 /* Setup the strikeout effect for non-trash or non-junk folders */
4945 if (non_trash_folder && non_junk_folder) {
4946 strikeout_col = COL_DELETED_OR_JUNK;
4947 strikeout_color_col = COL_JUNK_STRIKEOUT_COLOR;
4948 } else if (non_trash_folder) {
4949 strikeout_col = COL_DELETED;
4950 } else if (non_junk_folder) {
4951 strikeout_col = COL_JUNK;
4952 strikeout_color_col = COL_JUNK_STRIKEOUT_COLOR;
4955 cell = e_table_extras_get_cell (message_list->extras, "render_date");
4956 g_object_set (cell, "strikeout-column", strikeout_col, "strikeout-color-column", strikeout_color_col, NULL);
4958 cell = e_table_extras_get_cell (message_list->extras, "render_text");
4959 g_object_set (cell, "strikeout-column", strikeout_col, "strikeout-color-column", strikeout_color_col, NULL);
4961 cell = e_table_extras_get_cell (message_list->extras, "render_size");
4962 g_object_set (cell, "strikeout-column", strikeout_col, "strikeout-color-column", strikeout_color_col, NULL);
4964 cell = e_table_extras_get_cell (message_list->extras, "render_composite_from");
4965 composite_cell_set_strike_col (cell, strikeout_col, strikeout_color_col);
4967 cell = e_table_extras_get_cell (message_list->extras, "render_composite_to");
4968 composite_cell_set_strike_col (cell, strikeout_col, strikeout_color_col);
4970 /* Build the etree suitable for this folder */
4971 message_list_setup_etree (message_list);
4973 handler_id = g_signal_connect (
4974 folder, "changed",
4975 G_CALLBACK (message_list_folder_changed),
4976 message_list);
4977 message_list->priv->folder_changed_handler_id = handler_id;
4979 if (message_list->frozen == 0)
4980 mail_regen_list (message_list, NULL, FALSE);
4981 else
4982 message_list->priv->thaw_needs_regen = TRUE;
4986 GtkTargetList *
4987 message_list_get_copy_target_list (MessageList *message_list)
4989 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
4991 return message_list->priv->copy_target_list;
4994 GtkTargetList *
4995 message_list_get_paste_target_list (MessageList *message_list)
4997 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
4999 return message_list->priv->paste_target_list;
5002 void
5003 message_list_set_expanded_default (MessageList *message_list,
5004 gboolean expanded_default)
5006 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5008 message_list->priv->expanded_default = expanded_default;
5011 gboolean
5012 message_list_get_group_by_threads (MessageList *message_list)
5014 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
5016 return message_list->priv->group_by_threads;
5019 void
5020 message_list_set_group_by_threads (MessageList *message_list,
5021 gboolean group_by_threads)
5023 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5025 if (group_by_threads == message_list->priv->group_by_threads)
5026 return;
5028 message_list->priv->group_by_threads = group_by_threads;
5029 e_tree_set_grouped_view (E_TREE (message_list), group_by_threads);
5031 g_object_notify (G_OBJECT (message_list), "group-by-threads");
5033 /* Changing this property triggers a message list regen. */
5034 if (message_list->frozen == 0)
5035 mail_regen_list (message_list, NULL, FALSE);
5036 else
5037 message_list->priv->thaw_needs_regen = TRUE;
5040 gboolean
5041 message_list_get_show_deleted (MessageList *message_list)
5043 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
5045 return message_list->priv->show_deleted;
5048 void
5049 message_list_set_show_deleted (MessageList *message_list,
5050 gboolean show_deleted)
5052 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5054 if (show_deleted == message_list->priv->show_deleted)
5055 return;
5057 message_list->priv->show_deleted = show_deleted;
5059 g_object_notify (G_OBJECT (message_list), "show-deleted");
5061 /* Invalidate the thread tree. */
5062 message_list_set_thread_tree (message_list, NULL);
5064 /* Changing this property triggers a message list regen. */
5065 if (message_list->frozen == 0)
5066 mail_regen_list (message_list, NULL, FALSE);
5067 else
5068 message_list->priv->thaw_needs_regen = TRUE;
5071 gboolean
5072 message_list_get_show_junk (MessageList *message_list)
5074 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
5076 return message_list->priv->show_junk;
5079 void
5080 message_list_set_show_junk (MessageList *message_list,
5081 gboolean show_junk)
5083 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5085 if (show_junk == message_list->priv->show_junk)
5086 return;
5088 message_list->priv->show_junk = show_junk;
5090 g_object_notify (G_OBJECT (message_list), "show-junk");
5092 /* Invalidate the thread tree. */
5093 message_list_set_thread_tree (message_list, NULL);
5095 /* Changing this property triggers a message list regen. */
5096 if (message_list->frozen == 0)
5097 mail_regen_list (message_list, NULL, FALSE);
5098 else
5099 message_list->priv->thaw_needs_regen = TRUE;
5102 gboolean
5103 message_list_get_show_subject_above_sender (MessageList *message_list)
5105 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
5107 return message_list->priv->show_subject_above_sender;
5110 void
5111 message_list_set_show_subject_above_sender (MessageList *message_list,
5112 gboolean show_subject_above_sender)
5114 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5116 if (show_subject_above_sender == message_list->priv->show_subject_above_sender)
5117 return;
5119 message_list->priv->show_subject_above_sender = show_subject_above_sender;
5121 if (message_list->extras) {
5122 ECell *cell;
5124 cell = e_table_extras_get_cell (message_list->extras, "render_composite_from");
5125 if (cell)
5126 composite_cell_set_show_subject_above_sender (cell, show_subject_above_sender);
5128 cell = e_table_extras_get_cell (message_list->extras, "render_composite_to");
5129 if (cell)
5130 composite_cell_set_show_subject_above_sender (cell, show_subject_above_sender);
5132 if (message_list->priv->folder &&
5133 gtk_widget_get_realized (GTK_WIDGET (message_list)) &&
5134 gtk_widget_get_visible (GTK_WIDGET (message_list)))
5135 mail_regen_list (message_list, NULL, FALSE);
5138 g_object_notify (G_OBJECT (message_list), "show-subject-above-sender");
5141 gboolean
5142 message_list_get_thread_latest (MessageList *message_list)
5144 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
5146 return message_list->priv->thread_latest;
5149 void
5150 message_list_set_thread_latest (MessageList *message_list,
5151 gboolean thread_latest)
5153 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5155 if (thread_latest == message_list->priv->thread_latest)
5156 return;
5158 message_list->priv->thread_latest = thread_latest;
5160 g_object_notify (G_OBJECT (message_list), "thread-latest");
5163 gboolean
5164 message_list_get_thread_subject (MessageList *message_list)
5166 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
5168 return message_list->priv->thread_subject;
5171 void
5172 message_list_set_thread_subject (MessageList *message_list,
5173 gboolean thread_subject)
5175 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5177 if (thread_subject == message_list->priv->thread_subject)
5178 return;
5180 message_list->priv->thread_subject = thread_subject;
5182 g_object_notify (G_OBJECT (message_list), "thread-subject");
5185 static gboolean
5186 on_cursor_activated_idle (gpointer data)
5188 MessageList *message_list = data;
5189 ESelectionModel *esm;
5190 gint selected;
5192 esm = e_tree_get_selection_model (E_TREE (message_list));
5193 selected = e_selection_model_selected_count (esm);
5195 if (selected == 1 && message_list->cursor_uid) {
5196 d (printf ("emitting cursor changed signal, for uid %s\n", message_list->cursor_uid));
5197 g_signal_emit (
5198 message_list,
5199 signals[MESSAGE_SELECTED], 0,
5200 message_list->cursor_uid);
5201 } else {
5202 g_signal_emit (
5203 message_list,
5204 signals[MESSAGE_SELECTED], 0,
5205 NULL);
5208 message_list->idle_id = 0;
5209 return FALSE;
5212 static void
5213 on_cursor_activated_cmd (ETree *tree,
5214 gint row,
5215 GNode *node,
5216 gpointer user_data)
5218 MessageList *message_list = MESSAGE_LIST (user_data);
5219 const gchar *new_uid;
5221 if (node == NULL || G_NODE_IS_ROOT (node))
5222 new_uid = NULL;
5223 else
5224 new_uid = get_message_uid (message_list, node);
5226 /* Do not check the cursor_uid and the new_uid values, because the
5227 * selected item (set in on_selection_changed_cmd) can be different
5228 * from the one with a cursor (when selecting with Ctrl, for example).
5229 * This has a little side-effect, when keeping list it that state,
5230 * then changing folders forth and back will select and move cursor
5231 * to that selected item. Does anybody consider it as a bug? */
5232 if ((message_list->cursor_uid == NULL && new_uid == NULL)
5233 || (message_list->last_sel_single && message_list->cursor_uid != NULL && new_uid != NULL))
5234 return;
5236 g_free (message_list->cursor_uid);
5237 message_list->cursor_uid = g_strdup (new_uid);
5239 if (!message_list->idle_id) {
5240 message_list->idle_id =
5241 g_idle_add_full (
5242 G_PRIORITY_LOW, on_cursor_activated_idle,
5243 message_list, NULL);
5247 static void
5248 on_selection_changed_cmd (ETree *tree,
5249 MessageList *message_list)
5251 GPtrArray *uids = NULL;
5252 const gchar *newuid;
5253 guint selected_count;
5254 GNode *cursor;
5256 selected_count = message_list_selected_count (message_list);
5257 if (selected_count == 1) {
5258 uids = message_list_get_selected (message_list);
5260 if (uids->len == 1)
5261 newuid = g_ptr_array_index (uids, 0);
5262 else
5263 newuid = NULL;
5264 } else if ((cursor = e_tree_get_cursor (tree)))
5265 newuid = (gchar *) camel_message_info_get_uid (cursor->data);
5266 else
5267 newuid = NULL;
5269 /* If the selection isn't empty, then we ignore the no-uid check, since this event
5270 * is also used for other updating. If it is empty, it might just be a setup event
5271 * from etree which we do need to ignore */
5272 if ((newuid == NULL && message_list->cursor_uid == NULL && selected_count == 0) ||
5273 (message_list->last_sel_single && selected_count == 1 && message_list->cursor_uid != NULL && (newuid == NULL || !strcmp (message_list->cursor_uid, newuid)))) {
5274 /* noop */
5275 } else {
5276 g_free (message_list->cursor_uid);
5277 message_list->cursor_uid = g_strdup (newuid);
5278 if (message_list->idle_id == 0)
5279 message_list->idle_id = g_idle_add_full (
5280 G_PRIORITY_LOW,
5281 on_cursor_activated_idle,
5282 message_list, NULL);
5285 message_list->last_sel_single = selected_count == 1;
5287 if (uids)
5288 g_ptr_array_unref (uids);
5291 static gint
5292 on_click (ETree *tree,
5293 gint row,
5294 GNode *node,
5295 gint col,
5296 GdkEvent *event,
5297 MessageList *list)
5299 CamelFolder *folder;
5300 CamelMessageInfo *info;
5301 gboolean folder_is_trash;
5302 gint flag = 0;
5303 guint32 flags;
5305 if (col == COL_MESSAGE_STATUS)
5306 flag = CAMEL_MESSAGE_SEEN;
5307 else if (col == COL_FLAGGED)
5308 flag = CAMEL_MESSAGE_FLAGGED;
5309 else if (col != COL_FOLLOWUP_FLAG_STATUS)
5310 return FALSE;
5312 if (!(info = get_message_info (list, node)))
5313 return FALSE;
5315 folder = message_list_ref_folder (list);
5316 g_return_val_if_fail (folder != NULL, FALSE);
5318 if (col == COL_FOLLOWUP_FLAG_STATUS) {
5319 const gchar *tag, *cmp;
5321 tag = camel_message_info_get_user_tag (info, "follow-up");
5322 cmp = camel_message_info_get_user_tag (info, "completed-on");
5323 if (tag && tag[0]) {
5324 if (cmp && cmp[0]) {
5325 camel_message_info_set_user_tag (info, "follow-up", NULL);
5326 camel_message_info_set_user_tag (info, "due-by", NULL);
5327 camel_message_info_set_user_tag (info, "completed-on", NULL);
5328 } else {
5329 gchar *text;
5331 text = camel_header_format_date (time (NULL), 0);
5332 camel_message_info_set_user_tag (info, "completed-on", text);
5333 g_free (text);
5335 } else {
5336 /* default follow-up flag name to use when clicked in the message list column */
5337 camel_message_info_set_user_tag (info, "follow-up", _("Follow-up"));
5338 camel_message_info_set_user_tag (info, "completed-on", NULL);
5341 g_object_unref (folder);
5343 return TRUE;
5346 flags = camel_message_info_get_flags (info);
5348 folder_is_trash =
5349 ((camel_folder_get_flags (folder) & CAMEL_FOLDER_IS_TRASH) != 0);
5351 /* If a message was marked as deleted and the user flags it as
5352 * important or unread in a non-Trash folder, then undelete the
5353 * message. We avoid automatically undeleting messages while
5354 * viewing a Trash folder because it would cause the message to
5355 * suddenly disappear from the message list, which is confusing
5356 * and alarming to the user. */
5357 if (!folder_is_trash && flags & CAMEL_MESSAGE_DELETED) {
5358 if (col == COL_FLAGGED && !(flags & CAMEL_MESSAGE_FLAGGED))
5359 flag |= CAMEL_MESSAGE_DELETED;
5361 if (col == COL_MESSAGE_STATUS && (flags & CAMEL_MESSAGE_SEEN))
5362 flag |= CAMEL_MESSAGE_DELETED;
5365 camel_message_info_set_flags (info, flag, ~flags);
5367 /* Notify the folder tree model that the user has marked a message
5368 * as unread so it doesn't mistake the event as new mail arriving. */
5369 if (col == COL_MESSAGE_STATUS && (flags & CAMEL_MESSAGE_SEEN)) {
5370 EMFolderTreeModel *model;
5372 model = em_folder_tree_model_get_default ();
5373 em_folder_tree_model_user_marked_unread (model, folder, 1);
5376 if (flag == CAMEL_MESSAGE_SEEN && list->seen_id &&
5377 g_strcmp0 (list->cursor_uid, camel_message_info_get_uid (info)) == 0) {
5378 g_source_remove (list->seen_id);
5379 list->seen_id = 0;
5382 g_object_unref (folder);
5384 return TRUE;
5387 struct _ml_selected_data {
5388 MessageList *message_list;
5389 GPtrArray *uids;
5392 static void
5393 ml_getselected_cb (GNode *node,
5394 gpointer user_data)
5396 struct _ml_selected_data *data = user_data;
5397 const gchar *uid;
5399 if (G_NODE_IS_ROOT (node))
5400 return;
5402 uid = get_message_uid (data->message_list, node);
5403 g_return_if_fail (uid != NULL);
5404 g_ptr_array_add (data->uids, g_strdup (uid));
5407 GPtrArray *
5408 message_list_get_selected (MessageList *message_list)
5410 CamelFolder *folder;
5411 ESelectionModel *selection;
5413 struct _ml_selected_data data = {
5414 message_list,
5415 NULL
5418 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
5420 data.uids = g_ptr_array_new ();
5421 g_ptr_array_set_free_func (data.uids, (GDestroyNotify) g_free);
5423 selection = e_tree_get_selection_model (E_TREE (message_list));
5425 e_tree_selection_model_foreach (
5426 E_TREE_SELECTION_MODEL (selection),
5427 (ETreeForeachFunc) ml_getselected_cb, &data);
5429 folder = message_list_ref_folder (message_list);
5431 if (folder != NULL && data.uids->len > 0)
5432 camel_folder_sort_uids (folder, data.uids);
5434 g_clear_object (&folder);
5436 return data.uids;
5439 void
5440 message_list_set_selected (MessageList *message_list,
5441 GPtrArray *uids)
5443 gint i;
5444 ETreeSelectionModel *etsm;
5445 GNode *node;
5446 GPtrArray *paths;
5448 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5450 paths = g_ptr_array_new ();
5451 etsm = (ETreeSelectionModel *)
5452 e_tree_get_selection_model (E_TREE (message_list));
5453 for (i = 0; i < uids->len; i++) {
5454 node = g_hash_table_lookup (
5455 message_list->uid_nodemap, uids->pdata[i]);
5456 if (node != NULL)
5457 g_ptr_array_add (paths, node);
5460 e_tree_selection_model_select_paths (etsm, paths);
5461 g_ptr_array_free (paths, TRUE);
5464 struct ml_sort_uids_data {
5465 gchar *uid;
5466 gint row;
5469 static gint
5470 ml_sort_uids_cb (gconstpointer a,
5471 gconstpointer b)
5473 struct ml_sort_uids_data * const *pdataA = a;
5474 struct ml_sort_uids_data * const *pdataB = b;
5476 return (* pdataA)->row - (* pdataB)->row;
5479 void
5480 message_list_sort_uids (MessageList *message_list,
5481 GPtrArray *uids)
5483 struct ml_sort_uids_data *data;
5484 GPtrArray *array;
5485 GNode *node;
5486 ETreeTableAdapter *adapter;
5487 gint ii;
5489 g_return_if_fail (message_list != NULL);
5490 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5491 g_return_if_fail (uids != NULL);
5493 if (uids->len <= 1)
5494 return;
5496 adapter = e_tree_get_table_adapter (E_TREE (message_list));
5498 array = g_ptr_array_new_full (uids->len, g_free);
5500 for (ii = 0; ii < uids->len; ii++) {
5501 data = g_new0 (struct ml_sort_uids_data, 1);
5502 data->uid = g_ptr_array_index (uids, ii);
5504 node = g_hash_table_lookup (message_list->uid_nodemap, data->uid);
5505 if (node != NULL)
5506 data->row = e_tree_table_adapter_row_of_node (adapter, node);
5507 else
5508 data->row = ii;
5510 g_ptr_array_add (array, data);
5513 g_ptr_array_sort (array, ml_sort_uids_cb);
5515 for (ii = 0; ii < uids->len; ii++) {
5516 data = g_ptr_array_index (array, ii);
5518 uids->pdata[ii] = data->uid;
5521 g_ptr_array_free (array, TRUE);
5524 struct ml_count_data {
5525 MessageList *message_list;
5526 guint count;
5529 static void
5530 ml_getcount_cb (GNode *node,
5531 gpointer user_data)
5533 struct ml_count_data *data = user_data;
5535 if (!G_NODE_IS_ROOT (node))
5536 data->count++;
5539 guint
5540 message_list_count (MessageList *message_list)
5542 struct ml_count_data data = { message_list, 0 };
5544 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), 0);
5546 e_tree_path_foreach (
5547 E_TREE (message_list),
5548 (ETreeForeachFunc) ml_getcount_cb, &data);
5550 return data.count;
5553 guint
5554 message_list_selected_count (MessageList *message_list)
5556 ESelectionModel *selection;
5558 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), 0);
5560 selection = e_tree_get_selection_model (E_TREE (message_list));
5561 return e_selection_model_selected_count (selection);
5564 void
5565 message_list_freeze (MessageList *message_list)
5567 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5568 message_list->frozen++;
5571 void
5572 message_list_thaw (MessageList *message_list)
5574 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5575 g_return_if_fail (message_list->frozen != 0);
5577 message_list->frozen--;
5578 if (message_list->frozen == 0 && message_list->priv->thaw_needs_regen) {
5579 const gchar *search;
5581 if (message_list->frozen_search != NULL)
5582 search = message_list->frozen_search;
5583 else
5584 search = NULL;
5586 mail_regen_list (message_list, search, FALSE);
5588 g_free (message_list->frozen_search);
5589 message_list->frozen_search = NULL;
5590 message_list->priv->thaw_needs_regen = FALSE;
5594 /* set whether we are in threaded view or flat view */
5595 void
5596 message_list_set_threaded_expand_all (MessageList *message_list)
5598 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5600 if (message_list_get_group_by_threads (message_list)) {
5601 message_list->expand_all = 1;
5603 if (message_list->frozen == 0)
5604 mail_regen_list (message_list, NULL, FALSE);
5605 else
5606 message_list->priv->thaw_needs_regen = TRUE;
5610 void
5611 message_list_set_threaded_collapse_all (MessageList *message_list)
5613 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5615 if (message_list_get_group_by_threads (message_list)) {
5616 message_list->collapse_all = 1;
5618 if (message_list->frozen == 0)
5619 mail_regen_list (message_list, NULL, FALSE);
5620 else
5621 message_list->priv->thaw_needs_regen = TRUE;
5625 void
5626 message_list_set_search (MessageList *message_list,
5627 const gchar *search)
5629 RegenData *current_regen_data;
5631 g_return_if_fail (IS_MESSAGE_LIST (message_list));
5633 current_regen_data = message_list_ref_regen_data (message_list);
5635 if (!current_regen_data && (search == NULL || search[0] == '\0'))
5636 if (message_list->search == NULL || message_list->search[0] == '\0')
5637 return;
5639 if (!current_regen_data && search != NULL && message_list->search != NULL &&
5640 strcmp (search, message_list->search) == 0) {
5641 return;
5644 if (current_regen_data)
5645 regen_data_unref (current_regen_data);
5647 /* Invalidate the thread tree. */
5648 message_list_set_thread_tree (message_list, NULL);
5650 if (message_list->frozen == 0)
5651 mail_regen_list (message_list, search ? search : "", FALSE);
5652 else {
5653 g_free (message_list->frozen_search);
5654 message_list->frozen_search = g_strdup (search);
5655 message_list->priv->thaw_needs_regen = TRUE;
5659 struct sort_column_data {
5660 ETableCol *col;
5661 GtkSortType sort_type;
5664 struct sort_message_info_data {
5665 CamelMessageInfo *mi;
5666 GPtrArray *values; /* read values so far, in order of sort_array_data::sort_columns */
5669 struct sort_array_data {
5670 MessageList *message_list;
5671 CamelFolder *folder;
5672 GPtrArray *sort_columns; /* struct sort_column_data in order of sorting */
5673 GHashTable *message_infos; /* uid -> struct sort_message_info_data */
5674 gpointer cmp_cache;
5675 GCancellable *cancellable;
5678 static gint
5679 cmp_array_uids (gconstpointer a,
5680 gconstpointer b,
5681 gpointer user_data)
5683 const gchar *uid1 = *(const gchar **) a;
5684 const gchar *uid2 = *(const gchar **) b;
5685 struct sort_array_data *sort_data = user_data;
5686 gint i, res = 0;
5687 struct sort_message_info_data *md1, *md2;
5689 g_return_val_if_fail (sort_data != NULL, 0);
5691 md1 = g_hash_table_lookup (sort_data->message_infos, uid1);
5692 md2 = g_hash_table_lookup (sort_data->message_infos, uid2);
5694 g_return_val_if_fail (md1 != NULL, 0);
5695 g_return_val_if_fail (md1->mi != NULL, 0);
5696 g_return_val_if_fail (md2 != NULL, 0);
5697 g_return_val_if_fail (md2->mi != NULL, 0);
5699 if (g_cancellable_is_cancelled (sort_data->cancellable))
5700 return 0;
5702 for (i = 0;
5703 res == 0
5704 && i < sort_data->sort_columns->len
5705 && !g_cancellable_is_cancelled (sort_data->cancellable);
5706 i++) {
5707 gpointer v1, v2;
5708 struct sort_column_data *scol = g_ptr_array_index (sort_data->sort_columns, i);
5710 if (md1->values->len <= i) {
5711 camel_message_info_property_lock (md1->mi);
5712 v1 = ml_tree_value_at_ex (
5713 NULL, NULL,
5714 scol->col->spec->compare_col,
5715 md1->mi, sort_data->message_list);
5716 camel_message_info_property_unlock (md1->mi);
5717 g_ptr_array_add (md1->values, v1);
5718 } else {
5719 v1 = g_ptr_array_index (md1->values, i);
5722 if (md2->values->len <= i) {
5723 camel_message_info_property_lock (md2->mi);
5724 v2 = ml_tree_value_at_ex (
5725 NULL, NULL,
5726 scol->col->spec->compare_col,
5727 md2->mi, sort_data->message_list);
5728 camel_message_info_property_unlock (md2->mi);
5730 g_ptr_array_add (md2->values, v2);
5731 } else {
5732 v2 = g_ptr_array_index (md2->values, i);
5735 if (v1 != NULL && v2 != NULL) {
5736 res = (*scol->col->compare) (v1, v2, sort_data->cmp_cache);
5737 } else if (v1 != NULL || v2 != NULL) {
5738 res = v1 == NULL ? -1 : 1;
5741 if (scol->sort_type == GTK_SORT_DESCENDING)
5742 res = res * (-1);
5745 if (res == 0)
5746 res = camel_folder_cmp_uids (sort_data->folder, uid1, uid2);
5748 return res;
5751 static void
5752 free_message_info_data (gpointer uid,
5753 struct sort_message_info_data *data,
5754 struct sort_array_data *sort_data)
5756 if (data->values) {
5757 gint ii;
5759 for (ii = 0; ii < sort_data->sort_columns->len && ii < data->values->len; ii++) {
5760 struct sort_column_data *scol = g_ptr_array_index (sort_data->sort_columns, ii);
5762 message_list_free_value ((ETreeModel *) sort_data->message_list,
5763 scol->col->spec->compare_col,
5764 g_ptr_array_index (data->values, ii));
5767 g_ptr_array_free (data->values, TRUE);
5770 g_clear_object (&data->mi);
5771 g_free (data);
5774 static void
5775 ml_sort_uids_by_tree (MessageList *message_list,
5776 GPtrArray *uids,
5777 GCancellable *cancellable)
5779 ETreeTableAdapter *adapter;
5780 ETableSortInfo *sort_info;
5781 ETableHeader *full_header;
5782 CamelFolder *folder;
5783 struct sort_array_data sort_data;
5784 guint i, len;
5786 if (g_cancellable_is_cancelled (cancellable))
5787 return;
5789 g_return_if_fail (uids != NULL);
5791 folder = message_list_ref_folder (message_list);
5792 g_return_if_fail (folder != NULL);
5794 adapter = e_tree_get_table_adapter (E_TREE (message_list));
5795 g_return_if_fail (adapter != NULL);
5797 sort_info = e_tree_table_adapter_get_sort_info (adapter);
5798 full_header = e_tree_table_adapter_get_header (adapter);
5800 if (!sort_info || uids->len == 0 || !full_header || e_table_sort_info_sorting_get_count (sort_info) == 0) {
5801 camel_folder_sort_uids (folder, uids);
5802 g_object_unref (folder);
5803 return;
5806 len = e_table_sort_info_sorting_get_count (sort_info);
5808 sort_data.message_list = message_list;
5809 sort_data.folder = folder;
5810 sort_data.sort_columns = g_ptr_array_sized_new (len);
5811 sort_data.message_infos = g_hash_table_new (g_str_hash, g_str_equal);
5812 sort_data.cmp_cache = e_table_sorting_utils_create_cmp_cache ();
5813 sort_data.cancellable = cancellable;
5815 for (i = 0;
5816 i < len
5817 && !g_cancellable_is_cancelled (cancellable);
5818 i++) {
5819 ETableColumnSpecification *spec;
5820 struct sort_column_data *data;
5822 data = g_new0 (struct sort_column_data, 1);
5824 spec = e_table_sort_info_sorting_get_nth (
5825 sort_info, i, &data->sort_type);
5827 data->col = e_table_header_get_column_by_spec (full_header, spec);
5828 if (data->col == NULL) {
5829 gint last = e_table_header_count (full_header) - 1;
5830 data->col = e_table_header_get_column (full_header, last);
5833 g_ptr_array_add (sort_data.sort_columns, data);
5836 camel_folder_summary_prepare_fetch_all (camel_folder_get_folder_summary (folder), NULL);
5838 for (i = 0;
5839 i < uids->len
5840 && !g_cancellable_is_cancelled (cancellable);
5841 i++) {
5842 gchar *uid;
5843 CamelMessageInfo *mi;
5844 struct sort_message_info_data *md;
5846 uid = g_ptr_array_index (uids, i);
5847 mi = camel_folder_get_message_info (folder, uid);
5848 if (mi == NULL) {
5849 g_warning (
5850 "%s: Cannot find uid '%s' in folder '%s'",
5851 G_STRFUNC, uid,
5852 camel_folder_get_full_name (folder));
5853 continue;
5856 md = g_new0 (struct sort_message_info_data, 1);
5857 md->mi = mi;
5858 md->values = g_ptr_array_sized_new (len);
5860 g_hash_table_insert (sort_data.message_infos, uid, md);
5863 if (!g_cancellable_is_cancelled (cancellable))
5864 g_qsort_with_data (
5865 uids->pdata,
5866 uids->len,
5867 sizeof (gpointer),
5868 cmp_array_uids,
5869 &sort_data);
5871 camel_folder_summary_unlock (camel_folder_get_folder_summary (folder));
5873 /* FIXME Teach the hash table to destroy its own data. */
5874 g_hash_table_foreach (
5875 sort_data.message_infos,
5876 (GHFunc) free_message_info_data,
5877 &sort_data);
5878 g_hash_table_destroy (sort_data.message_infos);
5880 g_ptr_array_foreach (sort_data.sort_columns, (GFunc) g_free, NULL);
5881 g_ptr_array_free (sort_data.sort_columns, TRUE);
5883 e_table_sorting_utils_free_cmp_cache (sort_data.cmp_cache);
5885 g_object_unref (folder);
5888 static void
5889 message_list_regen_tweak_search_results (MessageList *message_list,
5890 GPtrArray *search_results,
5891 CamelFolder *folder,
5892 gboolean folder_changed,
5893 gboolean show_deleted,
5894 gboolean show_junk)
5896 CamelMessageInfo *info;
5897 CamelMessageFlags flags;
5898 const gchar *uid;
5899 gboolean needs_tweaking;
5900 gboolean uid_is_deleted;
5901 gboolean uid_is_junk;
5902 gboolean add_uid;
5903 guint ii;
5905 /* If we're responding to a "folder-changed" signal, then the
5906 * displayed message may not be included in the search results.
5907 * Include the displayed message anyway so it doesn't suddenly
5908 * disappear while the user is reading it. */
5909 needs_tweaking =
5910 ((folder_changed || message_list->just_set_folder) && message_list->cursor_uid != NULL);
5912 if (!needs_tweaking)
5913 return;
5915 uid = message_list->cursor_uid;
5917 /* Scan the search results for a particular UID.
5918 * If found then the results don't need tweaked. */
5919 for (ii = 0; ii < search_results->len; ii++) {
5920 if (g_str_equal (uid, search_results->pdata[ii]))
5921 return;
5924 info = camel_folder_get_message_info (folder, uid);
5926 /* XXX Should we emit a runtime warning here? */
5927 if (info == NULL)
5928 return;
5930 flags = camel_message_info_get_flags (info);
5931 uid_is_deleted = ((flags & CAMEL_MESSAGE_DELETED) != 0);
5932 uid_is_junk = ((flags & CAMEL_MESSAGE_JUNK) != 0);
5934 if (!folder_store_supports_vjunk_folder (folder))
5935 uid_is_junk = FALSE;
5937 add_uid =
5938 (!uid_is_junk || show_junk) &&
5939 (!uid_is_deleted || show_deleted);
5941 if (add_uid)
5942 g_ptr_array_add (
5943 search_results,
5944 (gpointer) camel_pstring_strdup (uid));
5946 g_clear_object (&info);
5949 static void
5950 message_list_regen_thread (GSimpleAsyncResult *simple,
5951 GObject *source_object,
5952 GCancellable *cancellable)
5954 MessageList *message_list;
5955 RegenData *regen_data;
5956 GPtrArray *uids, *searchuids = NULL;
5957 CamelMessageInfo *info;
5958 CamelFolder *folder;
5959 GNode *cursor;
5960 ETree *tree;
5961 GString *expr;
5962 gboolean hide_deleted;
5963 gboolean hide_junk;
5964 GError *local_error = NULL;
5966 message_list = MESSAGE_LIST (source_object);
5967 regen_data = g_simple_async_result_get_op_res_gpointer (simple);
5969 if (g_cancellable_is_cancelled (cancellable))
5970 return;
5972 /* Just for convenience. */
5973 folder = g_object_ref (regen_data->folder);
5975 hide_junk = message_list_get_hide_junk (message_list, folder);
5976 hide_deleted = message_list_get_hide_deleted (message_list, folder);
5978 tree = E_TREE (message_list);
5979 cursor = e_tree_get_cursor (tree);
5980 if (cursor != NULL)
5981 regen_data->last_row =
5982 e_tree_table_adapter_row_of_node (
5983 e_tree_get_table_adapter (tree), cursor);
5985 /* Construct the search expression. */
5987 expr = g_string_new ("");
5989 if (hide_deleted && hide_junk) {
5990 g_string_append_printf (
5991 expr, "(match-all (and %s %s))",
5992 EXCLUDE_DELETED_MESSAGES_EXPR,
5993 EXCLUDE_JUNK_MESSAGES_EXPR);
5994 } else if (hide_deleted) {
5995 g_string_append_printf (
5996 expr, "(match-all %s)",
5997 EXCLUDE_DELETED_MESSAGES_EXPR);
5998 } else if (hide_junk) {
5999 g_string_append_printf (
6000 expr, "(match-all %s)",
6001 EXCLUDE_JUNK_MESSAGES_EXPR);
6004 if (regen_data->search != NULL) {
6005 if (expr->len == 0) {
6006 g_string_assign (expr, regen_data->search);
6007 } else {
6008 g_string_prepend (expr, "(and ");
6009 g_string_append_c (expr, ' ');
6010 g_string_append (expr, regen_data->search);
6011 g_string_append_c (expr, ')');
6015 /* Execute the search. */
6017 if (expr->len == 0) {
6018 uids = camel_folder_get_uids (folder);
6019 dd (g_print ("%s: got %d uids in folder %p (%s : %s)\n", G_STRFUNC, uids ? uids->len : -1, folder,
6020 camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder))),
6021 camel_folder_get_full_name (folder)));
6022 } else {
6023 uids = camel_folder_search_by_expression (
6024 folder, expr->str, cancellable, &local_error);
6026 dd (g_print ("%s: got %d uids in folder %p (%s : %s) for expression:---%s---\n", G_STRFUNC,
6027 uids ? uids->len : -1, folder,
6028 camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder))),
6029 camel_folder_get_full_name (folder), expr->str));
6031 /* XXX This indicates we need to use a different
6032 * "free UID" function for some dumb reason. */
6033 searchuids = uids;
6035 if (uids != NULL) {
6036 message_list_regen_tweak_search_results (
6037 message_list,
6038 uids, folder,
6039 regen_data->folder_changed,
6040 !hide_deleted,
6041 !hide_junk);
6043 dd (g_print (" %s: got %d uids in folder %p (%s : %s) after tweak, hide_deleted:%d, hide_junk:%d\n", G_STRFUNC,
6044 uids ? uids->len : -1, folder,
6045 camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder))),
6046 camel_folder_get_full_name (folder), hide_deleted, hide_junk));
6050 g_string_free (expr, TRUE);
6052 /* Handle search error or cancellation. */
6054 if (local_error == NULL) {
6055 /* coverity[unchecked_value] */
6056 g_cancellable_set_error_if_cancelled (
6057 cancellable, &local_error);
6060 if (local_error != NULL) {
6061 g_simple_async_result_take_error (simple, local_error);
6062 goto exit;
6065 /* XXX This check might not be necessary. A successfully completed
6066 * search with no results should return an empty UID array, but
6067 * still need to verify that. */
6068 if (uids == NULL)
6069 goto exit;
6071 /* update/build a new tree */
6072 if (regen_data->group_by_threads) {
6073 CamelFolderThread *thread_tree;
6075 ml_sort_uids_by_tree (message_list, uids, cancellable);
6077 thread_tree = message_list_ref_thread_tree (message_list);
6079 if (thread_tree != NULL) {
6080 /* Make sure multiple threads will not access the same
6081 CamelFolderThread structure at the same time */
6082 g_mutex_lock (&message_list->priv->thread_tree_lock);
6083 camel_folder_thread_messages_apply (thread_tree, uids);
6084 g_mutex_unlock (&message_list->priv->thread_tree_lock);
6085 } else
6086 thread_tree = camel_folder_thread_messages_new (
6087 folder, uids, regen_data->thread_subject);
6089 /* We will build the ETreeModel content from this
6090 * CamelFolderThread during regen post-processing.
6092 * We're committed at this point so keep our own
6093 * reference in case the MessageList's reference
6094 * gets invalidated before regen post-processing. */
6095 regen_data->thread_tree = thread_tree;
6097 } else {
6098 guint ii;
6100 camel_folder_sort_uids (folder, uids);
6101 regen_data->summary = g_ptr_array_new ();
6103 camel_folder_summary_prepare_fetch_all (camel_folder_get_folder_summary (folder), NULL);
6105 for (ii = 0; ii < uids->len; ii++) {
6106 const gchar *uid;
6108 uid = g_ptr_array_index (uids, ii);
6109 info = camel_folder_get_message_info (folder, uid);
6110 if (info != NULL)
6111 g_ptr_array_add (regen_data->summary, info);
6115 exit:
6116 if (searchuids != NULL)
6117 camel_folder_search_free (folder, searchuids);
6118 else if (uids != NULL)
6119 camel_folder_free_uids (folder, uids);
6121 g_object_unref (folder);
6124 static void
6125 message_list_regen_done_cb (GObject *source_object,
6126 GAsyncResult *result,
6127 gpointer user_data)
6129 MessageList *message_list;
6130 GSimpleAsyncResult *simple;
6131 RegenData *regen_data;
6132 EActivity *activity;
6133 ETree *tree;
6134 ETreeTableAdapter *adapter;
6135 gboolean was_searching, is_searching;
6136 gint row_count;
6137 GError *local_error = NULL;
6139 message_list = MESSAGE_LIST (source_object);
6140 simple = G_SIMPLE_ASYNC_RESULT (result);
6141 regen_data = g_simple_async_result_get_op_res_gpointer (simple);
6143 /* Withdraw our RegenData from the private struct, if it hasn't
6144 * already been replaced. We have exclusive access to it now. */
6145 g_mutex_lock (&message_list->priv->regen_lock);
6146 if (message_list->priv->regen_data == regen_data) {
6147 regen_data_unref (message_list->priv->regen_data);
6148 message_list->priv->regen_data = NULL;
6149 e_tree_set_info_message (E_TREE (message_list), NULL);
6151 g_mutex_unlock (&message_list->priv->regen_lock);
6153 activity = regen_data->activity;
6155 if (g_simple_async_result_propagate_error (simple, &local_error) &&
6156 e_activity_handle_cancellation (activity, local_error)) {
6157 g_error_free (local_error);
6158 return;
6160 /* FIXME This should be handed off to an EAlertSink. */
6161 } else if (local_error != NULL) {
6162 g_warning ("%s: %s", G_STRFUNC, local_error->message);
6163 g_error_free (local_error);
6164 return;
6167 e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
6169 tree = E_TREE (message_list);
6170 adapter = e_tree_get_table_adapter (tree);
6172 /* Show the cursor unless we're responding to a
6173 * "folder-changed" signal from our CamelFolder. */
6174 if (!regen_data->folder_changed)
6175 e_tree_show_cursor_after_reflow (tree);
6177 g_signal_handlers_block_by_func (
6178 adapter, ml_tree_sorting_changed, message_list);
6180 was_searching = message_list_is_searching (message_list);
6182 g_free (message_list->search);
6183 message_list->search = g_strdup (regen_data->search);
6185 is_searching = message_list_is_searching (message_list);
6187 if (regen_data->group_by_threads) {
6188 ETableItem *table_item = e_tree_get_item (E_TREE (message_list));
6189 GPtrArray *selected;
6190 gchar *saveuid = NULL;
6191 gboolean forcing_expand_state;
6193 forcing_expand_state =
6194 message_list->expand_all ||
6195 message_list->collapse_all;
6197 if (message_list->just_set_folder) {
6198 message_list->just_set_folder = FALSE;
6199 if (regen_data->expand_state != NULL) {
6200 /* Load state from disk rather than use
6201 * the memory data when changing folders. */
6202 xmlFreeDoc (regen_data->expand_state);
6203 regen_data->expand_state = NULL;
6207 if (forcing_expand_state) {
6208 gint state;
6210 if (message_list->expand_all)
6211 state = 1; /* force expand */
6212 else
6213 state = -1; /* force collapse */
6215 e_tree_table_adapter_force_expanded_state (
6216 adapter, state);
6219 if (message_list->cursor_uid != NULL)
6220 saveuid = find_next_selectable (message_list);
6222 selected = message_list_get_selected (message_list);
6224 /* Show the cursor unless we're responding to a
6225 * "folder-changed" signal from our CamelFolder. */
6226 build_tree (
6227 message_list,
6228 regen_data->thread_tree,
6229 regen_data->folder_changed);
6231 message_list_set_thread_tree (
6232 message_list, regen_data->thread_tree);
6234 if (forcing_expand_state) {
6235 if (message_list->priv->folder != NULL && tree != NULL)
6236 save_tree_state (message_list, regen_data->folder);
6238 /* Disable forced expand/collapse state. */
6239 e_tree_table_adapter_force_expanded_state (adapter, 0);
6240 } else if (was_searching && !is_searching) {
6241 /* Load expand state from disk */
6242 load_tree_state (
6243 message_list,
6244 regen_data->folder,
6245 NULL);
6246 } else {
6247 /* Load expand state from the previous state or disk */
6248 load_tree_state (
6249 message_list,
6250 regen_data->folder,
6251 regen_data->expand_state);
6254 message_list->expand_all = 0;
6255 message_list->collapse_all = 0;
6257 /* restore cursor position only after the expand state is restored,
6258 thus the row numbers will actually match their real rows in UI */
6260 e_table_item_freeze (table_item);
6262 message_list_set_selected (message_list, selected);
6263 g_ptr_array_unref (selected);
6265 /* Show the cursor unless we're responding to a
6266 * "folder-changed" signal from our CamelFolder. */
6267 if (regen_data->folder_changed && table_item != NULL)
6268 table_item->queue_show_cursor = FALSE;
6270 e_table_item_thaw (table_item);
6272 if ((!saveuid || !g_hash_table_lookup (message_list->uid_nodemap, saveuid)) &&
6273 message_list->cursor_uid && g_hash_table_lookup (message_list->uid_nodemap, message_list->cursor_uid)) {
6274 /* this makes sure a visible node is selected, like when
6275 * collapsing all nodes and a children had been selected
6277 g_free (saveuid);
6278 saveuid = g_strdup (message_list->cursor_uid);
6281 if (message_list_selected_count (message_list) > 1) {
6282 g_free (saveuid);
6283 } else if (saveuid) {
6284 GNode *node;
6286 node = g_hash_table_lookup (
6287 message_list->uid_nodemap, saveuid);
6288 if (node == NULL) {
6289 g_free (message_list->cursor_uid);
6290 message_list->cursor_uid = NULL;
6291 g_signal_emit (
6292 message_list,
6293 signals[MESSAGE_SELECTED], 0, NULL);
6295 } else {
6296 GNode *parent = node;
6298 while ((parent = parent->parent) != NULL) {
6299 if (!e_tree_table_adapter_node_is_expanded (adapter, parent))
6300 node = parent;
6303 e_table_item_freeze (table_item);
6305 e_tree_set_cursor (E_TREE (message_list), node);
6307 /* Show the cursor unless we're responding to a
6308 * "folder-changed" signal from our CamelFolder. */
6309 if (regen_data->folder_changed && table_item != NULL)
6310 table_item->queue_show_cursor = FALSE;
6312 e_table_item_thaw (table_item);
6314 g_free (saveuid);
6315 } else if (message_list->cursor_uid && !g_hash_table_lookup (message_list->uid_nodemap, message_list->cursor_uid)) {
6316 g_free (message_list->cursor_uid);
6317 message_list->cursor_uid = NULL;
6318 g_signal_emit (
6319 message_list,
6320 signals[MESSAGE_SELECTED], 0, NULL);
6322 } else {
6323 build_flat (
6324 message_list,
6325 regen_data->summary,
6326 regen_data->folder_changed);
6329 row_count = e_table_model_row_count (E_TABLE_MODEL (adapter));
6331 if (regen_data->select_all) {
6332 message_list_select_all (message_list);
6334 } else if (regen_data->select_uid != NULL) {
6335 message_list_select_uid (
6336 message_list,
6337 regen_data->select_uid,
6338 regen_data->select_use_fallback);
6340 } else if (message_list->cursor_uid == NULL && regen_data->last_row != -1) {
6341 if (regen_data->last_row >= row_count)
6342 regen_data->last_row = row_count;
6344 if (regen_data->last_row >= 0) {
6345 GNode *node;
6347 node = e_tree_table_adapter_node_at_row (
6348 adapter, regen_data->last_row);
6349 if (node != NULL)
6350 select_node (message_list, node);
6354 if (gtk_widget_get_visible (GTK_WIDGET (message_list))) {
6355 const gchar *info_message;
6356 gboolean have_search_expr;
6358 /* space is used to indicate no search too */
6359 have_search_expr =
6360 (message_list->search != NULL) &&
6361 (*message_list->search != '\0') &&
6362 (strcmp (message_list->search, " ") != 0);
6364 if (row_count > 0) {
6365 info_message = NULL;
6366 } else if (have_search_expr) {
6367 info_message =
6368 _("No message satisfies your search criteria. "
6369 "Change search criteria by selecting a new "
6370 "Show message filter from the drop down list "
6371 "above or by running a new search either by "
6372 "clearing it with Search→Clear menu item or "
6373 "by changing the query above.");
6374 } else {
6375 info_message =
6376 _("There are no messages in this folder.");
6379 e_tree_set_info_message (tree, info_message);
6382 g_signal_handlers_unblock_by_func (
6383 adapter, ml_tree_sorting_changed, message_list);
6385 g_signal_emit (
6386 message_list,
6387 signals[MESSAGE_LIST_BUILT], 0);
6389 message_list->priv->any_row_changed = FALSE;
6390 message_list->just_set_folder = FALSE;
6393 static gboolean
6394 message_list_regen_idle_cb (gpointer user_data)
6396 GSimpleAsyncResult *simple;
6397 RegenData *regen_data;
6398 GCancellable *cancellable;
6399 MessageList *message_list;
6400 ETreeTableAdapter *adapter;
6401 gboolean searching;
6402 gint row_count;
6404 simple = G_SIMPLE_ASYNC_RESULT (user_data);
6405 regen_data = g_simple_async_result_get_op_res_gpointer (simple);
6406 cancellable = e_activity_get_cancellable (regen_data->activity);
6408 message_list = regen_data->message_list;
6410 g_mutex_lock (&message_list->priv->regen_lock);
6412 /* Capture MessageList state to use for this regen. */
6414 regen_data->group_by_threads =
6415 message_list_get_group_by_threads (message_list);
6416 regen_data->thread_subject =
6417 message_list_get_thread_subject (message_list);
6419 searching = message_list_is_searching (message_list);
6421 adapter = e_tree_get_table_adapter (E_TREE (message_list));
6422 row_count = e_table_model_row_count (E_TABLE_MODEL (adapter));
6424 if (row_count <= 0) {
6425 if (gtk_widget_get_visible (GTK_WIDGET (message_list))) {
6426 gchar *txt;
6428 txt = g_strdup_printf (
6429 "%s...", _("Generating message list"));
6430 e_tree_set_info_message (E_TREE (message_list), txt);
6431 g_free (txt);
6434 } else if (regen_data->group_by_threads &&
6435 !message_list->just_set_folder &&
6436 !searching) {
6437 if (message_list->priv->any_row_changed) {
6438 /* Something changed. If it was an expand
6439 * state change, then save the expand state. */
6440 message_list_save_state (message_list);
6441 } else {
6442 /* Remember the expand state and restore it
6443 * after regen. */
6444 regen_data->expand_state =
6445 e_tree_table_adapter_save_expanded_state_xml (
6446 adapter);
6448 } else {
6449 /* Remember the expand state and restore it after regen. */
6450 regen_data->expand_state = e_tree_table_adapter_save_expanded_state_xml (adapter);
6453 message_list->priv->regen_idle_id = 0;
6455 g_mutex_unlock (&message_list->priv->regen_lock);
6457 if (g_cancellable_is_cancelled (cancellable)) {
6458 g_simple_async_result_complete (simple);
6459 } else {
6460 g_simple_async_result_run_in_thread (
6461 simple,
6462 message_list_regen_thread,
6463 G_PRIORITY_DEFAULT,
6464 cancellable);
6467 return FALSE;
6470 static void
6471 mail_regen_cancel (MessageList *message_list)
6473 RegenData *regen_data = NULL;
6475 g_mutex_lock (&message_list->priv->regen_lock);
6477 if (message_list->priv->regen_data != NULL)
6478 regen_data = regen_data_ref (message_list->priv->regen_data);
6480 if (message_list->priv->regen_idle_id > 0) {
6481 g_source_remove (message_list->priv->regen_idle_id);
6482 message_list->priv->regen_idle_id = 0;
6485 g_mutex_unlock (&message_list->priv->regen_lock);
6487 /* Cancel outside the lock, since this will emit a signal. */
6488 if (regen_data != NULL) {
6489 e_activity_cancel (regen_data->activity);
6490 regen_data_unref (regen_data);
6494 static void
6495 mail_regen_list (MessageList *message_list,
6496 const gchar *search,
6497 gboolean folder_changed)
6499 GSimpleAsyncResult *simple;
6500 GCancellable *cancellable;
6501 RegenData *new_regen_data;
6502 RegenData *old_regen_data;
6503 gchar *prefixes, *tmp_search_copy = NULL;
6505 if (!search) {
6506 old_regen_data = message_list_ref_regen_data (message_list);
6508 if (old_regen_data && old_regen_data->folder == message_list->priv->folder) {
6509 tmp_search_copy = g_strdup (old_regen_data->search);
6510 search = tmp_search_copy;
6511 } else {
6512 tmp_search_copy = g_strdup (message_list->search);
6513 search = tmp_search_copy;
6516 if (old_regen_data)
6517 regen_data_unref (old_regen_data);
6518 } else if (search && !*search) {
6519 search = NULL;
6522 /* Report empty search as NULL, not as one/two-space string. */
6523 if (search && (strcmp (search, " ") == 0 || strcmp (search, " ") == 0))
6524 search = NULL;
6526 /* Can't list messages in a folder until we have a folder. */
6527 if (message_list->priv->folder == NULL) {
6528 g_free (message_list->search);
6529 message_list->search = g_strdup (search);
6530 g_free (tmp_search_copy);
6531 return;
6534 g_mutex_lock (&message_list->priv->re_prefixes_lock);
6536 g_strfreev (message_list->priv->re_prefixes);
6537 prefixes = g_settings_get_string (message_list->priv->mail_settings, "composer-localized-re");
6538 message_list->priv->re_prefixes = g_strsplit (prefixes ? prefixes : "", ",", -1);
6539 g_free (prefixes);
6541 g_strfreev (message_list->priv->re_separators);
6542 message_list->priv->re_separators = g_settings_get_strv (message_list->priv->mail_settings, "composer-localized-re-separators");
6544 if (message_list->priv->re_separators && !*message_list->priv->re_separators) {
6545 g_strfreev (message_list->priv->re_separators);
6546 message_list->priv->re_separators = NULL;
6549 g_mutex_unlock (&message_list->priv->re_prefixes_lock);
6551 g_mutex_lock (&message_list->priv->regen_lock);
6553 old_regen_data = message_list->priv->regen_data;
6555 /* If a regen is scheduled but not yet started, just
6556 * apply the argument values without cancelling it. */
6557 if (message_list->priv->regen_idle_id > 0) {
6558 g_return_if_fail (old_regen_data != NULL);
6560 if (g_strcmp0 (search, old_regen_data->search) != 0) {
6561 g_free (old_regen_data->search);
6562 old_regen_data->search = g_strdup (search);
6565 /* Only turn off the folder_changed flag, do not turn it on, because otherwise
6566 the view may not scroll to the cursor position, due to claiming that
6567 the regen was done for folder-changed signal, while the initial regen
6568 request would be due to change of the folder in the view (or other similar
6569 reasons). */
6570 if (!folder_changed)
6571 old_regen_data->folder_changed = folder_changed;
6573 /* Avoid cancelling on the way out. */
6574 old_regen_data = NULL;
6576 goto exit;
6579 cancellable = g_cancellable_new ();
6581 new_regen_data = regen_data_new (message_list, cancellable);
6582 new_regen_data->search = g_strdup (search);
6583 new_regen_data->folder_changed = folder_changed;
6585 /* We generate the message list content in a worker thread, and
6586 * then supply our own GAsyncReadyCallback to redraw the widget. */
6588 simple = g_simple_async_result_new (
6589 G_OBJECT (message_list),
6590 message_list_regen_done_cb,
6591 NULL, mail_regen_list);
6593 g_simple_async_result_set_check_cancellable (simple, cancellable);
6595 g_simple_async_result_set_op_res_gpointer (
6596 simple,
6597 regen_data_ref (new_regen_data),
6598 (GDestroyNotify) regen_data_unref);
6600 /* Set the RegenData immediately, but start the actual regen
6601 * operation from an idle callback. That way the caller has
6602 * the remainder of this main loop iteration to make further
6603 * MessageList changes without triggering additional regens. */
6605 message_list->priv->regen_data = regen_data_ref (new_regen_data);
6607 message_list->priv->regen_idle_id =
6608 g_idle_add_full (
6609 G_PRIORITY_DEFAULT_IDLE,
6610 message_list_regen_idle_cb,
6611 g_object_ref (simple),
6612 (GDestroyNotify) g_object_unref);
6614 g_object_unref (simple);
6616 regen_data_unref (new_regen_data);
6618 g_object_unref (cancellable);
6620 exit:
6621 g_mutex_unlock (&message_list->priv->regen_lock);
6623 /* Cancel outside the lock, since this will emit a signal. */
6624 if (old_regen_data != NULL) {
6625 e_activity_cancel (old_regen_data->activity);
6626 regen_data_unref (old_regen_data);
6629 g_free (tmp_search_copy);
6632 gboolean
6633 message_list_contains_uid (MessageList *message_list,
6634 const gchar *uid)
6636 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
6638 if (!uid || !*uid || !message_list->priv->folder)
6639 return FALSE;
6641 return g_hash_table_lookup (message_list->uid_nodemap, uid) != NULL;