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