Location | Tool | Test ID | Function | Issue |
---|---|---|---|---|
message-list.c:1538:4 | clang-analyzer | Value stored to 'found_re' is never read | ||
message-list.c:1538:4 | clang-analyzer | Value stored to 'found_re' is never read | ||
message-list.c:4773:20 | clang-analyzer | Access to field 'len' results in a dereference of a null pointer (loaded from variable 'uids') | ||
message-list.c:4773:20 | clang-analyzer | Access to field 'len' results in a dereference of a null pointer (loaded from variable 'uids') |
1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2 of the License, or (at your option) version 3.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with the program; if not, see <http://www.gnu.org/licenses/>
14 *
15 *
16 * Authors:
17 * Miguel de Icaza (miguel@ximian.com)
18 * Bertrand Guiheneuf (bg@aful.org)
19 * And just about everyone else in evolution ...
20 *
21 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
22 *
23 */
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32
33 #include <string.h>
34 #include <ctype.h>
35
36 #include <glib/gi18n.h>
37 #include <glib/gstdio.h>
38
39 #include "e-util/e-icon-factory.h"
40 #include "e-util/e-poolv.h"
41 #include "e-util/e-util-private.h"
42 #include "e-util/e-util.h"
43
44 #include "misc/e-selectable.h"
45
46 #include "shell/e-shell.h"
47 #include "shell/e-shell-settings.h"
48
49 #include "table/e-cell-checkbox.h"
50 #include "table/e-cell-hbox.h"
51 #include "table/e-cell-date.h"
52 #include "table/e-cell-size.h"
53 #include "table/e-cell-text.h"
54 #include "table/e-cell-toggle.h"
55 #include "table/e-cell-tree.h"
56 #include "table/e-cell-vbox.h"
57 #include "table/e-table-sorting-utils.h"
58 #include "table/e-tree-memory-callbacks.h"
59 #include "table/e-tree-memory.h"
60
61 #include "libemail-utils/mail-mt.h"
62 #include "libemail-engine/e-mail-utils.h"
63 #include "libemail-engine/mail-config.h"
64 #include "libemail-engine/mail-ops.h"
65 #include "libemail-engine/mail-tools.h"
66
67 #include "mail/e-mail-label-list-store.h"
68 #include "mail/e-mail-ui-session.h"
69 #include "mail/em-utils.h"
70 #include "mail/message-list.h"
71
72 /*#define TIMEIT */
73
74 #ifdef TIMEIT
75 #include <sys/time.h>
76 #include <unistd.h>
77 #endif
78
79 #ifdef G_OS_WIN32
80 #ifdef gmtime_r
81 #undef gmtime_r
82 #endif
83 #ifdef localtime_r
84 #undef localtime_r
85 #endif
86
87 /* The gmtime() and localtime() in Microsoft's C library are MT-safe */
88 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
89 #define localtime_r(tp,tmp) (localtime(tp)?(*(tmp)=*localtime(tp),(tmp)):0)
90 #endif
91
92 #define d(x)
93 #define t(x)
94
95 #define MESSAGE_LIST_GET_PRIVATE(obj) \
96 (G_TYPE_INSTANCE_GET_PRIVATE \
97 ((obj), MESSAGE_LIST_TYPE, MessageListPrivate))
98
99 struct _MLSelection {
100 GPtrArray *uids;
101 CamelFolder *folder;
102 };
103
104 struct _MessageListPrivate {
105 GtkWidget *invisible; /* 4 selection */
106
107 EMailSession *session;
108
109 struct _MLSelection clipboard;
110 gboolean destroyed;
111
112 gboolean thread_latest;
113 gboolean any_row_changed; /* save state before regen list when this is set to true */
114
115 GtkTargetList *copy_target_list;
116 GtkTargetList *paste_target_list;
117
118 /* This aids in automatic message selection. */
119 time_t newest_read_date;
120 const gchar *newest_read_uid;
121 time_t oldest_unread_date;
122 const gchar *oldest_unread_uid;
123 };
124
125 enum {
126 PROP_0,
127 PROP_COPY_TARGET_LIST,
128 PROP_PASTE_TARGET_LIST,
129 PROP_SESSION
130 };
131
132 /* Forward Declarations */
133 static void message_list_selectable_init
134 (ESelectableInterface *interface);
135
136 G_DEFINE_TYPE_WITH_CODE (
137 MessageList,
138 message_list,
139 E_TYPE_TREE,
140 G_IMPLEMENT_INTERFACE (
141 E_TYPE_SELECTABLE,
142 message_list_selectable_init))
143
144 static struct {
145 const gchar *target;
146 GdkAtom atom;
147 guint32 actions;
148 } ml_drag_info[] = {
149 { "x-uid-list", NULL, GDK_ACTION_MOVE | GDK_ACTION_COPY },
150 { "message/rfc822", NULL, GDK_ACTION_COPY },
151 { "text/uri-list", NULL, GDK_ACTION_COPY },
152 };
153
154 enum {
155 DND_X_UID_LIST, /* x-uid-list */
156 DND_MESSAGE_RFC822, /* message/rfc822 */
157 DND_TEXT_URI_LIST /* text/uri-list */
158 };
159
160 /* What we send */
161 static GtkTargetEntry ml_drag_types[] = {
162 { (gchar *) "x-uid-list", 0, DND_X_UID_LIST },
163 { (gchar *) "text/uri-list", 0, DND_TEXT_URI_LIST },
164 };
165
166 /* What we accept */
167 static GtkTargetEntry ml_drop_types[] = {
168 { (gchar *) "x-uid-list", 0, DND_X_UID_LIST },
169 { (gchar *) "message/rfc822", 0, DND_MESSAGE_RFC822 },
170 { (gchar *) "text/uri-list", 0, DND_TEXT_URI_LIST },
171 };
172
173 /*
174 * Default sizes for the ETable display
175 *
176 */
177 #define N_CHARS(x) (CHAR_WIDTH * (x))
178
179 #define COL_ICON_WIDTH (16)
180 #define COL_ATTACH_WIDTH (16)
181 #define COL_CHECK_BOX_WIDTH (16)
182 #define COL_FROM_EXPANSION (24.0)
183 #define COL_FROM_WIDTH_MIN (32)
184 #define COL_SUBJECT_EXPANSION (30.0)
185 #define COL_SUBJECT_WIDTH_MIN (32)
186 #define COL_SENT_EXPANSION (24.0)
187 #define COL_SENT_WIDTH_MIN (32)
188 #define COL_RECEIVED_EXPANSION (20.0)
189 #define COL_RECEIVED_WIDTH_MIN (32)
190 #define COL_TO_EXPANSION (24.0)
191 #define COL_TO_WIDTH_MIN (32)
192 #define COL_SIZE_EXPANSION (6.0)
193 #define COL_SIZE_WIDTH_MIN (32)
194 #define COL_SENDER_EXPANSION (24.0)
195 #define COL_SENDER_WIDTH_MIN (32)
196
197 enum {
198 NORMALISED_SUBJECT,
199 NORMALISED_FROM,
200 NORMALISED_TO,
201 NORMALISED_LAST
202 };
203
204 /* #define SMART_ADDRESS_COMPARE */
205
206 #ifdef SMART_ADDRESS_COMPARE
207 struct _EMailAddress {
208 ENameWestern *wname;
209 gchar *address;
210 };
211
212 typedef struct _EMailAddress EMailAddress;
213 #endif /* SMART_ADDRESS_COMPARE */
214
215 static void on_cursor_activated_cmd (ETree *tree, gint row, ETreePath path, gpointer user_data);
216 static void on_selection_changed_cmd (ETree *tree, MessageList *ml);
217 static gint on_click (ETree *tree, gint row, ETreePath path, gint col, GdkEvent *event, MessageList *list);
218 static gchar *filter_date (time_t date);
219 static gchar *filter_size (gint size);
220
221 /* note: @changes is owned/freed by the caller */
222 /*static void mail_do_regenerate_messagelist (MessageList *list, const gchar *search, const gchar *hideexpr, CamelFolderChangeInfo *changes);*/
223 static void mail_regen_list (MessageList *ml, const gchar *search, const gchar *hideexpr, CamelFolderChangeInfo *changes, gboolean scroll_to_cursor);
224 static void mail_regen_cancel (MessageList *ml);
225
226 static void clear_info (gchar *key, ETreePath *node, MessageList *ml);
227
228 static void folder_changed (CamelFolder *folder,
229 CamelFolderChangeInfo *info,
230 MessageList *ml);
231
232 enum {
233 MESSAGE_SELECTED,
234 MESSAGE_LIST_BUILT,
235 LAST_SIGNAL
236 };
237
238 static guint message_list_signals[LAST_SIGNAL] = {0, };
239
240 static const gchar *status_icons[] = {
241 "mail-unread",
242 "mail-read",
243 "mail-replied",
244 "mail-forward",
245 "stock_mail-unread-multiple",
246 "stock_mail-open-multiple"
247 };
248
249 static const gchar *score_icons[] = {
250 "stock_score-lowest",
251 "stock_score-lower",
252 "stock_score-low",
253 "stock_score-normal",
254 "stock_score-high",
255 "stock_score-higher",
256 "stock_score-highest"
257 };
258
259 static const gchar *attachment_icons[] = {
260 NULL, /* empty icon */
261 "mail-attachment",
262 "stock_new-meeting"
263 };
264
265 static const gchar *flagged_icons[] = {
266 NULL, /* empty icon */
267 "emblem-important"
268 };
269
270 static const gchar *followup_icons[] = {
271 NULL, /* empty icon */
272 "stock_mail-flag-for-followup",
273 "stock_mail-flag-for-followup-done"
274 };
275
276 #ifdef SMART_ADDRESS_COMPARE
277 static EMailAddress *
278 e_mail_address_new (const gchar *address)
279 {
280 CamelInternetAddress *cia;
281 EMailAddress *new;
282 const gchar *name = NULL, *addr = NULL;
283
284 cia = camel_internet_address_new ();
285 if (camel_address_unformat (CAMEL_ADDRESS (cia), address) == -1) {
286 g_object_unref (cia);
287 return NULL;
288 }
289 camel_internet_address_get (cia, 0, &name, &addr);
290
291 new = g_new (EMailAddress, 1);
292 new->address = g_strdup (addr);
293 if (name && *name) {
294 new->wname = e_name_western_parse (name);
295 } else {
296 new->wname = NULL;
297 }
298
299 g_object_unref (cia);
300
301 return new;
302 }
303
304 static void
305 e_mail_address_free (EMailAddress *addr)
306 {
307 g_return_if_fail (addr != NULL);
308
309 g_free (addr->address);
310 if (addr->wname)
311 e_name_western_free (addr->wname);
312 g_free (addr);
313 }
314
315 static gint
316 e_mail_address_compare (gconstpointer address1,
317 gconstpointer address2)
318 {
319 const EMailAddress *addr1 = address1;
320 const EMailAddress *addr2 = address2;
321 gint retval;
322
323 g_return_val_if_fail (addr1 != NULL, 1);
324 g_return_val_if_fail (addr2 != NULL, -1);
325
326 if (!addr1->wname && !addr2->wname) {
327 /* have to compare addresses, one or both don't have names */
328 g_return_val_if_fail (addr1->address != NULL, 1);
329 g_return_val_if_fail (addr2->address != NULL, -1);
330
331 return g_ascii_strcasecmp (addr1->address, addr2->address);
332 }
333
334 if (!addr1->wname)
335 return -1;
336 if (!addr2->wname)
337 return 1;
338
339 if (!addr1->wname->last && !addr2->wname->last) {
340 /* neither has a last name - default to address? */
341 /* FIXME: what do we compare next? */
342 g_return_val_if_fail (addr1->address != NULL, 1);
343 g_return_val_if_fail (addr2->address != NULL, -1);
344
345 return g_ascii_strcasecmp (addr1->address, addr2->address);
346 }
347
348 if (!addr1->wname->last)
349 return -1;
350 if (!addr2->wname->last)
351 return 1;
352
353 retval = g_ascii_strcasecmp (addr1->wname->last, addr2->wname->last);
354 if (retval)
355 return retval;
356
357 /* last names are identical - compare first names */
358
359 if (!addr1->wname->first && !addr2->wname->first)
360 return g_ascii_strcasecmp (addr1->address, addr2->address);
361
362 if (!addr1->wname->first)
363 return -1;
364 if (!addr2->wname->first)
365 return 1;
366
367 retval = g_ascii_strcasecmp (addr1->wname->first, addr2->wname->first);
368 if (retval)
369 return retval;
370
371 return g_ascii_strcasecmp (addr1->address, addr2->address);
372 }
373 #endif /* SMART_ADDRESS_COMPARE */
374
375 static gint
376 address_compare (gconstpointer address1,
377 gconstpointer address2,
378 gpointer cmp_cache)
379 {
380 #ifdef SMART_ADDRESS_COMPARE
381 EMailAddress *addr1, *addr2;
382 #endif /* SMART_ADDRESS_COMPARE */
383 gint retval;
384
385 g_return_val_if_fail (address1 != NULL, 1);
386 g_return_val_if_fail (address2 != NULL, -1);
387
388 #ifdef SMART_ADDRESS_COMPARE
389 addr1 = e_mail_address_new (address1);
390 addr2 = e_mail_address_new (address2);
391 retval = e_mail_address_compare (addr1, addr2);
392 e_mail_address_free (addr1);
393 e_mail_address_free (addr2);
394 #else
395 retval = g_ascii_strcasecmp ((gchar *) address1, (gchar *) address2);
396 #endif /* SMART_ADDRESS_COMPARE */
397
398 return retval;
399 }
400
401 static gchar *
402 filter_size (gint size)
403 {
404 gfloat fsize;
405
406 if (size < 1024) {
407 return g_strdup_printf ("%d", size);
408 } else {
409 fsize = ((gfloat) size) / 1024.0;
410 if (fsize < 1024.0) {
411 return g_strdup_printf ("%.2f K", fsize);
412 } else {
413 fsize /= 1024.0;
414 return g_strdup_printf ("%.2f M", fsize);
415 }
416 }
417 }
418
419 /* Gets the uid of the message displayed at a given view row */
420 static const gchar *
421 get_message_uid (MessageList *message_list,
422 ETreePath node)
423 {
424 CamelMessageInfo *info;
425
426 g_return_val_if_fail (node != NULL, NULL);
427 info = e_tree_memory_node_get_data (E_TREE_MEMORY (message_list->model), node);
428 /* correct me if I'm wrong, but this should never be NULL, should it? */
429 g_return_val_if_fail (info != NULL, NULL);
430
431 return camel_message_info_uid (info);
432 }
433
434 /* Gets the CamelMessageInfo for the message displayed at the given
435 * view row.
436 */
437 static CamelMessageInfo *
438 get_message_info (MessageList *message_list,
439 ETreePath node)
440 {
441 CamelMessageInfo *info;
442
443 g_return_val_if_fail (node != NULL, NULL);
444 info = e_tree_memory_node_get_data (E_TREE_MEMORY (message_list->model), node);
445 g_return_val_if_fail (info != NULL, NULL);
446
447 return info;
448 }
449
450 static const gchar *
451 get_normalised_string (MessageList *message_list,
452 CamelMessageInfo *info,
453 gint col)
454 {
455 const gchar *string, *str;
456 gchar *normalised;
457 EPoolv *poolv;
458 gint index;
459
460 switch (col) {
461 case COL_SUBJECT_NORM:
462 string = camel_message_info_subject (info);
463 index = NORMALISED_SUBJECT;
464 break;
465 case COL_FROM_NORM:
466 string = camel_message_info_from (info);
467 index = NORMALISED_FROM;
468 break;
469 case COL_TO_NORM:
470 string = camel_message_info_to (info);
471 index = NORMALISED_TO;
472 break;
473 default:
474 string = NULL;
475 index = NORMALISED_LAST;
476 g_warning ("Should not be reached\n");
477 }
478
479 /* slight optimisation */
480 if (string == NULL || string[0] == '\0')
481 return "";
482
483 poolv = g_hash_table_lookup (message_list->normalised_hash, camel_message_info_uid (info));
484 if (poolv == NULL) {
485 poolv = e_poolv_new (NORMALISED_LAST);
486 g_hash_table_insert (message_list->normalised_hash, (gchar *) camel_message_info_uid (info), poolv);
487 } else {
488 str = e_poolv_get (poolv, index);
489 if (*str)
490 return str;
491 }
492
493 if (col == COL_SUBJECT_NORM) {
494 EShell *shell = e_shell_get_default ();
495 gint skip_len;
496 const guchar *subject;
497 gboolean found_re = TRUE;
498
499 subject = (const guchar *) string;
500 while (found_re) {
501 found_re = em_utils_is_re_in_subject (shell, (const gchar *) subject, &skip_len) && skip_len > 0;
502 if (found_re)
503 subject += skip_len;
504
505 /* jump over any spaces */
506 while (*subject && isspace ((gint) *subject))
507 subject++;
508 }
509
510 /* jump over any spaces */
511 while (*subject && isspace ((gint) *subject))
512 subject++;
513
514 string = (const gchar *) subject;
515 normalised = g_utf8_collate_key (string, -1);
516 } else {
517 /* because addresses require strings, not collate keys */
518 normalised = g_strdup (string);
519 }
520
521 e_poolv_set (poolv, index, normalised, TRUE);
522
523 return e_poolv_get (poolv, index);
524 }
525
526 static void
527 clear_selection (MessageList *ml,
528 struct _MLSelection *selection)
529 {
530 if (selection->uids) {
531 em_utils_uids_free (selection->uids);
532 selection->uids = NULL;
533 }
534 if (selection->folder) {
535 g_object_unref (selection->folder);
536 selection->folder = NULL;
537 }
538 }
539
540 static ETreePath
541 ml_search_forward (MessageList *ml,
542 gint start,
543 gint end,
544 guint32 flags,
545 guint32 mask)
546 {
547 ETreePath path;
548 gint row;
549 CamelMessageInfo *info;
550 ETreeTableAdapter *etta;
551
552 etta = e_tree_get_table_adapter (E_TREE (ml));
553
554 for (row = start; row <= end; row++) {
555 path = e_tree_table_adapter_node_at_row (etta, row);
556 if (path
557 && (info = get_message_info (ml, path))
558 && (camel_message_info_flags (info) & mask) == flags)
559 return path;
560 }
561
562 return NULL;
563 }
564
565 static ETreePath
566 ml_search_backward (MessageList *ml,
567 gint start,
568 gint end,
569 guint32 flags,
570 guint32 mask)
571 {
572 ETreePath path;
573 gint row;
574 CamelMessageInfo *info;
575 ETreeTableAdapter *etta;
576
577 etta = e_tree_get_table_adapter (E_TREE (ml));
578
579 for (row = start; row >= end; row--) {
580 path = e_tree_table_adapter_node_at_row (etta, row);
581 if (path
582 && (info = get_message_info (ml, path))
583 && (camel_message_info_flags (info) & mask) == flags)
584 return path;
585 }
586
587 return NULL;
588 }
589
590 static ETreePath
591 ml_search_path (MessageList *ml,
592 MessageListSelectDirection direction,
593 guint32 flags,
594 guint32 mask)
595 {
596 ETreePath node;
597 gint row, count;
598 ETreeTableAdapter *etta;
599
600 etta = e_tree_get_table_adapter (E_TREE (ml));
601
602 if (ml->cursor_uid == NULL
603 || (node = g_hash_table_lookup (ml->uid_nodemap, ml->cursor_uid)) == NULL)
604 return NULL;
605
606 row = e_tree_table_adapter_row_of_node (etta, node);
607 if (row == -1)
608 return NULL;
609 count = e_table_model_row_count ((ETableModel *) etta);
610
611 if ((direction & MESSAGE_LIST_SELECT_DIRECTION) == MESSAGE_LIST_SELECT_NEXT)
612 node = ml_search_forward (ml, row + 1, count - 1, flags, mask);
613 else
614 node = ml_search_backward (ml, row - 1, 0, flags, mask);
615
616 if (node == NULL && (direction & MESSAGE_LIST_SELECT_WRAP)) {
617 if ((direction & MESSAGE_LIST_SELECT_DIRECTION) == MESSAGE_LIST_SELECT_NEXT)
618 node = ml_search_forward (ml, 0, row, flags, mask);
619 else
620 node = ml_search_backward (ml, count - 1, row, flags, mask);
621 }
622
623 return node;
624 }
625
626 static void
627 select_path (MessageList *ml,
628 ETreePath path)
629 {
630 ETree *tree;
631 ETreeTableAdapter *etta;
632 ETreeSelectionModel *etsm;
633
634 tree = E_TREE (ml);
635 etta = e_tree_get_table_adapter (tree);
636 etsm = (ETreeSelectionModel *) e_tree_get_selection_model (tree);
637
638 g_free (ml->cursor_uid);
639 ml->cursor_uid = NULL;
640
641 e_tree_table_adapter_show_node (etta, path);
642 e_tree_set_cursor (tree, path);
643 e_tree_selection_model_select_single_path (etsm, path);
644 }
645
646 /**
647 * message_list_select:
648 * @message_list: a MessageList
649 * @direction: the direction to search in
650 * @flags: a set of flag values
651 * @mask: a mask for comparing against @flags
652 *
653 * This moves the message list selection to a suitable row. @flags and
654 * @mask combine to specify what constitutes a suitable row. @direction is
655 * %MESSAGE_LIST_SELECT_NEXT if it should find the next matching
656 * message, or %MESSAGE_LIST_SELECT_PREVIOUS if it should find the
657 * previous. %MESSAGE_LIST_SELECT_WRAP is an option bit which specifies the
658 * search should wrap.
659 *
660 * If no suitable row is found, the selection will be
661 * unchanged.
662 *
663 * Returns %TRUE if a new message has been selected or %FALSE otherwise.
664 **/
665 gboolean
666 message_list_select (MessageList *ml,
667 MessageListSelectDirection direction,
668 guint32 flags,
669 guint32 mask)
670 {
671 ETreePath path;
672
673 path = ml_search_path (ml, direction, flags, mask);
674 if (path) {
675 select_path (ml, path);
676
677 /* This function is usually called in response to a key
678 * press, so grab focus if the message list is visible. */
679 if (gtk_widget_get_visible (GTK_WIDGET (ml)))
680 gtk_widget_grab_focus (GTK_WIDGET (ml));
681
682 return TRUE;
683 } else
684 return FALSE;
685 }
686
687 /**
688 * message_list_can_select:
689 * @ml:
690 * @direction:
691 * @flags:
692 * @mask:
693 *
694 * Returns true if the selection specified is possible with the current view.
695 *
696 * Return value:
697 **/
698 gboolean
699 message_list_can_select (MessageList *ml,
700 MessageListSelectDirection direction,
701 guint32 flags,
702 guint32 mask)
703 {
704 return ml_search_path (ml, direction, flags, mask) != NULL;
705 }
706
707 /**
708 * message_list_select_uid:
709 * @message_list:
710 * @uid:
711 *
712 * Selects the message with the given UID.
713 **/
714 void
715 message_list_select_uid (MessageList *message_list,
716 const gchar *uid,
717 gboolean with_fallback)
718 {
719 MessageListPrivate *priv;
720 GHashTable *uid_nodemap;
721 ETreePath node = NULL;
722
723 g_return_if_fail (IS_MESSAGE_LIST (message_list));
724
725 priv = message_list->priv;
726 uid_nodemap = message_list->uid_nodemap;
727
728 if (message_list->folder == NULL)
729 return;
730
731 /* Try to find the requested message UID. */
732 if (uid != NULL)
733 node = g_hash_table_lookup (uid_nodemap, uid);
734
735 /* If we're busy or waiting to regenerate the message list, cache
736 * the UID so we can try again when we're done. Otherwise if the
737 * requested message UID was not found and 'with_fallback' is set,
738 * try a couple fallbacks:
739 *
740 * 1) Oldest unread message in the list, by date received.
741 * 2) Newest read message in the list, by date received.
742 */
743 if (message_list->regen || message_list->regen_timeout_id) {
744 g_free (message_list->pending_select_uid);
745 message_list->pending_select_uid = g_strdup (uid);
746 message_list->pending_select_fallback = with_fallback;
747 } else if (with_fallback) {
748 if (node == NULL && priv->oldest_unread_uid != NULL)
749 node = g_hash_table_lookup (
750 uid_nodemap, priv->oldest_unread_uid);
751 if (node == NULL && priv->newest_read_uid != NULL)
752 node = g_hash_table_lookup (
753 uid_nodemap, priv->newest_read_uid);
754 }
755
756 if (node) {
757 ETree *tree;
758 ETreePath old_cur;
759
760 tree = E_TREE (message_list);
761 old_cur = e_tree_get_cursor (tree);
762
763 /* This will emit a changed signal that we'll pick up */
764 e_tree_set_cursor (tree, node);
765
766 if (old_cur == node)
767 g_signal_emit (
768 message_list,
769 message_list_signals[MESSAGE_SELECTED],
770 0, message_list->cursor_uid);
771 } else {
772 g_free (message_list->cursor_uid);
773 message_list->cursor_uid = NULL;
774 g_signal_emit (
775 message_list,
776 message_list_signals[MESSAGE_SELECTED],
777 0, NULL);
778 }
779 }
780
781 void
782 message_list_select_next_thread (MessageList *ml)
783 {
784 ETreePath node;
785 ETreeTableAdapter *etta;
786 gint i, count, row;
787
788 etta = e_tree_get_table_adapter (E_TREE (ml));
789
790 if (!ml->cursor_uid
791 || (node = g_hash_table_lookup (ml->uid_nodemap, ml->cursor_uid)) == NULL)
792 return;
793
794 row = e_tree_table_adapter_row_of_node (etta, node);
795 if (row == -1)
796 return;
797 count = e_table_model_row_count ((ETableModel *) etta);
798
799 /* find the next node which has a root parent (i.e. toplevel node) */
800 for (i = row + 1; i < count - 1; i++) {
801 node = e_tree_table_adapter_node_at_row (etta, i);
802 if (node
803 && e_tree_model_node_is_root (ml->model, e_tree_model_node_get_parent (ml->model, node))) {
804 select_path (ml, node);
805 return;
806 }
807 }
808 }
809
810 void
811 message_list_select_prev_thread (MessageList *ml)
812 {
813 ETreePath node;
814 ETreeTableAdapter *etta;
815 gint i, row;
816 gboolean skip_first;
817
818 etta = e_tree_get_table_adapter (E_TREE (ml));
819
820 if (!ml->cursor_uid
821 || (node = g_hash_table_lookup (ml->uid_nodemap, ml->cursor_uid)) == NULL)
822 return;
823
824 row = e_tree_table_adapter_row_of_node (etta, node);
825 if (row == -1)
826 return;
827
828 /* skip first found if in the middle of the thread */
829 skip_first = !e_tree_model_node_is_root (ml->model, e_tree_model_node_get_parent (ml->model, node));
830
831 /* find the previous node which has a root parent (i.e. toplevel node) */
832 for (i = row - 1; i >= 0; i--) {
833 node = e_tree_table_adapter_node_at_row (etta, i);
834 if (node
835 && e_tree_model_node_is_root (ml->model, e_tree_model_node_get_parent (ml->model, node))) {
836 if (skip_first) {
837 skip_first = FALSE;
838 continue;
839 }
840
841 select_path (ml, node);
842 return;
843 }
844 }
845 }
846
847 static gboolean
848 message_list_select_all_timeout_cb (MessageList *message_list)
849 {
850 ESelectionModel *etsm;
851
852 etsm = e_tree_get_selection_model (E_TREE (message_list));
853
854 e_selection_model_select_all (etsm);
855
856 return FALSE;
857 }
858
859 /**
860 * message_list_select_all:
861 * @message_list: Message List widget
862 *
863 * Selects all messages in the message list.
864 **/
865 void
866 message_list_select_all (MessageList *message_list)
867 {
868 g_return_if_fail (IS_MESSAGE_LIST (message_list));
869
870 if (message_list->threaded && message_list->regen_timeout_id) {
871 /* XXX The timeout below is added so that the execution
872 * thread to expand all conversation threads would
873 * have completed. The timeout 505 is just to ensure
874 * that the value is a small delta more than the
875 * timeout value in mail_regen_list(). */
876 g_timeout_add (
877 55, (GSourceFunc)
878 message_list_select_all_timeout_cb,
879 message_list);
880 } else
881 /* If there is no threading, just select all immediately. */
882 message_list_select_all_timeout_cb (message_list);
883 }
884
885 typedef struct thread_select_info {
886 MessageList *ml;
887 GPtrArray *paths;
888 } thread_select_info_t;
889
890 static gboolean
891 select_node (ETreeModel *model,
892 ETreePath path,
893 gpointer user_data)
894 {
895 thread_select_info_t *tsi = (thread_select_info_t *) user_data;
896
897 g_ptr_array_add (tsi->paths, path);
898 return FALSE; /*not done yet */
899 }
900
901 static void
902 select_thread (MessageList *message_list,
903 void (*selector) (ETreePath,
904 gpointer))
905 {
906 ETree *tree;
907 ETreeSelectionModel *etsm;
908 thread_select_info_t tsi;
909
910 tsi.ml = message_list;
911 tsi.paths = g_ptr_array_new ();
912
913 tree = E_TREE (message_list);
914 etsm = (ETreeSelectionModel *) e_tree_get_selection_model (tree);
915
916 e_tree_selected_path_foreach (tree, selector, &tsi);
917
918 e_tree_selection_model_select_paths (etsm, tsi.paths);
919
920 g_ptr_array_free (tsi.paths, TRUE);
921 }
922
923 static void
924 thread_select_foreach (ETreePath path,
925 gpointer user_data)
926 {
927 thread_select_info_t *tsi = (thread_select_info_t *) user_data;
928 ETreeModel *model = tsi->ml->model;
929 ETreePath node, last;
930
931 node = path;
932
933 do {
934 last = node;
935 node = e_tree_model_node_get_parent (model, node);
936 } while (!e_tree_model_node_is_root (model, node));
937
938 g_ptr_array_add (tsi->paths, last);
939
940 e_tree_model_node_traverse (model, last, select_node, tsi);
941 }
942
943 /**
944 * message_list_select_thread:
945 * @message_list: Message List widget
946 *
947 * Selects all messages in the current thread (based on cursor).
948 **/
949 void
950 message_list_select_thread (MessageList *message_list)
951 {
952 select_thread (message_list, thread_select_foreach);
953 }
954
955 static void
956 subthread_select_foreach (ETreePath path,
957 gpointer user_data)
958 {
959 thread_select_info_t *tsi = (thread_select_info_t *) user_data;
960 ETreeModel *model = tsi->ml->model;
961
962 e_tree_model_node_traverse (model, path, select_node, tsi);
963 }
964
965 /**
966 * message_list_select_subthread:
967 * @message_list: Message List widget
968 *
969 * Selects all messages in the current subthread (based on cursor).
970 **/
971 void
972 message_list_select_subthread (MessageList *message_list)
973 {
974 select_thread (message_list, subthread_select_foreach);
975 }
976
977 /**
978 * message_list_invert_selection:
979 * @message_list: Message List widget
980 *
981 * Invert the current selection in the message-list.
982 **/
983 void
984 message_list_invert_selection (MessageList *message_list)
985 {
986 ESelectionModel *etsm;
987
988 etsm = e_tree_get_selection_model (E_TREE (message_list));
989
990 e_selection_model_invert_selection (etsm);
991 }
992
993 void
994 message_list_copy (MessageList *ml,
995 gboolean cut)
996 {
997 MessageListPrivate *p = ml->priv;
998 GPtrArray *uids;
999
1000 clear_selection (ml, &p->clipboard);
1001
1002 uids = message_list_get_selected (ml);
1003
1004 if (uids->len > 0) {
1005 if (cut) {
1006 gint i;
1007
1008 camel_folder_freeze (ml->folder);
1009 for (i = 0; i < uids->len; i++)
1010 camel_folder_set_message_flags (
1011 ml->folder, uids->pdata[i],
1012 CAMEL_MESSAGE_SEEN |
1013 CAMEL_MESSAGE_DELETED,
1014 CAMEL_MESSAGE_SEEN |
1015 CAMEL_MESSAGE_DELETED);
1016
1017 camel_folder_thaw (ml->folder);
1018 }
1019
1020 p->clipboard.uids = uids;
1021 p->clipboard.folder = g_object_ref (ml->folder);
1022 gtk_selection_owner_set (p->invisible, GDK_SELECTION_CLIPBOARD, gtk_get_current_event_time ());
1023 } else {
1024 em_utils_uids_free (uids);
1025 gtk_selection_owner_set (NULL, GDK_SELECTION_CLIPBOARD, gtk_get_current_event_time ());
1026 }
1027 }
1028
1029 void
1030 message_list_paste (MessageList *ml)
1031 {
1032 gtk_selection_convert (
1033 ml->priv->invisible, GDK_SELECTION_CLIPBOARD,
1034 gdk_atom_intern ("x-uid-list", FALSE),
1035 GDK_CURRENT_TIME);
1036 }
1037
1038 /*
1039 * SimpleTableModel::col_count
1040 */
1041 static gint
1042 ml_column_count (ETreeModel *etm,
1043 gpointer data)
1044 {
1045 return COL_LAST;
1046 }
1047
1048 /*
1049 * SimpleTableModel::has_save_id
1050 */
1051 static gboolean
1052 ml_has_save_id (ETreeModel *etm,
1053 gpointer data)
1054 {
1055 return TRUE;
1056 }
1057
1058 /*
1059 * SimpleTableModel::get_save_id
1060 */
1061 static gchar *
1062 ml_get_save_id (ETreeModel *etm,
1063 ETreePath path,
1064 gpointer data)
1065 {
1066 CamelMessageInfo *info;
1067
1068 if (e_tree_model_node_is_root (etm, path))
1069 return g_strdup ("root");
1070
1071 /* Note: etable can ask for the save_id while we're clearing it,
1072 * which is the only time data should be null */
1073 info = e_tree_memory_node_get_data (E_TREE_MEMORY (etm), path);
1074 if (info == NULL)
1075 return NULL;
1076
1077 return g_strdup (camel_message_info_uid (info));
1078 }
1079
1080 /*
1081 * SimpleTableModel::has_save_id
1082 */
1083 static gboolean
1084 ml_has_get_node_by_id (ETreeModel *etm,
1085 gpointer data)
1086 {
1087 return TRUE;
1088 }
1089
1090 /*
1091 * SimpleTableModel::get_save_id
1092 */
1093 static ETreePath
1094 ml_get_node_by_id (ETreeModel *etm,
1095 const gchar *save_id,
1096 gpointer data)
1097 {
1098 MessageList *ml;
1099
1100 ml = data;
1101
1102 if (!strcmp (save_id, "root"))
1103 return e_tree_model_get_root (etm);
1104
1105 return g_hash_table_lookup (ml->uid_nodemap, save_id);
1106 }
1107
1108 static gpointer
1109 ml_duplicate_value (ETreeModel *etm,
1110 gint col,
1111 gconstpointer value,
1112 gpointer data)
1113 {
1114 switch (col) {
1115 case COL_MESSAGE_STATUS:
1116 case COL_FLAGGED:
1117 case COL_SCORE:
1118 case COL_ATTACHMENT:
1119 case COL_DELETED:
1120 case COL_UNREAD:
1121 case COL_SENT:
1122 case COL_RECEIVED:
1123 case COL_SIZE:
1124 case COL_FOLLOWUP_FLAG_STATUS:
1125 case COL_FOLLOWUP_DUE_BY:
1126 return (gpointer) value;
1127
1128 case COL_FROM:
1129 case COL_SUBJECT:
1130 case COL_TO:
1131 case COL_SENDER:
1132 case COL_RECIPIENTS:
1133 case COL_MIXED_SENDER:
1134 case COL_MIXED_RECIPIENTS:
1135 case COL_FOLLOWUP_FLAG:
1136 case COL_LOCATION:
1137 case COL_LABELS:
1138 return g_strdup (value);
1139 default:
1140 g_warning ("This shouldn't be reached\n");
1141 }
1142 return NULL;
1143 }
1144
1145 static void
1146 ml_free_value (ETreeModel *etm,
1147 gint col,
1148 gpointer value,
1149 gpointer data)
1150 {
1151 switch (col) {
1152 case COL_MESSAGE_STATUS:
1153 case COL_FLAGGED:
1154 case COL_SCORE:
1155 case COL_ATTACHMENT:
1156 case COL_DELETED:
1157 case COL_UNREAD:
1158 case COL_SENT:
1159 case COL_RECEIVED:
1160 case COL_SIZE:
1161 case COL_FOLLOWUP_FLAG_STATUS:
1162 case COL_FOLLOWUP_DUE_BY:
1163 case COL_FROM_NORM:
1164 case COL_SUBJECT_NORM:
1165 case COL_TO_NORM:
1166 case COL_SUBJECT_TRIMMED:
1167 case COL_COLOUR:
1168 break;
1169
1170 case COL_FROM:
1171 case COL_SUBJECT:
1172 case COL_TO:
1173 case COL_FOLLOWUP_FLAG:
1174 case COL_LOCATION:
1175 case COL_SENDER:
1176 case COL_RECIPIENTS:
1177 case COL_MIXED_SENDER:
1178 case COL_MIXED_RECIPIENTS:
1179 case COL_LABELS:
1180 g_free (value);
1181 break;
1182 default:
1183 g_warning ("%s: This shouldn't be reached (col:%d)", G_STRFUNC, col);
1184 }
1185 }
1186
1187 static gpointer
1188 ml_initialize_value (ETreeModel *etm,
1189 gint col,
1190 gpointer data)
1191 {
1192 switch (col) {
1193 case COL_MESSAGE_STATUS:
1194 case COL_FLAGGED:
1195 case COL_SCORE:
1196 case COL_ATTACHMENT:
1197 case COL_DELETED:
1198 case COL_UNREAD:
1199 case COL_SENT:
1200 case COL_RECEIVED:
1201 case COL_SIZE:
1202 case COL_FOLLOWUP_FLAG_STATUS:
1203 case COL_FOLLOWUP_DUE_BY:
1204 return NULL;
1205
1206 case COL_FROM:
1207 case COL_SUBJECT:
1208 case COL_TO:
1209 case COL_FOLLOWUP_FLAG:
1210 case COL_LOCATION:
1211 case COL_SENDER:
1212 case COL_RECIPIENTS:
1213 case COL_MIXED_SENDER:
1214 case COL_MIXED_RECIPIENTS:
1215 case COL_LABELS:
1216 return g_strdup ("");
1217 default:
1218 g_warning ("This shouldn't be reached\n");
1219 }
1220
1221 return NULL;
1222 }
1223
1224 static gboolean
1225 ml_value_is_empty (ETreeModel *etm,
1226 gint col,
1227 gconstpointer value,
1228 gpointer data)
1229 {
1230 switch (col) {
1231 case COL_MESSAGE_STATUS:
1232 case COL_FLAGGED:
1233 case COL_SCORE:
1234 case COL_ATTACHMENT:
1235 case COL_DELETED:
1236 case COL_UNREAD:
1237 case COL_SENT:
1238 case COL_RECEIVED:
1239 case COL_SIZE:
1240 case COL_FOLLOWUP_FLAG_STATUS:
1241 case COL_FOLLOWUP_DUE_BY:
1242 return value == NULL;
1243
1244 case COL_FROM:
1245 case COL_SUBJECT:
1246 case COL_TO:
1247 case COL_FOLLOWUP_FLAG:
1248 case COL_LOCATION:
1249 case COL_SENDER:
1250 case COL_RECIPIENTS:
1251 case COL_MIXED_SENDER:
1252 case COL_MIXED_RECIPIENTS:
1253 case COL_LABELS:
1254 return !(value && *(gchar *) value);
1255 default:
1256 g_warning ("This shouldn't be reached\n");
1257 return FALSE;
1258 }
1259 }
1260
1261 static const gchar *status_map[] = {
1262 N_("Unseen"),
1263 N_("Seen"),
1264 N_("Answered"),
1265 N_("Forwarded"),
1266 N_("Multiple Unseen Messages"),
1267 N_("Multiple Messages"),
1268 };
1269
1270 static const gchar *score_map[] = {
1271 N_("Lowest"),
1272 N_("Lower"),
1273 N_("Low"),
1274 N_("Normal"),
1275 N_("High"),
1276 N_("Higher"),
1277 N_("Highest"),
1278 };
1279
1280 static gchar *
1281 ml_value_to_string (ETreeModel *etm,
1282 gint col,
1283 gconstpointer value,
1284 gpointer data)
1285 {
1286 guint i;
1287
1288 switch (col) {
1289 case COL_MESSAGE_STATUS:
1290 i = GPOINTER_TO_UINT (value);
1291 if (i > 5)
1292 return g_strdup ("");
1293 return g_strdup (_(status_map[i]));
1294
1295 case COL_SCORE:
1296 i = GPOINTER_TO_UINT (value) + 3;
1297 if (i > 6)
1298 i = 3;
1299 return g_strdup (_(score_map[i]));
1300
1301 case COL_ATTACHMENT:
1302 case COL_FLAGGED:
1303 case COL_DELETED:
1304 case COL_UNREAD:
1305 case COL_FOLLOWUP_FLAG_STATUS:
1306 return g_strdup_printf ("%u", GPOINTER_TO_UINT (value));
1307
1308 case COL_SENT:
1309 case COL_RECEIVED:
1310 case COL_FOLLOWUP_DUE_BY:
1311 return filter_date (GPOINTER_TO_INT (value));
1312
1313 case COL_SIZE:
1314 return filter_size (GPOINTER_TO_INT (value));
1315
1316 case COL_FROM:
1317 case COL_SUBJECT:
1318 case COL_TO:
1319 case COL_FOLLOWUP_FLAG:
1320 case COL_LOCATION:
1321 case COL_SENDER:
1322 case COL_RECIPIENTS:
1323 case COL_MIXED_SENDER:
1324 case COL_MIXED_RECIPIENTS:
1325 case COL_LABELS:
1326 return g_strdup (value);
1327 default:
1328 g_warning ("This shouldn't be reached\n");
1329 return NULL;
1330 }
1331 }
1332
1333 static GdkPixbuf *
1334 ml_tree_icon_at (ETreeModel *etm,
1335 ETreePath path,
1336 gpointer model_data)
1337 {
1338 /* we dont really need an icon ... */
1339 return NULL;
1340 }
1341
1342 static void
1343 for_node_and_subtree_if_collapsed (MessageList *ml,
1344 ETreePath node,
1345 CamelMessageInfo *mi,
1346 ETreePathFunc func,
1347 gpointer data)
1348 {
1349 ETreeModel *etm = ml->model;
1350 ETreePath child;
1351
1352 func (NULL, (ETreePath) mi, data);
1353
1354 if (!node)
1355 return;
1356
1357 child = e_tree_model_node_get_first_child (etm, node);
1358 if (child && !e_tree_node_is_expanded (E_TREE (ml), node))
1359 e_tree_model_node_traverse (etm, node, func, data);
1360 }
1361
1362 static gboolean
1363 unread_foreach (ETreeModel *etm,
1364 ETreePath node,
1365 gpointer data)
1366 {
1367 gboolean *saw_unread = data;
1368 CamelMessageInfo *info;
1369
1370 if (!etm)
1371 info = (CamelMessageInfo *) node;
1372 else
1373 info = e_tree_memory_node_get_data ((ETreeMemory *) etm, node);
1374 g_return_val_if_fail (info != NULL, FALSE);
1375
1376 if (!(camel_message_info_flags (info) & CAMEL_MESSAGE_SEEN))
1377 *saw_unread = TRUE;
1378
1379 return FALSE;
1380 }
1381
1382 struct LatestData {
1383 gboolean sent;
1384 time_t latest;
1385 };
1386
1387 static gboolean
1388 latest_foreach (ETreeModel *etm,
1389 ETreePath node,
1390 gpointer data)
1391 {
1392 struct LatestData *ld = data;
1393 CamelMessageInfo *info;
1394 time_t date;
1395
1396 if (!etm)
1397 info = (CamelMessageInfo *) node;
1398 else
1399 info = e_tree_memory_node_get_data ((ETreeMemory *) etm, node);
1400 g_return_val_if_fail (info != NULL, FALSE);
1401
1402 date = ld->sent ? camel_message_info_date_sent (info)
1403 : camel_message_info_date_received (info);
1404
1405 if (ld->latest == 0 || date > ld->latest)
1406 ld->latest = date;
1407
1408 return FALSE;
1409 }
1410
1411 static gchar *
1412 sanitize_recipients (const gchar *string)
1413 {
1414 GString *gstring;
1415 gboolean quoted = FALSE;
1416 const gchar *p;
1417 GString *recipients = g_string_new ("");
1418 gchar *single_add;
1419 gchar **name;
1420
1421 if (!string || !*string)
1422 return (gchar *) "";
1423
1424 gstring = g_string_new ("");
1425
1426 for (p = string; *p; p = g_utf8_next_char (p)) {
1427 gunichar c = g_utf8_get_char (p);
1428
1429 if (c == '"')
1430 quoted = ~quoted;
1431 else if (c == ',' && !quoted) {
1432 single_add = g_string_free (gstring, FALSE);
1433 name = g_strsplit (single_add,"<",2);
1434 g_string_append (recipients, *name);
1435 g_string_append (recipients, ",");
1436 g_free (single_add);
1437 g_strfreev (name);
1438 gstring = g_string_new ("");
1439 continue;
1440 }
1441
1442 g_string_append_unichar (gstring, c);
1443 }
1444
1445 single_add = g_string_free (gstring, FALSE);
1446 name = g_strsplit (single_add,"<",2);
1447 g_string_append (recipients, *name);
1448 g_free (single_add);
1449 g_strfreev (name);
1450
1451 return g_string_free (recipients, FALSE);
1452 }
1453
1454 struct LabelsData {
1455 EMailLabelListStore *store;
1456 GHashTable *labels_tag2iter;
1457 };
1458
1459 static void
1460 add_label_if_known (struct LabelsData *ld,
1461 const gchar *tag)
1462 {
1463 GtkTreeIter label_defn;
1464
1465 if (e_mail_label_list_store_lookup (ld->store, tag, &label_defn)) {
1466 g_hash_table_insert (
1467 ld->labels_tag2iter,
1468 /* Should be the same as the "tag" arg */
1469 e_mail_label_list_store_get_tag (ld->store, &label_defn),
1470 gtk_tree_iter_copy (&label_defn));
1471 }
1472 }
1473
1474 static gboolean
1475 add_all_labels_foreach (ETreeModel *etm,
1476 ETreePath node,
1477 gpointer data)
1478 {
1479 struct LabelsData *ld = data;
1480 CamelMessageInfo *msg_info;
1481 const gchar *old_label;
1482 gchar *new_label;
1483 const CamelFlag *flag;
1484
1485 if (!etm)
1486 msg_info = (CamelMessageInfo *) node;
1487 else
1488 msg_info = e_tree_memory_node_get_data ((ETreeMemory *) etm, node);
1489 g_return_val_if_fail (msg_info != NULL, FALSE);
1490
1491 for (flag = camel_message_info_user_flags (msg_info); flag; flag = flag->next)
1492 add_label_if_known (ld, flag->name);
1493
1494 old_label = camel_message_info_user_tag (msg_info, "label");
1495 if (old_label != NULL) {
1496 /* Convert old-style labels ("<name>") to "$Label<name>". */
1497 new_label = g_alloca (strlen (old_label) + 10);
1498 g_stpcpy (g_stpcpy (new_label, "$Label"), old_label);
1499
1500 add_label_if_known (ld, new_label);
1501 }
1502
1503 return FALSE;
1504 }
1505
1506 static const gchar *
1507 get_trimmed_subject (CamelMessageInfo *info)
1508 {
1509 const gchar *subject;
1510 const gchar *mlist;
1511 gint mlist_len = 0;
1512 gboolean found_mlist;
1513
1514 subject = camel_message_info_subject (info);
1515 if (!subject || !*subject)
1516 return subject;
1517
1518 mlist = camel_message_info_mlist (info);
1519
1520 if (mlist && *mlist) {
1521 const gchar *mlist_end;
1522
1523 mlist_end = strchr (mlist, '@');
1524 if (mlist_end)
1525 mlist_len = mlist_end - mlist;
1526 else
1527 mlist_len = strlen (mlist);
1528 }
1529
1530 do {
1531 EShell *shell = e_shell_get_default ();
1532 gint skip_len;
1533 gboolean found_re = TRUE;
1534
1535 found_mlist = FALSE;
1536
1537 while (found_re) {
1538 found_re = FALSE;
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
1539
1540 found_re = em_utils_is_re_in_subject (shell, (const gchar *) subject, &skip_len) && skip_len > 0;
1541 if (found_re)
1542 subject += skip_len;
1543
1544 /* jump over any spaces */
1545 while (*subject && isspace ((gint) *subject))
1546 subject++;
1547 }
1548
1549 if (mlist_len &&
1550 *subject == '[' &&
1551 !g_ascii_strncasecmp ((gchar *) subject + 1, mlist, mlist_len) &&
1552 subject[1 + mlist_len] == ']') {
1553 subject += 1 + mlist_len + 1; /* jump over "[mailing-list]" */
1554 found_mlist = TRUE;
1555
1556 /* jump over any spaces */
1557 while (*subject && isspace ((gint) *subject))
1558 subject++;
1559 }
1560 } while (found_mlist);
1561
1562 /* jump over any spaces */
1563 while (*subject && isspace ((gint) *subject))
1564 subject++;
1565
1566 return subject;
1567 }
1568
1569 static gpointer
1570 ml_tree_value_at_ex (ETreeModel *etm,
1571 ETreePath path,
1572 gint col,
1573 CamelMessageInfo *msg_info,
1574 MessageList *message_list)
1575 {
1576 EMailSession *session;
1577 const gchar *str;
1578 guint32 flags;
1579
1580 session = message_list_get_session (message_list);
1581
1582 g_return_val_if_fail (msg_info != NULL, NULL);
1583
1584 switch (col) {
1585 case COL_MESSAGE_STATUS:
1586 flags = camel_message_info_flags (msg_info);
1587 if (flags & CAMEL_MESSAGE_ANSWERED)
1588 return GINT_TO_POINTER (2);
1589 else if (flags & CAMEL_MESSAGE_FORWARDED)
1590 return GINT_TO_POINTER (3);
1591 else if (flags & CAMEL_MESSAGE_SEEN)
1592 return GINT_TO_POINTER (1);
1593 else
1594 return GINT_TO_POINTER (0);
1595 case COL_FLAGGED:
1596 return GINT_TO_POINTER ((camel_message_info_flags (msg_info) & CAMEL_MESSAGE_FLAGGED) != 0);
1597 case COL_SCORE: {
1598 const gchar *tag;
1599 gint score = 0;
1600
1601 tag = camel_message_info_user_tag (msg_info, "score");
1602 if (tag)
1603 score = atoi (tag);
1604
1605 return GINT_TO_POINTER (score);
1606 }
1607 case COL_FOLLOWUP_FLAG_STATUS: {
1608 const gchar *tag, *cmp;
1609
1610 /* FIXME: this all should be methods off of message-tag-followup class,
1611 * FIXME: the tag names should be namespaced :( */
1612 tag = camel_message_info_user_tag (msg_info, "follow-up");
1613 cmp = camel_message_info_user_tag (msg_info, "completed-on");
1614 if (tag && tag[0]) {
1615 if (cmp && cmp[0])
1616 return GINT_TO_POINTER (2);
1617 else
1618 return GINT_TO_POINTER (1);
1619 } else
1620 return GINT_TO_POINTER (0);
1621 }
1622 case COL_FOLLOWUP_DUE_BY: {
1623 const gchar *tag;
1624 time_t due_by;
1625
1626 tag = camel_message_info_user_tag (msg_info, "due-by");
1627 if (tag && *tag) {
1628 due_by = camel_header_decode_date (tag, NULL);
1629 return GINT_TO_POINTER (due_by);
1630 } else {
1631 return GINT_TO_POINTER (0);
1632 }
1633 }
1634 case COL_FOLLOWUP_FLAG:
1635 str = camel_message_info_user_tag (msg_info, "follow-up");
1636 return (gpointer)(str ? str : "");
1637 case COL_ATTACHMENT:
1638 if (camel_message_info_user_flag (msg_info, "$has_cal"))
1639 return GINT_TO_POINTER (2);
1640 return GINT_TO_POINTER ((camel_message_info_flags (msg_info) & CAMEL_MESSAGE_ATTACHMENTS) != 0);
1641 case COL_FROM:
1642 str = camel_message_info_from (msg_info);
1643 return (gpointer)(str ? str : "");
1644 case COL_FROM_NORM:
1645 return (gpointer) get_normalised_string (message_list, msg_info, col);
1646 case COL_SUBJECT:
1647 str = camel_message_info_subject (msg_info);
1648 return (gpointer)(str ? str : "");
1649 case COL_SUBJECT_TRIMMED:
1650 str = get_trimmed_subject (msg_info);
1651 return (gpointer)(str ? str : "");
1652 case COL_SUBJECT_NORM:
1653 return (gpointer) get_normalised_string (message_list, msg_info, col);
1654 case COL_SENT: {
1655 struct LatestData ld;
1656 ld.sent = TRUE;
1657 ld.latest = 0;
1658
1659 for_node_and_subtree_if_collapsed (message_list, path, msg_info, latest_foreach, &ld);
1660
1661 return GINT_TO_POINTER (ld.latest);
1662 }
1663 case COL_RECEIVED: {
1664 struct LatestData ld;
1665 ld.sent = FALSE;
1666 ld.latest = 0;
1667
1668 for_node_and_subtree_if_collapsed (message_list, path, msg_info, latest_foreach, &ld);
1669
1670 return GINT_TO_POINTER (ld.latest);
1671 }
1672 case COL_TO:
1673 str = camel_message_info_to (msg_info);
1674 return (gpointer)(str ? str : "");
1675 case COL_TO_NORM:
1676 return (gpointer) get_normalised_string (message_list, msg_info, col);
1677 case COL_SIZE:
1678 return GINT_TO_POINTER (camel_message_info_size (msg_info));
1679 case COL_DELETED:
1680 return GINT_TO_POINTER ((camel_message_info_flags (msg_info) & CAMEL_MESSAGE_DELETED) != 0);
1681 case COL_UNREAD: {
1682 gboolean saw_unread = FALSE;
1683
1684 for_node_and_subtree_if_collapsed (message_list, path, msg_info, unread_foreach, &saw_unread);
1685
1686 return GINT_TO_POINTER (saw_unread);
1687 }
1688 case COL_COLOUR: {
1689 const gchar *colour, *due_by, *completed, *followup;
1690
1691 /* Priority: colour tag; label tag; important flag; due-by tag */
1692
1693 /* This is astonisngly poorly written code */
1694
1695 /* To add to the woes, what color to show when the user choose multiple labels ?
1696 Don't say that I need to have the new labels[with subject] column visible always */
1697
1698 colour = NULL;
1699 due_by = camel_message_info_user_tag (msg_info, "due-by");
1700 completed = camel_message_info_user_tag (msg_info, "completed-on");
1701 followup = camel_message_info_user_tag (msg_info, "follow-up");
1702 if (colour == NULL) {
1703 /* Get all applicable labels. */
1704 struct LabelsData ld;
1705
1706 ld.store = e_mail_ui_session_get_label_store (
1707 E_MAIL_UI_SESSION (session));
1708 ld.labels_tag2iter = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gtk_tree_iter_free);
1709 for_node_and_subtree_if_collapsed (message_list, path, msg_info, add_all_labels_foreach, &ld);
1710
1711 if (g_hash_table_size (ld.labels_tag2iter) == 1) {
1712 GHashTableIter iter;
1713 GtkTreeIter *label_defn;
1714 GdkColor colour_val;
1715 gchar *colour_alloced;
1716
1717 /* Extract the single label from the hashtable. */
1718 g_hash_table_iter_init (&iter, ld.labels_tag2iter);
1719 g_hash_table_iter_next (&iter, NULL, (gpointer *) &label_defn);
1720
1721 e_mail_label_list_store_get_color (ld.store, label_defn, &colour_val);
1722
1723 /* XXX Hack to avoid returning an allocated string. */
1724 colour_alloced = gdk_color_to_string (&colour_val);
1725 colour = g_intern_string (colour_alloced);
1726 g_free (colour_alloced);
1727 } else if (camel_message_info_flags (msg_info) & CAMEL_MESSAGE_FLAGGED) {
1728 /* FIXME: extract from the important.xpm somehow. */
1729 colour = "#A7453E";
1730 } else if (((followup && *followup) || (due_by && *due_by)) && !(completed && *completed)) {
1731 time_t now = time (NULL);
1732
1733 if ((followup && *followup) || now >= camel_header_decode_date (due_by, NULL))
1734 colour = "#A7453E";
1735 }
1736
1737 g_hash_table_destroy (ld.labels_tag2iter);
1738 }
1739
1740 if (!colour)
1741 colour = camel_message_info_user_tag (msg_info, "color");
1742
1743 return (gpointer) colour;
1744 }
1745 case COL_LOCATION: {
1746 /* Fixme : freeing memory stuff (mem leaks) */
1747 CamelStore *store;
1748 CamelFolder *folder;
1749 CamelService *service;
1750 const gchar *store_name;
1751 const gchar *folder_name;
1752
1753 folder = message_list->folder;
1754
1755 if (CAMEL_IS_VEE_FOLDER (folder))
1756 folder = camel_vee_folder_get_location (
1757 CAMEL_VEE_FOLDER (folder),
1758 (CamelVeeMessageInfo *) msg_info, NULL);
1759
1760 store = camel_folder_get_parent_store (folder);
1761 folder_name = camel_folder_get_full_name (folder);
1762
1763 service = CAMEL_SERVICE (store);
1764 store_name = camel_service_get_display_name (service);
1765
1766 return g_strdup_printf ("%s : %s", store_name, folder_name);
1767 }
1768 case COL_MIXED_RECIPIENTS:
1769 case COL_RECIPIENTS:{
1770 str = camel_message_info_to (msg_info);
1771
1772 return sanitize_recipients (str);
1773 }
1774 case COL_MIXED_SENDER:
1775 case COL_SENDER:{
1776 gchar **sender_name = NULL;
1777 str = camel_message_info_from (msg_info);
1778 if (str && str[0] != '\0') {
1779 gchar *res;
1780 sender_name = g_strsplit (str,"<",2);
1781 res = g_strdup (*sender_name);
1782 g_strfreev (sender_name);
1783 return (gpointer)(res);
1784 }
1785 else
1786 return (gpointer)("");
1787 }
1788 case COL_LABELS:{
1789 struct LabelsData ld;
1790 GString *result = g_string_new ("");
1791
1792 ld.store = e_mail_ui_session_get_label_store (
1793 E_MAIL_UI_SESSION (session));
1794 ld.labels_tag2iter = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gtk_tree_iter_free);
1795 for_node_and_subtree_if_collapsed (message_list, path, msg_info, add_all_labels_foreach, &ld);
1796
1797 if (g_hash_table_size (ld.labels_tag2iter) > 0) {
1798 GHashTableIter iter;
1799 GtkTreeIter *label_defn;
1800
1801 g_hash_table_iter_init (&iter, ld.labels_tag2iter);
1802 while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &label_defn)) {
1803 gchar *label_name, *label_name_clean;
1804
1805 if (result->len > 0)
1806 g_string_append (result, ", ");
1807
1808 label_name = e_mail_label_list_store_get_name (ld.store, label_defn);
1809 label_name_clean = e_str_without_underscores (label_name);
1810
1811 g_string_append (result, label_name_clean);
1812
1813 g_free (label_name_clean);
1814 g_free (label_name);
1815 }
1816 }
1817
1818 g_hash_table_destroy (ld.labels_tag2iter);
1819 return (gpointer) g_string_free (result, FALSE);
1820 }
1821 default:
1822 g_warning ("%s: This shouldn't be reached (col:%d)", G_STRFUNC, col);
1823 return NULL;
1824 }
1825 }
1826
1827 static gpointer
1828 ml_tree_value_at (ETreeModel *etm,
1829 ETreePath path,
1830 gint col,
1831 gpointer model_data)
1832 {
1833 MessageList *message_list = model_data;
1834 CamelMessageInfo *msg_info;
1835
1836 if (e_tree_model_node_is_root (etm, path))
1837 return NULL;
1838
1839 /* retrieve the message information array */
1840 msg_info = e_tree_memory_node_get_data (E_TREE_MEMORY (etm), path);
1841 g_return_val_if_fail (msg_info != NULL, NULL);
1842
1843 return ml_tree_value_at_ex (etm, path, col, msg_info, message_list);
1844 }
1845
1846 static gpointer
1847 ml_tree_sort_value_at (ETreeModel *etm,
1848 ETreePath path,
1849 gint col,
1850 gpointer model_data)
1851 {
1852 MessageList *message_list = model_data;
1853 struct LatestData ld;
1854
1855 if (!(col == COL_SENT || col == COL_RECEIVED))
1856 return ml_tree_value_at (etm, path, col, model_data);
1857
1858 if (e_tree_model_node_is_root (etm, path))
1859 return NULL;
1860
1861 ld.sent = (col == COL_SENT);
1862 ld.latest = 0;
1863
1864 latest_foreach (etm, path, &ld);
1865 if (message_list->priv->thread_latest)
1866 e_tree_model_node_traverse (etm, path, latest_foreach, &ld);
1867
1868 return GINT_TO_POINTER (ld.latest);
1869 }
1870
1871 static void
1872 ml_tree_set_value_at (ETreeModel *etm,
1873 ETreePath path,
1874 gint col,
1875 gconstpointer val,
1876 gpointer model_data)
1877 {
1878 g_warning ("This shouldn't be reached\n");
1879 }
1880
1881 static gboolean
1882 ml_tree_is_cell_editable (ETreeModel *etm,
1883 ETreePath path,
1884 gint col,
1885 gpointer model_data)
1886 {
1887 return FALSE;
1888 }
1889
1890 static gchar *
1891 filter_date (time_t date)
1892 {
1893 time_t nowdate = time (NULL);
1894 time_t yesdate;
1895 struct tm then, now, yesterday;
1896 gchar buf[26];
1897 gboolean done = FALSE;
1898
1899 if (date == 0)
1900 return g_strdup (_("?"));
1901
1902 localtime_r (&date, &then);
1903 localtime_r (&nowdate, &now);
1904 if (then.tm_mday == now.tm_mday &&
1905 then.tm_mon == now.tm_mon &&
1906 then.tm_year == now.tm_year) {
1907 e_utf8_strftime_fix_am_pm (buf, 26, _("Today %l:%M %p"), &then);
1908 done = TRUE;
1909 }
1910 if (!done) {
1911 yesdate = nowdate - 60 * 60 * 24;
1912 localtime_r (&yesdate, &yesterday);
1913 if (then.tm_mday == yesterday.tm_mday &&
1914 then.tm_mon == yesterday.tm_mon &&
1915 then.tm_year == yesterday.tm_year) {
1916 e_utf8_strftime_fix_am_pm (buf, 26, _("Yesterday %l:%M %p"), &then);
1917 done = TRUE;
1918 }
1919 }
1920 if (!done) {
1921 gint i;
1922 for (i = 2; i < 7; i++) {
1923 yesdate = nowdate - 60 * 60 * 24 * i;
1924 localtime_r (&yesdate, &yesterday);
1925 if (then.tm_mday == yesterday.tm_mday &&
1926 then.tm_mon == yesterday.tm_mon &&
1927 then.tm_year == yesterday.tm_year) {
1928 e_utf8_strftime_fix_am_pm (buf, 26, _("%a %l:%M %p"), &then);
1929 done = TRUE;
1930 break;
1931 }
1932 }
1933 }
1934 if (!done) {
1935 if (then.tm_year == now.tm_year) {
1936 e_utf8_strftime_fix_am_pm (buf, 26, _("%b %d %l:%M %p"), &then);
1937 } else {
1938 e_utf8_strftime_fix_am_pm (buf, 26, _("%b %d %Y"), &then);
1939 }
1940 }
1941 #if 0
1942 #ifdef CTIME_R_THREE_ARGS
1943 ctime_r (&date, buf, 26);
1944 #else
1945 ctime_r (&date, buf);
1946 #endif
1947 #endif
1948
1949 return g_strdup (buf);
1950 }
1951
1952 static ECell * create_composite_cell (gint col)
1953 {
1954 ECell *cell_vbox, *cell_hbox, *cell_sub, *cell_date, *cell_from, *cell_tree, *cell_attach;
1955 GSettings *settings;
1956 gchar *fixed_name = NULL;
1957 gboolean show_email;
1958 gint alt_col = (col == COL_FROM) ? COL_SENDER : COL_RECIPIENTS;
1959 gboolean same_font = FALSE;
1960
1961 settings = g_settings_new ("org.gnome.evolution.mail");
1962 show_email = g_settings_get_boolean (settings, "show-email");
1963 same_font = g_settings_get_boolean (settings, "vertical-view-fonts");
1964 g_object_unref (settings);
1965 if (!same_font) {
1966 settings = g_settings_new ("org.gnome.desktop.interface");
1967 fixed_name = g_settings_get_string (settings, "monospace-font-name");
1968 g_object_unref (settings);
1969 }
1970
1971 cell_vbox = e_cell_vbox_new ();
1972
1973 cell_hbox = e_cell_hbox_new ();
1974
1975 /* Exclude the meeting icon. */
1976 cell_attach = e_cell_toggle_new (attachment_icons, G_N_ELEMENTS (attachment_icons));
1977
1978 cell_date = e_cell_date_new (NULL, GTK_JUSTIFY_RIGHT);
1979 e_cell_date_set_format_component (E_CELL_DATE (cell_date), "mail");
1980 g_object_set (
1981 cell_date,
1982 "bold_column", COL_UNREAD,
1983 "color_column", COL_COLOUR,
1984 NULL);
1985
1986 cell_from = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT);
1987 g_object_set (
1988 cell_from,
1989 "bold_column", COL_UNREAD,
1990 "color_column", COL_COLOUR,
1991 NULL);
1992
1993 e_cell_hbox_append (E_CELL_HBOX (cell_hbox), cell_from, show_email ? col : alt_col, 68);
1994 e_cell_hbox_append (E_CELL_HBOX (cell_hbox), cell_attach, COL_ATTACHMENT, 5);
1995 e_cell_hbox_append (E_CELL_HBOX (cell_hbox), cell_date, COL_SENT, 27);
1996 g_object_unref (cell_from);
1997 g_object_unref (cell_attach);
1998 g_object_unref (cell_date);
1999
2000 cell_sub = e_cell_text_new (fixed_name? fixed_name : NULL, GTK_JUSTIFY_LEFT);
2001 g_object_set (
2002 cell_sub,
2003 "color_column", COL_COLOUR,
2004 NULL);
2005 cell_tree = e_cell_tree_new (TRUE, cell_sub);
2006 e_cell_vbox_append (E_CELL_VBOX (cell_vbox), cell_hbox, COL_FROM);
2007 e_cell_vbox_append (E_CELL_VBOX (cell_vbox), cell_tree, COL_SUBJECT);
2008 g_object_unref (cell_sub);
2009 g_object_unref (cell_hbox);
2010 g_object_unref (cell_tree);
2011
2012 g_object_set_data (G_OBJECT (cell_vbox), "cell_date", cell_date);
2013 g_object_set_data (G_OBJECT (cell_vbox), "cell_sub", cell_sub);
2014 g_object_set_data (G_OBJECT (cell_vbox), "cell_from", cell_from);
2015
2016 g_free (fixed_name);
2017
2018 return cell_vbox;
2019 }
2020
2021 static void
2022 composite_cell_set_strike_col (ECell *cell,
2023 gint col)
2024 {
2025 g_object_set (g_object_get_data (G_OBJECT (cell), "cell_date"), "strikeout_column", col, NULL);
2026 g_object_set (g_object_get_data (G_OBJECT (cell), "cell_from"), "strikeout_column", col, NULL);
2027 }
2028
2029 static ETableExtras *
2030 message_list_create_extras (void)
2031 {
2032 ETableExtras *extras;
2033 ECell *cell;
2034
2035 extras = e_table_extras_new ();
2036 e_table_extras_add_icon_name (extras, "status", "mail-unread");
2037 e_table_extras_add_icon_name (extras, "score", "stock_score-higher");
2038 e_table_extras_add_icon_name (extras, "attachment", "mail-attachment");
2039 e_table_extras_add_icon_name (extras, "flagged", "emblem-important");
2040 e_table_extras_add_icon_name (extras, "followup", "stock_mail-flag-for-followup");
2041
2042 e_table_extras_add_compare (extras, "address_compare", address_compare);
2043
2044 cell = e_cell_toggle_new (
2045 status_icons, G_N_ELEMENTS (status_icons));
2046 e_table_extras_add_cell (extras, "render_message_status", cell);
2047 g_object_unref (cell);
2048
2049 cell = e_cell_toggle_new (
2050 attachment_icons, G_N_ELEMENTS (attachment_icons));
2051 e_table_extras_add_cell (extras, "render_attachment", cell);
2052 g_object_unref (cell);
2053
2054 cell = e_cell_toggle_new (
2055 flagged_icons, G_N_ELEMENTS (flagged_icons));
2056 e_table_extras_add_cell (extras, "render_flagged", cell);
2057 g_object_unref (cell);
2058
2059 cell = e_cell_toggle_new (
2060 followup_icons, G_N_ELEMENTS (followup_icons));
2061 e_table_extras_add_cell (extras, "render_flag_status", cell);
2062 g_object_unref (cell);
2063
2064 cell = e_cell_toggle_new (
2065 score_icons, G_N_ELEMENTS (score_icons));
2066 e_table_extras_add_cell (extras, "render_score", cell);
2067 g_object_unref (cell);
2068
2069 /* date cell */
2070 cell = e_cell_date_new (NULL, GTK_JUSTIFY_LEFT);
2071 e_cell_date_set_format_component (E_CELL_DATE (cell), "mail");
2072 g_object_set (
2073 cell,
2074 "bold_column", COL_UNREAD,
2075 "color_column", COL_COLOUR,
2076 NULL);
2077 e_table_extras_add_cell (extras, "render_date", cell);
2078 g_object_unref (cell);
2079
2080 /* text cell */
2081 cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT);
2082 g_object_set (
2083 cell,
2084 "bold_column", COL_UNREAD,
2085 "color_column", COL_COLOUR,
2086 NULL);
2087 e_table_extras_add_cell (extras, "render_text", cell);
2088 g_object_unref (cell);
2089
2090 cell = e_cell_tree_new (TRUE, cell);
2091 e_table_extras_add_cell (extras, "render_tree", cell);
2092 g_object_unref (cell);
2093
2094 /* size cell */
2095 cell = e_cell_size_new (NULL, GTK_JUSTIFY_RIGHT);
2096 g_object_set (
2097 cell,
2098 "bold_column", COL_UNREAD,
2099 "color_column", COL_COLOUR,
2100 NULL);
2101 e_table_extras_add_cell (extras, "render_size", cell);
2102 g_object_unref (cell);
2103
2104 /* Composite cell for wide view */
2105 cell = create_composite_cell (COL_FROM);
2106 e_table_extras_add_cell (extras, "render_composite_from", cell);
2107 g_object_unref (cell);
2108
2109 cell = create_composite_cell (COL_TO);
2110 e_table_extras_add_cell (extras, "render_composite_to", cell);
2111 g_object_unref (cell);
2112
2113 /* set proper format component for a default 'date' cell renderer */
2114 cell = e_table_extras_get_cell (extras, "date");
2115 e_cell_date_set_format_component (E_CELL_DATE (cell), "mail");
2116
2117 return extras;
2118 }
2119
2120 static void
2121 save_tree_state (MessageList *ml)
2122 {
2123 gchar *filename;
2124
2125 if (ml->folder == NULL || (ml->search && *ml->search))
2126 return;
2127
2128 filename = mail_config_folder_to_cachename (ml->folder, "et-expanded-");
2129 e_tree_save_expanded_state (E_TREE (ml), filename);
2130 g_free (filename);
2131
2132 ml->priv->any_row_changed = FALSE;
2133 }
2134
2135 static void
2136 load_tree_state (MessageList *ml,
2137 xmlDoc *expand_state)
2138 {
2139 if (ml->folder == NULL)
2140 return;
2141
2142 if (expand_state) {
2143 e_tree_load_expanded_state_xml (E_TREE (ml), expand_state);
2144 } else if (!ml->search || !*ml->search) {
2145 /* only when not searching */
2146 gchar *filename;
2147
2148 filename = mail_config_folder_to_cachename (ml->folder, "et-expanded-");
2149 e_tree_load_expanded_state (E_TREE (ml), filename);
2150 g_free (filename);
2151 }
2152
2153 ml->priv->any_row_changed = FALSE;
2154 }
2155
2156 void
2157 message_list_save_state (MessageList *ml)
2158 {
2159 save_tree_state (ml);
2160 }
2161
2162 static void
2163 message_list_setup_etree (MessageList *message_list,
2164 gboolean outgoing)
2165 {
2166 /* build the spec based on the folder, and possibly from a saved file */
2167 /* otherwise, leave default */
2168 if (message_list->folder) {
2169 gint data = 1;
2170 ETableItem *item;
2171
2172 item = e_tree_get_item (E_TREE (message_list));
2173
2174 g_object_set (message_list, "uniform_row_height", TRUE, NULL);
2175 g_object_set_data (G_OBJECT (((GnomeCanvasItem *) item)->canvas), "freeze-cursor", &data);
2176
2177 /* build based on saved file */
2178 load_tree_state (message_list, NULL);
2179 }
2180 }
2181
2182 static void
2183 ml_selection_get (GtkWidget *widget,
2184 GtkSelectionData *data,
2185 guint info,
2186 guint time_stamp,
2187 MessageList *ml)
2188 {
2189 struct _MLSelection *selection;
2190
2191 selection = &ml->priv->clipboard;
2192
2193 if (selection->uids == NULL)
2194 return;
2195
2196 if (info & 2) {
2197 /* text/plain */
2198 d (printf ("setting text/plain selection for uids\n"));
2199 em_utils_selection_set_mailbox (data, selection->folder, selection->uids);
2200 } else {
2201 /* x-uid-list */
2202 d (printf ("setting x-uid-list selection for uids\n"));
2203 em_utils_selection_set_uidlist (data, selection->folder, selection->uids);
2204 }
2205 }
2206
2207 static gboolean
2208 ml_selection_clear_event (GtkWidget *widget,
2209 GdkEventSelection *event,
2210 MessageList *ml)
2211 {
2212 MessageListPrivate *p = ml->priv;
2213
2214 clear_selection (ml, &p->clipboard);
2215
2216 return TRUE;
2217 }
2218
2219 static void
2220 ml_selection_received (GtkWidget *widget,
2221 GtkSelectionData *selection_data,
2222 guint time,
2223 MessageList *message_list)
2224 {
2225 EMailSession *session;
2226 GdkAtom target;
2227
2228 target = gtk_selection_data_get_target (selection_data);
2229
2230 if (target != gdk_atom_intern ("x-uid-list", FALSE)) {
2231 d (printf ("Unknown selection received by message-list\n"));
2232 return;
2233 }
2234
2235 session = message_list_get_session (message_list);
2236
2237 /* FIXME Not passing a GCancellable or GError here. */
2238 em_utils_selection_get_uidlist (
2239 selection_data, session, message_list->folder,
2240 FALSE, NULL, NULL);
2241 }
2242
2243 static void
2244 ml_tree_drag_data_get (ETree *tree,
2245 gint row,
2246 ETreePath path,
2247 gint col,
2248 GdkDragContext *context,
2249 GtkSelectionData *data,
2250 guint info,
2251 guint time,
2252 MessageList *ml)
2253 {
2254 GPtrArray *uids;
2255
2256 uids = message_list_get_selected (ml);
2257
2258 if (uids->len > 0) {
2259 switch (info) {
2260 case DND_X_UID_LIST:
2261 em_utils_selection_set_uidlist (data, ml->folder, uids);
2262 break;
2263 case DND_TEXT_URI_LIST:
2264 em_utils_selection_set_urilist (data, ml->folder, uids);
2265 break;
2266 }
2267 }
2268
2269 em_utils_uids_free (uids);
2270 }
2271
2272 /* TODO: merge this with the folder tree stuff via empopup targets */
2273 /* Drop handling */
2274 struct _drop_msg {
2275 MailMsg base;
2276
2277 GdkDragContext *context;
2278
2279 /* Only selection->data and selection->length are valid */
2280 GtkSelectionData *selection;
2281
2282 CamelFolder *folder;
2283 MessageList *message_list;
2284
2285 guint32 action;
2286 guint info;
2287
2288 guint move : 1;
2289 guint moved : 1;
2290 guint aborted : 1;
2291 };
2292
2293 static gchar *
2294 ml_drop_async_desc (struct _drop_msg *m)
2295 {
2296 const gchar *full_name;
2297
2298 full_name = camel_folder_get_full_name (m->folder);
2299
2300 if (m->move)
2301 return g_strdup_printf (_("Moving messages into folder %s"), full_name);
2302 else
2303 return g_strdup_printf (_("Copying messages into folder %s"), full_name);
2304 }
2305
2306 static void
2307 ml_drop_async_exec (struct _drop_msg *m,
2308 GCancellable *cancellable,
2309 GError **error)
2310 {
2311 EMailSession *session;
2312
2313 session = message_list_get_session (m->message_list);
2314
2315 switch (m->info) {
2316 case DND_X_UID_LIST:
2317 em_utils_selection_get_uidlist (
2318 m->selection, session, m->folder,
2319 m->action == GDK_ACTION_MOVE,
2320 cancellable, error);
2321 break;
2322 case DND_MESSAGE_RFC822:
2323 em_utils_selection_get_message (m->selection, m->folder);
2324 break;
2325 case DND_TEXT_URI_LIST:
2326 em_utils_selection_get_urilist (m->selection, m->folder);
2327 break;
2328 }
2329 }
2330
2331 static void
2332 ml_drop_async_done (struct _drop_msg *m)
2333 {
2334 gboolean success, delete;
2335
2336 /* ?? */
2337 if (m->aborted) {
2338 success = FALSE;
2339 delete = FALSE;
2340 } else {
2341 success = (m->base.error == NULL);
2342 delete = success && m->move && !m->moved;
2343 }
2344
2345 gtk_drag_finish (m->context, success, delete, GDK_CURRENT_TIME);
2346 }
2347
2348 static void
2349 ml_drop_async_free (struct _drop_msg *m)
2350 {
2351 g_object_unref (m->context);
2352 g_object_unref (m->folder);
2353 g_object_unref (m->message_list);
2354 gtk_selection_data_free (m->selection);
2355 }
2356
2357 static MailMsgInfo ml_drop_async_info = {
2358 sizeof (struct _drop_msg),
2359 (MailMsgDescFunc) ml_drop_async_desc,
2360 (MailMsgExecFunc) ml_drop_async_exec,
2361 (MailMsgDoneFunc) ml_drop_async_done,
2362 (MailMsgFreeFunc) ml_drop_async_free
2363 };
2364
2365 static void
2366 ml_drop_action (struct _drop_msg *m)
2367 {
2368 m->move = m->action == GDK_ACTION_MOVE;
2369 mail_msg_unordered_push (m);
2370 }
2371
2372 static void
2373 ml_tree_drag_data_received (ETree *tree,
2374 gint row,
2375 ETreePath path,
2376 gint col,
2377 GdkDragContext *context,
2378 gint x,
2379 gint y,
2380 GtkSelectionData *selection_data,
2381 guint info,
2382 guint time,
2383 MessageList *ml)
2384 {
2385 struct _drop_msg *m;
2386
2387 if (ml->folder == NULL)
2388 return;
2389
2390 if (gtk_selection_data_get_data (selection_data) == NULL)
2391 return;
2392
2393 if (gtk_selection_data_get_length (selection_data) == -1)
2394 return;
2395
2396 m = mail_msg_new (&ml_drop_async_info);
2397 m->context = g_object_ref (context);
2398 m->folder = g_object_ref (ml->folder);
2399 m->message_list = g_object_ref (ml);
2400 m->action = gdk_drag_context_get_selected_action (context);
2401 m->info = info;
2402
2403 /* need to copy, goes away once we exit */
2404 m->selection = gtk_selection_data_copy (selection_data);
2405
2406 ml_drop_action (m);
2407 }
2408
2409 struct search_child_struct {
2410 gboolean found;
2411 gconstpointer looking_for;
2412 };
2413
2414 static void
2415 search_child_cb (GtkWidget *widget,
2416 gpointer data)
2417 {
2418 struct search_child_struct *search = (struct search_child_struct *) data;
2419
2420 search->found = search->found || g_direct_equal (widget, search->looking_for);
2421 }
2422
2423 static gboolean
2424 is_tree_widget_children (ETree *tree,
2425 gconstpointer widget)
2426 {
2427 struct search_child_struct search;
2428
2429 search.found = FALSE;
2430 search.looking_for = widget;
2431
2432 gtk_container_foreach (GTK_CONTAINER (tree), search_child_cb, &search);
2433
2434 return search.found;
2435 }
2436
2437 static gboolean
2438 ml_tree_drag_motion (ETree *tree,
2439 GdkDragContext *context,
2440 gint x,
2441 gint y,
2442 guint time,
2443 MessageList *ml)
2444 {
2445 GList *targets;
2446 GdkDragAction action, actions = 0;
2447 GtkWidget *source_widget;
2448
2449 /* If drop target is name of the account/store and not actual folder, don't allow any action */
2450 if (!ml->folder) {
2451 gdk_drag_status (context, 0, time);
2452 return TRUE;
2453 }
2454
2455 source_widget = gtk_drag_get_source_widget (context);
2456
2457 /* If source widget is packed under 'tree', don't allow any action */
2458 if (is_tree_widget_children (tree, source_widget)) {
2459 gdk_drag_status (context, 0, time);
2460 return TRUE;
2461 }
2462
2463 if (EM_IS_FOLDER_TREE (source_widget)) {
2464 EMFolderTree *folder_tree;
2465 CamelFolder *folder = NULL;
2466 CamelStore *selected_store;
2467 gchar *selected_folder_name;
2468 gboolean has_selection;
2469
2470 folder_tree = EM_FOLDER_TREE (source_widget);
2471
2472 has_selection = em_folder_tree_get_selected (
2473 folder_tree, &selected_store, &selected_folder_name);
2474
2475 /* Sanity checks */
2476 g_warn_if_fail (
2477 (has_selection && selected_store != NULL) ||
2478 (!has_selection && selected_store == NULL));
2479 g_warn_if_fail (
2480 (has_selection && selected_folder_name != NULL) ||
2481 (!has_selection && selected_folder_name == NULL));
2482
2483 if (has_selection) {
2484 folder = camel_store_get_folder_sync (
2485 selected_store, selected_folder_name,
2486 CAMEL_STORE_FOLDER_INFO_FAST, NULL, NULL);
2487 g_object_unref (selected_store);
2488 g_free (selected_folder_name);
2489 }
2490
2491 if (folder == ml->folder) {
2492 gdk_drag_status (context, 0, time);
2493 return TRUE;
2494 }
2495 }
2496
2497 targets = gdk_drag_context_list_targets (context);
2498 while (targets != NULL) {
2499 gint i;
2500
2501 d (printf ("atom drop '%s'\n", gdk_atom_name (targets->data)));
2502 for (i = 0; i < G_N_ELEMENTS (ml_drag_info); i++)
2503 if (targets->data == (gpointer) ml_drag_info[i].atom)
2504 actions |= ml_drag_info[i].actions;
2505
2506 targets = g_list_next (targets);
2507 }
2508 d (printf ("\n"));
2509
2510 actions &= gdk_drag_context_get_actions (context);
2511 action = gdk_drag_context_get_suggested_action (context);
2512 if (action == GDK_ACTION_COPY && (actions & GDK_ACTION_MOVE))
2513 action = GDK_ACTION_MOVE;
2514
2515 gdk_drag_status (context, action, time);
2516
2517 return action != 0;
2518 }
2519
2520 static void
2521 on_model_row_changed (ETableModel *model,
2522 gint row,
2523 MessageList *ml)
2524 {
2525 ml->priv->any_row_changed = TRUE;
2526 }
2527
2528 static gboolean
2529 ml_tree_sorting_changed (ETreeTableAdapter *adapter,
2530 MessageList *ml)
2531 {
2532 g_return_val_if_fail (ml != NULL, FALSE);
2533
2534 if (ml->threaded && ml->frozen == 0) {
2535 if (ml->thread_tree) {
2536 /* free the previous thread_tree to recreate it fully */
2537 camel_folder_thread_messages_unref (ml->thread_tree);
2538 ml->thread_tree = NULL;
2539 }
2540
2541 mail_regen_list (ml, ml->search, NULL, NULL, TRUE);
2542
2543 return TRUE;
2544 }
2545
2546 return FALSE;
2547 }
2548
2549 static void
2550 message_list_set_session (MessageList *message_list,
2551 EMailSession *session)
2552 {
2553 g_return_if_fail (E_IS_MAIL_SESSION (session));
2554 g_return_if_fail (message_list->priv->session == NULL);
2555
2556 message_list->priv->session = g_object_ref (session);
2557 }
2558
2559 static void
2560 message_list_init (MessageList *message_list)
2561 {
2562 MessageListPrivate *p;
2563 GtkTargetList *target_list;
2564 GdkAtom matom;
2565
2566 message_list->priv = MESSAGE_LIST_GET_PRIVATE (message_list);
2567
2568 message_list->normalised_hash = g_hash_table_new_full (
2569 g_str_hash, g_str_equal,
2570 (GDestroyNotify) NULL,
2571 (GDestroyNotify) e_poolv_destroy);
2572
2573 message_list->search = NULL;
2574 message_list->ensure_uid = NULL;
2575
2576 message_list->uid_nodemap = g_hash_table_new (g_str_hash, g_str_equal);
2577
2578 message_list->cursor_uid = NULL;
2579 message_list->last_sel_single = FALSE;
2580
2581 message_list->regen_lock = g_mutex_new ();
2582
2583 /* TODO: Should this only get the selection if we're realised? */
2584 p = message_list->priv;
2585 p->invisible = gtk_invisible_new ();
2586 p->destroyed = FALSE;
2587 g_object_ref_sink (p->invisible);
2588 p->any_row_changed = FALSE;
2589
2590 matom = gdk_atom_intern ("x-uid-list", FALSE);
2591 gtk_selection_add_target (p->invisible, GDK_SELECTION_CLIPBOARD, matom, 0);
2592 gtk_selection_add_target (p->invisible, GDK_SELECTION_CLIPBOARD, GDK_SELECTION_TYPE_STRING, 2);
2593
2594 g_signal_connect (
2595 p->invisible, "selection_get",
2596 G_CALLBACK (ml_selection_get), message_list);
2597 g_signal_connect (
2598 p->invisible, "selection_clear_event",
2599 G_CALLBACK (ml_selection_clear_event), message_list);
2600 g_signal_connect (
2601 p->invisible, "selection_received",
2602 G_CALLBACK (ml_selection_received), message_list);
2603
2604 /* FIXME This is currently unused. */
2605 target_list = gtk_target_list_new (NULL, 0);
2606 message_list->priv->copy_target_list = target_list;
2607
2608 /* FIXME This is currently unused. */
2609 target_list = gtk_target_list_new (NULL, 0);
2610 message_list->priv->paste_target_list = target_list;
2611 }
2612
2613 static void
2614 message_list_set_property (GObject *object,
2615 guint property_id,
2616 const GValue *value,
2617 GParamSpec *pspec)
2618 {
2619 switch (property_id) {
2620 case PROP_SESSION:
2621 message_list_set_session (
2622 MESSAGE_LIST (object),
2623 g_value_get_object (value));
2624 return;
2625 }
2626
2627 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2628 }
2629
2630 static void
2631 message_list_get_property (GObject *object,
2632 guint property_id,
2633 GValue *value,
2634 GParamSpec *pspec)
2635 {
2636 switch (property_id) {
2637 case PROP_COPY_TARGET_LIST:
2638 g_value_set_boxed (
2639 value, message_list_get_copy_target_list (
2640 MESSAGE_LIST (object)));
2641 return;
2642
2643 case PROP_PASTE_TARGET_LIST:
2644 g_value_set_boxed (
2645 value, message_list_get_paste_target_list (
2646 MESSAGE_LIST (object)));
2647 return;
2648
2649 case PROP_SESSION:
2650 g_value_set_object (
2651 value, message_list_get_session (
2652 MESSAGE_LIST (object)));
2653 return;
2654 }
2655
2656 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2657 }
2658
2659 static void
2660 message_list_dispose (GObject *object)
2661 {
2662 MessageList *message_list = MESSAGE_LIST (object);
2663 MessageListPrivate *priv;
2664
2665 priv = message_list->priv;
2666
2667 if (priv->session != NULL) {
2668 g_object_unref (priv->session);
2669 priv->session = NULL;
2670 }
2671
2672 if (priv->copy_target_list != NULL) {
2673 gtk_target_list_unref (priv->copy_target_list);
2674 priv->copy_target_list = NULL;
2675 }
2676
2677 if (priv->paste_target_list != NULL) {
2678 gtk_target_list_unref (priv->paste_target_list);
2679 priv->paste_target_list = NULL;
2680 }
2681
2682 priv->destroyed = TRUE;
2683
2684 if (message_list->folder)
2685 mail_regen_cancel (message_list);
2686
2687 if (message_list->uid_nodemap) {
2688 g_hash_table_foreach (message_list->uid_nodemap, (GHFunc) clear_info, message_list);
2689 g_hash_table_destroy (message_list->uid_nodemap);
2690 message_list->uid_nodemap = NULL;
2691 }
2692
2693 if (message_list->folder) {
2694 g_signal_handlers_disconnect_by_func (
2695 message_list->folder, folder_changed, message_list);
2696 g_object_unref (message_list->folder);
2697 message_list->folder = NULL;
2698 }
2699
2700 if (priv->invisible) {
2701 g_object_unref (priv->invisible);
2702 priv->invisible = NULL;
2703 }
2704
2705 if (message_list->extras) {
2706 g_object_unref (message_list->extras);
2707 message_list->extras = NULL;
2708 }
2709
2710 if (message_list->model) {
2711 g_object_unref (message_list->model);
2712 message_list->model = NULL;
2713 }
2714
2715 if (message_list->idle_id != 0) {
2716 g_source_remove (message_list->idle_id);
2717 message_list->idle_id = 0;
2718 }
2719
2720 if (message_list->seen_id) {
2721 g_source_remove (message_list->seen_id);
2722 message_list->seen_id = 0;
2723 }
2724
2725 /* Chain up to parent's dispose() method. */
2726 G_OBJECT_CLASS (message_list_parent_class)->dispose (object);
2727 }
2728
2729 static void
2730 message_list_finalize (GObject *object)
2731 {
2732 MessageList *message_list = MESSAGE_LIST (object);
2733 MessageListPrivate *priv = message_list->priv;
2734
2735 g_hash_table_destroy (message_list->normalised_hash);
2736
2737 if (message_list->ensure_uid) {
2738 g_free (message_list->ensure_uid);
2739 message_list->ensure_uid = NULL;
2740 }
2741
2742 if (message_list->thread_tree)
2743 camel_folder_thread_messages_unref (message_list->thread_tree);
2744
2745 g_free (message_list->search);
2746 g_free (message_list->ensure_uid);
2747 g_free (message_list->frozen_search);
2748 g_free (message_list->cursor_uid);
2749
2750 g_mutex_free (message_list->regen_lock);
2751
2752 clear_selection (message_list, &priv->clipboard);
2753
2754 /* Chain up to parent's finalize() method. */
2755 G_OBJECT_CLASS (message_list_parent_class)->finalize (object);
2756 }
2757
2758 static void
2759 message_list_selectable_update_actions (ESelectable *selectable,
2760 EFocusTracker *focus_tracker,
2761 GdkAtom *clipboard_targets,
2762 gint n_clipboard_targets)
2763 {
2764 GtkAction *action;
2765 gboolean sensitive;
2766
2767 action = e_focus_tracker_get_select_all_action (focus_tracker);
2768 sensitive = (e_tree_row_count (E_TREE (selectable)) > 0);
2769 gtk_action_set_tooltip (action, _("Select all visible messages"));
2770 gtk_action_set_sensitive (action, sensitive);
2771 }
2772
2773 static void
2774 message_list_selectable_select_all (ESelectable *selectable)
2775 {
2776 message_list_select_all (MESSAGE_LIST (selectable));
2777 }
2778
2779 static void
2780 message_list_class_init (MessageListClass *class)
2781 {
2782 GObjectClass *object_class;
2783 gint i;
2784
2785 for (i = 0; i < G_N_ELEMENTS (ml_drag_info); i++)
2786 ml_drag_info[i].atom = gdk_atom_intern (ml_drag_info[i].target, FALSE);
2787
2788 g_type_class_add_private (class, sizeof (MessageListPrivate));
2789
2790 object_class = G_OBJECT_CLASS (class);
2791 object_class->set_property = message_list_set_property;
2792 object_class->get_property = message_list_get_property;
2793 object_class->dispose = message_list_dispose;
2794 object_class->finalize = message_list_finalize;
2795
2796 class->message_list_built = NULL;
2797
2798 /* Inherited from ESelectableInterface */
2799 g_object_class_override_property (
2800 object_class,
2801 PROP_COPY_TARGET_LIST,
2802 "copy-target-list");
2803
2804 /* Inherited from ESelectableInterface */
2805 g_object_class_override_property (
2806 object_class,
2807 PROP_PASTE_TARGET_LIST,
2808 "paste-target-list");
2809
2810 g_object_class_install_property (
2811 object_class,
2812 PROP_SESSION,
2813 g_param_spec_object (
2814 "session",
2815 "Mail Session",
2816 "The mail session",
2817 E_TYPE_MAIL_SESSION,
2818 G_PARAM_READWRITE |
2819 G_PARAM_CONSTRUCT_ONLY));
2820
2821 message_list_signals[MESSAGE_SELECTED] = g_signal_new (
2822 "message_selected",
2823 MESSAGE_LIST_TYPE,
2824 G_SIGNAL_RUN_LAST,
2825 G_STRUCT_OFFSET (MessageListClass, message_selected),
2826 NULL,
2827 NULL,
2828 g_cclosure_marshal_VOID__STRING,
2829 G_TYPE_NONE, 1,
2830 G_TYPE_STRING);
2831
2832 message_list_signals[MESSAGE_LIST_BUILT] = g_signal_new (
2833 "message_list_built",
2834 MESSAGE_LIST_TYPE,
2835 G_SIGNAL_RUN_LAST,
2836 G_STRUCT_OFFSET (MessageListClass, message_list_built),
2837 NULL,
2838 NULL,
2839 g_cclosure_marshal_VOID__VOID,
2840 G_TYPE_NONE, 0);
2841 }
2842
2843 static void
2844 message_list_selectable_init (ESelectableInterface *interface)
2845 {
2846 interface->update_actions = message_list_selectable_update_actions;
2847 interface->select_all = message_list_selectable_select_all;
2848 }
2849
2850 static void
2851 message_list_construct (MessageList *message_list)
2852 {
2853 AtkObject *a11y;
2854 gboolean constructed;
2855 gchar *etspecfile;
2856 GSettings *settings;
2857
2858 message_list->model =
2859 e_tree_memory_callbacks_new (
2860 ml_tree_icon_at,
2861
2862 ml_column_count,
2863
2864 ml_has_save_id,
2865 ml_get_save_id,
2866
2867 ml_has_get_node_by_id,
2868 ml_get_node_by_id,
2869
2870 ml_tree_sort_value_at,
2871 ml_tree_value_at,
2872 ml_tree_set_value_at,
2873 ml_tree_is_cell_editable,
2874
2875 ml_duplicate_value,
2876 ml_free_value,
2877 ml_initialize_value,
2878 ml_value_is_empty,
2879 ml_value_to_string,
2880
2881 message_list);
2882
2883 settings = g_settings_new ("org.gnome.evolution.mail");
2884 e_tree_memory_set_expanded_default (
2885 E_TREE_MEMORY (message_list->model),
2886 g_settings_get_boolean (settings, "thread-expand"));
2887 message_list->priv->thread_latest =
2888 g_settings_get_boolean (settings, "thread-latest");
2889 g_object_unref (settings);
2890
2891 /*
2892 * The etree
2893 */
2894 message_list->extras = message_list_create_extras ();
2895
2896 etspecfile = g_build_filename (EVOLUTION_ETSPECDIR, "message-list.etspec", NULL);
2897 constructed = e_tree_construct_from_spec_file (
2898 E_TREE (message_list), message_list->model,
2899 message_list->extras, etspecfile, NULL);
2900 g_free (etspecfile);
2901
2902 if (constructed)
2903 e_tree_root_node_set_visible (E_TREE (message_list), FALSE);
2904
2905 if (atk_get_root () != NULL) {
2906 a11y = gtk_widget_get_accessible (GTK_WIDGET (message_list));
2907 atk_object_set_name (a11y, _("Messages"));
2908 }
2909
2910 g_signal_connect (
2911 e_tree_get_table_adapter (E_TREE (message_list)),
2912 "model_row_changed",
2913 G_CALLBACK (on_model_row_changed), message_list);
2914
2915 g_signal_connect (
2916 message_list, "cursor_activated",
2917 G_CALLBACK (on_cursor_activated_cmd), message_list);
2918
2919 g_signal_connect (
2920 message_list, "click",
2921 G_CALLBACK (on_click), message_list);
2922
2923 g_signal_connect (
2924 message_list, "selection_change",
2925 G_CALLBACK (on_selection_changed_cmd), message_list);
2926
2927 e_tree_drag_source_set (
2928 E_TREE (message_list), GDK_BUTTON1_MASK,
2929 ml_drag_types, G_N_ELEMENTS (ml_drag_types),
2930 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2931
2932 g_signal_connect (
2933 message_list, "tree_drag_data_get",
2934 G_CALLBACK (ml_tree_drag_data_get), message_list);
2935
2936 e_tree_drag_dest_set (
2937 E_TREE (message_list), GTK_DEST_DEFAULT_ALL,
2938 ml_drop_types, G_N_ELEMENTS (ml_drop_types),
2939 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2940
2941 g_signal_connect (
2942 message_list, "tree_drag_data_received",
2943 G_CALLBACK (ml_tree_drag_data_received), message_list);
2944
2945 g_signal_connect (
2946 message_list, "drag-motion",
2947 G_CALLBACK (ml_tree_drag_motion), message_list);
2948
2949 g_signal_connect (
2950 e_tree_get_table_adapter (E_TREE (message_list)),
2951 "sorting_changed",
2952 G_CALLBACK (ml_tree_sorting_changed), message_list);
2953 }
2954
2955 /**
2956 * message_list_new:
2957 *
2958 * Creates a new message-list widget.
2959 *
2960 * Returns a new message-list widget.
2961 **/
2962 GtkWidget *
2963 message_list_new (EMailSession *session)
2964 {
2965 GtkWidget *message_list;
2966
2967 g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
2968
2969 message_list = g_object_new (
2970 message_list_get_type (),
2971 "session", session, NULL);
2972
2973 message_list_construct (MESSAGE_LIST (message_list));
2974
2975 return message_list;
2976 }
2977
2978 EMailSession *
2979 message_list_get_session (MessageList *message_list)
2980 {
2981 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
2982
2983 return message_list->priv->session;
2984 }
2985
2986 static void
2987 clear_info (gchar *key,
2988 ETreePath *node,
2989 MessageList *ml)
2990 {
2991 CamelMessageInfo *info;
2992
2993 info = e_tree_memory_node_get_data ((ETreeMemory *) ml->model, node);
2994 camel_folder_free_message_info (ml->folder, info);
2995 e_tree_memory_node_set_data ((ETreeMemory *) ml->model, node, NULL);
2996 }
2997
2998 static void
2999 clear_tree (MessageList *ml,
3000 gboolean tfree)
3001 {
3002 ETreeModel *etm = ml->model;
3003
3004 #ifdef TIMEIT
3005 struct timeval start, end;
3006 gulong diff;
3007
3008 printf ("Clearing tree\n");
3009 gettimeofday (&start, NULL);
3010 #endif
3011
3012 /* we also reset the uid_rowmap since it is no longer useful/valid anyway */
3013 if (ml->folder)
3014 g_hash_table_foreach (ml->uid_nodemap, (GHFunc) clear_info, ml);
3015 g_hash_table_destroy (ml->uid_nodemap);
3016 ml->uid_nodemap = g_hash_table_new (g_str_hash, g_str_equal);
3017
3018 ml->priv->newest_read_date = 0;
3019 ml->priv->newest_read_uid = NULL;
3020 ml->priv->oldest_unread_date = 0;
3021 ml->priv->oldest_unread_uid = NULL;
3022
3023 if (ml->tree_root) {
3024 /* we should be frozen already */
3025 e_tree_memory_node_remove (E_TREE_MEMORY (etm), ml->tree_root);
3026 }
3027
3028 ml->tree_root = e_tree_memory_node_insert (E_TREE_MEMORY (etm), NULL, 0, NULL);
3029 if (tfree)
3030 e_tree_model_rebuilt (E_TREE_MODEL (etm));
3031 #ifdef TIMEIT
3032 gettimeofday (&end, NULL);
3033 diff = end.tv_sec * 1000 + end.tv_usec / 1000;
3034 diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
3035 printf ("Clearing tree took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
3036 #endif
3037
3038 }
3039
3040 static gboolean
3041 folder_store_supports_vjunk_folder (CamelFolder *folder)
3042 {
3043 CamelStore *store;
3044
3045 g_return_val_if_fail (folder != NULL, FALSE);
3046
3047 store = camel_folder_get_parent_store (folder);
3048 if (!store)
3049 return FALSE;
3050
3051 return (store->flags & (CAMEL_STORE_VJUNK | CAMEL_STORE_REAL_JUNK_FOLDER)) != 0 || CAMEL_IS_VEE_FOLDER (folder);
3052 }
3053
3054 /* Check if the given node is selectable in the current message list,
3055 * which depends on the type of the folder (normal, junk, trash). */
3056 static gboolean
3057 is_node_selectable (MessageList *ml,
3058 CamelMessageInfo *info)
3059 {
3060 gboolean is_junk_folder;
3061 gboolean is_trash_folder;
3062 guint32 flags;
3063 gboolean flag_junk;
3064 gboolean flag_deleted;
3065 gboolean store_has_vjunk;
3066
3067 g_return_val_if_fail (ml != NULL, FALSE);
3068 g_return_val_if_fail (ml->folder != NULL, FALSE);
3069 g_return_val_if_fail (info != NULL, FALSE);
3070
3071 store_has_vjunk = folder_store_supports_vjunk_folder (ml->folder);
3072
3073 /* check folder type */
3074 is_junk_folder = store_has_vjunk && (ml->folder->folder_flags & CAMEL_FOLDER_IS_JUNK) != 0;
3075 is_trash_folder = ml->folder->folder_flags & CAMEL_FOLDER_IS_TRASH;
3076
3077 /* check flags set on current message */
3078 flags = camel_message_info_flags (info);
3079 flag_junk = store_has_vjunk && (flags & CAMEL_MESSAGE_JUNK) != 0;
3080 flag_deleted = flags & CAMEL_MESSAGE_DELETED;
3081
3082 /* perform actions depending on folder type */
3083 if (is_junk_folder) {
3084 /* messages in a junk folder are selectable only if
3085 * the message is marked as junk and if not deleted
3086 * when hidedeleted is set */
3087 if (flag_junk && !(flag_deleted && ml->hidedeleted))
3088 return TRUE;
3089
3090 } else if (is_trash_folder) {
3091 /* messages in a trash folder are selectable unless
3092 * not deleted any more */
3093 if (flag_deleted)
3094 return TRUE;
3095 } else {
3096 /* in normal folders it depends on hidedeleted,
3097 * hidejunk and the message flags */
3098 if (!(flag_junk && ml->hidejunk)
3099 && !(flag_deleted && ml->hidedeleted))
3100 return TRUE;
3101 }
3102
3103 return FALSE;
3104 }
3105
3106 /* We try and find something that is selectable in our tree. There is
3107 * actually no assurance that we'll find something that will still be
3108 * there next time, but its probably going to work most of the time. */
3109 static gchar *
3110 find_next_selectable (MessageList *ml)
3111 {
3112 ETreePath node;
3113 gint last;
3114 gint vrow_orig;
3115 gint vrow;
3116 ETree *et = E_TREE (ml);
3117 CamelMessageInfo *info;
3118
3119 node = g_hash_table_lookup (ml->uid_nodemap, ml->cursor_uid);
3120 if (node == NULL)
3121 return NULL;
3122
3123 info = get_message_info (ml, node);
3124 if (info && is_node_selectable (ml, info))
3125 return NULL;
3126
3127 last = e_tree_row_count (et);
3128
3129 /* model_to_view_row etc simply dont work for sorted views. Sigh. */
3130 vrow_orig = e_tree_row_of_node (et, node);
3131
3132 /* We already checked this node. */
3133 vrow = vrow_orig + 1;
3134
3135 while (vrow < last) {
3136 node = e_tree_node_at_row (et, vrow);
3137 info = get_message_info (ml, node);
3138 if (info && is_node_selectable (ml, info))
3139 return g_strdup (camel_message_info_uid (info));
3140 vrow++;
3141 }
3142
3143 /* We didn't find any undeleted entries _below_ the currently selected one
3144 * * so let's try to find one _above_ */
3145 vrow = vrow_orig - 1;
3146
3147 while (vrow >= 0) {
3148 node = e_tree_node_at_row (et, vrow);
3149 info = get_message_info (ml, node);
3150 if (info && is_node_selectable (ml, info))
3151 return g_strdup (camel_message_info_uid (info));
3152 vrow--;
3153 }
3154
3155 return NULL;
3156 }
3157
3158 static ETreePath *
3159 ml_uid_nodemap_insert (MessageList *message_list,
3160 CamelMessageInfo *info,
3161 ETreePath *parent_node,
3162 gint row)
3163 {
3164 ETreeMemory *tree;
3165 ETreePath *node;
3166 const gchar *uid;
3167 time_t date;
3168 guint flags;
3169
3170 if (parent_node == NULL)
3171 parent_node = message_list->tree_root;
3172
3173 tree = E_TREE_MEMORY (message_list->model);
3174 node = e_tree_memory_node_insert (tree, parent_node, row, info);
3175
3176 uid = camel_message_info_uid (info);
3177 flags = camel_message_info_flags (info);
3178 date = camel_message_info_date_received (info);
3179
3180 camel_folder_ref_message_info (message_list->folder, info);
3181 g_hash_table_insert (message_list->uid_nodemap, (gpointer) uid, node);
3182
3183 /* Track the latest seen and unseen messages shown, used in
3184 * fallback heuristics for automatic message selection. */
3185 if (flags & CAMEL_MESSAGE_SEEN) {
3186 if (date > message_list->priv->newest_read_date) {
3187 message_list->priv->newest_read_date = date;
3188 message_list->priv->newest_read_uid = uid;
3189 }
3190 } else {
3191 if (message_list->priv->oldest_unread_date == 0) {
3192 message_list->priv->oldest_unread_date = date;
3193 message_list->priv->oldest_unread_uid = uid;
3194 } else if (date < message_list->priv->oldest_unread_date) {
3195 message_list->priv->oldest_unread_date = date;
3196 message_list->priv->oldest_unread_uid = uid;
3197 }
3198 }
3199
3200 return node;
3201 }
3202
3203 static void
3204 ml_uid_nodemap_remove (MessageList *message_list,
3205 CamelMessageInfo *info)
3206 {
3207 const gchar *uid;
3208
3209 uid = camel_message_info_uid (info);
3210
3211 if (uid == message_list->priv->newest_read_uid) {
3212 message_list->priv->newest_read_date = 0;
3213 message_list->priv->newest_read_uid = NULL;
3214 }
3215
3216 if (uid == message_list->priv->oldest_unread_uid) {
3217 message_list->priv->oldest_unread_date = 0;
3218 message_list->priv->oldest_unread_uid = NULL;
3219 }
3220
3221 g_hash_table_remove (message_list->uid_nodemap, uid);
3222 camel_folder_free_message_info (message_list->folder, info);
3223 }
3224
3225 /* only call if we have a tree model */
3226 /* builds the tree structure */
3227
3228 #define BROKEN_ETREE /* avoid some broken code in etree(?) by not using the incremental update */
3229
3230 static void build_subtree (MessageList *ml, ETreePath parent, CamelFolderThreadNode *c, gint *row);
3231
3232 static void build_subtree_diff (MessageList *ml, ETreePath parent, ETreePath path, CamelFolderThreadNode *c, gint *row);
3233
3234 static void
3235 build_tree (MessageList *ml,
3236 CamelFolderThread *thread,
3237 CamelFolderChangeInfo *changes,
3238 gboolean can_scroll_to_cursor)
3239 {
3240 gint row = 0;
3241 ETreeModel *etm = ml->model;
3242 ETableItem *table_item = e_tree_get_item (E_TREE (ml));
3243 #ifndef BROKEN_ETREE
3244 ETreePath *top;
3245 #endif
3246 gchar *saveuid = NULL;
3247 #ifdef BROKEN_ETREE
3248 GPtrArray *selected;
3249 #endif
3250 #ifdef TIMEIT
3251 struct timeval start, end;
3252 gulong diff;
3253
3254 printf ("Building tree\n");
3255 gettimeofday (&start, NULL);
3256 #endif
3257
3258 #ifdef TIMEIT
3259 gettimeofday (&end, NULL);
3260 diff = end.tv_sec * 1000 + end.tv_usec / 1000;
3261 diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
3262 printf ("Loading tree state took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
3263 #endif
3264
3265 if (ml->tree_root == NULL) {
3266 ml->tree_root = e_tree_memory_node_insert (E_TREE_MEMORY (etm), NULL, 0, NULL);
3267 }
3268
3269 if (ml->cursor_uid)
3270 saveuid = find_next_selectable (ml);
3271
3272 #ifndef BROKEN_ETREE
3273 top = e_tree_model_node_get_first_child (etm, ml->tree_root);
3274 if (top == NULL || changes == NULL) {
3275 #else
3276 selected = message_list_get_selected (ml);
3277 #endif
3278 e_tree_memory_freeze (E_TREE_MEMORY (etm));
3279 clear_tree (ml, FALSE);
3280
3281 build_subtree (ml, ml->tree_root, thread->tree, &row);
3282
3283 if (!can_scroll_to_cursor && table_item)
3284 table_item->queue_show_cursor = FALSE;
3285
3286 e_tree_memory_thaw (E_TREE_MEMORY (etm));
3287 #ifdef BROKEN_ETREE
3288
3289 /* it's required to thaw & freeze, to propagate changes */
3290 e_tree_memory_freeze (E_TREE_MEMORY (etm));
3291
3292 message_list_set_selected (ml, selected);
3293 em_utils_uids_free (selected);
3294
3295 if (!can_scroll_to_cursor && table_item)
3296 table_item->queue_show_cursor = FALSE;
3297
3298 e_tree_memory_thaw (E_TREE_MEMORY (etm));
3299 #else
3300 } else {
3301 static gint tree_equal (ETreeModel *etm, ETreePath ap, CamelFolderThreadNode *bp);
3302
3303 build_subtree_diff (ml, ml->tree_root, top, thread->tree, &row);
3304 top = e_tree_model_node_get_first_child (etm, ml->tree_root);
3305 tree_equal (ml->model, top, thread->tree);
3306 }
3307 #endif
3308 if (!saveuid && ml->cursor_uid && g_hash_table_lookup (ml->uid_nodemap, ml->cursor_uid)) {
3309 /* this makes sure a visible node is selected, like when
3310 * collapsing all nodes and a children had been selected
3311 */
3312 saveuid = g_strdup (ml->cursor_uid);
3313 }
3314
3315 if (saveuid) {
3316 ETreePath node = g_hash_table_lookup (ml->uid_nodemap, saveuid);
3317 if (node == NULL) {
3318 g_free (ml->cursor_uid);
3319 ml->cursor_uid = NULL;
3320 g_signal_emit (ml, message_list_signals[MESSAGE_SELECTED], 0, NULL);
3321 } else {
3322 ETree *tree = E_TREE (ml);
3323 ETreePath parent = node;
3324
3325 while (parent = e_tree_model_node_get_parent (etm, parent), parent) {
3326 if (!e_tree_node_is_expanded (tree, parent))
3327 node = parent;
3328 }
3329
3330 e_tree_memory_freeze (E_TREE_MEMORY (etm));
3331
3332 e_tree_set_cursor (E_TREE (ml), node);
3333
3334 if (!can_scroll_to_cursor && table_item)
3335 table_item->queue_show_cursor = FALSE;
3336
3337 e_tree_memory_thaw (E_TREE_MEMORY (etm));
3338 }
3339 g_free (saveuid);
3340 } else if (ml->cursor_uid && !g_hash_table_lookup (ml->uid_nodemap, ml->cursor_uid)) {
3341 g_free (ml->cursor_uid);
3342 ml->cursor_uid = NULL;
3343 g_signal_emit (ml, message_list_signals[MESSAGE_SELECTED], 0, NULL);
3344 }
3345
3346 #ifdef TIMEIT
3347 gettimeofday (&end, NULL);
3348 diff = end.tv_sec * 1000 + end.tv_usec / 1000;
3349 diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
3350 printf ("Building tree took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
3351 #endif
3352 }
3353
3354 /* this is about 20% faster than build_subtree_diff,
3355 * entirely because e_tree_model_node_insert (xx, -1 xx)
3356 * is faster than inserting to the right row :( */
3357 /* Otherwise, this code would probably go as it does the same thing essentially */
3358 static void
3359 build_subtree (MessageList *ml,
3360 ETreePath parent,
3361 CamelFolderThreadNode *c,
3362 gint *row)
3363 {
3364 ETreePath node;
3365
3366 while (c) {
3367 /* phantom nodes no longer allowed */
3368 if (!c->message) {
3369 g_warning ("c->message shouldn't be NULL\n");
3370 c = c->next;
3371 continue;
3372 }
3373
3374 node = ml_uid_nodemap_insert (
3375 ml, (CamelMessageInfo *) c->message, parent, -1);
3376
3377 if (c->child) {
3378 build_subtree (ml, node, c->child, row);
3379 }
3380 c = c->next;
3381 }
3382 }
3383
3384 /* compares a thread tree node with the etable tree node to see if they point to
3385 * the same object */
3386 static gint
3387 node_equal (ETreeModel *etm,
3388 ETreePath ap,
3389 CamelFolderThreadNode *bp)
3390 {
3391 CamelMessageInfo *info;
3392
3393 info = e_tree_memory_node_get_data (E_TREE_MEMORY (etm), ap);
3394
3395 if (bp->message && strcmp (camel_message_info_uid (info), camel_message_info_uid (bp->message)) == 0)
3396 return 1;
3397
3398 return 0;
3399 }
3400
3401 #ifndef BROKEN_ETREE
3402 /* debug function - compare the two trees to see if they are the same */
3403 static gint
3404 tree_equal (ETreeModel *etm,
3405 ETreePath ap,
3406 CamelFolderThreadNode *bp)
3407 {
3408 CamelMessageInfo *info;
3409
3410 while (ap && bp) {
3411 if (!node_equal (etm, ap, bp)) {
3412 g_warning ("Nodes in tree differ");
3413 info = e_tree_memory_node_get_data (E_TREE_MEMORY (etm), ap);
3414 printf ("table uid = %s\n", camel_message_info_uid (info));
3415 printf ("camel uid = %s\n", camel_message_info_uid (bp->message));
3416 return FALSE;
3417 } else {
3418 if (!tree_equal (etm, e_tree_model_node_get_first_child (etm, ap), bp->child))
3419 return FALSE;
3420 }
3421 bp = bp->next;
3422 ap = e_tree_model_node_get_next (etm, ap);
3423 }
3424
3425 if (ap || bp) {
3426 g_warning ("Tree differs, out of nodes in one branch");
3427 if (ap) {
3428 info = e_tree_memory_node_get_data (E_TREE_MEMORY (etm), ap);
3429 if (info)
3430 printf ("table uid = %s\n", camel_message_info_uid (info));
3431 else
3432 printf ("info is empty?\n");
3433 }
3434 if (bp) {
3435 printf ("camel uid = %s\n", camel_message_info_uid (bp->message));
3436 return FALSE;
3437 }
3438 return FALSE;
3439 }
3440 return TRUE;
3441 }
3442 #endif
3443
3444 /* adds a single node, retains save state, and handles adding children if required */
3445 static void
3446 add_node_diff (MessageList *ml,
3447 ETreePath parent,
3448 ETreePath path,
3449 CamelFolderThreadNode *c,
3450 gint *row,
3451 gint myrow)
3452 {
3453 CamelMessageInfo *info;
3454 ETreePath node;
3455
3456 g_return_if_fail (c->message != NULL);
3457
3458 /* XXX Casting away constness. */
3459 info = (CamelMessageInfo *) c->message;
3460
3461 /* we just update the hashtable key */
3462 ml_uid_nodemap_remove (ml, info);
3463 node = ml_uid_nodemap_insert (ml, info, parent, myrow);
3464 (*row)++;
3465
3466 if (c->child) {
3467 build_subtree_diff (ml, node, NULL, c->child, row);
3468 }
3469 }
3470
3471 /* removes node, children recursively and all associated data */
3472 static void
3473 remove_node_diff (MessageList *ml,
3474 ETreePath node,
3475 gint depth)
3476 {
3477 ETreeModel *etm = ml->model;
3478 ETreePath cp, cn;
3479 CamelMessageInfo *info;
3480
3481 t (printf ("Removing node: %s\n", (gchar *) e_tree_memory_node_get_data (etm, node)));
3482
3483 /* we depth-first remove all node data's ... */
3484 cp = e_tree_model_node_get_first_child (etm, node);
3485 while (cp) {
3486 cn = e_tree_model_node_get_next (etm, cp);
3487 remove_node_diff (ml, cp, depth + 1);
3488 cp = cn;
3489 }
3490
3491 /* and the rowid entry - if and only if it is referencing this node */
3492 info = e_tree_memory_node_get_data (E_TREE_MEMORY (etm), node);
3493
3494 /* and only at the toplevel, remove the node (etree should optimise this remove somewhat) */
3495 if (depth == 0)
3496 e_tree_memory_node_remove (E_TREE_MEMORY (etm), node);
3497
3498 g_return_if_fail (info);
3499 ml_uid_nodemap_remove (ml, info);
3500 }
3501
3502 /* applies a new tree structure to an existing tree, but only by changing things
3503 * that have changed */
3504 static void
3505 build_subtree_diff (MessageList *ml,
3506 ETreePath parent,
3507 ETreePath path,
3508 CamelFolderThreadNode *c,
3509 gint *row)
3510 {
3511 ETreeModel *etm = ml->model;
3512 ETreePath ap, *ai, *at, *tmp;
3513 CamelFolderThreadNode *bp, *bi, *bt;
3514 gint i, j, myrow = 0;
3515
3516 ap = path;
3517 bp = c;
3518
3519 while (ap || bp) {
3520 t (printf ("Processing row: %d (subtree row %d)\n", *row, myrow));
3521 if (ap == NULL) {
3522 t (printf ("out of old nodes\n"));
3523 /* ran out of old nodes - remaining nodes are added */
3524 add_node_diff (ml, parent, ap, bp, row, myrow);
3525 myrow++;
3526 bp = bp->next;
3527 } else if (bp == NULL) {
3528 t (printf ("out of new nodes\n"));
3529 /* ran out of new nodes - remaining nodes are removed */
3530 tmp = e_tree_model_node_get_next (etm, ap);
3531 remove_node_diff (ml, ap, 0);
3532 ap = tmp;
3533 } else if (node_equal (etm, ap, bp)) {
3534 /*t(printf("nodes match, verify\n"));*/
3535 /* matching nodes, verify details/children */
3536 #if 0
3537 if (bp->message) {
3538 gpointer olduid, oldrow;
3539 /* if this is a message row, check/update the row id map */
3540 if (g_hash_table_lookup_extended (ml->uid_rowmap, camel_message_info_uid (bp->message), &olduid, &oldrow)) {
3541 if ((gint) oldrow != (*row)) {
3542 g_hash_table_insert (ml->uid_rowmap, olduid, (gpointer)(*row));
3543 }
3544 } else {
3545 g_warning ("Cannot find uid %s in table?", camel_message_info_uid (bp->message));
3546 }
3547 }
3548 #endif
3549 *row = (*row)+1;
3550 myrow++;
3551 tmp = e_tree_model_node_get_first_child (etm, ap);
3552 /* make child lists match (if either has one) */
3553 if (bp->child || tmp) {
3554 build_subtree_diff (ml, ap, tmp, bp->child, row);
3555 }
3556 ap = e_tree_model_node_get_next (etm, ap);
3557 bp = bp->next;
3558 } else {
3559 t (printf ("searching for matches\n"));
3560 /* we have to scan each side for a match */
3561 bi = bp->next;
3562 ai = e_tree_model_node_get_next (etm, ap);
3563 for (i = 1; bi != NULL; i++,bi = bi->next) {
3564 if (node_equal (etm, ap, bi))
3565 break;
3566 }
3567 for (j = 1; ai != NULL; j++,ai = e_tree_model_node_get_next (etm, ai)) {
3568 if (node_equal (etm, ai, bp))
3569 break;
3570 }
3571 if (i < j) {
3572 /* smaller run of new nodes - must be nodes to add */
3573 if (bi) {
3574 bt = bp;
3575 while (bt != bi) {
3576 t (printf ("adding new node 0\n"));
3577 add_node_diff (ml, parent, NULL, bt, row, myrow);
3578 myrow++;
3579 bt = bt->next;
3580 }
3581 bp = bi;
3582 } else {
3583 t (printf ("adding new node 1\n"));
3584 /* no match in new nodes, add one, try next */
3585 add_node_diff (ml, parent, NULL, bp, row, myrow);
3586 myrow++;
3587 bp = bp->next;
3588 }
3589 } else {
3590 /* bigger run of old nodes - must be nodes to remove */
3591 if (ai) {
3592 at = ap;
3593 while (at != ai) {
3594 t (printf ("removing old node 0\n"));
3595 tmp = e_tree_model_node_get_next (etm, at);
3596 remove_node_diff (ml, at, 0);
3597 at = tmp;
3598 }
3599 ap = ai;
3600 } else {
3601 t (printf ("adding new node 2\n"));
3602 /* didn't find match in old nodes, must be new node? */
3603 add_node_diff (ml, parent, NULL, bp, row, myrow);
3604 myrow++;
3605 bp = bp->next;
3606 #if 0
3607 tmp = e_tree_model_node_get_next (etm, ap);
3608 remove_node_diff (etm, ap, 0);
3609 ap = tmp;
3610 #endif
3611 }
3612 }
3613 }
3614 }
3615 }
3616
3617 #ifndef BROKEN_ETREE
3618 static void build_flat_diff (MessageList *ml, CamelFolderChangeInfo *changes);
3619 #endif
3620
3621 static void
3622 build_flat (MessageList *ml,
3623 GPtrArray *summary,
3624 CamelFolderChangeInfo *changes)
3625 {
3626 ETreeModel *etm = ml->model;
3627 gchar *saveuid = NULL;
3628 gint i;
3629 #ifdef BROKEN_ETREE
3630 GPtrArray *selected;
3631 #endif
3632 #ifdef TIMEIT
3633 struct timeval start, end;
3634 gulong diff;
3635
3636 printf ("Building flat\n");
3637 gettimeofday (&start, NULL);
3638 #endif
3639
3640 if (ml->cursor_uid)
3641 saveuid = find_next_selectable (ml);
3642
3643 #ifndef BROKEN_ETREE
3644 if (changes) {
3645 build_flat_diff (ml, changes);
3646 } else {
3647 #else
3648 selected = message_list_get_selected (ml);
3649 #endif
3650 e_tree_memory_freeze (E_TREE_MEMORY (etm));
3651 clear_tree (ml, FALSE);
3652 for (i = 0; i < summary->len; i++) {
3653 CamelMessageInfo *info = summary->pdata[i];
3654
3655 ml_uid_nodemap_insert (ml, info, NULL, -1);
3656 }
3657 e_tree_memory_thaw (E_TREE_MEMORY (etm));
3658 #ifdef BROKEN_ETREE
3659 message_list_set_selected (ml, selected);
3660 em_utils_uids_free (selected);
3661 #else
3662 }
3663 #endif
3664
3665 if (saveuid) {
3666 ETreePath node = g_hash_table_lookup (ml->uid_nodemap, saveuid);
3667 if (node == NULL) {
3668 g_free (ml->cursor_uid);
3669 ml->cursor_uid = NULL;
3670 g_signal_emit (ml, message_list_signals[MESSAGE_SELECTED], 0, NULL);
3671 } else {
3672 e_tree_set_cursor (E_TREE (ml), node);
3673 }
3674 g_free (saveuid);
3675 }
3676
3677 #ifdef TIMEIT
3678 gettimeofday (&end, NULL);
3679 diff = end.tv_sec * 1000 + end.tv_usec / 1000;
3680 diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
3681 printf ("Building flat took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
3682 #endif
3683
3684 }
3685
3686 static void
3687 message_list_change_first_visible_parent (MessageList *ml,
3688 ETreePath node)
3689 {
3690 ETreePath first_visible = NULL;
3691
3692 while (node && (node = e_tree_model_node_get_parent (ml->model, node))) {
3693 if (!e_tree_node_is_expanded (E_TREE (ml), node))
3694 first_visible = node;
3695 }
3696
3697 if (first_visible != NULL) {
3698 e_tree_model_pre_change (ml->model);
3699 e_tree_model_node_data_changed (ml->model, first_visible);
3700 }
3701 }
3702
3703 #ifndef BROKEN_ETREE
3704
3705 static void
3706 build_flat_diff (MessageList *ml,
3707 CamelFolderChangeInfo *changes)
3708 {
3709 gint i;
3710 ETreePath node;
3711 CamelMessageInfo *info;
3712
3713 #ifdef TIMEIT
3714 struct timeval start, end;
3715 gulong diff;
3716
3717 gettimeofday (&start, NULL);
3718 #endif
3719
3720 d (printf ("updating changes to display\n"));
3721
3722 /* remove individual nodes? */
3723 d (printf ("Removing messages from view:\n"));
3724 for (i = 0; i < changes->uid_removed->len; i++) {
3725 node = g_hash_table_lookup (ml->uid_nodemap, changes->uid_removed->pdata[i]);
3726 if (node) {
3727 info = e_tree_memory_node_get_data (E_TREE_MEMORY (ml->model), node);
3728 e_tree_memory_node_remove (E_TREE_MEMORY (ml->model), node);
3729 ml_uid_nodemap_remove (ml, info);
3730 }
3731 }
3732
3733 /* add new nodes? - just append to the end */
3734 d (printf ("Adding messages to view:\n"));
3735 for (i = 0; i < changes->uid_added->len; i++) {
3736 info = camel_folder_get_message_info (ml->folder, changes->uid_added->pdata[i]);
3737 if (info) {
3738 d (printf (" %s\n", (gchar *) changes->uid_added->pdata[i]));
3739 ml_uid_nodemap_insert (ml, info, NULL, -1);
3740 }
3741 }
3742
3743 /* and update changes too */
3744 d (printf ("Changing messages to view:\n"));
3745 for (i = 0; i < changes->uid_changed->len; i++) {
3746 ETreePath *node = g_hash_table_lookup (ml->uid_nodemap, changes->uid_changed->pdata[i]);
3747 if (node) {
3748 e_tree_model_pre_change (ml->model);
3749 e_tree_model_node_data_changed (ml->model, node);
3750
3751 message_list_change_first_visible_parent (ml, node);
3752 }
3753 }
3754
3755 #ifdef TIMEIT
3756 gettimeofday (&end, NULL);
3757 diff = end.tv_sec * 1000 + end.tv_usec / 1000;
3758 diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
3759 printf ("Inserting changes took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
3760 #endif
3761
3762 }
3763 #endif /* BROKEN_ETREE */
3764
3765 static CamelFolderChangeInfo *
3766 mail_folder_hide_by_flag (CamelFolder *folder,
3767 MessageList *ml,
3768 CamelFolderChangeInfo *changes,
3769 gint flag)
3770 {
3771 CamelFolderChangeInfo *newchanges;
3772 CamelMessageInfo *info;
3773 gint i;
3774
3775 newchanges = camel_folder_change_info_new ();
3776
3777 for (i = 0; i < changes->uid_changed->len; i++) {
3778 ETreePath node;
3779 guint32 flags;
3780
3781 node = g_hash_table_lookup (
3782 ml->uid_nodemap, changes->uid_changed->pdata[i]);
3783 info = camel_folder_get_message_info (
3784 folder, changes->uid_changed->pdata[i]);
3785 if (info)
3786 flags = camel_message_info_flags (info);
3787
3788 if (node != NULL && info != NULL && (flags & flag) != 0)
3789 camel_folder_change_info_remove_uid (
3790 newchanges, changes->uid_changed->pdata[i]);
3791 else if (node == NULL && info != NULL && (flags & flag) == 0)
3792 camel_folder_change_info_add_uid (
3793 newchanges, changes->uid_changed->pdata[i]);
3794 else
3795 camel_folder_change_info_change_uid (
3796 newchanges, changes->uid_changed->pdata[i]);
3797 if (info)
3798 camel_folder_free_message_info (folder, info);
3799 }
3800
3801 if (newchanges->uid_added->len > 0 || newchanges->uid_removed->len > 0) {
3802 for (i = 0; i < changes->uid_added->len; i++)
3803 camel_folder_change_info_add_uid (
3804 newchanges, changes->uid_added->pdata[i]);
3805 for (i = 0; i < changes->uid_removed->len; i++)
3806 camel_folder_change_info_remove_uid (
3807 newchanges, changes->uid_removed->pdata[i]);
3808 } else {
3809 camel_folder_change_info_clear (newchanges);
3810 camel_folder_change_info_cat (newchanges, changes);
3811 }
3812
3813 return newchanges;
3814 }
3815
3816 static void
3817 folder_changed (CamelFolder *folder,
3818 CamelFolderChangeInfo *changes,
3819 MessageList *ml)
3820 {
3821 CamelFolderChangeInfo *altered_changes = NULL;
3822 gint i;
3823
3824 if (ml->priv->destroyed)
3825 return;
3826
3827 d (printf ("folder changed event, changes = %p\n", changes));
3828 if (changes) {
3829 for (i = 0; i < changes->uid_removed->len; i++)
3830 g_hash_table_remove (
3831 ml->normalised_hash,
3832 changes->uid_removed->pdata[i]);
3833
3834 /* check if the hidden state has changed, if so modify accordingly, then regenerate */
3835 if (ml->hidejunk || ml->hidedeleted)
3836 altered_changes = mail_folder_hide_by_flag (
3837 folder, ml, changes,
3838 (ml->hidejunk ? CAMEL_MESSAGE_JUNK : 0) |
3839 (ml->hidedeleted ? CAMEL_MESSAGE_DELETED : 0));
3840 else {
3841 altered_changes = camel_folder_change_info_new ();
3842 camel_folder_change_info_cat (altered_changes, changes);
3843 }
3844
3845 if (altered_changes->uid_added->len == 0 && altered_changes->uid_removed->len == 0 && altered_changes->uid_changed->len < 100) {
3846 for (i = 0; i < altered_changes->uid_changed->len; i++) {
3847 ETreePath node = g_hash_table_lookup (ml->uid_nodemap, altered_changes->uid_changed->pdata[i]);
3848 if (node) {
3849 e_tree_model_pre_change (ml->model);
3850 e_tree_model_node_data_changed (ml->model, node);
3851
3852 message_list_change_first_visible_parent (ml, node);
3853 }
3854 }
3855
3856 camel_folder_change_info_free (altered_changes);
3857
3858 g_signal_emit (ml, message_list_signals[MESSAGE_LIST_BUILT], 0);
3859 return;
3860 }
3861 }
3862
3863 /* XXX This apparently eats the ChangeFolderChangeInfo. */
3864 mail_regen_list (ml, ml->search, NULL, altered_changes, FALSE);
3865 }
3866
3867 /**
3868 * message_list_set_folder:
3869 * @message_list: Message List widget
3870 * @folder: folder backend to be set
3871 * @outgoing: whether this is an outgoing folder
3872 *
3873 * Sets @folder to be the backend folder for @message_list. If
3874 * @outgoing is %TRUE, then the message-list UI changes to default to
3875 * the "Outgoing folder" column view.
3876 **/
3877 void
3878 message_list_set_folder (MessageList *message_list,
3879 CamelFolder *folder,
3880 gboolean outgoing)
3881 {
3882 ETreeModel *etm = message_list->model;
3883 gboolean hide_deleted;
3884 GSettings *settings;
3885
3886 g_return_if_fail (IS_MESSAGE_LIST (message_list));
3887
3888 if (message_list->folder == folder)
3889 return;
3890
3891 g_free (message_list->search);
3892 message_list->search = NULL;
3893
3894 g_free (message_list->frozen_search);
3895 message_list->frozen_search = NULL;
3896
3897 if (message_list->seen_id) {
3898 g_source_remove (message_list->seen_id);
3899 message_list->seen_id = 0;
3900 }
3901
3902 /* reset the normalised sort performance hack */
3903 g_hash_table_remove_all (message_list->normalised_hash);
3904
3905 mail_regen_cancel (message_list);
3906
3907 if (message_list->folder != NULL) {
3908 save_tree_state (message_list);
3909 }
3910
3911 e_tree_memory_freeze (E_TREE_MEMORY (etm));
3912 clear_tree (message_list, TRUE);
3913 e_tree_memory_thaw (E_TREE_MEMORY (etm));
3914
3915 /* remove the cursor activate idle handler */
3916 if (message_list->idle_id != 0) {
3917 g_source_remove (message_list->idle_id);
3918 message_list->idle_id = 0;
3919 }
3920
3921 if (message_list->folder) {
3922 g_signal_handlers_disconnect_by_func (
3923 message_list->folder, folder_changed, message_list);
3924
3925 if (message_list->uid_nodemap)
3926 g_hash_table_foreach (message_list->uid_nodemap, (GHFunc) clear_info, message_list);
3927
3928 g_object_unref (message_list->folder);
3929 message_list->folder = NULL;
3930 }
3931
3932 if (message_list->thread_tree) {
3933 camel_folder_thread_messages_unref (message_list->thread_tree);
3934 message_list->thread_tree = NULL;
3935 }
3936
3937 if (message_list->cursor_uid) {
3938 g_free (message_list->cursor_uid);
3939 message_list->cursor_uid = NULL;
3940 }
3941
3942 /* Always emit message-selected, event when an account node
3943 * (folder == NULL) is selected, so that views know what happened and
3944 * can stop all running operations etc. */
3945 g_signal_emit (message_list, message_list_signals[MESSAGE_SELECTED], 0, NULL);
3946
3947 if (CAMEL_IS_FOLDER (folder)) {
3948 CamelStore *store;
3949 gboolean non_trash_folder;
3950 gint strikeout_col;
3951 ECell *cell;
3952
3953 message_list->folder = g_object_ref (folder);
3954 message_list->just_set_folder = TRUE;
3955
3956 store = camel_folder_get_parent_store (folder);
3957
3958 non_trash_folder =
3959 ((store->flags & CAMEL_STORE_VTRASH) == 0) ||
3960 ((folder->folder_flags & CAMEL_FOLDER_IS_TRASH) == 0);
3961
3962 /* Setup the strikeout effect for non-trash folders */
3963 strikeout_col = non_trash_folder ? COL_DELETED : -1;
3964
3965 cell = e_table_extras_get_cell (message_list->extras, "render_date");
3966 g_object_set (cell, "strikeout_column", strikeout_col, NULL);
3967
3968 cell = e_table_extras_get_cell (message_list->extras, "render_text");
3969 g_object_set (cell, "strikeout_column", strikeout_col, NULL);
3970
3971 cell = e_table_extras_get_cell (message_list->extras, "render_size");
3972 g_object_set (cell, "strikeout_column", strikeout_col, NULL);
3973
3974 cell = e_table_extras_get_cell (message_list->extras, "render_composite_from");
3975 composite_cell_set_strike_col (cell, strikeout_col);
3976
3977 cell = e_table_extras_get_cell (message_list->extras, "render_composite_to");
3978 composite_cell_set_strike_col (cell, strikeout_col);
3979
3980 /* Build the etree suitable for this folder */
3981 message_list_setup_etree (message_list, outgoing);
3982
3983 g_signal_connect (
3984 folder, "changed",
3985 G_CALLBACK (folder_changed), message_list);
3986
3987 settings = g_settings_new ("org.gnome.evolution.mail");
3988 hide_deleted = !g_settings_get_boolean (settings, "show-deleted");
3989 g_object_unref (settings);
3990
3991 message_list->hidedeleted =
3992 hide_deleted && non_trash_folder;
3993 message_list->hidejunk =
3994 folder_store_supports_vjunk_folder (message_list->folder) &&
3995 !(folder->folder_flags & CAMEL_FOLDER_IS_JUNK) &&
3996 !(folder->folder_flags & CAMEL_FOLDER_IS_TRASH);
3997
3998 if (message_list->frozen == 0)
3999 mail_regen_list (message_list, message_list->search, NULL, NULL, TRUE);
4000 }
4001 }
4002
4003 GtkTargetList *
4004 message_list_get_copy_target_list (MessageList *message_list)
4005 {
4006 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
4007
4008 return message_list->priv->copy_target_list;
4009 }
4010
4011 GtkTargetList *
4012 message_list_get_paste_target_list (MessageList *message_list)
4013 {
4014 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
4015
4016 return message_list->priv->paste_target_list;
4017 }
4018
4019 static gboolean
4020 on_cursor_activated_idle (gpointer data)
4021 {
4022 MessageList *message_list = data;
4023 ESelectionModel *esm;
4024 gint selected;
4025
4026 esm = e_tree_get_selection_model (E_TREE (message_list));
4027 selected = e_selection_model_selected_count (esm);
4028
4029 if (selected == 1 && message_list->cursor_uid) {
4030 d (printf ("emitting cursor changed signal, for uid %s\n", message_list->cursor_uid));
4031 g_signal_emit (message_list, message_list_signals[MESSAGE_SELECTED], 0, message_list->cursor_uid);
4032 } else {
4033 g_signal_emit (message_list, message_list_signals[MESSAGE_SELECTED], 0, NULL);
4034 }
4035
4036 message_list->idle_id = 0;
4037 return FALSE;
4038 }
4039
4040 static void
4041 on_cursor_activated_cmd (ETree *tree,
4042 gint row,
4043 ETreePath path,
4044 gpointer user_data)
4045 {
4046 MessageList *message_list = MESSAGE_LIST (user_data);
4047 const gchar *new_uid;
4048
4049 if (path == NULL)
4050 new_uid = NULL;
4051 else
4052 new_uid = get_message_uid (message_list, path);
4053
4054 /* Do not check the cursor_uid and the new_uid values, because the
4055 * selected item (set in on_selection_changed_cmd) can be different
4056 * from the one with a cursor (when selecting with Ctrl, for example).
4057 * This has a little side-effect, when keeping list it that state,
4058 * then changing folders forth and back will select and move cursor
4059 * to that selected item. Does anybody consider it as a bug? */
4060 if ((message_list->cursor_uid == NULL && new_uid == NULL)
4061 || (message_list->last_sel_single && message_list->cursor_uid != NULL && new_uid != NULL))
4062 return;
4063
4064 g_free (message_list->cursor_uid);
4065 message_list->cursor_uid = g_strdup (new_uid);
4066
4067 if (!message_list->idle_id) {
4068 message_list->idle_id =
4069 g_idle_add_full (
4070 G_PRIORITY_LOW, on_cursor_activated_idle,
4071 message_list, NULL);
4072 }
4073 }
4074
4075 static void
4076 on_selection_changed_cmd (ETree *tree,
4077 MessageList *ml)
4078 {
4079 GPtrArray *uids;
4080 const gchar *newuid;
4081 ETreePath cursor;
4082
4083 /* not sure if we could just ignore this for the cursor, i think sometimes you
4084 * only get a selection changed when you should also get a cursor activated? */
4085 uids = message_list_get_selected (ml);
4086 if (uids->len == 1)
4087 newuid = g_ptr_array_index (uids, 0);
4088 else if ((cursor = e_tree_get_cursor (tree)))
4089 newuid = (gchar *) camel_message_info_uid (e_tree_memory_node_get_data ((ETreeMemory *) tree, cursor));
4090 else
4091 newuid = NULL;
4092
4093 /* If the selection isn't empty, then we ignore the no-uid check, since this event
4094 * is also used for other updating. If it is empty, it might just be a setup event
4095 * from etree which we do need to ignore */
4096 if ((newuid == NULL && ml->cursor_uid == NULL && uids->len == 0) ||
4097 (ml->last_sel_single && uids->len == 1 && newuid != NULL && ml->cursor_uid != NULL && !strcmp (ml->cursor_uid, newuid))) {
4098 /* noop */
4099 } else {
4100 g_free (ml->cursor_uid);
4101 ml->cursor_uid = g_strdup (newuid);
4102 if (!ml->idle_id)
4103 ml->idle_id = g_idle_add_full (G_PRIORITY_LOW, on_cursor_activated_idle, ml, NULL);
4104 }
4105
4106 ml->last_sel_single = uids->len == 1;
4107
4108 em_utils_uids_free (uids);
4109 }
4110
4111 static gint
4112 on_click (ETree *tree,
4113 gint row,
4114 ETreePath path,
4115 gint col,
4116 GdkEvent *event,
4117 MessageList *list)
4118 {
4119 CamelMessageInfo *info;
4120 gboolean folder_is_trash;
4121 const gchar *uid;
4122 gint flag = 0;
4123 guint32 flags;
4124
4125 if (col == COL_MESSAGE_STATUS)
4126 flag = CAMEL_MESSAGE_SEEN;
4127 else if (col == COL_FLAGGED)
4128 flag = CAMEL_MESSAGE_FLAGGED;
4129 else if (col != COL_FOLLOWUP_FLAG_STATUS)
4130 return FALSE;
4131
4132 if (!(info = get_message_info (list, path)))
4133 return FALSE;
4134
4135 if (col == COL_FOLLOWUP_FLAG_STATUS) {
4136 const gchar *tag, *cmp;
4137
4138 tag = camel_message_info_user_tag (info, "follow-up");
4139 cmp = camel_message_info_user_tag (info, "completed-on");
4140 if (tag && tag[0]) {
4141 if (cmp && cmp[0]) {
4142 camel_message_info_set_user_tag (info, "follow-up", NULL);
4143 camel_message_info_set_user_tag (info, "due-by", NULL);
4144 camel_message_info_set_user_tag (info, "completed-on", NULL);
4145 } else {
4146 gchar *text;
4147
4148 text = camel_header_format_date (time (NULL), 0);
4149 camel_message_info_set_user_tag (info, "completed-on", text);
4150 g_free (text);
4151 }
4152 } else {
4153 /* default follow-up flag name to use when clicked in the message list column */
4154 camel_message_info_set_user_tag (info, "follow-up", _("Follow-up"));
4155 camel_message_info_set_user_tag (info, "completed-on", NULL);
4156 }
4157
4158 return TRUE;
4159 }
4160
4161 flags = camel_message_info_flags (info);
4162
4163 folder_is_trash =
4164 ((list->folder->folder_flags & CAMEL_FOLDER_IS_TRASH) != 0);
4165
4166 /* If a message was marked as deleted and the user flags it as
4167 * important or unread in a non-Trash folder, then undelete the
4168 * message. We avoid automatically undeleting messages while
4169 * viewing a Trash folder because it would cause the message to
4170 * suddenly disappear from the message list, which is confusing
4171 * and alarming to the user. */
4172 if (!folder_is_trash && flags & CAMEL_MESSAGE_DELETED) {
4173 if (col == COL_FLAGGED && !(flags & CAMEL_MESSAGE_FLAGGED))
4174 flag |= CAMEL_MESSAGE_DELETED;
4175
4176 if (col == COL_MESSAGE_STATUS && (flags & CAMEL_MESSAGE_SEEN))
4177 flag |= CAMEL_MESSAGE_DELETED;
4178 }
4179
4180 uid = camel_message_info_uid (info);
4181 camel_folder_set_message_flags (list->folder, uid, flag, ~flags);
4182
4183 /* Notify the folder tree model that the user has marked a message
4184 * as unread so it doesn't mistake the event as new mail arriving. */
4185 if (col == COL_MESSAGE_STATUS && (flags & CAMEL_MESSAGE_SEEN)) {
4186 EMFolderTreeModel *model;
4187
4188 model = em_folder_tree_model_get_default ();
4189 em_folder_tree_model_user_marked_unread (
4190 model, list->folder, 1);
4191 }
4192
4193 if (flag == CAMEL_MESSAGE_SEEN && list->seen_id) {
4194 g_source_remove (list->seen_id);
4195 list->seen_id = 0;
4196 }
4197
4198 return TRUE;
4199 }
4200
4201 struct _ml_selected_data {
4202 MessageList *ml;
4203 GPtrArray *uids;
4204 };
4205
4206 static void
4207 ml_getselected_cb (ETreePath path,
4208 gpointer user_data)
4209 {
4210 struct _ml_selected_data *data = user_data;
4211 const gchar *uid;
4212
4213 if (e_tree_model_node_is_root (data->ml->model, path))
4214 return;
4215
4216 uid = get_message_uid (data->ml, path);
4217 g_return_if_fail (uid != NULL);
4218 g_ptr_array_add (data->uids, g_strdup (uid));
4219 }
4220
4221 GPtrArray *
4222 message_list_get_uids (MessageList *ml)
4223 {
4224 struct _ml_selected_data data = {
4225 ml,
4226 g_ptr_array_new ()
4227 };
4228
4229 e_tree_path_foreach (E_TREE (ml), ml_getselected_cb, &data);
4230
4231 if (ml->folder && data.uids->len)
4232 camel_folder_sort_uids (ml->folder, data.uids);
4233
4234 return data.uids;
4235 }
4236
4237 GPtrArray *
4238 message_list_get_selected (MessageList *ml)
4239 {
4240 struct _ml_selected_data data = {
4241 ml,
4242 g_ptr_array_new ()
4243 };
4244
4245 e_tree_selected_path_foreach (E_TREE (ml), ml_getselected_cb, &data);
4246
4247 if (ml->folder && data.uids->len)
4248 camel_folder_sort_uids (ml->folder, data.uids);
4249
4250 return data.uids;
4251 }
4252
4253 void
4254 message_list_set_selected (MessageList *ml,
4255 GPtrArray *uids)
4256 {
4257 gint i;
4258 ETreeSelectionModel *etsm;
4259 ETreePath node;
4260 GPtrArray *paths = g_ptr_array_new ();
4261
4262 etsm = (ETreeSelectionModel *) e_tree_get_selection_model (E_TREE (ml));
4263 for (i = 0; i < uids->len; i++) {
4264 node = g_hash_table_lookup (ml->uid_nodemap, uids->pdata[i]);
4265 if (node)
4266 g_ptr_array_add (paths, node);
4267 }
4268
4269 e_tree_selection_model_select_paths (etsm, paths);
4270 g_ptr_array_free (paths, TRUE);
4271 }
4272
4273 struct ml_count_data {
4274 MessageList *ml;
4275 guint count;
4276 };
4277
4278 static void
4279 ml_getcount_cb (ETreePath path,
4280 gpointer user_data)
4281 {
4282 struct ml_count_data *data = user_data;
4283
4284 if (!e_tree_model_node_is_root (data->ml->model, path))
4285 data->count++;
4286 }
4287
4288 guint
4289 message_list_count (MessageList *message_list)
4290 {
4291 struct ml_count_data data = { message_list, 0 };
4292
4293 g_return_val_if_fail (message_list != NULL, 0);
4294 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), 0);
4295
4296 e_tree_path_foreach (E_TREE (message_list), ml_getcount_cb, &data);
4297
4298 return data.count;
4299 }
4300
4301 static void
4302 ml_getselcount_cb (gint model_row,
4303 gpointer user_data)
4304 {
4305 struct ml_count_data *data = user_data;
4306
4307 data->count++;
4308 }
4309
4310 guint
4311 message_list_selected_count (MessageList *message_list)
4312 {
4313 struct ml_count_data data = { message_list, 0 };
4314
4315 g_return_val_if_fail (message_list != NULL, 0);
4316 g_return_val_if_fail (IS_MESSAGE_LIST (message_list), 0);
4317
4318 e_tree_selected_row_foreach (E_TREE (message_list), ml_getselcount_cb, &data);
4319
4320 return data.count;
4321 }
4322