2 * This program is free software; you can redistribute it and/or modify it
3 * under the terms of the GNU Lesser General Public License as published by
4 * the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful, but
7 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * You should have received a copy of the GNU Lesser General Public License
12 * along with this program; if not, see <http://www.gnu.org/licenses/>.
16 * Miguel de Icaza (miguel@ximian.com)
17 * Bertrand Guiheneuf (bg@aful.org)
18 * And just about everyone else in evolution ...
20 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
24 #include "evolution-config.h"
26 #include <sys/types.h>
32 #include <glib/gi18n.h>
33 #include <glib/gstdio.h>
35 #include "e-mail-label-list-store.h"
36 #include "e-mail-notes.h"
37 #include "e-mail-ui-session.h"
55 /* The gmtime() and localtime() in Microsoft's C library are MT-safe */
56 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
57 #define localtime_r(tp,tmp) (localtime(tp)?(*(tmp)=*localtime(tp),(tmp)):0)
60 #include "message-list.h"
64 #define dd(x) G_STMT_START { if (camel_debug ("message-list")) { x; } } G_STMT_END
66 #define MESSAGE_LIST_GET_PRIVATE(obj) \
67 (G_TYPE_INSTANCE_GET_PRIVATE \
68 ((obj), MESSAGE_LIST_TYPE, MessageListPrivate))
70 /* Common search expression segments. */
71 #define EXCLUDE_DELETED_MESSAGES_EXPR "(not (system-flag \"deleted\"))"
72 #define EXCLUDE_JUNK_MESSAGES_EXPR "(not (system-flag \"junk\"))"
74 typedef struct _ExtendedGNode ExtendedGNode
;
75 typedef struct _RegenData RegenData
;
82 struct _MessageListPrivate
{
83 GtkWidget
*invisible
; /* 4 selection */
85 EMailSession
*session
;
88 gulong folder_changed_handler_id
;
90 /* For message list regeneration. */
92 RegenData
*regen_data
;
95 gboolean thaw_needs_regen
;
97 GMutex thread_tree_lock
;
98 CamelFolderThread
*thread_tree
;
100 struct _MLSelection clipboard
;
103 gboolean expanded_default
;
104 gboolean group_by_threads
;
105 gboolean show_deleted
;
107 gboolean thread_latest
;
108 gboolean thread_subject
;
109 gboolean any_row_changed
; /* save state before regen list when this is set to true */
110 gboolean show_subject_above_sender
;
112 GtkTargetList
*copy_target_list
;
113 GtkTargetList
*paste_target_list
;
115 /* XXX Not sure if we really need a separate frozen counter
116 * for the tree model but the old ETreeMemory class had
117 * its own frozen counter so we preserve it here. */
118 GNode
*tree_model_root
;
119 gint tree_model_frozen
;
121 /* This aids in automatic message selection. */
122 time_t newest_read_date
;
123 const gchar
*newest_read_uid
;
124 time_t oldest_unread_date
;
125 const gchar
*oldest_unread_uid
;
127 GSettings
*mail_settings
;
129 gchar
**re_separators
;
130 GMutex re_prefixes_lock
;
132 GdkRGBA
*new_mail_bg_color
;
135 /* XXX Plain GNode suffers from O(N) tail insertions, and that won't
136 * do for large mail folders. This structure extends GNode with
137 * a pointer to its last child, so we get O(1) tail insertions. */
138 struct _ExtendedGNode
{
144 volatile gint ref_count
;
147 MessageList
*message_list
;
151 gboolean group_by_threads
;
152 gboolean thread_subject
;
154 CamelFolderThread
*thread_tree
;
156 /* This indicates we're regenerating the message list because
157 * we received a "folder-changed" signal from our CamelFolder. */
158 gboolean folder_changed
;
163 gint last_row
; /* last selected (cursor) row */
165 xmlDoc
*expand_state
; /* expanded state to be restored */
167 /* These may be set during a regen operation. Use the
168 * select_lock to ensure consistency and thread-safety.
169 * These are applied after the operation is finished. */
173 gboolean select_use_fallback
;
178 PROP_COPY_TARGET_LIST
,
180 PROP_GROUP_BY_THREADS
,
181 PROP_PASTE_TARGET_LIST
,
185 PROP_SHOW_SUBJECT_ABOVE_SENDER
,
190 /* Forward Declarations */
191 static void message_list_selectable_init
192 (ESelectableInterface
*iface
);
193 static void message_list_tree_model_init
194 (ETreeModelInterface
*iface
);
195 static gboolean message_list_get_hide_deleted
196 (MessageList
*message_list
,
197 CamelFolder
*folder
);
199 G_DEFINE_TYPE_WITH_CODE (
203 G_IMPLEMENT_INTERFACE (
204 E_TYPE_EXTENSIBLE
, NULL
)
205 G_IMPLEMENT_INTERFACE (
207 message_list_selectable_init
)
208 G_IMPLEMENT_INTERFACE (
210 message_list_tree_model_init
))
217 { "x-uid-list", NULL
, GDK_ACTION_MOVE
| GDK_ACTION_COPY
},
218 { "message/rfc822", NULL
, GDK_ACTION_COPY
},
219 { "text/uri-list", NULL
, GDK_ACTION_COPY
},
223 DND_X_UID_LIST
, /* x-uid-list */
224 DND_MESSAGE_RFC822
, /* message/rfc822 */
225 DND_TEXT_URI_LIST
/* text/uri-list */
229 static GtkTargetEntry ml_drag_types
[] = {
230 { (gchar
*) "x-uid-list", 0, DND_X_UID_LIST
},
231 { (gchar
*) "text/uri-list", 0, DND_TEXT_URI_LIST
},
235 static GtkTargetEntry ml_drop_types
[] = {
236 { (gchar
*) "x-uid-list", 0, DND_X_UID_LIST
},
237 { (gchar
*) "message/rfc822", 0, DND_MESSAGE_RFC822
},
238 { (gchar
*) "text/uri-list", 0, DND_TEXT_URI_LIST
},
242 * Default sizes for the ETable display
245 #define N_CHARS(x) (CHAR_WIDTH * (x))
247 #define COL_ICON_WIDTH (16)
248 #define COL_ATTACH_WIDTH (16)
249 #define COL_CHECK_BOX_WIDTH (16)
250 #define COL_FROM_EXPANSION (24.0)
251 #define COL_FROM_WIDTH_MIN (32)
252 #define COL_SUBJECT_EXPANSION (30.0)
253 #define COL_SUBJECT_WIDTH_MIN (32)
254 #define COL_SENT_EXPANSION (24.0)
255 #define COL_SENT_WIDTH_MIN (32)
256 #define COL_RECEIVED_EXPANSION (20.0)
257 #define COL_RECEIVED_WIDTH_MIN (32)
258 #define COL_TO_EXPANSION (24.0)
259 #define COL_TO_WIDTH_MIN (32)
260 #define COL_SIZE_EXPANSION (6.0)
261 #define COL_SIZE_WIDTH_MIN (32)
262 #define COL_SENDER_EXPANSION (24.0)
263 #define COL_SENDER_WIDTH_MIN (32)
272 static void on_cursor_activated_cmd (ETree
*tree
,
276 static void on_selection_changed_cmd (ETree
*tree
,
277 MessageList
*message_list
);
278 static gint
on_click (ETree
*tree
,
283 MessageList
*message_list
);
285 static void mail_regen_list (MessageList
*message_list
,
287 gboolean folder_changed
);
288 static void mail_regen_cancel (MessageList
*message_list
);
290 static void clear_info (gchar
*key
,
292 MessageList
*message_list
);
300 static guint signals
[LAST_SIGNAL
] = {0, };
302 static const gchar
*status_map
[] = {
307 N_("Multiple Unseen Messages"),
308 N_("Multiple Messages")
311 static const gchar
*status_icons
[] = {
316 "stock_mail-unread-multiple",
317 "stock_mail-open-multiple"
320 static const gchar
*score_map
[] = {
330 static const gchar
*score_icons
[] = {
331 "stock_score-lowest",
334 "stock_score-normal",
336 "stock_score-higher",
337 "stock_score-highest"
340 static const gchar
*attachment_icons
[] = {
341 NULL
, /* empty icon */
348 static const gchar
*flagged_icons
[] = {
349 NULL
, /* empty icon */
353 static const gchar
*followup_icons
[] = {
354 NULL
, /* empty icon */
355 "stock_mail-flag-for-followup",
356 "stock_mail-flag-for-followup-done"
360 extended_g_node_new (gpointer data
)
364 node
= (GNode
*) g_slice_new0 (ExtendedGNode
);
371 extended_g_node_unlink (GNode
*node
)
373 g_return_if_fail (node
!= NULL
);
375 /* Update the last_child pointer before we unlink. */
376 if (node
->parent
!= NULL
) {
377 ExtendedGNode
*ext_parent
;
379 ext_parent
= (ExtendedGNode
*) node
->parent
;
380 if (ext_parent
->last_child
== node
) {
381 g_warn_if_fail (node
->next
== NULL
);
382 ext_parent
->last_child
= node
->prev
;
386 g_node_unlink (node
);
390 extended_g_nodes_free (GNode
*node
)
392 while (node
!= NULL
) {
393 GNode
*next
= node
->next
;
394 if (node
->children
!= NULL
)
395 extended_g_nodes_free (node
->children
);
396 g_slice_free (ExtendedGNode
, (ExtendedGNode
*) node
);
402 extended_g_node_destroy (GNode
*root
)
404 g_return_if_fail (root
!= NULL
);
406 if (!G_NODE_IS_ROOT (root
))
407 extended_g_node_unlink (root
);
409 extended_g_nodes_free (root
);
413 extended_g_node_insert_before (GNode
*parent
,
417 ExtendedGNode
*ext_parent
;
419 g_return_val_if_fail (parent
!= NULL
, node
);
420 g_return_val_if_fail (node
!= NULL
, node
);
421 g_return_val_if_fail (G_NODE_IS_ROOT (node
), node
);
423 g_return_val_if_fail (sibling
->parent
== parent
, node
);
425 ext_parent
= (ExtendedGNode
*) parent
;
427 /* This is where tracking the last child pays off. */
428 if (sibling
== NULL
&& ext_parent
->last_child
!= NULL
) {
429 node
->parent
= parent
;
430 node
->prev
= ext_parent
->last_child
;
431 ext_parent
->last_child
->next
= node
;
433 g_node_insert_before (parent
, sibling
, node
);
437 ext_parent
->last_child
= node
;
443 extended_g_node_insert (GNode
*parent
,
449 g_return_val_if_fail (parent
!= NULL
, node
);
450 g_return_val_if_fail (node
!= NULL
, node
);
451 g_return_val_if_fail (G_NODE_IS_ROOT (node
), node
);
454 sibling
= g_node_nth_child (parent
, position
);
455 else if (position
== 0)
456 sibling
= parent
->children
;
457 else /* if (position < 0) */
460 return extended_g_node_insert_before (parent
, sibling
, node
);
464 regen_data_new (MessageList
*message_list
,
465 GCancellable
*cancellable
)
467 RegenData
*regen_data
;
469 EMailSession
*session
;
471 activity
= e_activity_new ();
472 e_activity_set_cancellable (activity
, cancellable
);
473 e_activity_set_text (activity
, _("Generating message list"));
475 regen_data
= g_slice_new0 (RegenData
);
476 regen_data
->ref_count
= 1;
477 regen_data
->activity
= g_object_ref (activity
);
478 regen_data
->message_list
= g_object_ref (message_list
);
479 regen_data
->folder
= message_list_ref_folder (message_list
);
480 regen_data
->last_row
= -1;
482 if (message_list
->just_set_folder
)
483 regen_data
->select_uid
= g_strdup (message_list
->cursor_uid
);
485 g_mutex_init (®en_data
->select_lock
);
487 session
= message_list_get_session (message_list
);
488 e_mail_ui_session_add_activity (E_MAIL_UI_SESSION (session
), activity
);
490 g_object_unref (activity
);
496 regen_data_ref (RegenData
*regen_data
)
498 g_return_val_if_fail (regen_data
!= NULL
, NULL
);
499 g_return_val_if_fail (regen_data
->ref_count
> 0, NULL
);
501 g_atomic_int_inc (®en_data
->ref_count
);
507 regen_data_unref (RegenData
*regen_data
)
509 g_return_if_fail (regen_data
!= NULL
);
510 g_return_if_fail (regen_data
->ref_count
> 0);
512 if (g_atomic_int_dec_and_test (®en_data
->ref_count
)) {
514 g_clear_object (®en_data
->activity
);
515 g_clear_object (®en_data
->message_list
);
517 g_free (regen_data
->search
);
519 if (regen_data
->thread_tree
!= NULL
)
520 camel_folder_thread_messages_unref (
521 regen_data
->thread_tree
);
523 if (regen_data
->summary
!= NULL
) {
526 length
= regen_data
->summary
->len
;
528 for (ii
= 0; ii
< length
; ii
++)
529 g_clear_object (®en_data
->summary
->pdata
[ii
]);
531 g_ptr_array_free (regen_data
->summary
, TRUE
);
534 g_clear_object (®en_data
->folder
);
536 if (regen_data
->expand_state
!= NULL
)
537 xmlFreeDoc (regen_data
->expand_state
);
539 g_mutex_clear (®en_data
->select_lock
);
540 g_free (regen_data
->select_uid
);
542 g_slice_free (RegenData
, regen_data
);
546 static CamelFolderThread
*
547 message_list_ref_thread_tree (MessageList
*message_list
)
549 CamelFolderThread
*thread_tree
= NULL
;
551 g_return_val_if_fail (IS_MESSAGE_LIST (message_list
), NULL
);
553 g_mutex_lock (&message_list
->priv
->thread_tree_lock
);
555 if (message_list
->priv
->thread_tree
!= NULL
) {
556 thread_tree
= message_list
->priv
->thread_tree
;
557 camel_folder_thread_messages_ref (thread_tree
);
560 g_mutex_unlock (&message_list
->priv
->thread_tree_lock
);
566 message_list_set_thread_tree (MessageList
*message_list
,
567 CamelFolderThread
*thread_tree
)
569 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
571 g_mutex_lock (&message_list
->priv
->thread_tree_lock
);
573 if (thread_tree
!= NULL
)
574 camel_folder_thread_messages_ref (thread_tree
);
576 if (message_list
->priv
->thread_tree
!= NULL
)
577 camel_folder_thread_messages_unref (
578 message_list
->priv
->thread_tree
);
580 message_list
->priv
->thread_tree
= thread_tree
;
582 g_mutex_unlock (&message_list
->priv
->thread_tree_lock
);
586 message_list_ref_regen_data (MessageList
*message_list
)
588 RegenData
*regen_data
= NULL
;
590 g_mutex_lock (&message_list
->priv
->regen_lock
);
592 if (message_list
->priv
->regen_data
!= NULL
)
593 regen_data
= regen_data_ref (message_list
->priv
->regen_data
);
595 g_mutex_unlock (&message_list
->priv
->regen_lock
);
601 message_list_tree_model_freeze (MessageList
*message_list
)
603 if (message_list
->priv
->tree_model_frozen
== 0)
604 e_tree_model_pre_change (E_TREE_MODEL (message_list
));
606 message_list
->priv
->tree_model_frozen
++;
610 message_list_tree_model_thaw (MessageList
*message_list
)
612 if (message_list
->priv
->tree_model_frozen
> 0)
613 message_list
->priv
->tree_model_frozen
--;
615 if (message_list
->priv
->tree_model_frozen
== 0)
616 e_tree_model_node_changed (
617 E_TREE_MODEL (message_list
),
618 message_list
->priv
->tree_model_root
);
622 message_list_tree_model_insert (MessageList
*message_list
,
627 ETreeModel
*tree_model
;
629 gboolean tree_model_frozen
;
632 g_return_val_if_fail (
633 message_list
->priv
->tree_model_root
== NULL
, NULL
);
635 tree_model
= E_TREE_MODEL (message_list
);
636 tree_model_frozen
= (message_list
->priv
->tree_model_frozen
> 0);
638 if (!tree_model_frozen
)
639 e_tree_model_pre_change (tree_model
);
641 node
= extended_g_node_new (data
);
643 if (parent
!= NULL
) {
644 extended_g_node_insert (parent
, position
, node
);
645 if (!tree_model_frozen
)
646 e_tree_model_node_inserted (tree_model
, parent
, node
);
648 message_list
->priv
->tree_model_root
= node
;
649 if (!tree_model_frozen
)
650 e_tree_model_node_changed (tree_model
, node
);
657 message_list_tree_model_remove (MessageList
*message_list
,
660 ETreeModel
*tree_model
;
661 GNode
*parent
= node
->parent
;
662 gboolean tree_model_frozen
;
663 gint old_position
= 0;
665 g_return_if_fail (node
!= NULL
);
667 tree_model
= E_TREE_MODEL (message_list
);
668 tree_model_frozen
= (message_list
->priv
->tree_model_frozen
> 0);
670 if (!tree_model_frozen
) {
671 e_tree_model_pre_change (tree_model
);
672 old_position
= g_node_child_position (parent
, node
);
675 extended_g_node_unlink (node
);
677 if (!tree_model_frozen
)
678 e_tree_model_node_removed (
679 tree_model
, parent
, node
, old_position
);
681 extended_g_node_destroy (node
);
683 if (node
== message_list
->priv
->tree_model_root
)
684 message_list
->priv
->tree_model_root
= NULL
;
686 if (!tree_model_frozen
)
687 e_tree_model_node_deleted (tree_model
, node
);
691 address_compare (gconstpointer address1
,
692 gconstpointer address2
,
697 g_return_val_if_fail (address1
!= NULL
, 1);
698 g_return_val_if_fail (address2
!= NULL
, -1);
700 retval
= g_ascii_strcasecmp ((gchar
*) address1
, (gchar
*) address2
);
706 filter_size (gint size
)
711 return g_strdup_printf ("%d", size
);
713 fsize
= ((gfloat
) size
) / 1024.0;
714 if (fsize
< 1024.0) {
715 return g_strdup_printf ("%.2f K", fsize
);
718 return g_strdup_printf ("%.2f M", fsize
);
723 /* Gets the uid of the message displayed at a given view row */
725 get_message_uid (MessageList
*message_list
,
728 g_return_val_if_fail (node
!= NULL
, NULL
);
729 g_return_val_if_fail (node
->data
!= NULL
, NULL
);
731 return camel_message_info_get_uid (node
->data
);
734 /* Gets the CamelMessageInfo for the message displayed at the given
737 static CamelMessageInfo
*
738 get_message_info (MessageList
*message_list
,
741 g_return_val_if_fail (node
!= NULL
, NULL
);
742 g_return_val_if_fail (node
->data
!= NULL
, NULL
);
748 get_normalised_string (MessageList
*message_list
,
749 CamelMessageInfo
*info
,
752 const gchar
*string
, *str
;
758 case COL_SUBJECT_NORM
:
759 string
= camel_message_info_get_subject (info
);
760 index
= NORMALISED_SUBJECT
;
763 string
= camel_message_info_get_from (info
);
764 index
= NORMALISED_FROM
;
767 string
= camel_message_info_get_to (info
);
768 index
= NORMALISED_TO
;
772 index
= NORMALISED_LAST
;
773 g_warning ("Should not be reached\n");
776 /* slight optimisation */
777 if (string
== NULL
|| string
[0] == '\0')
780 poolv
= g_hash_table_lookup (message_list
->normalised_hash
, camel_message_info_get_uid (info
));
782 poolv
= e_poolv_new (NORMALISED_LAST
);
783 g_hash_table_insert (message_list
->normalised_hash
, (gchar
*) camel_message_info_get_uid (info
), poolv
);
785 str
= e_poolv_get (poolv
, index
);
790 if (col
== COL_SUBJECT_NORM
) {
792 const gchar
*subject
;
793 gboolean found_re
= TRUE
;
797 g_mutex_lock (&message_list
->priv
->re_prefixes_lock
);
798 found_re
= em_utils_is_re_in_subject (
799 subject
, &skip_len
, (const gchar
* const *) message_list
->priv
->re_prefixes
,
800 (const gchar
* const *) message_list
->priv
->re_separators
) && skip_len
> 0;
801 g_mutex_unlock (&message_list
->priv
->re_prefixes_lock
);
806 /* jump over any spaces */
807 while (*subject
&& isspace ((gint
) *subject
))
811 /* jump over any spaces */
812 while (*subject
&& isspace ((gint
) *subject
))
816 normalised
= g_utf8_collate_key (string
, -1);
818 /* because addresses require strings, not collate keys */
819 normalised
= g_strdup (string
);
822 e_poolv_set (poolv
, index
, normalised
, TRUE
);
824 return e_poolv_get (poolv
, index
);
828 clear_selection (MessageList
*message_list
,
829 struct _MLSelection
*selection
)
831 if (selection
->uids
!= NULL
) {
832 g_ptr_array_unref (selection
->uids
);
833 selection
->uids
= NULL
;
836 g_clear_object (&selection
->folder
);
840 ml_get_next_node (GNode
*node
,
848 next
= g_node_first_child (node
);
850 if (!next
&& node
!= subroot
) {
851 next
= g_node_next_sibling (node
);
854 if (!next
&& node
!= subroot
) {
857 GNode
*sibl
= g_node_next_sibling (next
);
875 ml_get_prev_node (GNode
*node
,
886 prev
= g_node_prev_sibling (node
);
899 GNode
*child
= g_node_last_child (prev
);
902 child
= g_node_last_child (child
);
910 ml_get_last_tree_node (GNode
*node
,
918 while (node
->parent
&& node
->parent
!= subroot
)
924 child
= g_node_last_sibling (node
);
929 while (child
= g_node_last_child (child
), child
) {
937 ml_search_forward (MessageList
*message_list
,
942 gboolean include_collapsed
,
947 CamelMessageInfo
*info
;
948 ETreeTableAdapter
*etta
;
950 etta
= e_tree_get_table_adapter (E_TREE (message_list
));
952 for (row
= start
; row
<= end
; row
++) {
953 node
= e_tree_table_adapter_node_at_row (etta
, row
);
954 if (node
!= NULL
&& !skip_first
955 && (info
= get_message_info (message_list
, node
))
956 && (camel_message_info_get_flags (info
) & mask
) == flags
)
961 if (node
&& include_collapsed
&& !e_tree_table_adapter_node_is_expanded (etta
, node
) && g_node_first_child (node
)) {
962 GNode
*subnode
= node
;
964 while (subnode
= ml_get_next_node (subnode
, node
), subnode
&& subnode
!= node
) {
965 if ((info
= get_message_info (message_list
, subnode
)) &&
966 (camel_message_info_get_flags (info
) & mask
) == flags
)
976 ml_search_backward (MessageList
*message_list
,
981 gboolean include_collapsed
,
986 CamelMessageInfo
*info
;
987 ETreeTableAdapter
*etta
;
989 etta
= e_tree_get_table_adapter (E_TREE (message_list
));
991 for (row
= start
; row
>= end
; row
--) {
992 node
= e_tree_table_adapter_node_at_row (etta
, row
);
993 if (node
!= NULL
&& !skip_first
994 && (info
= get_message_info (message_list
, node
))
995 && (camel_message_info_get_flags (info
) & mask
) == flags
) {
996 if (include_collapsed
&& !e_tree_table_adapter_node_is_expanded (etta
, node
) && g_node_first_child (node
)) {
997 GNode
*subnode
= ml_get_last_tree_node (g_node_first_child (node
), node
);
999 while (subnode
&& subnode
!= node
) {
1000 if ((info
= get_message_info (message_list
, subnode
)) &&
1001 (camel_message_info_get_flags (info
) & mask
) == flags
)
1004 subnode
= ml_get_prev_node (subnode
, node
);
1011 if (node
&& include_collapsed
&& !skip_first
&& !e_tree_table_adapter_node_is_expanded (etta
, node
) && g_node_first_child (node
)) {
1012 GNode
*subnode
= ml_get_last_tree_node (g_node_first_child (node
), node
);
1014 while (subnode
&& subnode
!= node
) {
1015 if ((info
= get_message_info (message_list
, subnode
)) &&
1016 (camel_message_info_get_flags (info
) & mask
) == flags
)
1019 subnode
= ml_get_prev_node (subnode
, node
);
1030 ml_search_path (MessageList
*message_list
,
1031 MessageListSelectDirection direction
,
1035 ETreeTableAdapter
*adapter
;
1036 gboolean include_collapsed
;
1041 if (message_list
->cursor_uid
== NULL
)
1044 node
= g_hash_table_lookup (
1045 message_list
->uid_nodemap
,
1046 message_list
->cursor_uid
);
1050 adapter
= e_tree_get_table_adapter (E_TREE (message_list
));
1051 row_count
= e_table_model_row_count (E_TABLE_MODEL (adapter
));
1053 row
= e_tree_table_adapter_row_of_node (adapter
, node
);
1057 include_collapsed
= (direction
& MESSAGE_LIST_SELECT_INCLUDE_COLLAPSED
) != 0;
1059 if ((direction
& MESSAGE_LIST_SELECT_DIRECTION
) == MESSAGE_LIST_SELECT_NEXT
)
1060 node
= ml_search_forward (
1061 message_list
, row
, row_count
- 1, flags
, mask
, include_collapsed
, TRUE
);
1063 node
= ml_search_backward (
1064 message_list
, row
, 0, flags
, mask
, include_collapsed
, TRUE
);
1066 if (node
== NULL
&& (direction
& MESSAGE_LIST_SELECT_WRAP
)) {
1067 if ((direction
& MESSAGE_LIST_SELECT_DIRECTION
) == MESSAGE_LIST_SELECT_NEXT
)
1068 node
= ml_search_forward (
1069 message_list
, 0, row
, flags
, mask
, include_collapsed
, FALSE
);
1071 node
= ml_search_backward (
1072 message_list
, row_count
- 1, row
, flags
, mask
, include_collapsed
, FALSE
);
1079 select_node (MessageList
*message_list
,
1083 ETreeTableAdapter
*etta
;
1084 ETreeSelectionModel
*etsm
;
1086 tree
= E_TREE (message_list
);
1087 etta
= e_tree_get_table_adapter (tree
);
1088 etsm
= (ETreeSelectionModel
*) e_tree_get_selection_model (tree
);
1090 g_free (message_list
->cursor_uid
);
1091 message_list
->cursor_uid
= NULL
;
1093 e_tree_table_adapter_show_node (etta
, node
);
1094 e_tree_set_cursor (tree
, node
);
1095 e_tree_selection_model_select_single_path (etsm
, node
);
1099 * message_list_select:
1100 * @message_list: a MessageList
1101 * @direction: the direction to search in
1102 * @flags: a set of flag values
1103 * @mask: a mask for comparing against @flags
1105 * This moves the message list selection to a suitable row. @flags and
1106 * @mask combine to specify what constitutes a suitable row. @direction is
1107 * %MESSAGE_LIST_SELECT_NEXT if it should find the next matching
1108 * message, or %MESSAGE_LIST_SELECT_PREVIOUS if it should find the
1109 * previous. %MESSAGE_LIST_SELECT_WRAP is an option bit which specifies the
1110 * search should wrap.
1112 * If no suitable row is found, the selection will be
1115 * Returns %TRUE if a new message has been selected or %FALSE otherwise.
1118 message_list_select (MessageList
*message_list
,
1119 MessageListSelectDirection direction
,
1125 g_return_val_if_fail (IS_MESSAGE_LIST (message_list
), FALSE
);
1127 node
= ml_search_path (message_list
, direction
, flags
, mask
);
1129 select_node (message_list
, node
);
1131 /* This function is usually called in response to a key
1132 * press, so grab focus if the message list is visible. */
1133 if (gtk_widget_get_visible (GTK_WIDGET (message_list
)))
1134 gtk_widget_grab_focus (GTK_WIDGET (message_list
));
1142 * message_list_can_select:
1148 * Returns true if the selection specified is possible with the current view.
1153 message_list_can_select (MessageList
*message_list
,
1154 MessageListSelectDirection direction
,
1158 g_return_val_if_fail (IS_MESSAGE_LIST (message_list
), FALSE
);
1160 return ml_search_path (message_list
, direction
, flags
, mask
) != NULL
;
1164 * message_list_select_uid:
1168 * Selects the message with the given UID.
1171 message_list_select_uid (MessageList
*message_list
,
1173 gboolean with_fallback
)
1175 MessageListPrivate
*priv
;
1176 GHashTable
*uid_nodemap
;
1178 RegenData
*regen_data
= NULL
;
1180 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
1182 priv
= message_list
->priv
;
1183 uid_nodemap
= message_list
->uid_nodemap
;
1185 if (message_list
->priv
->folder
== NULL
)
1188 /* Try to find the requested message UID. */
1190 node
= g_hash_table_lookup (uid_nodemap
, uid
);
1192 regen_data
= message_list_ref_regen_data (message_list
);
1194 /* If we're busy or waiting to regenerate the message list, cache
1195 * the UID so we can try again when we're done. Otherwise if the
1196 * requested message UID was not found and 'with_fallback' is set,
1197 * try a couple fallbacks:
1199 * 1) Oldest unread message in the list, by date received.
1200 * 2) Newest read message in the list, by date received.
1202 if (regen_data
!= NULL
) {
1203 g_mutex_lock (®en_data
->select_lock
);
1204 g_free (regen_data
->select_uid
);
1205 regen_data
->select_uid
= g_strdup (uid
);
1206 regen_data
->select_use_fallback
= with_fallback
;
1207 g_mutex_unlock (®en_data
->select_lock
);
1209 regen_data_unref (regen_data
);
1211 } else if (with_fallback
) {
1212 if (node
== NULL
&& priv
->oldest_unread_uid
!= NULL
)
1213 node
= g_hash_table_lookup (
1214 uid_nodemap
, priv
->oldest_unread_uid
);
1215 if (node
== NULL
&& priv
->newest_read_uid
!= NULL
)
1216 node
= g_hash_table_lookup (
1217 uid_nodemap
, priv
->newest_read_uid
);
1224 tree
= E_TREE (message_list
);
1225 old_cur
= e_tree_get_cursor (tree
);
1227 /* This will emit a changed signal that we'll pick up */
1228 e_tree_set_cursor (tree
, node
);
1230 if (old_cur
== node
)
1233 signals
[MESSAGE_SELECTED
],
1234 0, message_list
->cursor_uid
);
1235 } else if (message_list
->just_set_folder
) {
1236 g_free (message_list
->cursor_uid
);
1237 message_list
->cursor_uid
= g_strdup (uid
);
1240 signals
[MESSAGE_SELECTED
], 0, message_list
->cursor_uid
);
1242 g_free (message_list
->cursor_uid
);
1243 message_list
->cursor_uid
= NULL
;
1246 signals
[MESSAGE_SELECTED
],
1252 message_list_select_next_thread (MessageList
*message_list
)
1254 ETreeTableAdapter
*adapter
;
1260 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
1262 if (message_list
->cursor_uid
== NULL
)
1265 node
= g_hash_table_lookup (
1266 message_list
->uid_nodemap
,
1267 message_list
->cursor_uid
);
1271 adapter
= e_tree_get_table_adapter (E_TREE (message_list
));
1272 row_count
= e_table_model_row_count ((ETableModel
*) adapter
);
1274 row
= e_tree_table_adapter_row_of_node (adapter
, node
);
1278 /* find the next node which has a root parent (i.e. toplevel node) */
1279 for (ii
= row
+ 1; ii
< row_count
- 1; ii
++) {
1280 node
= e_tree_table_adapter_node_at_row (adapter
, ii
);
1281 if (node
!= NULL
&& G_NODE_IS_ROOT (node
->parent
)) {
1282 select_node (message_list
, node
);
1289 message_list_select_prev_thread (MessageList
*message_list
)
1291 ETreeTableAdapter
*adapter
;
1293 gboolean skip_first
;
1297 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
1299 if (message_list
->cursor_uid
== NULL
)
1302 node
= g_hash_table_lookup (
1303 message_list
->uid_nodemap
,
1304 message_list
->cursor_uid
);
1308 adapter
= e_tree_get_table_adapter (E_TREE (message_list
));
1310 row
= e_tree_table_adapter_row_of_node (adapter
, node
);
1314 /* skip first found if in the middle of the thread */
1315 skip_first
= !G_NODE_IS_ROOT (node
->parent
);
1317 /* find the previous node which has a root parent (i.e. toplevel node) */
1318 for (ii
= row
- 1; ii
>= 0; ii
--) {
1319 node
= e_tree_table_adapter_node_at_row (adapter
, ii
);
1320 if (node
!= NULL
&& G_NODE_IS_ROOT (node
->parent
)) {
1326 select_node (message_list
, node
);
1333 * message_list_select_all:
1334 * @message_list: Message List widget
1336 * Selects all messages in the message list.
1339 message_list_select_all (MessageList
*message_list
)
1341 RegenData
*regen_data
= NULL
;
1343 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
1345 regen_data
= message_list_ref_regen_data (message_list
);
1347 if (regen_data
!= NULL
&& regen_data
->group_by_threads
) {
1348 regen_data
->select_all
= TRUE
;
1351 ESelectionModel
*selection_model
;
1353 tree
= E_TREE (message_list
);
1354 selection_model
= e_tree_get_selection_model (tree
);
1355 e_selection_model_select_all (selection_model
);
1358 if (regen_data
!= NULL
)
1359 regen_data_unref (regen_data
);
1362 typedef struct thread_select_info
{
1363 MessageList
*message_list
;
1365 } thread_select_info_t
;
1368 select_thread_node (ETreeModel
*model
,
1372 thread_select_info_t
*tsi
= (thread_select_info_t
*) user_data
;
1374 g_ptr_array_add (tsi
->paths
, node
);
1376 return FALSE
; /*not done yet */
1380 select_thread (MessageList
*message_list
,
1381 ETreeForeachFunc selector
)
1384 ETreeSelectionModel
*etsm
;
1385 thread_select_info_t tsi
;
1387 tsi
.message_list
= message_list
;
1388 tsi
.paths
= g_ptr_array_new ();
1390 tree
= E_TREE (message_list
);
1391 etsm
= (ETreeSelectionModel
*) e_tree_get_selection_model (tree
);
1393 e_tree_selection_model_foreach (etsm
, selector
, &tsi
);
1395 e_tree_selection_model_select_paths (etsm
, tsi
.paths
);
1397 g_ptr_array_free (tsi
.paths
, TRUE
);
1401 thread_select_foreach (ETreePath path
,
1404 thread_select_info_t
*tsi
= (thread_select_info_t
*) user_data
;
1405 ETreeModel
*tree_model
;
1406 GNode
*last
, *node
= path
;
1408 tree_model
= E_TREE_MODEL (tsi
->message_list
);
1412 node
= node
->parent
;
1413 } while (node
&& !G_NODE_IS_ROOT (node
));
1415 g_ptr_array_add (tsi
->paths
, last
);
1417 e_tree_model_node_traverse (
1419 (ETreePathFunc
) select_thread_node
, tsi
);
1423 * message_list_select_thread:
1424 * @message_list: Message List widget
1426 * Selects all messages in the current thread (based on cursor).
1429 message_list_select_thread (MessageList
*message_list
)
1431 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
1433 select_thread (message_list
, thread_select_foreach
);
1437 subthread_select_foreach (ETreePath path
,
1440 thread_select_info_t
*tsi
= (thread_select_info_t
*) user_data
;
1441 ETreeModel
*tree_model
;
1443 tree_model
= E_TREE_MODEL (tsi
->message_list
);
1445 e_tree_model_node_traverse (
1447 (ETreePathFunc
) select_thread_node
, tsi
);
1451 * message_list_select_subthread:
1452 * @message_list: Message List widget
1454 * Selects all messages in the current subthread (based on cursor).
1457 message_list_select_subthread (MessageList
*message_list
)
1459 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
1461 select_thread (message_list
, subthread_select_foreach
);
1465 message_list_copy (MessageList
*message_list
,
1468 MessageListPrivate
*priv
;
1471 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
1473 priv
= message_list
->priv
;
1475 clear_selection (message_list
, &priv
->clipboard
);
1477 uids
= message_list_get_selected (message_list
);
1479 if (uids
->len
> 0) {
1481 CamelFolder
*folder
;
1484 folder
= message_list_ref_folder (message_list
);
1486 camel_folder_freeze (folder
);
1488 for (i
= 0; i
< uids
->len
; i
++)
1489 camel_folder_set_message_flags (
1490 folder
, uids
->pdata
[i
],
1491 CAMEL_MESSAGE_SEEN
|
1492 CAMEL_MESSAGE_DELETED
,
1493 CAMEL_MESSAGE_SEEN
|
1494 CAMEL_MESSAGE_DELETED
);
1496 camel_folder_thaw (folder
);
1498 g_object_unref (folder
);
1501 priv
->clipboard
.uids
= g_ptr_array_ref (uids
);
1502 priv
->clipboard
.folder
= message_list_ref_folder (message_list
);
1504 gtk_selection_owner_set (
1506 GDK_SELECTION_CLIPBOARD
,
1507 gtk_get_current_event_time ());
1509 gtk_selection_owner_set (
1510 NULL
, GDK_SELECTION_CLIPBOARD
,
1511 gtk_get_current_event_time ());
1514 g_ptr_array_unref (uids
);
1518 message_list_paste (MessageList
*message_list
)
1520 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
1522 gtk_selection_convert (
1523 message_list
->priv
->invisible
,
1524 GDK_SELECTION_CLIPBOARD
,
1525 gdk_atom_intern ("x-uid-list", FALSE
),
1530 for_node_and_subtree_if_collapsed (MessageList
*message_list
,
1532 CamelMessageInfo
*mi
,
1536 ETreeModel
*tree_model
;
1537 ETreeTableAdapter
*adapter
;
1538 GNode
*child
= NULL
;
1540 tree_model
= E_TREE_MODEL (message_list
);
1541 adapter
= e_tree_get_table_adapter (E_TREE (message_list
));
1543 func (NULL
, (ETreePath
) mi
, data
);
1546 child
= g_node_first_child (node
);
1548 if (child
&& !e_tree_table_adapter_node_is_expanded (adapter
, node
))
1549 e_tree_model_node_traverse (tree_model
, node
, func
, data
);
1553 unread_foreach (ETreeModel
*etm
,
1557 gboolean
*saw_unread
= data
;
1558 CamelMessageInfo
*info
;
1561 info
= (CamelMessageInfo
*) path
;
1563 info
= ((GNode
*) path
)->data
;
1564 g_return_val_if_fail (info
!= NULL
, FALSE
);
1566 if (!(camel_message_info_get_flags (info
) & CAMEL_MESSAGE_SEEN
))
1578 latest_foreach (ETreeModel
*etm
,
1582 struct LatestData
*ld
= data
;
1583 CamelMessageInfo
*info
;
1587 info
= (CamelMessageInfo
*) path
;
1589 info
= ((GNode
*) path
)->data
;
1590 g_return_val_if_fail (info
!= NULL
, FALSE
);
1592 date
= ld
->sent
? camel_message_info_get_date_sent (info
)
1593 : camel_message_info_get_date_received (info
);
1595 if (ld
->latest
== 0 || date
> ld
->latest
)
1602 sanitize_recipients (const gchar
*string
)
1605 gboolean quoted
= FALSE
;
1607 GString
*recipients
= g_string_new ("");
1611 if (!string
|| !*string
)
1612 return g_string_free (recipients
, FALSE
);
1614 gstring
= g_string_new ("");
1616 for (p
= string
; *p
; p
= g_utf8_next_char (p
)) {
1617 gunichar c
= g_utf8_get_char (p
);
1621 else if (c
== ',' && !quoted
) {
1622 single_add
= g_string_free (gstring
, FALSE
);
1623 name
= g_strsplit (single_add
,"<",2);
1624 g_string_append (recipients
, *name
);
1625 g_string_append (recipients
, ",");
1626 g_free (single_add
);
1628 gstring
= g_string_new ("");
1632 g_string_append_unichar (gstring
, c
);
1635 single_add
= g_string_free (gstring
, FALSE
);
1636 name
= g_strsplit (single_add
,"<",2);
1637 g_string_append (recipients
, *name
);
1638 g_free (single_add
);
1641 return g_string_free (recipients
, FALSE
);
1645 EMailLabelListStore
*store
;
1646 GHashTable
*labels_tag2iter
;
1650 add_label_if_known (struct LabelsData
*ld
,
1653 GtkTreeIter label_defn
;
1655 if (e_mail_label_list_store_lookup (ld
->store
, tag
, &label_defn
)) {
1656 g_hash_table_insert (
1657 ld
->labels_tag2iter
,
1658 /* Should be the same as the "tag" arg */
1659 e_mail_label_list_store_get_tag (ld
->store
, &label_defn
),
1660 gtk_tree_iter_copy (&label_defn
));
1665 add_all_labels_foreach (ETreeModel
*etm
,
1669 struct LabelsData
*ld
= data
;
1670 CamelMessageInfo
*msg_info
;
1671 const gchar
*old_label
;
1673 const CamelNamedFlags
*flags
;
1677 msg_info
= (CamelMessageInfo
*) path
;
1679 msg_info
= ((GNode
*) path
)->data
;
1680 g_return_val_if_fail (msg_info
!= NULL
, FALSE
);
1682 camel_message_info_property_lock (msg_info
);
1683 flags
= camel_message_info_get_user_flags (msg_info
);
1684 len
= camel_named_flags_get_length (flags
);
1686 for (ii
= 0; ii
< len
; ii
++)
1687 add_label_if_known (ld
, camel_named_flags_get (flags
, ii
));
1689 old_label
= camel_message_info_get_user_tag (msg_info
, "label");
1690 if (old_label
!= NULL
) {
1691 /* Convert old-style labels ("<name>") to "$Label<name>". */
1692 new_label
= g_alloca (strlen (old_label
) + 10);
1693 g_stpcpy (g_stpcpy (new_label
, "$Label"), old_label
);
1695 add_label_if_known (ld
, new_label
);
1698 camel_message_info_property_unlock (msg_info
);
1703 static const gchar
*
1704 get_trimmed_subject (CamelMessageInfo
*info
,
1705 MessageList
*message_list
)
1707 const gchar
*subject
;
1710 gboolean found_mlist
;
1712 subject
= camel_message_info_get_subject (info
);
1713 if (!subject
|| !*subject
)
1716 mlist
= camel_message_info_get_mlist (info
);
1718 if (mlist
&& *mlist
) {
1719 const gchar
*mlist_end
;
1721 mlist_end
= strchr (mlist
, '@');
1723 mlist_len
= mlist_end
- mlist
;
1725 mlist_len
= strlen (mlist
);
1730 gboolean found_re
= TRUE
;
1732 found_mlist
= FALSE
;
1737 g_mutex_lock (&message_list
->priv
->re_prefixes_lock
);
1738 found_re
= em_utils_is_re_in_subject (
1739 subject
, &skip_len
, (const gchar
* const *) message_list
->priv
->re_prefixes
,
1740 (const gchar
* const *) message_list
->priv
->re_separators
) && skip_len
> 0;
1741 g_mutex_unlock (&message_list
->priv
->re_prefixes_lock
);
1743 subject
+= skip_len
;
1745 /* jump over any spaces */
1746 while (*subject
&& isspace ((gint
) *subject
))
1752 !g_ascii_strncasecmp ((gchar
*) subject
+ 1, mlist
, mlist_len
) &&
1753 subject
[1 + mlist_len
] == ']') {
1754 subject
+= 1 + mlist_len
+ 1; /* jump over "[mailing-list]" */
1757 /* jump over any spaces */
1758 while (*subject
&& isspace ((gint
) *subject
))
1761 } while (found_mlist
);
1763 /* jump over any spaces */
1764 while (*subject
&& isspace ((gint
) *subject
))
1771 ml_tree_value_at_ex (ETreeModel
*etm
,
1774 CamelMessageInfo
*msg_info
,
1775 MessageList
*message_list
)
1777 EMailSession
*session
;
1781 session
= message_list_get_session (message_list
);
1783 g_return_val_if_fail (msg_info
!= NULL
, NULL
);
1786 case COL_MESSAGE_STATUS
:
1787 flags
= camel_message_info_get_flags (msg_info
);
1788 if (flags
& CAMEL_MESSAGE_ANSWERED
)
1789 return GINT_TO_POINTER (2);
1790 else if (flags
& CAMEL_MESSAGE_FORWARDED
)
1791 return GINT_TO_POINTER (3);
1792 else if (flags
& CAMEL_MESSAGE_SEEN
)
1793 return GINT_TO_POINTER (1);
1795 return GINT_TO_POINTER (0);
1797 return GINT_TO_POINTER ((camel_message_info_get_flags (msg_info
) & CAMEL_MESSAGE_FLAGGED
) != 0);
1802 tag
= camel_message_info_get_user_tag (msg_info
, "score");
1806 return GINT_TO_POINTER (score
);
1808 case COL_FOLLOWUP_FLAG_STATUS
: {
1809 const gchar
*tag
, *cmp
;
1811 /* FIXME: this all should be methods off of message-tag-followup class,
1812 * FIXME: the tag names should be namespaced :( */
1813 tag
= camel_message_info_get_user_tag (msg_info
, "follow-up");
1814 cmp
= camel_message_info_get_user_tag (msg_info
, "completed-on");
1815 if (tag
&& tag
[0]) {
1817 return GINT_TO_POINTER (2);
1819 return GINT_TO_POINTER (1);
1821 return GINT_TO_POINTER (0);
1823 case COL_FOLLOWUP_DUE_BY
: {
1827 tag
= camel_message_info_get_user_tag (msg_info
, "due-by");
1831 due_by
= camel_header_decode_date (tag
, NULL
);
1832 res
= g_new0 (gint64
, 1);
1833 *res
= (gint64
) due_by
;
1840 case COL_FOLLOWUP_FLAG
:
1841 str
= camel_message_info_get_user_tag (msg_info
, "follow-up");
1842 return (gpointer
)(str
? str
: "");
1843 case COL_ATTACHMENT
:
1844 if (camel_message_info_get_flags (msg_info
) & CAMEL_MESSAGE_JUNK
)
1845 return GINT_TO_POINTER (4);
1846 if (camel_message_info_get_user_flag (msg_info
, E_MAIL_NOTES_USER_FLAG
))
1847 return GINT_TO_POINTER (3);
1848 if (camel_message_info_get_user_flag (msg_info
, "$has_cal"))
1849 return GINT_TO_POINTER (2);
1850 return GINT_TO_POINTER ((camel_message_info_get_flags (msg_info
) & CAMEL_MESSAGE_ATTACHMENTS
) != 0);
1852 str
= camel_message_info_get_from (msg_info
);
1853 return (gpointer
)(str
? str
: "");
1855 return (gpointer
) get_normalised_string (message_list
, msg_info
, col
);
1857 str
= camel_message_info_get_subject (msg_info
);
1858 return (gpointer
)(str
? str
: "");
1859 case COL_SUBJECT_TRIMMED
:
1860 str
= get_trimmed_subject (msg_info
, message_list
);
1861 return (gpointer
)(str
? str
: "");
1862 case COL_SUBJECT_NORM
:
1863 return (gpointer
) get_normalised_string (message_list
, msg_info
, col
);
1865 struct LatestData ld
;
1871 for_node_and_subtree_if_collapsed (message_list
, node
, msg_info
, latest_foreach
, &ld
);
1873 res
= g_new0 (gint64
, 1);
1874 *res
= (gint64
) ld
.latest
;
1878 case COL_RECEIVED
: {
1879 struct LatestData ld
;
1885 for_node_and_subtree_if_collapsed (message_list
, node
, msg_info
, latest_foreach
, &ld
);
1887 res
= g_new0 (gint64
, 1);
1888 *res
= (gint64
) ld
.latest
;
1893 str
= camel_message_info_get_to (msg_info
);
1894 return (gpointer
)(str
? str
: "");
1896 return (gpointer
) get_normalised_string (message_list
, msg_info
, col
);
1898 return GINT_TO_POINTER (camel_message_info_get_size (msg_info
));
1900 return GINT_TO_POINTER ((camel_message_info_get_flags (msg_info
) & CAMEL_MESSAGE_DELETED
) != 0);
1901 case COL_DELETED_OR_JUNK
:
1902 return GINT_TO_POINTER ((camel_message_info_get_flags (msg_info
) & (CAMEL_MESSAGE_DELETED
| CAMEL_MESSAGE_JUNK
)) != 0);
1904 return GINT_TO_POINTER ((camel_message_info_get_flags (msg_info
) & CAMEL_MESSAGE_JUNK
) != 0);
1905 case COL_JUNK_STRIKEOUT_COLOR
:
1906 return GUINT_TO_POINTER (((camel_message_info_get_flags (msg_info
) & CAMEL_MESSAGE_JUNK
) != 0) ? 0xFF0000 : 0x0);
1908 gboolean saw_unread
= FALSE
;
1910 for_node_and_subtree_if_collapsed (message_list
, node
, msg_info
, unread_foreach
, &saw_unread
);
1912 return GINT_TO_POINTER (saw_unread
);
1915 const gchar
*colour
, *due_by
, *completed
, *followup
;
1917 /* Priority: colour tag; label tag; important flag; due-by tag */
1919 /* This is astonisngly poorly written code */
1921 /* To add to the woes, what color to show when the user choose multiple labels ?
1922 Don't say that I need to have the new labels[with subject] column visible always */
1925 due_by
= camel_message_info_get_user_tag (msg_info
, "due-by");
1926 completed
= camel_message_info_get_user_tag (msg_info
, "completed-on");
1927 followup
= camel_message_info_get_user_tag (msg_info
, "follow-up");
1928 if (colour
== NULL
) {
1929 /* Get all applicable labels. */
1930 struct LabelsData ld
;
1932 ld
.store
= e_mail_ui_session_get_label_store (
1933 E_MAIL_UI_SESSION (session
));
1934 ld
.labels_tag2iter
= g_hash_table_new_full (g_str_hash
, g_str_equal
, g_free
, (GDestroyNotify
) gtk_tree_iter_free
);
1935 for_node_and_subtree_if_collapsed (message_list
, node
, msg_info
, add_all_labels_foreach
, &ld
);
1937 if (g_hash_table_size (ld
.labels_tag2iter
) == 1) {
1938 GHashTableIter iter
;
1939 GtkTreeIter
*label_defn
;
1940 GdkColor colour_val
;
1941 gchar
*colour_alloced
;
1943 /* Extract the single label from the hashtable. */
1944 g_hash_table_iter_init (&iter
, ld
.labels_tag2iter
);
1945 if (g_hash_table_iter_next (&iter
, NULL
, (gpointer
*) &label_defn
)) {
1946 e_mail_label_list_store_get_color (ld
.store
, label_defn
, &colour_val
);
1948 /* XXX Hack to avoid returning an allocated string. */
1949 colour_alloced
= gdk_color_to_string (&colour_val
);
1950 colour
= g_intern_string (colour_alloced
);
1951 g_free (colour_alloced
);
1953 } else if (camel_message_info_get_flags (msg_info
) & CAMEL_MESSAGE_FLAGGED
) {
1954 /* FIXME: extract from the important.xpm somehow. */
1956 } else if (((followup
&& *followup
) || (due_by
&& *due_by
)) && !(completed
&& *completed
)) {
1957 time_t now
= time (NULL
);
1959 if ((followup
&& *followup
) || now
>= camel_header_decode_date (due_by
, NULL
))
1963 g_hash_table_destroy (ld
.labels_tag2iter
);
1967 colour
= camel_message_info_get_user_tag (msg_info
, "color");
1969 return (gpointer
) colour
;
1972 return GINT_TO_POINTER (camel_message_info_get_user_flag (msg_info
, "ignore-thread") ? 1 : 0);
1974 case COL_LOCATION
: {
1975 /* Fixme : freeing memory stuff (mem leaks) */
1977 CamelFolder
*folder
;
1978 CamelService
*service
;
1979 const gchar
*store_name
;
1980 const gchar
*folder_name
;
1982 folder
= message_list
->priv
->folder
;
1984 if (CAMEL_IS_VEE_FOLDER (folder
))
1985 folder
= camel_vee_folder_get_location (
1986 CAMEL_VEE_FOLDER (folder
),
1987 (CamelVeeMessageInfo
*) msg_info
, NULL
);
1989 store
= camel_folder_get_parent_store (folder
);
1990 folder_name
= camel_folder_get_full_name (folder
);
1992 service
= CAMEL_SERVICE (store
);
1993 store_name
= camel_service_get_display_name (service
);
1995 return g_strdup_printf ("%s : %s", store_name
, folder_name
);
1997 case COL_MIXED_RECIPIENTS
:
1998 case COL_RECIPIENTS
:{
1999 str
= camel_message_info_get_to (msg_info
);
2001 return sanitize_recipients (str
);
2003 case COL_MIXED_SENDER
:
2005 gchar
**sender_name
= NULL
;
2006 str
= camel_message_info_get_from (msg_info
);
2007 if (str
&& str
[0] != '\0') {
2009 sender_name
= g_strsplit (str
,"<",2);
2010 res
= g_strdup (*sender_name
);
2011 g_strfreev (sender_name
);
2012 return (gpointer
)(res
);
2015 return (gpointer
) g_strdup ("");
2018 struct LabelsData ld
;
2019 GString
*result
= g_string_new ("");
2021 ld
.store
= e_mail_ui_session_get_label_store (
2022 E_MAIL_UI_SESSION (session
));
2023 ld
.labels_tag2iter
= g_hash_table_new_full (g_str_hash
, g_str_equal
, g_free
, (GDestroyNotify
) gtk_tree_iter_free
);
2024 for_node_and_subtree_if_collapsed (message_list
, node
, msg_info
, add_all_labels_foreach
, &ld
);
2026 if (g_hash_table_size (ld
.labels_tag2iter
) > 0) {
2027 GHashTableIter iter
;
2028 GtkTreeIter
*label_defn
;
2030 g_hash_table_iter_init (&iter
, ld
.labels_tag2iter
);
2031 while (g_hash_table_iter_next (&iter
, NULL
, (gpointer
*) &label_defn
)) {
2032 gchar
*label_name
, *label_name_clean
;
2034 if (result
->len
> 0)
2035 g_string_append (result
, ", ");
2037 label_name
= e_mail_label_list_store_get_name (ld
.store
, label_defn
);
2038 label_name_clean
= e_str_without_underscores (label_name
);
2040 g_string_append (result
, label_name_clean
);
2042 g_free (label_name_clean
);
2043 g_free (label_name
);
2047 g_hash_table_destroy (ld
.labels_tag2iter
);
2048 return (gpointer
) g_string_free (result
, FALSE
);
2051 return (gpointer
) camel_pstring_strdup (camel_message_info_get_uid (msg_info
));
2054 g_warning ("%s: This shouldn't be reached (col:%d)", G_STRFUNC
, col
);
2060 filter_date (const gint64
*pdate
)
2062 time_t nowdate
= time (NULL
);
2063 time_t yesdate
, date
;
2064 struct tm then
, now
, yesterday
;
2066 gboolean done
= FALSE
;
2068 if (!pdate
|| *pdate
== 0)
2069 return g_strdup (_("?"));
2071 date
= (time_t) *pdate
;
2072 localtime_r (&date
, &then
);
2073 localtime_r (&nowdate
, &now
);
2074 if (then
.tm_mday
== now
.tm_mday
&&
2075 then
.tm_mon
== now
.tm_mon
&&
2076 then
.tm_year
== now
.tm_year
) {
2077 e_utf8_strftime_fix_am_pm (buf
, 26, _("Today %l:%M %p"), &then
);
2081 yesdate
= nowdate
- 60 * 60 * 24;
2082 localtime_r (&yesdate
, &yesterday
);
2083 if (then
.tm_mday
== yesterday
.tm_mday
&&
2084 then
.tm_mon
== yesterday
.tm_mon
&&
2085 then
.tm_year
== yesterday
.tm_year
) {
2086 e_utf8_strftime_fix_am_pm (buf
, 26, _("Yesterday %l:%M %p"), &then
);
2092 for (i
= 2; i
< 7; i
++) {
2093 yesdate
= nowdate
- 60 * 60 * 24 * i
;
2094 localtime_r (&yesdate
, &yesterday
);
2095 if (then
.tm_mday
== yesterday
.tm_mday
&&
2096 then
.tm_mon
== yesterday
.tm_mon
&&
2097 then
.tm_year
== yesterday
.tm_year
) {
2098 e_utf8_strftime_fix_am_pm (buf
, 26, _("%a %l:%M %p"), &then
);
2105 if (then
.tm_year
== now
.tm_year
) {
2106 e_utf8_strftime_fix_am_pm (buf
, 26, _("%b %d %l:%M %p"), &then
);
2108 e_utf8_strftime_fix_am_pm (buf
, 26, _("%b %d %Y"), &then
);
2112 return g_strdup (buf
);
2116 create_composite_cell (GSettings
*mail_settings
,
2119 ECell
*cell_vbox
, *cell_hbox
, *cell_sub
, *cell_date
, *cell_from
, *top_cell_tree
, *bottom_cell_tree
, *cell_attach
;
2120 gboolean show_email
;
2121 gboolean show_subject_above_sender
;
2123 show_email
= g_settings_get_boolean (mail_settings
, "show-email");
2124 show_subject_above_sender
= g_settings_get_boolean (mail_settings
, "show-subject-above-sender");
2127 col
= (col
== COL_FROM
) ? COL_SENDER
: COL_RECIPIENTS
;
2129 cell_vbox
= e_cell_vbox_new ();
2131 cell_hbox
= e_cell_hbox_new ();
2133 /* Exclude the meeting icon. */
2134 cell_attach
= e_cell_toggle_new (attachment_icons
, G_N_ELEMENTS (attachment_icons
));
2136 cell_date
= e_cell_date_new (NULL
, GTK_JUSTIFY_RIGHT
);
2137 e_cell_date_set_format_component (E_CELL_DATE (cell_date
), "mail");
2140 "bold_column", COL_UNREAD
,
2141 "italic-column", COL_ITALIC
,
2142 "color_column", COL_COLOUR
,
2145 cell_from
= e_cell_text_new (NULL
, GTK_JUSTIFY_LEFT
);
2148 "bold_column", COL_UNREAD
,
2149 "italic-column", COL_ITALIC
,
2150 "color_column", COL_COLOUR
,
2153 e_cell_hbox_append (E_CELL_HBOX (cell_hbox
), cell_from
, show_subject_above_sender
? COL_SUBJECT
: col
, 68);
2154 e_cell_hbox_append (E_CELL_HBOX (cell_hbox
), cell_attach
, COL_ATTACHMENT
, 5);
2155 e_cell_hbox_append (E_CELL_HBOX (cell_hbox
), cell_date
, COL_SENT
, 27);
2156 g_object_unref (cell_from
);
2157 g_object_unref (cell_attach
);
2158 g_object_unref (cell_date
);
2160 cell_sub
= e_cell_text_new (NULL
, GTK_JUSTIFY_LEFT
);
2163 "color_column", COL_COLOUR
,
2165 top_cell_tree
= e_cell_tree_new (TRUE
, FALSE
, cell_hbox
);
2166 bottom_cell_tree
= e_cell_tree_new (TRUE
, TRUE
, cell_sub
);
2167 e_cell_vbox_append (E_CELL_VBOX (cell_vbox
), top_cell_tree
, show_subject_above_sender
? COL_SUBJECT
: col
);
2168 e_cell_vbox_append (E_CELL_VBOX (cell_vbox
), bottom_cell_tree
, show_subject_above_sender
? col
: COL_SUBJECT
);
2169 g_object_unref (cell_sub
);
2170 g_object_unref (cell_hbox
);
2171 g_object_unref (top_cell_tree
);
2172 g_object_unref (bottom_cell_tree
);
2174 g_object_set_data (G_OBJECT (cell_vbox
), "cell_date", cell_date
);
2175 g_object_set_data (G_OBJECT (cell_vbox
), "cell_sub", cell_sub
);
2176 g_object_set_data (G_OBJECT (cell_vbox
), "cell_from", cell_from
);
2177 g_object_set_data (G_OBJECT (cell_vbox
), "cell_hbox", cell_hbox
);
2178 g_object_set_data (G_OBJECT (cell_vbox
), "address_model_col", GINT_TO_POINTER (col
));
2184 composite_cell_set_show_subject_above_sender (ECell
*cell
,
2185 gboolean show_subject_above_sender
)
2187 ECellVbox
*cell_vbox
;
2188 ECellHbox
*cell_hbox
;
2191 gint address_model_col
, cell_from_index
;
2193 g_return_if_fail (E_IS_CELL_VBOX (cell
));
2195 cell_obj
= G_OBJECT (cell
);
2196 address_model_col
= GPOINTER_TO_INT (g_object_get_data (cell_obj
, "address_model_col"));
2198 cell_vbox
= E_CELL_VBOX (cell
);
2199 g_return_if_fail (cell_vbox
->subcell_count
== 2);
2200 g_return_if_fail (cell_vbox
->model_cols
!= NULL
);
2202 cell_from
= g_object_get_data (cell_obj
, "cell_from");
2203 g_return_if_fail (E_IS_CELL (cell_from
));
2205 cell_hbox
= g_object_get_data (cell_obj
, "cell_hbox");
2206 g_return_if_fail (E_IS_CELL_HBOX (cell_hbox
));
2208 for (cell_from_index
= 0; cell_from_index
< cell_hbox
->subcell_count
; cell_from_index
++) {
2209 if (cell_hbox
->subcells
[cell_from_index
] == cell_from
)
2213 g_return_if_fail (cell_from_index
< cell_hbox
->subcell_count
);
2215 cell_hbox
->model_cols
[cell_from_index
] = show_subject_above_sender
? COL_SUBJECT
: address_model_col
;
2216 cell_vbox
->model_cols
[0] = show_subject_above_sender
? COL_SUBJECT
: address_model_col
;
2217 cell_vbox
->model_cols
[1] = show_subject_above_sender
? address_model_col
: COL_SUBJECT
;
2221 composite_cell_set_strike_col (ECell
*cell
,
2223 gint strikeout_color_col
)
2225 g_object_set (g_object_get_data (G_OBJECT (cell
), "cell_date"),
2226 "strikeout-column", strikeout_col
,
2227 "strikeout-color-column", strikeout_color_col
,
2230 g_object_set (g_object_get_data (G_OBJECT (cell
), "cell_from"),
2231 "strikeout-column", strikeout_col
,
2232 "strikeout-color-column", strikeout_color_col
,
2236 static ETableExtras
*
2237 message_list_create_extras (GSettings
*mail_settings
)
2239 ETableExtras
*extras
;
2242 extras
= e_table_extras_new ();
2243 e_table_extras_add_icon_name (extras
, "status", "mail-unread");
2244 e_table_extras_add_icon_name (extras
, "score", "stock_score-higher");
2245 e_table_extras_add_icon_name (extras
, "attachment", "mail-attachment");
2246 e_table_extras_add_icon_name (extras
, "flagged", "emblem-important");
2247 e_table_extras_add_icon_name (extras
, "followup", "stock_mail-flag-for-followup");
2249 e_table_extras_add_compare (extras
, "address_compare", address_compare
);
2251 cell
= e_cell_toggle_new (status_icons
, G_N_ELEMENTS (status_icons
));
2252 e_cell_toggle_set_icon_descriptions (E_CELL_TOGGLE (cell
), status_map
, G_N_ELEMENTS (status_map
));
2253 e_table_extras_add_cell (extras
, "render_message_status", cell
);
2254 g_object_unref (cell
);
2256 cell
= e_cell_toggle_new (
2257 attachment_icons
, G_N_ELEMENTS (attachment_icons
));
2258 e_table_extras_add_cell (extras
, "render_attachment", cell
);
2259 g_object_unref (cell
);
2261 cell
= e_cell_toggle_new (
2262 flagged_icons
, G_N_ELEMENTS (flagged_icons
));
2263 e_table_extras_add_cell (extras
, "render_flagged", cell
);
2264 g_object_unref (cell
);
2266 cell
= e_cell_toggle_new (
2267 followup_icons
, G_N_ELEMENTS (followup_icons
));
2268 e_table_extras_add_cell (extras
, "render_flag_status", cell
);
2269 g_object_unref (cell
);
2271 cell
= e_cell_toggle_new (
2272 score_icons
, G_N_ELEMENTS (score_icons
));
2273 e_table_extras_add_cell (extras
, "render_score", cell
);
2274 g_object_unref (cell
);
2277 cell
= e_cell_date_new (NULL
, GTK_JUSTIFY_LEFT
);
2278 e_cell_date_set_format_component (E_CELL_DATE (cell
), "mail");
2281 "bold_column", COL_UNREAD
,
2282 "italic-column", COL_ITALIC
,
2283 "color_column", COL_COLOUR
,
2285 e_table_extras_add_cell (extras
, "render_date", cell
);
2286 g_object_unref (cell
);
2289 cell
= e_cell_text_new (NULL
, GTK_JUSTIFY_LEFT
);
2292 "bold_column", COL_UNREAD
,
2293 "italic-column", COL_ITALIC
,
2294 "color_column", COL_COLOUR
,
2296 e_table_extras_add_cell (extras
, "render_text", cell
);
2297 g_object_unref (cell
);
2299 cell
= e_cell_tree_new (TRUE
, TRUE
, cell
);
2300 e_table_extras_add_cell (extras
, "render_tree", cell
);
2301 g_object_unref (cell
);
2304 cell
= e_cell_size_new (NULL
, GTK_JUSTIFY_RIGHT
);
2307 "bold_column", COL_UNREAD
,
2308 "italic-column", COL_ITALIC
,
2309 "color_column", COL_COLOUR
,
2311 e_table_extras_add_cell (extras
, "render_size", cell
);
2312 g_object_unref (cell
);
2314 /* Composite cell for wide view */
2315 cell
= create_composite_cell (mail_settings
, COL_FROM
);
2316 e_table_extras_add_cell (extras
, "render_composite_from", cell
);
2317 g_object_unref (cell
);
2319 cell
= create_composite_cell (mail_settings
, COL_TO
);
2320 e_table_extras_add_cell (extras
, "render_composite_to", cell
);
2321 g_object_unref (cell
);
2323 /* set proper format component for a default 'date' cell renderer */
2324 cell
= e_table_extras_get_cell (extras
, "date");
2325 e_cell_date_set_format_component (E_CELL_DATE (cell
), "mail");
2331 message_list_is_searching (MessageList
*message_list
)
2333 g_return_val_if_fail (IS_MESSAGE_LIST (message_list
), FALSE
);
2335 return message_list
->search
&& *message_list
->search
;
2339 save_tree_state (MessageList
*message_list
,
2340 CamelFolder
*folder
)
2342 ETreeTableAdapter
*adapter
;
2348 if (message_list_is_searching (message_list
))
2351 adapter
= e_tree_get_table_adapter (E_TREE (message_list
));
2353 filename
= mail_config_folder_to_cachename (folder
, "et-expanded-");
2354 e_tree_table_adapter_save_expanded_state (adapter
, filename
);
2357 message_list
->priv
->any_row_changed
= FALSE
;
2361 load_tree_state (MessageList
*message_list
,
2362 CamelFolder
*folder
,
2363 xmlDoc
*expand_state
)
2365 ETreeTableAdapter
*adapter
;
2370 adapter
= e_tree_get_table_adapter (E_TREE (message_list
));
2372 if (expand_state
!= NULL
) {
2373 e_tree_table_adapter_load_expanded_state_xml (
2374 adapter
, expand_state
);
2378 filename
= mail_config_folder_to_cachename (
2379 folder
, "et-expanded-");
2380 e_tree_table_adapter_load_expanded_state (adapter
, filename
);
2384 message_list
->priv
->any_row_changed
= FALSE
;
2388 message_list_save_state (MessageList
*message_list
)
2390 CamelFolder
*folder
;
2392 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
2394 folder
= message_list_ref_folder (message_list
);
2396 if (folder
!= NULL
) {
2397 save_tree_state (message_list
, folder
);
2398 g_object_unref (folder
);
2403 message_list_setup_etree (MessageList
*message_list
)
2405 CamelFolder
*folder
;
2407 /* Build the spec based on the folder, and possibly
2408 * from a saved file. Otherwise, leave default. */
2410 folder
= message_list_ref_folder (message_list
);
2412 if (folder
!= NULL
) {
2416 item
= e_tree_get_item (E_TREE (message_list
));
2418 g_object_set (message_list
, "uniform_row_height", TRUE
, NULL
);
2420 G_OBJECT (((GnomeCanvasItem
*) item
)->canvas
),
2421 "freeze-cursor", &data
);
2423 /* build based on saved file */
2424 load_tree_state (message_list
, folder
, NULL
);
2426 g_object_unref (folder
);
2431 ml_selection_get (GtkWidget
*widget
,
2432 GtkSelectionData
*data
,
2435 MessageList
*message_list
)
2437 struct _MLSelection
*selection
;
2439 selection
= &message_list
->priv
->clipboard
;
2441 if (selection
->uids
== NULL
)
2446 d (printf ("setting text/plain selection for uids\n"));
2447 em_utils_selection_set_mailbox (data
, selection
->folder
, selection
->uids
);
2450 d (printf ("setting x-uid-list selection for uids\n"));
2451 em_utils_selection_set_uidlist (data
, selection
->folder
, selection
->uids
);
2456 ml_selection_clear_event (GtkWidget
*widget
,
2457 GdkEventSelection
*event
,
2458 MessageList
*message_list
)
2460 MessageListPrivate
*p
= message_list
->priv
;
2462 clear_selection (message_list
, &p
->clipboard
);
2468 ml_selection_received (GtkWidget
*widget
,
2469 GtkSelectionData
*selection_data
,
2471 MessageList
*message_list
)
2473 EMailSession
*session
;
2474 CamelFolder
*folder
;
2477 target
= gtk_selection_data_get_target (selection_data
);
2479 if (target
!= gdk_atom_intern ("x-uid-list", FALSE
)) {
2480 d (printf ("Unknown selection received by message-list\n"));
2484 folder
= message_list_ref_folder (message_list
);
2485 session
= message_list_get_session (message_list
);
2487 /* FIXME Not passing a GCancellable or GError here. */
2488 em_utils_selection_get_uidlist (
2489 selection_data
, session
, folder
, FALSE
, NULL
, NULL
);
2491 g_clear_object (&folder
);
2495 ml_tree_drag_data_get (ETree
*tree
,
2499 GdkDragContext
*context
,
2500 GtkSelectionData
*data
,
2503 MessageList
*message_list
)
2505 CamelFolder
*folder
;
2508 folder
= message_list_ref_folder (message_list
);
2509 uids
= message_list_get_selected (message_list
);
2511 if (uids
->len
> 0) {
2513 case DND_X_UID_LIST
:
2514 em_utils_selection_set_uidlist (data
, folder
, uids
);
2516 case DND_TEXT_URI_LIST
:
2517 em_utils_selection_set_urilist (data
, folder
, uids
);
2522 g_clear_object (&folder
);
2523 g_ptr_array_unref (uids
);
2526 /* TODO: merge this with the folder tree stuff via empopup targets */
2531 GdkDragContext
*context
;
2533 /* Only selection->data and selection->length are valid */
2534 GtkSelectionData
*selection
;
2536 CamelFolder
*folder
;
2537 MessageList
*message_list
;
2548 ml_drop_async_desc (struct _drop_msg
*m
)
2550 const gchar
*full_name
;
2552 full_name
= camel_folder_get_full_name (m
->folder
);
2555 return g_strdup_printf (_("Moving messages into folder %s"), full_name
);
2557 return g_strdup_printf (_("Copying messages into folder %s"), full_name
);
2561 ml_drop_async_exec (struct _drop_msg
*m
,
2562 GCancellable
*cancellable
,
2565 EMailSession
*session
;
2567 session
= message_list_get_session (m
->message_list
);
2570 case DND_X_UID_LIST
:
2571 em_utils_selection_get_uidlist (
2572 m
->selection
, session
, m
->folder
,
2573 m
->action
== GDK_ACTION_MOVE
,
2574 cancellable
, error
);
2576 case DND_MESSAGE_RFC822
:
2577 em_utils_selection_get_message (m
->selection
, m
->folder
);
2579 case DND_TEXT_URI_LIST
:
2580 em_utils_selection_get_urilist (m
->selection
, m
->folder
);
2586 ml_drop_async_done (struct _drop_msg
*m
)
2588 gboolean success
, delete;
2595 success
= (m
->base
.error
== NULL
);
2596 delete = success
&& m
->move
&& !m
->moved
;
2599 gtk_drag_finish (m
->context
, success
, delete, GDK_CURRENT_TIME
);
2603 ml_drop_async_free (struct _drop_msg
*m
)
2605 g_object_unref (m
->context
);
2606 g_object_unref (m
->folder
);
2607 g_object_unref (m
->message_list
);
2608 gtk_selection_data_free (m
->selection
);
2611 static MailMsgInfo ml_drop_async_info
= {
2612 sizeof (struct _drop_msg
),
2613 (MailMsgDescFunc
) ml_drop_async_desc
,
2614 (MailMsgExecFunc
) ml_drop_async_exec
,
2615 (MailMsgDoneFunc
) ml_drop_async_done
,
2616 (MailMsgFreeFunc
) ml_drop_async_free
2620 ml_drop_action (struct _drop_msg
*m
)
2622 m
->move
= m
->action
== GDK_ACTION_MOVE
;
2623 mail_msg_unordered_push (m
);
2627 ml_tree_drag_data_received (ETree
*tree
,
2631 GdkDragContext
*context
,
2634 GtkSelectionData
*selection_data
,
2637 MessageList
*message_list
)
2639 CamelFolder
*folder
;
2640 struct _drop_msg
*m
;
2642 if (gtk_selection_data_get_data (selection_data
) == NULL
)
2645 if (gtk_selection_data_get_length (selection_data
) == -1)
2648 folder
= message_list_ref_folder (message_list
);
2652 m
= mail_msg_new (&ml_drop_async_info
);
2653 m
->context
= g_object_ref (context
);
2654 m
->folder
= g_object_ref (folder
);
2655 m
->message_list
= g_object_ref (message_list
);
2656 m
->action
= gdk_drag_context_get_selected_action (context
);
2659 /* need to copy, goes away once we exit */
2660 m
->selection
= gtk_selection_data_copy (selection_data
);
2664 g_object_unref (folder
);
2667 struct search_child_struct
{
2669 gconstpointer looking_for
;
2673 search_child_cb (GtkWidget
*widget
,
2676 struct search_child_struct
*search
= (struct search_child_struct
*) data
;
2678 search
->found
= search
->found
|| g_direct_equal (widget
, search
->looking_for
);
2682 is_tree_widget_children (ETree
*tree
,
2683 gconstpointer widget
)
2685 struct search_child_struct search
;
2687 search
.found
= FALSE
;
2688 search
.looking_for
= widget
;
2690 gtk_container_foreach (GTK_CONTAINER (tree
), search_child_cb
, &search
);
2692 return search
.found
;
2696 ml_tree_drag_motion (ETree
*tree
,
2697 GdkDragContext
*context
,
2701 MessageList
*message_list
)
2704 GdkDragAction action
, actions
= 0;
2705 GtkWidget
*source_widget
;
2707 /* If drop target is name of the account/store
2708 * and not actual folder, don't allow any action. */
2709 if (message_list
->priv
->folder
== NULL
) {
2710 gdk_drag_status (context
, 0, time
);
2714 source_widget
= gtk_drag_get_source_widget (context
);
2716 /* If source widget is packed under 'tree', don't allow any action */
2717 if (is_tree_widget_children (tree
, source_widget
)) {
2718 gdk_drag_status (context
, 0, time
);
2722 if (EM_IS_FOLDER_TREE (source_widget
)) {
2723 EMFolderTree
*folder_tree
;
2724 CamelFolder
*selected_folder
= NULL
;
2725 CamelStore
*selected_store
;
2726 gchar
*selected_folder_name
;
2727 gboolean has_selection
;
2729 folder_tree
= EM_FOLDER_TREE (source_widget
);
2731 has_selection
= em_folder_tree_get_selected (
2732 folder_tree
, &selected_store
, &selected_folder_name
);
2736 (has_selection
&& selected_store
!= NULL
) ||
2737 (!has_selection
&& selected_store
== NULL
));
2739 (has_selection
&& selected_folder_name
!= NULL
) ||
2740 (!has_selection
&& selected_folder_name
== NULL
));
2742 if (has_selection
) {
2743 selected_folder
= camel_store_get_folder_sync (
2744 selected_store
, selected_folder_name
,
2746 g_object_unref (selected_store
);
2747 g_free (selected_folder_name
);
2750 if (selected_folder
== message_list
->priv
->folder
) {
2751 gdk_drag_status (context
, 0, time
);
2756 targets
= gdk_drag_context_list_targets (context
);
2757 while (targets
!= NULL
) {
2760 d (printf ("atom drop '%s'\n", gdk_atom_name (targets
->data
)));
2761 for (i
= 0; i
< G_N_ELEMENTS (ml_drag_info
); i
++)
2762 if (targets
->data
== (gpointer
) ml_drag_info
[i
].atom
)
2763 actions
|= ml_drag_info
[i
].actions
;
2765 targets
= g_list_next (targets
);
2769 actions
&= gdk_drag_context_get_actions (context
);
2770 action
= gdk_drag_context_get_suggested_action (context
);
2771 if (action
== GDK_ACTION_COPY
&& (actions
& GDK_ACTION_MOVE
))
2772 action
= GDK_ACTION_MOVE
;
2774 gdk_drag_status (context
, action
, time
);
2780 on_model_row_changed (ETableModel
*model
,
2782 MessageList
*message_list
)
2784 message_list
->priv
->any_row_changed
= TRUE
;
2788 ml_tree_sorting_changed (ETreeTableAdapter
*adapter
,
2789 MessageList
*message_list
)
2791 gboolean group_by_threads
;
2793 g_return_val_if_fail (message_list
!= NULL
, FALSE
);
2795 group_by_threads
= message_list_get_group_by_threads (message_list
);
2797 if (group_by_threads
&& message_list
->frozen
== 0) {
2799 /* Invalidate the thread tree. */
2800 message_list_set_thread_tree (message_list
, NULL
);
2802 mail_regen_list (message_list
, NULL
, FALSE
);
2805 } else if (group_by_threads
) {
2806 message_list
->priv
->thaw_needs_regen
= TRUE
;
2813 ml_get_bg_color_cb (ETableItem
*item
,
2816 GdkRGBA
*inout_background
,
2817 MessageList
*message_list
)
2819 CamelMessageInfo
*msg_info
;
2822 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
2823 g_return_if_fail (inout_background
!= NULL
);
2825 if (!message_list
->priv
->new_mail_bg_color
|| row
< 0)
2828 path
= e_tree_table_adapter_node_at_row (e_tree_get_table_adapter (E_TREE (message_list
)), row
);
2829 if (!path
|| G_NODE_IS_ROOT ((GNode
*) path
))
2832 /* retrieve the message information array */
2833 msg_info
= ((GNode
*) path
)->data
;
2834 g_return_if_fail (msg_info
!= NULL
);
2836 if (!(camel_message_info_get_flags (msg_info
) & CAMEL_MESSAGE_SEEN
))
2837 *inout_background
= *(message_list
->priv
->new_mail_bg_color
);
2841 ml_style_updated_cb (MessageList
*message_list
)
2843 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
2845 if (message_list
->priv
->new_mail_bg_color
) {
2846 gdk_rgba_free (message_list
->priv
->new_mail_bg_color
);
2847 message_list
->priv
->new_mail_bg_color
= NULL
;
2850 gtk_widget_style_get (GTK_WIDGET (message_list
),
2851 "new-mail-bg-color", &message_list
->priv
->new_mail_bg_color
,
2856 message_list_get_preferred_width (GtkWidget
*widget
,
2857 gint
*out_minimum_width
,
2858 gint
*out_natural_width
)
2860 /* Chain up to parent's method. */
2861 GTK_WIDGET_CLASS (message_list_parent_class
)->get_preferred_width (widget
, out_minimum_width
, out_natural_width
);
2863 if (out_minimum_width
&& *out_minimum_width
< 50)
2864 *out_minimum_width
= 50;
2866 if (out_natural_width
&& out_minimum_width
&&
2867 *out_natural_width
< *out_minimum_width
)
2868 *out_natural_width
= *out_minimum_width
;
2872 message_list_set_session (MessageList
*message_list
,
2873 EMailSession
*session
)
2875 g_return_if_fail (E_IS_MAIL_SESSION (session
));
2876 g_return_if_fail (message_list
->priv
->session
== NULL
);
2878 message_list
->priv
->session
= g_object_ref (session
);
2882 message_list_set_property (GObject
*object
,
2884 const GValue
*value
,
2887 switch (property_id
) {
2889 message_list_set_folder (
2890 MESSAGE_LIST (object
),
2891 g_value_get_object (value
));
2894 case PROP_GROUP_BY_THREADS
:
2895 message_list_set_group_by_threads (
2896 MESSAGE_LIST (object
),
2897 g_value_get_boolean (value
));
2901 message_list_set_session (
2902 MESSAGE_LIST (object
),
2903 g_value_get_object (value
));
2906 case PROP_SHOW_DELETED
:
2907 message_list_set_show_deleted (
2908 MESSAGE_LIST (object
),
2909 g_value_get_boolean (value
));
2912 case PROP_SHOW_JUNK
:
2913 message_list_set_show_junk (
2914 MESSAGE_LIST (object
),
2915 g_value_get_boolean (value
));
2918 case PROP_SHOW_SUBJECT_ABOVE_SENDER
:
2919 message_list_set_show_subject_above_sender (
2920 MESSAGE_LIST (object
),
2921 g_value_get_boolean (value
));
2924 case PROP_THREAD_LATEST
:
2925 message_list_set_thread_latest (
2926 MESSAGE_LIST (object
),
2927 g_value_get_boolean (value
));
2930 case PROP_THREAD_SUBJECT
:
2931 message_list_set_thread_subject (
2932 MESSAGE_LIST (object
),
2933 g_value_get_boolean (value
));
2937 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
2941 message_list_get_property (GObject
*object
,
2946 switch (property_id
) {
2947 case PROP_COPY_TARGET_LIST
:
2950 message_list_get_copy_target_list (
2951 MESSAGE_LIST (object
)));
2955 g_value_take_object (
2957 message_list_ref_folder (
2958 MESSAGE_LIST (object
)));
2961 case PROP_GROUP_BY_THREADS
:
2962 g_value_set_boolean (
2964 message_list_get_group_by_threads (
2965 MESSAGE_LIST (object
)));
2968 case PROP_PASTE_TARGET_LIST
:
2971 message_list_get_paste_target_list (
2972 MESSAGE_LIST (object
)));
2976 g_value_set_object (
2978 message_list_get_session (
2979 MESSAGE_LIST (object
)));
2982 case PROP_SHOW_DELETED
:
2983 g_value_set_boolean (
2985 message_list_get_show_deleted (
2986 MESSAGE_LIST (object
)));
2989 case PROP_SHOW_JUNK
:
2990 g_value_set_boolean (
2992 message_list_get_show_junk (
2993 MESSAGE_LIST (object
)));
2996 case PROP_SHOW_SUBJECT_ABOVE_SENDER
:
2997 g_value_set_boolean (
2999 message_list_get_show_subject_above_sender (
3000 MESSAGE_LIST (object
)));
3003 case PROP_THREAD_LATEST
:
3004 g_value_set_boolean (
3006 message_list_get_thread_latest (
3007 MESSAGE_LIST (object
)));
3010 case PROP_THREAD_SUBJECT
:
3011 g_value_set_boolean (
3013 message_list_get_thread_subject (
3014 MESSAGE_LIST (object
)));
3018 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
3022 message_list_dispose (GObject
*object
)
3024 MessageList
*message_list
= MESSAGE_LIST (object
);
3025 MessageListPrivate
*priv
;
3027 priv
= message_list
->priv
;
3029 if (priv
->folder_changed_handler_id
> 0) {
3030 g_signal_handler_disconnect (
3032 priv
->folder_changed_handler_id
);
3033 priv
->folder_changed_handler_id
= 0;
3036 if (priv
->copy_target_list
!= NULL
) {
3037 gtk_target_list_unref (priv
->copy_target_list
);
3038 priv
->copy_target_list
= NULL
;
3041 if (priv
->paste_target_list
!= NULL
) {
3042 gtk_target_list_unref (priv
->paste_target_list
);
3043 priv
->paste_target_list
= NULL
;
3046 priv
->destroyed
= TRUE
;
3048 if (message_list
->priv
->folder
!= NULL
)
3049 mail_regen_cancel (message_list
);
3051 if (message_list
->uid_nodemap
) {
3052 g_hash_table_foreach (
3053 message_list
->uid_nodemap
,
3054 (GHFunc
) clear_info
, message_list
);
3055 g_hash_table_destroy (message_list
->uid_nodemap
);
3056 message_list
->uid_nodemap
= NULL
;
3059 g_clear_object (&priv
->session
);
3060 g_clear_object (&priv
->folder
);
3061 g_clear_object (&priv
->invisible
);
3062 g_clear_object (&priv
->mail_settings
);
3064 g_clear_object (&message_list
->extras
);
3066 if (message_list
->idle_id
> 0) {
3067 g_source_remove (message_list
->idle_id
);
3068 message_list
->idle_id
= 0;
3071 if (message_list
->seen_id
> 0) {
3072 g_source_remove (message_list
->seen_id
);
3073 message_list
->seen_id
= 0;
3076 /* Chain up to parent's dispose() method. */
3077 G_OBJECT_CLASS (message_list_parent_class
)->dispose (object
);
3081 message_list_finalize (GObject
*object
)
3083 MessageList
*message_list
= MESSAGE_LIST (object
);
3085 g_hash_table_destroy (message_list
->normalised_hash
);
3087 if (message_list
->priv
->thread_tree
!= NULL
)
3088 camel_folder_thread_messages_unref (
3089 message_list
->priv
->thread_tree
);
3091 g_free (message_list
->search
);
3092 g_free (message_list
->frozen_search
);
3093 g_free (message_list
->cursor_uid
);
3094 g_strfreev (message_list
->priv
->re_prefixes
);
3095 g_strfreev (message_list
->priv
->re_separators
);
3097 g_mutex_clear (&message_list
->priv
->regen_lock
);
3098 g_mutex_clear (&message_list
->priv
->thread_tree_lock
);
3099 g_mutex_clear (&message_list
->priv
->re_prefixes_lock
);
3101 clear_selection (message_list
, &message_list
->priv
->clipboard
);
3103 if (message_list
->priv
->tree_model_root
!= NULL
)
3104 extended_g_node_destroy (message_list
->priv
->tree_model_root
);
3106 if (message_list
->priv
->new_mail_bg_color
) {
3107 gdk_rgba_free (message_list
->priv
->new_mail_bg_color
);
3108 message_list
->priv
->new_mail_bg_color
= NULL
;
3111 /* Chain up to parent's finalize() method. */
3112 G_OBJECT_CLASS (message_list_parent_class
)->finalize (object
);
3116 message_list_constructed (GObject
*object
)
3118 /* Chain up to parent's constructed() method. */
3119 G_OBJECT_CLASS (message_list_parent_class
)->constructed (object
);
3121 e_extensible_load_extensions (E_EXTENSIBLE (object
));
3125 message_list_selectable_update_actions (ESelectable
*selectable
,
3126 EFocusTracker
*focus_tracker
,
3127 GdkAtom
*clipboard_targets
,
3128 gint n_clipboard_targets
)
3130 ETreeTableAdapter
*adapter
;
3134 adapter
= e_tree_get_table_adapter (E_TREE (selectable
));
3135 row_count
= e_table_model_row_count (E_TABLE_MODEL (adapter
));
3137 action
= e_focus_tracker_get_select_all_action (focus_tracker
);
3138 gtk_action_set_tooltip (action
, _("Select all visible messages"));
3139 gtk_action_set_sensitive (action
, row_count
> 0);
3143 message_list_selectable_select_all (ESelectable
*selectable
)
3145 message_list_select_all (MESSAGE_LIST (selectable
));
3149 message_list_get_root (ETreeModel
*tree_model
)
3151 MessageList
*message_list
= MESSAGE_LIST (tree_model
);
3153 return message_list
->priv
->tree_model_root
;
3157 message_list_get_parent (ETreeModel
*tree_model
,
3160 return ((GNode
*) path
)->parent
;
3164 message_list_get_first_child (ETreeModel
*tree_model
,
3167 return g_node_first_child ((GNode
*) path
);
3171 message_list_get_next (ETreeModel
*tree_model
,
3174 return g_node_next_sibling ((GNode
*) path
);
3178 message_list_is_root (ETreeModel
*tree_model
,
3181 return G_NODE_IS_ROOT ((GNode
*) path
);
3185 message_list_is_expandable (ETreeModel
*tree_model
,
3188 return (g_node_first_child ((GNode
*) path
) != NULL
);
3192 message_list_get_n_nodes (ETreeModel
*tree_model
)
3196 root
= e_tree_model_get_root (tree_model
);
3201 /* The root node is an empty placeholder, so
3202 * subtract one from the count to exclude it. */
3204 return g_node_n_nodes ((GNode
*) root
, G_TRAVERSE_ALL
) - 1;
3208 message_list_get_n_children (ETreeModel
*tree_model
,
3211 return g_node_n_children ((GNode
*) path
);
3215 message_list_depth (ETreeModel
*tree_model
,
3218 return g_node_depth ((GNode
*) path
);
3222 message_list_get_expanded_default (ETreeModel
*tree_model
)
3224 MessageList
*message_list
= MESSAGE_LIST (tree_model
);
3226 return message_list
->priv
->expanded_default
;
3230 message_list_column_count (ETreeModel
*tree_model
)
3236 message_list_get_save_id (ETreeModel
*tree_model
,
3239 CamelMessageInfo
*info
;
3241 if (G_NODE_IS_ROOT ((GNode
*) path
))
3242 return g_strdup ("root");
3244 /* Note: ETable can ask for the save_id while we're clearing
3245 * it, which is the only time info should be NULL. */
3246 info
= ((GNode
*) path
)->data
;
3250 return g_strdup (camel_message_info_get_uid (info
));
3254 message_list_get_node_by_id (ETreeModel
*tree_model
,
3255 const gchar
*save_id
)
3257 MessageList
*message_list
;
3259 message_list
= MESSAGE_LIST (tree_model
);
3261 if (!strcmp (save_id
, "root"))
3262 return e_tree_model_get_root (tree_model
);
3264 return g_hash_table_lookup (message_list
->uid_nodemap
, save_id
);
3268 message_list_sort_value_at (ETreeModel
*tree_model
,
3272 MessageList
*message_list
;
3274 struct LatestData ld
;
3277 message_list
= MESSAGE_LIST (tree_model
);
3279 if (!(col
== COL_SENT
|| col
== COL_RECEIVED
))
3280 return e_tree_model_value_at (tree_model
, path
, col
);
3282 path_node
= (GNode
*) path
;
3284 if (G_NODE_IS_ROOT (path_node
))
3287 ld
.sent
= (col
== COL_SENT
);
3290 latest_foreach (tree_model
, path
, &ld
);
3291 if (message_list
->priv
->thread_latest
&& (!e_tree_get_sort_children_ascending (E_TREE (message_list
)) ||
3292 !path_node
|| !path_node
->parent
|| !path_node
->parent
->parent
))
3293 e_tree_model_node_traverse (
3294 tree_model
, path
, latest_foreach
, &ld
);
3296 res
= g_new0 (gint64
, 1);
3297 *res
= (gint64
) ld
.latest
;
3303 message_list_value_at (ETreeModel
*tree_model
,
3307 MessageList
*message_list
;
3308 CamelMessageInfo
*msg_info
;
3311 message_list
= MESSAGE_LIST (tree_model
);
3313 if (!path
|| G_NODE_IS_ROOT ((GNode
*) path
))
3316 /* retrieve the message information array */
3317 msg_info
= ((GNode
*) path
)->data
;
3318 g_return_val_if_fail (msg_info
!= NULL
, NULL
);
3320 camel_message_info_property_lock (msg_info
);
3321 result
= ml_tree_value_at_ex (tree_model
, path
, col
, msg_info
, message_list
);
3322 camel_message_info_property_unlock (msg_info
);
3328 message_list_duplicate_value (ETreeModel
*tree_model
,
3330 gconstpointer value
)
3333 case COL_MESSAGE_STATUS
:
3336 case COL_ATTACHMENT
:
3338 case COL_DELETED_OR_JUNK
:
3340 case COL_JUNK_STRIKEOUT_COLOR
:
3343 case COL_FOLLOWUP_FLAG
:
3344 case COL_FOLLOWUP_FLAG_STATUS
:
3345 return (gpointer
) value
;
3348 return (gpointer
) camel_pstring_strdup (value
);
3354 case COL_RECIPIENTS
:
3355 case COL_MIXED_SENDER
:
3356 case COL_MIXED_RECIPIENTS
:
3359 return g_strdup (value
);
3363 case COL_FOLLOWUP_DUE_BY
:
3366 const gint64
*pvalue
= value
;
3368 res
= g_new0 (gint64
, 1);
3376 g_return_val_if_reached (NULL
);
3381 message_list_free_value (ETreeModel
*tree_model
,
3386 case COL_MESSAGE_STATUS
:
3389 case COL_ATTACHMENT
:
3391 case COL_DELETED_OR_JUNK
:
3393 case COL_JUNK_STRIKEOUT_COLOR
:
3396 case COL_FOLLOWUP_FLAG
:
3397 case COL_FOLLOWUP_FLAG_STATUS
:
3403 case COL_SUBJECT_NORM
:
3404 case COL_SUBJECT_TRIMMED
:
3410 camel_pstring_free (value
);
3415 case COL_RECIPIENTS
:
3416 case COL_MIXED_SENDER
:
3417 case COL_MIXED_RECIPIENTS
:
3421 case COL_FOLLOWUP_DUE_BY
:
3426 g_warn_if_reached ();
3431 message_list_initialize_value (ETreeModel
*tree_model
,
3435 case COL_MESSAGE_STATUS
:
3438 case COL_ATTACHMENT
:
3440 case COL_DELETED_OR_JUNK
:
3442 case COL_JUNK_STRIKEOUT_COLOR
:
3450 case COL_FOLLOWUP_FLAG
:
3451 case COL_FOLLOWUP_FLAG_STATUS
:
3452 case COL_FOLLOWUP_DUE_BY
:
3458 case COL_RECIPIENTS
:
3459 case COL_MIXED_SENDER
:
3460 case COL_MIXED_RECIPIENTS
:
3462 return g_strdup ("");
3465 g_return_val_if_reached (NULL
);
3470 message_list_value_is_empty (ETreeModel
*tree_model
,
3472 gconstpointer value
)
3475 case COL_MESSAGE_STATUS
:
3478 case COL_ATTACHMENT
:
3480 case COL_DELETED_OR_JUNK
:
3482 case COL_JUNK_STRIKEOUT_COLOR
:
3487 case COL_FOLLOWUP_FLAG_STATUS
:
3488 case COL_FOLLOWUP_DUE_BY
:
3489 return value
== NULL
;
3494 case COL_FOLLOWUP_FLAG
:
3497 case COL_RECIPIENTS
:
3498 case COL_MIXED_SENDER
:
3499 case COL_MIXED_RECIPIENTS
:
3502 return !(value
&& *(gchar
*) value
);
3505 g_return_val_if_reached (FALSE
);
3510 message_list_value_to_string (ETreeModel
*tree_model
,
3512 gconstpointer value
)
3517 case COL_MESSAGE_STATUS
:
3518 ii
= GPOINTER_TO_UINT (value
);
3520 return g_strdup ("");
3521 return g_strdup (status_map
[ii
]);
3524 ii
= GPOINTER_TO_UINT (value
) + 3;
3527 return g_strdup (score_map
[ii
]);
3529 case COL_ATTACHMENT
:
3532 case COL_DELETED_OR_JUNK
:
3534 case COL_JUNK_STRIKEOUT_COLOR
:
3536 case COL_FOLLOWUP_FLAG_STATUS
:
3537 ii
= GPOINTER_TO_UINT (value
);
3538 return g_strdup_printf ("%u", ii
);
3542 case COL_FOLLOWUP_DUE_BY
:
3543 return filter_date (value
);
3546 return filter_size (GPOINTER_TO_INT (value
));
3551 case COL_FOLLOWUP_FLAG
:
3554 case COL_RECIPIENTS
:
3555 case COL_MIXED_SENDER
:
3556 case COL_MIXED_RECIPIENTS
:
3559 return g_strdup (value
);
3562 g_return_val_if_reached (NULL
);
3568 message_list_class_init (MessageListClass
*class)
3570 GObjectClass
*object_class
;
3571 GtkWidgetClass
*widget_class
;
3573 if (!ml_drag_info
[0].atom
) {
3576 for (ii
= 0; ii
< G_N_ELEMENTS (ml_drag_info
); ii
++) {
3577 ml_drag_info
[ii
].atom
= gdk_atom_intern (ml_drag_info
[ii
].target
, FALSE
);
3580 for (ii
= 0; ii
< G_N_ELEMENTS (status_map
); ii
++) {
3581 status_map
[ii
] = _(status_map
[ii
]);
3584 for (ii
= 0; ii
< G_N_ELEMENTS (score_map
); ii
++) {
3585 score_map
[ii
] = _(score_map
[ii
]);
3589 g_type_class_add_private (class, sizeof (MessageListPrivate
));
3591 widget_class
= GTK_WIDGET_CLASS (class);
3592 widget_class
->get_preferred_width
= message_list_get_preferred_width
;
3594 object_class
= G_OBJECT_CLASS (class);
3595 object_class
->set_property
= message_list_set_property
;
3596 object_class
->get_property
= message_list_get_property
;
3597 object_class
->dispose
= message_list_dispose
;
3598 object_class
->finalize
= message_list_finalize
;
3599 object_class
->constructed
= message_list_constructed
;
3601 class->message_list_built
= NULL
;
3603 /* Inherited from ESelectableInterface */
3604 g_object_class_override_property (
3606 PROP_COPY_TARGET_LIST
,
3607 "copy-target-list");
3609 g_object_class_install_property (
3612 g_param_spec_object (
3615 "The source folder",
3618 G_PARAM_STATIC_STRINGS
));
3620 g_object_class_install_property (
3622 PROP_GROUP_BY_THREADS
,
3623 g_param_spec_boolean (
3626 "Group messages into conversation threads",
3630 G_PARAM_STATIC_STRINGS
));
3632 /* Inherited from ESelectableInterface */
3633 g_object_class_override_property (
3635 PROP_PASTE_TARGET_LIST
,
3636 "paste-target-list");
3638 g_object_class_install_property (
3641 g_param_spec_object (
3645 E_TYPE_MAIL_SESSION
,
3647 G_PARAM_CONSTRUCT_ONLY
|
3648 G_PARAM_STATIC_STRINGS
));
3650 g_object_class_install_property (
3653 g_param_spec_boolean (
3656 "Show messages marked for deletion",
3660 G_PARAM_STATIC_STRINGS
));
3662 g_object_class_install_property (
3665 g_param_spec_boolean (
3668 "Show messages marked as junk",
3672 G_PARAM_STATIC_STRINGS
));
3674 g_object_class_install_property (
3676 PROP_SHOW_SUBJECT_ABOVE_SENDER
,
3677 g_param_spec_boolean (
3678 "show-subject-above-sender",
3679 "Show Subject Above Sender",
3684 G_PARAM_STATIC_STRINGS
));
3686 g_object_class_install_property (
3689 g_param_spec_boolean (
3692 "Sort threads by latest message",
3696 G_PARAM_STATIC_STRINGS
));
3698 g_object_class_install_property (
3700 PROP_THREAD_SUBJECT
,
3701 g_param_spec_boolean (
3704 "Thread messages by Subject headers",
3708 G_PARAM_STATIC_STRINGS
));
3710 gtk_widget_class_install_style_property (
3711 GTK_WIDGET_CLASS (class),
3712 g_param_spec_boxed (
3713 "new-mail-bg-color",
3714 "New Mail Background Color",
3715 "Background color to use for new mails",
3719 signals
[MESSAGE_SELECTED
] = g_signal_new (
3723 G_STRUCT_OFFSET (MessageListClass
, message_selected
),
3726 g_cclosure_marshal_VOID__STRING
,
3730 signals
[MESSAGE_LIST_BUILT
] = g_signal_new (
3731 "message_list_built",
3734 G_STRUCT_OFFSET (MessageListClass
, message_list_built
),
3737 g_cclosure_marshal_VOID__VOID
,
3742 message_list_selectable_init (ESelectableInterface
*iface
)
3744 iface
->update_actions
= message_list_selectable_update_actions
;
3745 iface
->select_all
= message_list_selectable_select_all
;
3749 message_list_tree_model_init (ETreeModelInterface
*iface
)
3751 iface
->get_root
= message_list_get_root
;
3752 iface
->get_parent
= message_list_get_parent
;
3753 iface
->get_first_child
= message_list_get_first_child
;
3754 iface
->get_next
= message_list_get_next
;
3755 iface
->is_root
= message_list_is_root
;
3756 iface
->is_expandable
= message_list_is_expandable
;
3757 iface
->get_n_nodes
= message_list_get_n_nodes
;
3758 iface
->get_n_children
= message_list_get_n_children
;
3759 iface
->depth
= message_list_depth
;
3760 iface
->get_expanded_default
= message_list_get_expanded_default
;
3761 iface
->column_count
= message_list_column_count
;
3762 iface
->get_save_id
= message_list_get_save_id
;
3763 iface
->get_node_by_id
= message_list_get_node_by_id
;
3764 iface
->sort_value_at
= message_list_sort_value_at
;
3765 iface
->value_at
= message_list_value_at
;
3766 iface
->duplicate_value
= message_list_duplicate_value
;
3767 iface
->free_value
= message_list_free_value
;
3768 iface
->initialize_value
= message_list_initialize_value
;
3769 iface
->value_is_empty
= message_list_value_is_empty
;
3770 iface
->value_to_string
= message_list_value_to_string
;
3774 message_list_init (MessageList
*message_list
)
3776 MessageListPrivate
*p
;
3777 GtkTargetList
*target_list
;
3780 message_list
->priv
= MESSAGE_LIST_GET_PRIVATE (message_list
);
3782 message_list
->normalised_hash
= g_hash_table_new_full (
3783 g_str_hash
, g_str_equal
,
3784 (GDestroyNotify
) NULL
,
3785 (GDestroyNotify
) e_poolv_destroy
);
3787 message_list
->uid_nodemap
= g_hash_table_new (g_str_hash
, g_str_equal
);
3789 message_list
->cursor_uid
= NULL
;
3790 message_list
->last_sel_single
= FALSE
;
3792 g_mutex_init (&message_list
->priv
->regen_lock
);
3793 g_mutex_init (&message_list
->priv
->thread_tree_lock
);
3794 g_mutex_init (&message_list
->priv
->re_prefixes_lock
);
3796 /* TODO: Should this only get the selection if we're realised? */
3797 p
= message_list
->priv
;
3798 p
->invisible
= gtk_invisible_new ();
3799 p
->destroyed
= FALSE
;
3800 g_object_ref_sink (p
->invisible
);
3801 p
->any_row_changed
= FALSE
;
3803 matom
= gdk_atom_intern ("x-uid-list", FALSE
);
3804 gtk_selection_add_target (p
->invisible
, GDK_SELECTION_CLIPBOARD
, matom
, 0);
3805 gtk_selection_add_target (p
->invisible
, GDK_SELECTION_CLIPBOARD
, GDK_SELECTION_TYPE_STRING
, 2);
3808 p
->invisible
, "selection_get",
3809 G_CALLBACK (ml_selection_get
), message_list
);
3811 p
->invisible
, "selection_clear_event",
3812 G_CALLBACK (ml_selection_clear_event
), message_list
);
3814 p
->invisible
, "selection_received",
3815 G_CALLBACK (ml_selection_received
), message_list
);
3817 /* FIXME This is currently unused. */
3818 target_list
= gtk_target_list_new (NULL
, 0);
3819 message_list
->priv
->copy_target_list
= target_list
;
3821 /* FIXME This is currently unused. */
3822 target_list
= gtk_target_list_new (NULL
, 0);
3823 message_list
->priv
->paste_target_list
= target_list
;
3825 message_list
->priv
->mail_settings
= e_util_ref_settings ("org.gnome.evolution.mail");
3826 message_list
->priv
->re_prefixes
= NULL
;
3827 message_list
->priv
->re_separators
= NULL
;
3828 message_list
->priv
->group_by_threads
= TRUE
;
3829 message_list
->priv
->new_mail_bg_color
= NULL
;
3833 message_list_construct (MessageList
*message_list
)
3835 ETreeTableAdapter
*adapter
;
3836 ETableSpecification
*specification
;
3839 gboolean constructed
;
3841 GError
*local_error
= NULL
;
3846 message_list
->extras
= message_list_create_extras (message_list
->priv
->mail_settings
);
3848 etspecfile
= g_build_filename (
3849 EVOLUTION_ETSPECDIR
, "message-list.etspec", NULL
);
3850 specification
= e_table_specification_new (etspecfile
, &local_error
);
3852 /* Failure here is fatal. */
3853 if (local_error
!= NULL
) {
3854 g_error ("%s: %s", etspecfile
, local_error
->message
);
3855 g_return_if_reached ();
3858 constructed
= e_tree_construct (
3859 E_TREE (message_list
),
3860 E_TREE_MODEL (message_list
),
3861 message_list
->extras
, specification
);
3863 g_object_unref (specification
);
3864 g_free (etspecfile
);
3866 adapter
= e_tree_get_table_adapter (E_TREE (message_list
));
3869 e_tree_table_adapter_root_node_set_visible (adapter
, FALSE
);
3871 if (atk_get_root () != NULL
) {
3872 a11y
= gtk_widget_get_accessible (GTK_WIDGET (message_list
));
3873 atk_object_set_name (a11y
, _("Messages"));
3877 adapter
, "model_row_changed",
3878 G_CALLBACK (on_model_row_changed
), message_list
);
3881 message_list
, "cursor_activated",
3882 G_CALLBACK (on_cursor_activated_cmd
), message_list
);
3885 message_list
, "click",
3886 G_CALLBACK (on_click
), message_list
);
3889 message_list
, "selection_change",
3890 G_CALLBACK (on_selection_changed_cmd
), message_list
);
3892 e_tree_drag_source_set (
3893 E_TREE (message_list
), GDK_BUTTON1_MASK
,
3894 ml_drag_types
, G_N_ELEMENTS (ml_drag_types
),
3895 GDK_ACTION_MOVE
| GDK_ACTION_COPY
);
3898 message_list
, "tree_drag_data_get",
3899 G_CALLBACK (ml_tree_drag_data_get
), message_list
);
3902 GTK_WIDGET (message_list
),
3903 GTK_DEST_DEFAULT_ALL
,
3905 G_N_ELEMENTS (ml_drop_types
),
3910 message_list
, "tree_drag_data_received",
3911 G_CALLBACK (ml_tree_drag_data_received
), message_list
);
3914 message_list
, "drag-motion",
3915 G_CALLBACK (ml_tree_drag_motion
), message_list
);
3918 adapter
, "sorting_changed",
3919 G_CALLBACK (ml_tree_sorting_changed
), message_list
);
3921 item
= e_tree_get_item (E_TREE (message_list
));
3922 g_signal_connect (item
, "get-bg-color",
3923 G_CALLBACK (ml_get_bg_color_cb
), message_list
);
3925 g_signal_connect (message_list
, "realize",
3926 G_CALLBACK (ml_style_updated_cb
), NULL
);
3928 g_signal_connect (message_list
, "style-updated",
3929 G_CALLBACK (ml_style_updated_cb
), NULL
);
3935 * Creates a new message-list widget.
3937 * Returns a new message-list widget.
3940 message_list_new (EMailSession
*session
)
3942 GtkWidget
*message_list
;
3944 g_return_val_if_fail (E_IS_MAIL_SESSION (session
), NULL
);
3946 message_list
= g_object_new (
3947 message_list_get_type (),
3948 "session", session
, NULL
);
3950 message_list_construct (MESSAGE_LIST (message_list
));
3952 return message_list
;
3956 message_list_get_session (MessageList
*message_list
)
3958 g_return_val_if_fail (IS_MESSAGE_LIST (message_list
), NULL
);
3960 return message_list
->priv
->session
;
3964 clear_info (gchar
*key
,
3966 MessageList
*message_list
)
3968 g_clear_object (&node
->data
);
3972 clear_tree (MessageList
*message_list
,
3975 ETreeModel
*tree_model
;
3976 CamelFolder
*folder
;
3979 struct timeval start
, end
;
3982 printf ("Clearing tree\n");
3983 gettimeofday (&start
, NULL
);
3986 tree_model
= E_TREE_MODEL (message_list
);
3988 /* we also reset the uid_rowmap since it is no longer useful/valid anyway */
3989 folder
= message_list_ref_folder (message_list
);
3991 g_hash_table_foreach (
3992 message_list
->uid_nodemap
,
3993 (GHFunc
) clear_info
, message_list
);
3994 g_hash_table_destroy (message_list
->uid_nodemap
);
3995 message_list
->uid_nodemap
= g_hash_table_new (g_str_hash
, g_str_equal
);
3996 g_clear_object (&folder
);
3998 message_list
->priv
->newest_read_date
= 0;
3999 message_list
->priv
->newest_read_uid
= NULL
;
4000 message_list
->priv
->oldest_unread_date
= 0;
4001 message_list
->priv
->oldest_unread_uid
= NULL
;
4003 if (message_list
->priv
->tree_model_root
!= NULL
) {
4004 /* we should be frozen already */
4005 message_list_tree_model_remove (
4006 message_list
, message_list
->priv
->tree_model_root
);
4009 e_tree_table_adapter_clear_nodes_silent (e_tree_get_table_adapter (E_TREE (message_list
)));
4011 /* Create a new placeholder root node. */
4012 message_list_tree_model_insert (message_list
, NULL
, 0, NULL
);
4013 g_warn_if_fail (message_list
->priv
->tree_model_root
!= NULL
);
4015 /* Also reset cursor node, it had been just erased */
4016 e_tree_set_cursor (E_TREE (message_list
), message_list
->priv
->tree_model_root
);
4019 e_tree_model_rebuilt (tree_model
);
4021 gettimeofday (&end
, NULL
);
4022 diff
= end
.tv_sec
* 1000 + end
.tv_usec
/ 1000;
4023 diff
-= start
.tv_sec
* 1000 + start
.tv_usec
/ 1000;
4024 printf ("Clearing tree took %ld.%03ld seconds\n", diff
/ 1000, diff
% 1000);
4029 message_list_folder_filters_system_flag (const gchar
*expr
,
4034 if (!expr
|| !*expr
)
4037 g_return_val_if_fail (flag
&& *flag
, FALSE
);
4039 while (pos
= strstr (expr
, flag
), pos
) {
4040 /* This is searching for something like 'system-flag "' + flag + '"'
4041 in the expression, without fully parsing it. */
4042 if (pos
> expr
&& pos
[-1] == '\"' && pos
[strlen(flag
)] == '\"') {
4043 const gchar
*system_flag
= "system-flag";
4044 gint ii
= 2, jj
= strlen (system_flag
) - 1;
4046 while (pos
- ii
>= expr
&& g_ascii_isspace (pos
[-ii
]))
4049 while (pos
- ii
>= expr
&& jj
>= 0 && system_flag
[jj
] == pos
[-ii
]) {
4065 folder_store_supports_vjunk_folder (CamelFolder
*folder
)
4069 g_return_val_if_fail (folder
!= NULL
, FALSE
);
4071 store
= camel_folder_get_parent_store (folder
);
4075 if (CAMEL_IS_VEE_FOLDER (folder
))
4078 if (camel_store_get_flags (store
) & CAMEL_STORE_VJUNK
)
4081 if (camel_store_get_flags (store
) & CAMEL_STORE_REAL_JUNK_FOLDER
)
4088 message_list_get_hide_junk (MessageList
*message_list
,
4089 CamelFolder
*folder
)
4091 guint32 folder_flags
;
4096 if (message_list_get_show_junk (message_list
))
4099 if (!folder_store_supports_vjunk_folder (folder
))
4102 folder_flags
= camel_folder_get_flags (folder
);
4104 if (folder_flags
& CAMEL_FOLDER_IS_JUNK
)
4107 if (folder_flags
& CAMEL_FOLDER_IS_TRASH
)
4110 if (CAMEL_IS_VEE_FOLDER (folder
)) {
4111 const gchar
*expr
= camel_vee_folder_get_expression (CAMEL_VEE_FOLDER (folder
));
4112 if (message_list_folder_filters_system_flag (expr
, "Junk"))
4120 message_list_get_hide_deleted (MessageList
*message_list
,
4121 CamelFolder
*folder
)
4124 gboolean non_trash_folder
;
4129 if (message_list_get_show_deleted (message_list
))
4132 store
= camel_folder_get_parent_store (folder
);
4133 g_return_val_if_fail (store
!= NULL
, FALSE
);
4136 ((camel_store_get_flags (store
) & CAMEL_STORE_VTRASH
) == 0) ||
4137 ((camel_folder_get_flags (folder
) & CAMEL_FOLDER_IS_TRASH
) == 0);
4139 if (non_trash_folder
&& CAMEL_IS_VEE_FOLDER (folder
)) {
4140 const gchar
*expr
= camel_vee_folder_get_expression (CAMEL_VEE_FOLDER (folder
));
4141 if (message_list_folder_filters_system_flag (expr
, "Deleted"))
4145 return non_trash_folder
;
4148 /* Check if the given node is selectable in the current message list,
4149 * which depends on the type of the folder (normal, junk, trash). */
4151 is_node_selectable (MessageList
*message_list
,
4152 CamelMessageInfo
*info
)
4154 CamelFolder
*folder
;
4155 gboolean is_junk_folder
;
4156 gboolean is_trash_folder
;
4157 guint32 flags
, folder_flags
;
4159 gboolean flag_deleted
;
4161 gboolean hide_deleted
;
4162 gboolean store_has_vjunk
;
4163 gboolean selectable
= FALSE
;
4165 g_return_val_if_fail (info
!= NULL
, FALSE
);
4167 folder
= message_list_ref_folder (message_list
);
4168 g_return_val_if_fail (folder
!= NULL
, FALSE
);
4170 store_has_vjunk
= folder_store_supports_vjunk_folder (folder
);
4171 folder_flags
= camel_folder_get_flags (folder
);
4173 /* check folder type */
4174 is_junk_folder
= store_has_vjunk
&& (folder_flags
& CAMEL_FOLDER_IS_JUNK
) != 0;
4175 is_trash_folder
= folder_flags
& CAMEL_FOLDER_IS_TRASH
;
4177 hide_junk
= message_list_get_hide_junk (message_list
, folder
);
4178 hide_deleted
= message_list_get_hide_deleted (message_list
, folder
);
4180 g_object_unref (folder
);
4182 /* check flags set on current message */
4183 flags
= camel_message_info_get_flags (info
);
4184 flag_junk
= store_has_vjunk
&& (flags
& CAMEL_MESSAGE_JUNK
) != 0;
4185 flag_deleted
= flags
& CAMEL_MESSAGE_DELETED
;
4187 /* perform actions depending on folder type */
4188 if (is_junk_folder
) {
4189 /* messages in a junk folder are selectable only if
4190 * the message is marked as junk and if not deleted
4191 * when hide_deleted is set */
4192 if (flag_junk
&& !(flag_deleted
&& hide_deleted
))
4195 } else if (is_trash_folder
) {
4196 /* messages in a trash folder are selectable unless
4197 * not deleted any more */
4201 /* in normal folders it depends on hide_deleted,
4202 * hide_junk and the message flags */
4203 if (!(flag_junk
&& hide_junk
)
4204 && !(flag_deleted
&& hide_deleted
))
4211 /* We try and find something that is selectable in our tree. There is
4212 * actually no assurance that we'll find something that will still be
4213 * there next time, but its probably going to work most of the time. */
4215 find_next_selectable (MessageList
*message_list
)
4217 ETreeTableAdapter
*adapter
;
4222 CamelMessageInfo
*info
;
4224 node
= g_hash_table_lookup (
4225 message_list
->uid_nodemap
,
4226 message_list
->cursor_uid
);
4230 info
= get_message_info (message_list
, node
);
4231 if (info
&& is_node_selectable (message_list
, info
))
4234 adapter
= e_tree_get_table_adapter (E_TREE (message_list
));
4235 row_count
= e_table_model_row_count (E_TABLE_MODEL (adapter
));
4237 /* model_to_view_row etc simply dont work for sorted views. Sigh. */
4238 vrow_orig
= e_tree_table_adapter_row_of_node (adapter
, node
);
4240 /* We already checked this node. */
4241 vrow
= vrow_orig
+ 1;
4243 while (vrow
< row_count
) {
4244 node
= e_tree_table_adapter_node_at_row (adapter
, vrow
);
4245 info
= get_message_info (message_list
, node
);
4246 if (info
&& is_node_selectable (message_list
, info
))
4247 return g_strdup (camel_message_info_get_uid (info
));
4251 /* We didn't find any undeleted entries _below_ the currently selected one
4252 * * so let's try to find one _above_ */
4253 vrow
= vrow_orig
- 1;
4256 node
= e_tree_table_adapter_node_at_row (adapter
, vrow
);
4257 info
= get_message_info (message_list
, node
);
4258 if (info
&& is_node_selectable (message_list
, info
))
4259 return g_strdup (camel_message_info_get_uid (info
));
4267 ml_uid_nodemap_insert (MessageList
*message_list
,
4268 CamelMessageInfo
*info
,
4272 CamelFolder
*folder
;
4278 folder
= message_list_ref_folder (message_list
);
4279 g_return_val_if_fail (folder
!= NULL
, NULL
);
4282 parent
= message_list
->priv
->tree_model_root
;
4284 node
= message_list_tree_model_insert (
4285 message_list
, parent
, row
, info
);
4287 uid
= camel_message_info_get_uid (info
);
4288 flags
= camel_message_info_get_flags (info
);
4289 date
= camel_message_info_get_date_received (info
);
4291 g_object_ref (info
);
4292 g_hash_table_insert (message_list
->uid_nodemap
, (gpointer
) uid
, node
);
4294 /* Track the latest seen and unseen messages shown, used in
4295 * fallback heuristics for automatic message selection. */
4296 if (flags
& CAMEL_MESSAGE_SEEN
) {
4297 if (date
> message_list
->priv
->newest_read_date
) {
4298 message_list
->priv
->newest_read_date
= date
;
4299 message_list
->priv
->newest_read_uid
= uid
;
4302 if (message_list
->priv
->oldest_unread_date
== 0) {
4303 message_list
->priv
->oldest_unread_date
= date
;
4304 message_list
->priv
->oldest_unread_uid
= uid
;
4305 } else if (date
< message_list
->priv
->oldest_unread_date
) {
4306 message_list
->priv
->oldest_unread_date
= date
;
4307 message_list
->priv
->oldest_unread_uid
= uid
;
4311 g_object_unref (folder
);
4317 ml_uid_nodemap_remove (MessageList
*message_list
,
4318 CamelMessageInfo
*info
)
4320 CamelFolder
*folder
;
4323 folder
= message_list_ref_folder (message_list
);
4324 g_return_if_fail (folder
!= NULL
);
4326 uid
= camel_message_info_get_uid (info
);
4328 if (uid
== message_list
->priv
->newest_read_uid
) {
4329 message_list
->priv
->newest_read_date
= 0;
4330 message_list
->priv
->newest_read_uid
= NULL
;
4333 if (uid
== message_list
->priv
->oldest_unread_uid
) {
4334 message_list
->priv
->oldest_unread_date
= 0;
4335 message_list
->priv
->oldest_unread_uid
= NULL
;
4338 g_hash_table_remove (message_list
->uid_nodemap
, uid
);
4339 g_clear_object (&info
);
4341 g_object_unref (folder
);
4344 /* only call if we have a tree model */
4345 /* builds the tree structure */
4347 static void build_subtree (MessageList
*message_list
,
4349 CamelFolderThreadNode
*c
,
4352 static void build_subtree_diff (MessageList
*message_list
,
4355 CamelFolderThreadNode
*c
,
4359 build_tree (MessageList
*message_list
,
4360 CamelFolderThread
*thread
,
4361 gboolean folder_changed
)
4364 ETableItem
*table_item
= e_tree_get_item (E_TREE (message_list
));
4366 struct timeval start
, end
;
4369 printf ("Building tree\n");
4370 gettimeofday (&start
, NULL
);
4374 gettimeofday (&end
, NULL
);
4375 diff
= end
.tv_sec
* 1000 + end
.tv_usec
/ 1000;
4376 diff
-= start
.tv_sec
* 1000 + start
.tv_usec
/ 1000;
4377 printf ("Loading tree state took %ld.%03ld seconds\n", diff
/ 1000, diff
% 1000);
4380 if (message_list
->priv
->tree_model_root
== NULL
) {
4381 message_list_tree_model_insert (message_list
, NULL
, 0, NULL
);
4382 g_warn_if_fail (message_list
->priv
->tree_model_root
!= NULL
);
4386 e_table_item_freeze (table_item
);
4388 message_list_tree_model_freeze (message_list
);
4390 clear_tree (message_list
, FALSE
);
4394 message_list
->priv
->tree_model_root
,
4395 thread
? thread
->tree
: NULL
, &row
);
4397 message_list_tree_model_thaw (message_list
);
4400 /* Show the cursor unless we're responding to a
4401 * "folder-changed" signal from our CamelFolder. */
4403 table_item
->queue_show_cursor
= FALSE
;
4404 e_table_item_thaw (table_item
);
4408 gettimeofday (&end
, NULL
);
4409 diff
= end
.tv_sec
* 1000 + end
.tv_usec
/ 1000;
4410 diff
-= start
.tv_sec
* 1000 + start
.tv_usec
/ 1000;
4411 printf ("Building tree took %ld.%03ld seconds\n", diff
/ 1000, diff
% 1000);
4415 /* this is about 20% faster than build_subtree_diff,
4416 * entirely because e_tree_model_node_insert (xx, -1 xx)
4417 * is faster than inserting to the right row :( */
4418 /* Otherwise, this code would probably go as it does the same thing essentially */
4420 build_subtree (MessageList
*message_list
,
4422 CamelFolderThreadNode
*c
,
4428 /* phantom nodes no longer allowed */
4430 g_warning ("c->message shouldn't be NULL\n");
4435 node
= ml_uid_nodemap_insert (
4437 (CamelMessageInfo
*) c
->message
, parent
, -1);
4440 build_subtree (message_list
, node
, c
->child
, row
);
4446 /* compares a thread tree node with the etable tree node to see if they point to
4447 * the same object */
4449 node_equal (ETreeModel
*etm
,
4451 CamelFolderThreadNode
*bp
)
4453 if (bp
->message
&& strcmp (camel_message_info_get_uid (ap
->data
), camel_message_info_get_uid (bp
->message
)) == 0)
4459 /* adds a single node, retains save state, and handles adding children if required */
4461 add_node_diff (MessageList
*message_list
,
4464 CamelFolderThreadNode
*c
,
4468 CamelMessageInfo
*info
;
4471 g_return_if_fail (c
->message
!= NULL
);
4473 /* XXX Casting away constness. */
4474 info
= (CamelMessageInfo
*) c
->message
;
4476 /* we just update the hashtable key */
4477 ml_uid_nodemap_remove (message_list
, info
);
4478 new_node
= ml_uid_nodemap_insert (message_list
, info
, parent
, myrow
);
4482 build_subtree_diff (
4483 message_list
, new_node
, NULL
, c
->child
, row
);
4487 /* removes node, children recursively and all associated data */
4489 remove_node_diff (MessageList
*message_list
,
4494 CamelMessageInfo
*info
;
4496 t (printf ("Removing node: %s\n", (gchar
*) node
->data
));
4498 /* we depth-first remove all node data's ... */
4499 cp
= g_node_first_child (node
);
4501 cn
= g_node_next_sibling (cp
);
4502 remove_node_diff (message_list
, cp
, depth
+ 1);
4506 /* and the rowid entry - if and only if it is referencing this node */
4509 /* and only at the toplevel, remove the node (etree should optimise this remove somewhat) */
4511 message_list_tree_model_remove (message_list
, node
);
4513 g_return_if_fail (info
);
4514 ml_uid_nodemap_remove (message_list
, info
);
4517 /* applies a new tree structure to an existing tree, but only by changing things
4518 * that have changed */
4520 build_subtree_diff (MessageList
*message_list
,
4523 CamelFolderThreadNode
*c
,
4526 ETreeModel
*tree_model
;
4527 GNode
*ap
, *ai
, *at
, *tmp
;
4528 CamelFolderThreadNode
*bp
, *bi
, *bt
;
4529 gint i
, j
, myrow
= 0;
4531 tree_model
= E_TREE_MODEL (message_list
);
4537 t (printf ("Processing row: %d (subtree row %d)\n", *row
, myrow
));
4539 t (printf ("out of old nodes\n"));
4540 /* ran out of old nodes - remaining nodes are added */
4542 message_list
, parent
, ap
, bp
, row
, myrow
);
4545 } else if (bp
== NULL
) {
4546 t (printf ("out of new nodes\n"));
4547 /* ran out of new nodes - remaining nodes are removed */
4548 tmp
= g_node_next_sibling (ap
);
4549 remove_node_diff (message_list
, ap
, 0);
4551 } else if (node_equal (tree_model
, ap
, bp
)) {
4554 tmp
= g_node_first_child (ap
);
4555 /* make child lists match (if either has one) */
4556 if (bp
->child
|| tmp
) {
4557 build_subtree_diff (
4558 message_list
, ap
, tmp
, bp
->child
, row
);
4560 ap
= g_node_next_sibling (ap
);
4563 t (printf ("searching for matches\n"));
4564 /* we have to scan each side for a match */
4566 ai
= g_node_next_sibling (ap
);
4567 for (i
= 1; bi
!= NULL
; i
++,bi
= bi
->next
) {
4568 if (node_equal (tree_model
, ap
, bi
))
4571 for (j
= 1; ai
!= NULL
; j
++,ai
= g_node_next_sibling (ai
)) {
4572 if (node_equal (tree_model
, ai
, bp
))
4576 /* smaller run of new nodes - must be nodes to add */
4580 t (printf ("adding new node 0\n"));
4582 message_list
, parent
, NULL
, bt
, row
, myrow
);
4588 t (printf ("adding new node 1\n"));
4589 /* no match in new nodes, add one, try next */
4591 message_list
, parent
, NULL
, bp
, row
, myrow
);
4596 /* bigger run of old nodes - must be nodes to remove */
4599 while (at
!= NULL
&& at
!= ai
) {
4600 t (printf ("removing old node 0\n"));
4601 tmp
= g_node_next_sibling (at
);
4602 remove_node_diff (message_list
, at
, 0);
4607 t (printf ("adding new node 2\n"));
4608 /* didn't find match in old nodes, must be new node? */
4610 message_list
, parent
, NULL
, bp
, row
, myrow
);
4620 build_flat (MessageList
*message_list
,
4622 gboolean folder_changed
)
4624 gchar
*saveuid
= NULL
;
4626 GPtrArray
*selected
;
4628 struct timeval start
, end
;
4631 printf ("Building flat\n");
4632 gettimeofday (&start
, NULL
);
4635 if (message_list
->cursor_uid
!= NULL
)
4636 saveuid
= find_next_selectable (message_list
);
4638 selected
= message_list_get_selected (message_list
);
4640 message_list_tree_model_freeze (message_list
);
4642 clear_tree (message_list
, FALSE
);
4644 for (i
= 0; i
< summary
->len
; i
++) {
4645 CamelMessageInfo
*info
= summary
->pdata
[i
];
4647 ml_uid_nodemap_insert (message_list
, info
, NULL
, -1);
4650 message_list_tree_model_thaw (message_list
);
4652 message_list_set_selected (message_list
, selected
);
4654 g_ptr_array_unref (selected
);
4659 node
= g_hash_table_lookup (
4660 message_list
->uid_nodemap
, saveuid
);
4662 g_free (message_list
->cursor_uid
);
4663 message_list
->cursor_uid
= NULL
;
4666 signals
[MESSAGE_SELECTED
], 0, NULL
);
4667 } else if (!folder_changed
|| !e_tree_get_item (E_TREE (message_list
))) {
4668 e_tree_set_cursor (E_TREE (message_list
), node
);
4674 gettimeofday (&end
, NULL
);
4675 diff
= end
.tv_sec
* 1000 + end
.tv_usec
/ 1000;
4676 diff
-= start
.tv_sec
* 1000 + start
.tv_usec
/ 1000;
4677 printf ("Building flat took %ld.%03ld seconds\n", diff
/ 1000, diff
% 1000);
4683 message_list_change_first_visible_parent (MessageList
*message_list
,
4686 ETreeModel
*tree_model
;
4687 ETreeTableAdapter
*adapter
;
4688 GNode
*first_visible
= NULL
;
4690 tree_model
= E_TREE_MODEL (message_list
);
4691 adapter
= e_tree_get_table_adapter (E_TREE (message_list
));
4693 while (node
!= NULL
&& (node
= node
->parent
) != NULL
) {
4694 if (!e_tree_table_adapter_node_is_expanded (adapter
, node
))
4695 first_visible
= node
;
4698 if (first_visible
!= NULL
) {
4699 e_tree_model_pre_change (tree_model
);
4700 e_tree_model_node_data_changed (tree_model
, first_visible
);
4704 static CamelFolderChangeInfo
*
4705 mail_folder_hide_by_flag (CamelFolder
*folder
,
4706 MessageList
*message_list
,
4707 CamelFolderChangeInfo
*changes
,
4710 CamelFolderChangeInfo
*newchanges
;
4711 CamelMessageInfo
*info
;
4714 newchanges
= camel_folder_change_info_new ();
4716 for (i
= 0; i
< changes
->uid_changed
->len
; i
++) {
4720 node
= g_hash_table_lookup (
4721 message_list
->uid_nodemap
,
4722 changes
->uid_changed
->pdata
[i
]);
4723 info
= camel_folder_get_message_info (
4724 folder
, changes
->uid_changed
->pdata
[i
]);
4726 flags
= camel_message_info_get_flags (info
);
4728 if (node
!= NULL
&& info
!= NULL
&& (flags
& flag
) != 0)
4729 camel_folder_change_info_remove_uid (
4730 newchanges
, changes
->uid_changed
->pdata
[i
]);
4731 else if (node
== NULL
&& info
!= NULL
&& (flags
& flag
) == 0)
4732 camel_folder_change_info_add_uid (
4733 newchanges
, changes
->uid_changed
->pdata
[i
]);
4735 camel_folder_change_info_change_uid (
4736 newchanges
, changes
->uid_changed
->pdata
[i
]);
4738 g_clear_object (&info
);
4741 if (newchanges
->uid_added
->len
> 0 || newchanges
->uid_removed
->len
> 0) {
4742 for (i
= 0; i
< changes
->uid_added
->len
; i
++)
4743 camel_folder_change_info_add_uid (
4744 newchanges
, changes
->uid_added
->pdata
[i
]);
4745 for (i
= 0; i
< changes
->uid_removed
->len
; i
++)
4746 camel_folder_change_info_remove_uid (
4747 newchanges
, changes
->uid_removed
->pdata
[i
]);
4749 camel_folder_change_info_clear (newchanges
);
4750 camel_folder_change_info_cat (newchanges
, changes
);
4757 message_list_folder_changed (CamelFolder
*folder
,
4758 CamelFolderChangeInfo
*changes
,
4759 MessageList
*message_list
)
4761 CamelFolderChangeInfo
*altered_changes
= NULL
;
4762 ETreeModel
*tree_model
;
4763 gboolean need_list_regen
= TRUE
;
4765 gboolean hide_deleted
;
4768 if (message_list
->priv
->destroyed
)
4771 tree_model
= E_TREE_MODEL (message_list
);
4773 hide_junk
= message_list_get_hide_junk (message_list
, folder
);
4774 hide_deleted
= message_list_get_hide_deleted (message_list
, folder
);
4777 printf ("%s: changes:%p added:%d removed:%d changed:%d recent:%d for '%s'\n", G_STRFUNC
, changes
,
4778 changes
? changes
->uid_added
->len
: -1,
4779 changes
? changes
->uid_removed
->len
: -1,
4780 changes
? changes
->uid_changed
->len
: -1,
4781 changes
? changes
->uid_recent
->len
: -1,
4782 camel_folder_get_full_name (folder
)));
4783 if (changes
!= NULL
) {
4784 for (i
= 0; i
< changes
->uid_removed
->len
; i
++)
4785 g_hash_table_remove (
4786 message_list
->normalised_hash
,
4787 changes
->uid_removed
->pdata
[i
]);
4789 /* Check if the hidden state has changed.
4790 * If so, modify accordingly and regenerate. */
4791 if (hide_junk
|| hide_deleted
)
4792 altered_changes
= mail_folder_hide_by_flag (
4793 folder
, message_list
, changes
,
4794 (hide_junk
? CAMEL_MESSAGE_JUNK
: 0) |
4795 (hide_deleted
? CAMEL_MESSAGE_DELETED
: 0));
4797 altered_changes
= camel_folder_change_info_new ();
4798 camel_folder_change_info_cat (altered_changes
, changes
);
4801 if (altered_changes
->uid_added
->len
== 0 && altered_changes
->uid_removed
->len
== 0 && altered_changes
->uid_changed
->len
< 100) {
4802 for (i
= 0; i
< altered_changes
->uid_changed
->len
; i
++) {
4805 node
= g_hash_table_lookup (
4806 message_list
->uid_nodemap
,
4807 altered_changes
->uid_changed
->pdata
[i
]);
4809 e_tree_model_pre_change (tree_model
);
4810 e_tree_model_node_data_changed (tree_model
, node
);
4812 message_list_change_first_visible_parent (message_list
, node
);
4818 signals
[MESSAGE_LIST_BUILT
], 0);
4820 need_list_regen
= FALSE
;
4824 if (need_list_regen
) {
4825 /* Use 'folder_changed = TRUE' only if this is not the first change after the folder
4826 had been set. There could happen a race condition on folder enter which prevented
4827 the message list to scroll to the cursor position due to the folder_changed = TRUE,
4828 by cancelling the full rebuild request. */
4829 mail_regen_list (message_list
, NULL
, !message_list
->just_set_folder
);
4832 if (altered_changes
!= NULL
)
4833 camel_folder_change_info_free (altered_changes
);
4837 message_list_ref_folder (MessageList
*message_list
)
4839 CamelFolder
*folder
= NULL
;
4841 /* XXX Do we need a property lock to guard this? */
4843 g_return_val_if_fail (IS_MESSAGE_LIST (message_list
), NULL
);
4845 if (message_list
->priv
->folder
!= NULL
)
4846 folder
= g_object_ref (message_list
->priv
->folder
);
4852 * message_list_set_folder:
4853 * @message_list: Message List widget
4854 * @folder: folder backend to be set
4856 * Sets @folder to be the backend folder for @message_list.
4859 message_list_set_folder (MessageList
*message_list
,
4860 CamelFolder
*folder
)
4862 /* XXX Do we need a property lock to guard this? */
4864 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
4866 if (folder
== message_list
->priv
->folder
)
4869 if (folder
!= NULL
) {
4870 g_return_if_fail (CAMEL_IS_FOLDER (folder
));
4871 g_object_ref (folder
);
4874 g_free (message_list
->search
);
4875 message_list
->search
= NULL
;
4877 g_free (message_list
->frozen_search
);
4878 message_list
->frozen_search
= NULL
;
4880 if (message_list
->seen_id
) {
4881 g_source_remove (message_list
->seen_id
);
4882 message_list
->seen_id
= 0;
4885 /* reset the normalised sort performance hack */
4886 g_hash_table_remove_all (message_list
->normalised_hash
);
4888 mail_regen_cancel (message_list
);
4890 if (message_list
->priv
->folder
!= NULL
)
4891 save_tree_state (message_list
, message_list
->priv
->folder
);
4893 message_list_tree_model_freeze (message_list
);
4894 clear_tree (message_list
, TRUE
);
4895 message_list_tree_model_thaw (message_list
);
4897 /* remove the cursor activate idle handler */
4898 if (message_list
->idle_id
!= 0) {
4899 g_source_remove (message_list
->idle_id
);
4900 message_list
->idle_id
= 0;
4903 if (message_list
->priv
->folder
!= NULL
) {
4904 g_signal_handler_disconnect (
4905 message_list
->priv
->folder
,
4906 message_list
->priv
->folder_changed_handler_id
);
4907 message_list
->priv
->folder_changed_handler_id
= 0;
4909 if (message_list
->uid_nodemap
!= NULL
)
4910 g_hash_table_foreach (
4911 message_list
->uid_nodemap
,
4912 (GHFunc
) clear_info
, message_list
);
4914 g_clear_object (&message_list
->priv
->folder
);
4917 /* Invalidate the thread tree. */
4918 message_list_set_thread_tree (message_list
, NULL
);
4920 g_free (message_list
->cursor_uid
);
4921 message_list
->cursor_uid
= NULL
;
4923 /* Always emit message-selected, event when an account node
4924 * (folder == NULL) is selected, so that views know what happened and
4925 * can stop all running operations etc. */
4926 g_signal_emit (message_list
, signals
[MESSAGE_SELECTED
], 0, NULL
);
4928 if (folder
!= NULL
) {
4929 gboolean non_trash_folder
;
4930 gboolean non_junk_folder
;
4931 gint strikeout_col
, strikeout_color_col
;
4935 message_list
->priv
->folder
= folder
;
4936 message_list
->just_set_folder
= TRUE
;
4938 non_trash_folder
= !(camel_folder_get_flags (folder
) & CAMEL_FOLDER_IS_TRASH
);
4939 non_junk_folder
= !(camel_folder_get_flags (folder
) & CAMEL_FOLDER_IS_JUNK
);
4942 strikeout_color_col
= -1;
4944 /* Setup the strikeout effect for non-trash or non-junk folders */
4945 if (non_trash_folder
&& non_junk_folder
) {
4946 strikeout_col
= COL_DELETED_OR_JUNK
;
4947 strikeout_color_col
= COL_JUNK_STRIKEOUT_COLOR
;
4948 } else if (non_trash_folder
) {
4949 strikeout_col
= COL_DELETED
;
4950 } else if (non_junk_folder
) {
4951 strikeout_col
= COL_JUNK
;
4952 strikeout_color_col
= COL_JUNK_STRIKEOUT_COLOR
;
4955 cell
= e_table_extras_get_cell (message_list
->extras
, "render_date");
4956 g_object_set (cell
, "strikeout-column", strikeout_col
, "strikeout-color-column", strikeout_color_col
, NULL
);
4958 cell
= e_table_extras_get_cell (message_list
->extras
, "render_text");
4959 g_object_set (cell
, "strikeout-column", strikeout_col
, "strikeout-color-column", strikeout_color_col
, NULL
);
4961 cell
= e_table_extras_get_cell (message_list
->extras
, "render_size");
4962 g_object_set (cell
, "strikeout-column", strikeout_col
, "strikeout-color-column", strikeout_color_col
, NULL
);
4964 cell
= e_table_extras_get_cell (message_list
->extras
, "render_composite_from");
4965 composite_cell_set_strike_col (cell
, strikeout_col
, strikeout_color_col
);
4967 cell
= e_table_extras_get_cell (message_list
->extras
, "render_composite_to");
4968 composite_cell_set_strike_col (cell
, strikeout_col
, strikeout_color_col
);
4970 /* Build the etree suitable for this folder */
4971 message_list_setup_etree (message_list
);
4973 handler_id
= g_signal_connect (
4975 G_CALLBACK (message_list_folder_changed
),
4977 message_list
->priv
->folder_changed_handler_id
= handler_id
;
4979 if (message_list
->frozen
== 0)
4980 mail_regen_list (message_list
, NULL
, FALSE
);
4982 message_list
->priv
->thaw_needs_regen
= TRUE
;
4987 message_list_get_copy_target_list (MessageList
*message_list
)
4989 g_return_val_if_fail (IS_MESSAGE_LIST (message_list
), NULL
);
4991 return message_list
->priv
->copy_target_list
;
4995 message_list_get_paste_target_list (MessageList
*message_list
)
4997 g_return_val_if_fail (IS_MESSAGE_LIST (message_list
), NULL
);
4999 return message_list
->priv
->paste_target_list
;
5003 message_list_set_expanded_default (MessageList
*message_list
,
5004 gboolean expanded_default
)
5006 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
5008 message_list
->priv
->expanded_default
= expanded_default
;
5012 message_list_get_group_by_threads (MessageList
*message_list
)
5014 g_return_val_if_fail (IS_MESSAGE_LIST (message_list
), FALSE
);
5016 return message_list
->priv
->group_by_threads
;
5020 message_list_set_group_by_threads (MessageList
*message_list
,
5021 gboolean group_by_threads
)
5023 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
5025 if (group_by_threads
== message_list
->priv
->group_by_threads
)
5028 message_list
->priv
->group_by_threads
= group_by_threads
;
5029 e_tree_set_grouped_view (E_TREE (message_list
), group_by_threads
);
5031 g_object_notify (G_OBJECT (message_list
), "group-by-threads");
5033 /* Changing this property triggers a message list regen. */
5034 if (message_list
->frozen
== 0)
5035 mail_regen_list (message_list
, NULL
, FALSE
);
5037 message_list
->priv
->thaw_needs_regen
= TRUE
;
5041 message_list_get_show_deleted (MessageList
*message_list
)
5043 g_return_val_if_fail (IS_MESSAGE_LIST (message_list
), FALSE
);
5045 return message_list
->priv
->show_deleted
;
5049 message_list_set_show_deleted (MessageList
*message_list
,
5050 gboolean show_deleted
)
5052 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
5054 if (show_deleted
== message_list
->priv
->show_deleted
)
5057 message_list
->priv
->show_deleted
= show_deleted
;
5059 g_object_notify (G_OBJECT (message_list
), "show-deleted");
5061 /* Invalidate the thread tree. */
5062 message_list_set_thread_tree (message_list
, NULL
);
5064 /* Changing this property triggers a message list regen. */
5065 if (message_list
->frozen
== 0)
5066 mail_regen_list (message_list
, NULL
, FALSE
);
5068 message_list
->priv
->thaw_needs_regen
= TRUE
;
5072 message_list_get_show_junk (MessageList
*message_list
)
5074 g_return_val_if_fail (IS_MESSAGE_LIST (message_list
), FALSE
);
5076 return message_list
->priv
->show_junk
;
5080 message_list_set_show_junk (MessageList
*message_list
,
5083 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
5085 if (show_junk
== message_list
->priv
->show_junk
)
5088 message_list
->priv
->show_junk
= show_junk
;
5090 g_object_notify (G_OBJECT (message_list
), "show-junk");
5092 /* Invalidate the thread tree. */
5093 message_list_set_thread_tree (message_list
, NULL
);
5095 /* Changing this property triggers a message list regen. */
5096 if (message_list
->frozen
== 0)
5097 mail_regen_list (message_list
, NULL
, FALSE
);
5099 message_list
->priv
->thaw_needs_regen
= TRUE
;
5103 message_list_get_show_subject_above_sender (MessageList
*message_list
)
5105 g_return_val_if_fail (IS_MESSAGE_LIST (message_list
), FALSE
);
5107 return message_list
->priv
->show_subject_above_sender
;
5111 message_list_set_show_subject_above_sender (MessageList
*message_list
,
5112 gboolean show_subject_above_sender
)
5114 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
5116 if (show_subject_above_sender
== message_list
->priv
->show_subject_above_sender
)
5119 message_list
->priv
->show_subject_above_sender
= show_subject_above_sender
;
5121 if (message_list
->extras
) {
5124 cell
= e_table_extras_get_cell (message_list
->extras
, "render_composite_from");
5126 composite_cell_set_show_subject_above_sender (cell
, show_subject_above_sender
);
5128 cell
= e_table_extras_get_cell (message_list
->extras
, "render_composite_to");
5130 composite_cell_set_show_subject_above_sender (cell
, show_subject_above_sender
);
5132 if (message_list
->priv
->folder
&&
5133 gtk_widget_get_realized (GTK_WIDGET (message_list
)) &&
5134 gtk_widget_get_visible (GTK_WIDGET (message_list
)))
5135 mail_regen_list (message_list
, NULL
, FALSE
);
5138 g_object_notify (G_OBJECT (message_list
), "show-subject-above-sender");
5142 message_list_get_thread_latest (MessageList
*message_list
)
5144 g_return_val_if_fail (IS_MESSAGE_LIST (message_list
), FALSE
);
5146 return message_list
->priv
->thread_latest
;
5150 message_list_set_thread_latest (MessageList
*message_list
,
5151 gboolean thread_latest
)
5153 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
5155 if (thread_latest
== message_list
->priv
->thread_latest
)
5158 message_list
->priv
->thread_latest
= thread_latest
;
5160 g_object_notify (G_OBJECT (message_list
), "thread-latest");
5164 message_list_get_thread_subject (MessageList
*message_list
)
5166 g_return_val_if_fail (IS_MESSAGE_LIST (message_list
), FALSE
);
5168 return message_list
->priv
->thread_subject
;
5172 message_list_set_thread_subject (MessageList
*message_list
,
5173 gboolean thread_subject
)
5175 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
5177 if (thread_subject
== message_list
->priv
->thread_subject
)
5180 message_list
->priv
->thread_subject
= thread_subject
;
5182 g_object_notify (G_OBJECT (message_list
), "thread-subject");
5186 on_cursor_activated_idle (gpointer data
)
5188 MessageList
*message_list
= data
;
5189 ESelectionModel
*esm
;
5192 esm
= e_tree_get_selection_model (E_TREE (message_list
));
5193 selected
= e_selection_model_selected_count (esm
);
5195 if (selected
== 1 && message_list
->cursor_uid
) {
5196 d (printf ("emitting cursor changed signal, for uid %s\n", message_list
->cursor_uid
));
5199 signals
[MESSAGE_SELECTED
], 0,
5200 message_list
->cursor_uid
);
5204 signals
[MESSAGE_SELECTED
], 0,
5208 message_list
->idle_id
= 0;
5213 on_cursor_activated_cmd (ETree
*tree
,
5218 MessageList
*message_list
= MESSAGE_LIST (user_data
);
5219 const gchar
*new_uid
;
5221 if (node
== NULL
|| G_NODE_IS_ROOT (node
))
5224 new_uid
= get_message_uid (message_list
, node
);
5226 /* Do not check the cursor_uid and the new_uid values, because the
5227 * selected item (set in on_selection_changed_cmd) can be different
5228 * from the one with a cursor (when selecting with Ctrl, for example).
5229 * This has a little side-effect, when keeping list it that state,
5230 * then changing folders forth and back will select and move cursor
5231 * to that selected item. Does anybody consider it as a bug? */
5232 if ((message_list
->cursor_uid
== NULL
&& new_uid
== NULL
)
5233 || (message_list
->last_sel_single
&& message_list
->cursor_uid
!= NULL
&& new_uid
!= NULL
))
5236 g_free (message_list
->cursor_uid
);
5237 message_list
->cursor_uid
= g_strdup (new_uid
);
5239 if (!message_list
->idle_id
) {
5240 message_list
->idle_id
=
5242 G_PRIORITY_LOW
, on_cursor_activated_idle
,
5243 message_list
, NULL
);
5248 on_selection_changed_cmd (ETree
*tree
,
5249 MessageList
*message_list
)
5251 GPtrArray
*uids
= NULL
;
5252 const gchar
*newuid
;
5253 guint selected_count
;
5256 selected_count
= message_list_selected_count (message_list
);
5257 if (selected_count
== 1) {
5258 uids
= message_list_get_selected (message_list
);
5261 newuid
= g_ptr_array_index (uids
, 0);
5264 } else if ((cursor
= e_tree_get_cursor (tree
)))
5265 newuid
= (gchar
*) camel_message_info_get_uid (cursor
->data
);
5269 /* If the selection isn't empty, then we ignore the no-uid check, since this event
5270 * is also used for other updating. If it is empty, it might just be a setup event
5271 * from etree which we do need to ignore */
5272 if ((newuid
== NULL
&& message_list
->cursor_uid
== NULL
&& selected_count
== 0) ||
5273 (message_list
->last_sel_single
&& selected_count
== 1 && message_list
->cursor_uid
!= NULL
&& (newuid
== NULL
|| !strcmp (message_list
->cursor_uid
, newuid
)))) {
5276 g_free (message_list
->cursor_uid
);
5277 message_list
->cursor_uid
= g_strdup (newuid
);
5278 if (message_list
->idle_id
== 0)
5279 message_list
->idle_id
= g_idle_add_full (
5281 on_cursor_activated_idle
,
5282 message_list
, NULL
);
5285 message_list
->last_sel_single
= selected_count
== 1;
5288 g_ptr_array_unref (uids
);
5292 on_click (ETree
*tree
,
5299 CamelFolder
*folder
;
5300 CamelMessageInfo
*info
;
5301 gboolean folder_is_trash
;
5305 if (col
== COL_MESSAGE_STATUS
)
5306 flag
= CAMEL_MESSAGE_SEEN
;
5307 else if (col
== COL_FLAGGED
)
5308 flag
= CAMEL_MESSAGE_FLAGGED
;
5309 else if (col
!= COL_FOLLOWUP_FLAG_STATUS
)
5312 if (!(info
= get_message_info (list
, node
)))
5315 folder
= message_list_ref_folder (list
);
5316 g_return_val_if_fail (folder
!= NULL
, FALSE
);
5318 if (col
== COL_FOLLOWUP_FLAG_STATUS
) {
5319 const gchar
*tag
, *cmp
;
5321 tag
= camel_message_info_get_user_tag (info
, "follow-up");
5322 cmp
= camel_message_info_get_user_tag (info
, "completed-on");
5323 if (tag
&& tag
[0]) {
5324 if (cmp
&& cmp
[0]) {
5325 camel_message_info_set_user_tag (info
, "follow-up", NULL
);
5326 camel_message_info_set_user_tag (info
, "due-by", NULL
);
5327 camel_message_info_set_user_tag (info
, "completed-on", NULL
);
5331 text
= camel_header_format_date (time (NULL
), 0);
5332 camel_message_info_set_user_tag (info
, "completed-on", text
);
5336 /* default follow-up flag name to use when clicked in the message list column */
5337 camel_message_info_set_user_tag (info
, "follow-up", _("Follow-up"));
5338 camel_message_info_set_user_tag (info
, "completed-on", NULL
);
5341 g_object_unref (folder
);
5346 flags
= camel_message_info_get_flags (info
);
5349 ((camel_folder_get_flags (folder
) & CAMEL_FOLDER_IS_TRASH
) != 0);
5351 /* If a message was marked as deleted and the user flags it as
5352 * important or unread in a non-Trash folder, then undelete the
5353 * message. We avoid automatically undeleting messages while
5354 * viewing a Trash folder because it would cause the message to
5355 * suddenly disappear from the message list, which is confusing
5356 * and alarming to the user. */
5357 if (!folder_is_trash
&& flags
& CAMEL_MESSAGE_DELETED
) {
5358 if (col
== COL_FLAGGED
&& !(flags
& CAMEL_MESSAGE_FLAGGED
))
5359 flag
|= CAMEL_MESSAGE_DELETED
;
5361 if (col
== COL_MESSAGE_STATUS
&& (flags
& CAMEL_MESSAGE_SEEN
))
5362 flag
|= CAMEL_MESSAGE_DELETED
;
5365 camel_message_info_set_flags (info
, flag
, ~flags
);
5367 /* Notify the folder tree model that the user has marked a message
5368 * as unread so it doesn't mistake the event as new mail arriving. */
5369 if (col
== COL_MESSAGE_STATUS
&& (flags
& CAMEL_MESSAGE_SEEN
)) {
5370 EMFolderTreeModel
*model
;
5372 model
= em_folder_tree_model_get_default ();
5373 em_folder_tree_model_user_marked_unread (model
, folder
, 1);
5376 if (flag
== CAMEL_MESSAGE_SEEN
&& list
->seen_id
&&
5377 g_strcmp0 (list
->cursor_uid
, camel_message_info_get_uid (info
)) == 0) {
5378 g_source_remove (list
->seen_id
);
5382 g_object_unref (folder
);
5387 struct _ml_selected_data
{
5388 MessageList
*message_list
;
5393 ml_getselected_cb (GNode
*node
,
5396 struct _ml_selected_data
*data
= user_data
;
5399 if (G_NODE_IS_ROOT (node
))
5402 uid
= get_message_uid (data
->message_list
, node
);
5403 g_return_if_fail (uid
!= NULL
);
5404 g_ptr_array_add (data
->uids
, g_strdup (uid
));
5408 message_list_get_selected (MessageList
*message_list
)
5410 CamelFolder
*folder
;
5411 ESelectionModel
*selection
;
5413 struct _ml_selected_data data
= {
5418 g_return_val_if_fail (IS_MESSAGE_LIST (message_list
), NULL
);
5420 data
.uids
= g_ptr_array_new ();
5421 g_ptr_array_set_free_func (data
.uids
, (GDestroyNotify
) g_free
);
5423 selection
= e_tree_get_selection_model (E_TREE (message_list
));
5425 e_tree_selection_model_foreach (
5426 E_TREE_SELECTION_MODEL (selection
),
5427 (ETreeForeachFunc
) ml_getselected_cb
, &data
);
5429 folder
= message_list_ref_folder (message_list
);
5431 if (folder
!= NULL
&& data
.uids
->len
> 0)
5432 camel_folder_sort_uids (folder
, data
.uids
);
5434 g_clear_object (&folder
);
5440 message_list_set_selected (MessageList
*message_list
,
5444 ETreeSelectionModel
*etsm
;
5448 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
5450 paths
= g_ptr_array_new ();
5451 etsm
= (ETreeSelectionModel
*)
5452 e_tree_get_selection_model (E_TREE (message_list
));
5453 for (i
= 0; i
< uids
->len
; i
++) {
5454 node
= g_hash_table_lookup (
5455 message_list
->uid_nodemap
, uids
->pdata
[i
]);
5457 g_ptr_array_add (paths
, node
);
5460 e_tree_selection_model_select_paths (etsm
, paths
);
5461 g_ptr_array_free (paths
, TRUE
);
5464 struct ml_sort_uids_data
{
5470 ml_sort_uids_cb (gconstpointer a
,
5473 struct ml_sort_uids_data
* const *pdataA
= a
;
5474 struct ml_sort_uids_data
* const *pdataB
= b
;
5476 return (* pdataA
)->row
- (* pdataB
)->row
;
5480 message_list_sort_uids (MessageList
*message_list
,
5483 struct ml_sort_uids_data
*data
;
5486 ETreeTableAdapter
*adapter
;
5489 g_return_if_fail (message_list
!= NULL
);
5490 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
5491 g_return_if_fail (uids
!= NULL
);
5496 adapter
= e_tree_get_table_adapter (E_TREE (message_list
));
5498 array
= g_ptr_array_new_full (uids
->len
, g_free
);
5500 for (ii
= 0; ii
< uids
->len
; ii
++) {
5501 data
= g_new0 (struct ml_sort_uids_data
, 1);
5502 data
->uid
= g_ptr_array_index (uids
, ii
);
5504 node
= g_hash_table_lookup (message_list
->uid_nodemap
, data
->uid
);
5506 data
->row
= e_tree_table_adapter_row_of_node (adapter
, node
);
5510 g_ptr_array_add (array
, data
);
5513 g_ptr_array_sort (array
, ml_sort_uids_cb
);
5515 for (ii
= 0; ii
< uids
->len
; ii
++) {
5516 data
= g_ptr_array_index (array
, ii
);
5518 uids
->pdata
[ii
] = data
->uid
;
5521 g_ptr_array_free (array
, TRUE
);
5524 struct ml_count_data
{
5525 MessageList
*message_list
;
5530 ml_getcount_cb (GNode
*node
,
5533 struct ml_count_data
*data
= user_data
;
5535 if (!G_NODE_IS_ROOT (node
))
5540 message_list_count (MessageList
*message_list
)
5542 struct ml_count_data data
= { message_list
, 0 };
5544 g_return_val_if_fail (IS_MESSAGE_LIST (message_list
), 0);
5546 e_tree_path_foreach (
5547 E_TREE (message_list
),
5548 (ETreeForeachFunc
) ml_getcount_cb
, &data
);
5554 message_list_selected_count (MessageList
*message_list
)
5556 ESelectionModel
*selection
;
5558 g_return_val_if_fail (IS_MESSAGE_LIST (message_list
), 0);
5560 selection
= e_tree_get_selection_model (E_TREE (message_list
));
5561 return e_selection_model_selected_count (selection
);
5565 message_list_freeze (MessageList
*message_list
)
5567 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
5568 message_list
->frozen
++;
5572 message_list_thaw (MessageList
*message_list
)
5574 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
5575 g_return_if_fail (message_list
->frozen
!= 0);
5577 message_list
->frozen
--;
5578 if (message_list
->frozen
== 0 && message_list
->priv
->thaw_needs_regen
) {
5579 const gchar
*search
;
5581 if (message_list
->frozen_search
!= NULL
)
5582 search
= message_list
->frozen_search
;
5586 mail_regen_list (message_list
, search
, FALSE
);
5588 g_free (message_list
->frozen_search
);
5589 message_list
->frozen_search
= NULL
;
5590 message_list
->priv
->thaw_needs_regen
= FALSE
;
5594 /* set whether we are in threaded view or flat view */
5596 message_list_set_threaded_expand_all (MessageList
*message_list
)
5598 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
5600 if (message_list_get_group_by_threads (message_list
)) {
5601 message_list
->expand_all
= 1;
5603 if (message_list
->frozen
== 0)
5604 mail_regen_list (message_list
, NULL
, FALSE
);
5606 message_list
->priv
->thaw_needs_regen
= TRUE
;
5611 message_list_set_threaded_collapse_all (MessageList
*message_list
)
5613 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
5615 if (message_list_get_group_by_threads (message_list
)) {
5616 message_list
->collapse_all
= 1;
5618 if (message_list
->frozen
== 0)
5619 mail_regen_list (message_list
, NULL
, FALSE
);
5621 message_list
->priv
->thaw_needs_regen
= TRUE
;
5626 message_list_set_search (MessageList
*message_list
,
5627 const gchar
*search
)
5629 RegenData
*current_regen_data
;
5631 g_return_if_fail (IS_MESSAGE_LIST (message_list
));
5633 current_regen_data
= message_list_ref_regen_data (message_list
);
5635 if (!current_regen_data
&& (search
== NULL
|| search
[0] == '\0'))
5636 if (message_list
->search
== NULL
|| message_list
->search
[0] == '\0')
5639 if (!current_regen_data
&& search
!= NULL
&& message_list
->search
!= NULL
&&
5640 strcmp (search
, message_list
->search
) == 0) {
5644 if (current_regen_data
)
5645 regen_data_unref (current_regen_data
);
5647 /* Invalidate the thread tree. */
5648 message_list_set_thread_tree (message_list
, NULL
);
5650 if (message_list
->frozen
== 0)
5651 mail_regen_list (message_list
, search
? search
: "", FALSE
);
5653 g_free (message_list
->frozen_search
);
5654 message_list
->frozen_search
= g_strdup (search
);
5655 message_list
->priv
->thaw_needs_regen
= TRUE
;
5659 struct sort_column_data
{
5661 GtkSortType sort_type
;
5664 struct sort_message_info_data
{
5665 CamelMessageInfo
*mi
;
5666 GPtrArray
*values
; /* read values so far, in order of sort_array_data::sort_columns */
5669 struct sort_array_data
{
5670 MessageList
*message_list
;
5671 CamelFolder
*folder
;
5672 GPtrArray
*sort_columns
; /* struct sort_column_data in order of sorting */
5673 GHashTable
*message_infos
; /* uid -> struct sort_message_info_data */
5675 GCancellable
*cancellable
;
5679 cmp_array_uids (gconstpointer a
,
5683 const gchar
*uid1
= *(const gchar
**) a
;
5684 const gchar
*uid2
= *(const gchar
**) b
;
5685 struct sort_array_data
*sort_data
= user_data
;
5687 struct sort_message_info_data
*md1
, *md2
;
5689 g_return_val_if_fail (sort_data
!= NULL
, 0);
5691 md1
= g_hash_table_lookup (sort_data
->message_infos
, uid1
);
5692 md2
= g_hash_table_lookup (sort_data
->message_infos
, uid2
);
5694 g_return_val_if_fail (md1
!= NULL
, 0);
5695 g_return_val_if_fail (md1
->mi
!= NULL
, 0);
5696 g_return_val_if_fail (md2
!= NULL
, 0);
5697 g_return_val_if_fail (md2
->mi
!= NULL
, 0);
5699 if (g_cancellable_is_cancelled (sort_data
->cancellable
))
5704 && i
< sort_data
->sort_columns
->len
5705 && !g_cancellable_is_cancelled (sort_data
->cancellable
);
5708 struct sort_column_data
*scol
= g_ptr_array_index (sort_data
->sort_columns
, i
);
5710 if (md1
->values
->len
<= i
) {
5711 camel_message_info_property_lock (md1
->mi
);
5712 v1
= ml_tree_value_at_ex (
5714 scol
->col
->spec
->compare_col
,
5715 md1
->mi
, sort_data
->message_list
);
5716 camel_message_info_property_unlock (md1
->mi
);
5717 g_ptr_array_add (md1
->values
, v1
);
5719 v1
= g_ptr_array_index (md1
->values
, i
);
5722 if (md2
->values
->len
<= i
) {
5723 camel_message_info_property_lock (md2
->mi
);
5724 v2
= ml_tree_value_at_ex (
5726 scol
->col
->spec
->compare_col
,
5727 md2
->mi
, sort_data
->message_list
);
5728 camel_message_info_property_unlock (md2
->mi
);
5730 g_ptr_array_add (md2
->values
, v2
);
5732 v2
= g_ptr_array_index (md2
->values
, i
);
5735 if (v1
!= NULL
&& v2
!= NULL
) {
5736 res
= (*scol
->col
->compare
) (v1
, v2
, sort_data
->cmp_cache
);
5737 } else if (v1
!= NULL
|| v2
!= NULL
) {
5738 res
= v1
== NULL
? -1 : 1;
5741 if (scol
->sort_type
== GTK_SORT_DESCENDING
)
5746 res
= camel_folder_cmp_uids (sort_data
->folder
, uid1
, uid2
);
5752 free_message_info_data (gpointer uid
,
5753 struct sort_message_info_data
*data
,
5754 struct sort_array_data
*sort_data
)
5759 for (ii
= 0; ii
< sort_data
->sort_columns
->len
&& ii
< data
->values
->len
; ii
++) {
5760 struct sort_column_data
*scol
= g_ptr_array_index (sort_data
->sort_columns
, ii
);
5762 message_list_free_value ((ETreeModel
*) sort_data
->message_list
,
5763 scol
->col
->spec
->compare_col
,
5764 g_ptr_array_index (data
->values
, ii
));
5767 g_ptr_array_free (data
->values
, TRUE
);
5770 g_clear_object (&data
->mi
);
5775 ml_sort_uids_by_tree (MessageList
*message_list
,
5777 GCancellable
*cancellable
)
5779 ETreeTableAdapter
*adapter
;
5780 ETableSortInfo
*sort_info
;
5781 ETableHeader
*full_header
;
5782 CamelFolder
*folder
;
5783 struct sort_array_data sort_data
;
5786 if (g_cancellable_is_cancelled (cancellable
))
5789 g_return_if_fail (uids
!= NULL
);
5791 folder
= message_list_ref_folder (message_list
);
5792 g_return_if_fail (folder
!= NULL
);
5794 adapter
= e_tree_get_table_adapter (E_TREE (message_list
));
5795 g_return_if_fail (adapter
!= NULL
);
5797 sort_info
= e_tree_table_adapter_get_sort_info (adapter
);
5798 full_header
= e_tree_table_adapter_get_header (adapter
);
5800 if (!sort_info
|| uids
->len
== 0 || !full_header
|| e_table_sort_info_sorting_get_count (sort_info
) == 0) {
5801 camel_folder_sort_uids (folder
, uids
);
5802 g_object_unref (folder
);
5806 len
= e_table_sort_info_sorting_get_count (sort_info
);
5808 sort_data
.message_list
= message_list
;
5809 sort_data
.folder
= folder
;
5810 sort_data
.sort_columns
= g_ptr_array_sized_new (len
);
5811 sort_data
.message_infos
= g_hash_table_new (g_str_hash
, g_str_equal
);
5812 sort_data
.cmp_cache
= e_table_sorting_utils_create_cmp_cache ();
5813 sort_data
.cancellable
= cancellable
;
5817 && !g_cancellable_is_cancelled (cancellable
);
5819 ETableColumnSpecification
*spec
;
5820 struct sort_column_data
*data
;
5822 data
= g_new0 (struct sort_column_data
, 1);
5824 spec
= e_table_sort_info_sorting_get_nth (
5825 sort_info
, i
, &data
->sort_type
);
5827 data
->col
= e_table_header_get_column_by_spec (full_header
, spec
);
5828 if (data
->col
== NULL
) {
5829 gint last
= e_table_header_count (full_header
) - 1;
5830 data
->col
= e_table_header_get_column (full_header
, last
);
5833 g_ptr_array_add (sort_data
.sort_columns
, data
);
5836 camel_folder_summary_prepare_fetch_all (camel_folder_get_folder_summary (folder
), NULL
);
5840 && !g_cancellable_is_cancelled (cancellable
);
5843 CamelMessageInfo
*mi
;
5844 struct sort_message_info_data
*md
;
5846 uid
= g_ptr_array_index (uids
, i
);
5847 mi
= camel_folder_get_message_info (folder
, uid
);
5850 "%s: Cannot find uid '%s' in folder '%s'",
5852 camel_folder_get_full_name (folder
));
5856 md
= g_new0 (struct sort_message_info_data
, 1);
5858 md
->values
= g_ptr_array_sized_new (len
);
5860 g_hash_table_insert (sort_data
.message_infos
, uid
, md
);
5863 if (!g_cancellable_is_cancelled (cancellable
))
5871 camel_folder_summary_unlock (camel_folder_get_folder_summary (folder
));
5873 /* FIXME Teach the hash table to destroy its own data. */
5874 g_hash_table_foreach (
5875 sort_data
.message_infos
,
5876 (GHFunc
) free_message_info_data
,
5878 g_hash_table_destroy (sort_data
.message_infos
);
5880 g_ptr_array_foreach (sort_data
.sort_columns
, (GFunc
) g_free
, NULL
);
5881 g_ptr_array_free (sort_data
.sort_columns
, TRUE
);
5883 e_table_sorting_utils_free_cmp_cache (sort_data
.cmp_cache
);
5885 g_object_unref (folder
);
5889 message_list_regen_tweak_search_results (MessageList
*message_list
,
5890 GPtrArray
*search_results
,
5891 CamelFolder
*folder
,
5892 gboolean folder_changed
,
5893 gboolean show_deleted
,
5896 CamelMessageInfo
*info
;
5897 CamelMessageFlags flags
;
5899 gboolean needs_tweaking
;
5900 gboolean uid_is_deleted
;
5901 gboolean uid_is_junk
;
5905 /* If we're responding to a "folder-changed" signal, then the
5906 * displayed message may not be included in the search results.
5907 * Include the displayed message anyway so it doesn't suddenly
5908 * disappear while the user is reading it. */
5910 ((folder_changed
|| message_list
->just_set_folder
) && message_list
->cursor_uid
!= NULL
);
5912 if (!needs_tweaking
)
5915 uid
= message_list
->cursor_uid
;
5917 /* Scan the search results for a particular UID.
5918 * If found then the results don't need tweaked. */
5919 for (ii
= 0; ii
< search_results
->len
; ii
++) {
5920 if (g_str_equal (uid
, search_results
->pdata
[ii
]))
5924 info
= camel_folder_get_message_info (folder
, uid
);
5926 /* XXX Should we emit a runtime warning here? */
5930 flags
= camel_message_info_get_flags (info
);
5931 uid_is_deleted
= ((flags
& CAMEL_MESSAGE_DELETED
) != 0);
5932 uid_is_junk
= ((flags
& CAMEL_MESSAGE_JUNK
) != 0);
5934 if (!folder_store_supports_vjunk_folder (folder
))
5935 uid_is_junk
= FALSE
;
5938 (!uid_is_junk
|| show_junk
) &&
5939 (!uid_is_deleted
|| show_deleted
);
5944 (gpointer
) camel_pstring_strdup (uid
));
5946 g_clear_object (&info
);
5950 message_list_regen_thread (GSimpleAsyncResult
*simple
,
5951 GObject
*source_object
,
5952 GCancellable
*cancellable
)
5954 MessageList
*message_list
;
5955 RegenData
*regen_data
;
5956 GPtrArray
*uids
, *searchuids
= NULL
;
5957 CamelMessageInfo
*info
;
5958 CamelFolder
*folder
;
5962 gboolean hide_deleted
;
5964 GError
*local_error
= NULL
;
5966 message_list
= MESSAGE_LIST (source_object
);
5967 regen_data
= g_simple_async_result_get_op_res_gpointer (simple
);
5969 if (g_cancellable_is_cancelled (cancellable
))
5972 /* Just for convenience. */
5973 folder
= g_object_ref (regen_data
->folder
);
5975 hide_junk
= message_list_get_hide_junk (message_list
, folder
);
5976 hide_deleted
= message_list_get_hide_deleted (message_list
, folder
);
5978 tree
= E_TREE (message_list
);
5979 cursor
= e_tree_get_cursor (tree
);
5981 regen_data
->last_row
=
5982 e_tree_table_adapter_row_of_node (
5983 e_tree_get_table_adapter (tree
), cursor
);
5985 /* Construct the search expression. */
5987 expr
= g_string_new ("");
5989 if (hide_deleted
&& hide_junk
) {
5990 g_string_append_printf (
5991 expr
, "(match-all (and %s %s))",
5992 EXCLUDE_DELETED_MESSAGES_EXPR
,
5993 EXCLUDE_JUNK_MESSAGES_EXPR
);
5994 } else if (hide_deleted
) {
5995 g_string_append_printf (
5996 expr
, "(match-all %s)",
5997 EXCLUDE_DELETED_MESSAGES_EXPR
);
5998 } else if (hide_junk
) {
5999 g_string_append_printf (
6000 expr
, "(match-all %s)",
6001 EXCLUDE_JUNK_MESSAGES_EXPR
);
6004 if (regen_data
->search
!= NULL
) {
6005 if (expr
->len
== 0) {
6006 g_string_assign (expr
, regen_data
->search
);
6008 g_string_prepend (expr
, "(and ");
6009 g_string_append_c (expr
, ' ');
6010 g_string_append (expr
, regen_data
->search
);
6011 g_string_append_c (expr
, ')');
6015 /* Execute the search. */
6017 if (expr
->len
== 0) {
6018 uids
= camel_folder_get_uids (folder
);
6019 dd (g_print ("%s: got %d uids in folder %p (%s : %s)\n", G_STRFUNC
, uids
? uids
->len
: -1, folder
,
6020 camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder
))),
6021 camel_folder_get_full_name (folder
)));
6023 uids
= camel_folder_search_by_expression (
6024 folder
, expr
->str
, cancellable
, &local_error
);
6026 dd (g_print ("%s: got %d uids in folder %p (%s : %s) for expression:---%s---\n", G_STRFUNC
,
6027 uids
? uids
->len
: -1, folder
,
6028 camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder
))),
6029 camel_folder_get_full_name (folder
), expr
->str
));
6031 /* XXX This indicates we need to use a different
6032 * "free UID" function for some dumb reason. */
6036 message_list_regen_tweak_search_results (
6039 regen_data
->folder_changed
,
6043 dd (g_print (" %s: got %d uids in folder %p (%s : %s) after tweak, hide_deleted:%d, hide_junk:%d\n", G_STRFUNC
,
6044 uids
? uids
->len
: -1, folder
,
6045 camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder
))),
6046 camel_folder_get_full_name (folder
), hide_deleted
, hide_junk
));
6050 g_string_free (expr
, TRUE
);
6052 /* Handle search error or cancellation. */
6054 if (local_error
== NULL
) {
6055 /* coverity[unchecked_value] */
6056 g_cancellable_set_error_if_cancelled (
6057 cancellable
, &local_error
);
6060 if (local_error
!= NULL
) {
6061 g_simple_async_result_take_error (simple
, local_error
);
6065 /* XXX This check might not be necessary. A successfully completed
6066 * search with no results should return an empty UID array, but
6067 * still need to verify that. */
6071 /* update/build a new tree */
6072 if (regen_data
->group_by_threads
) {
6073 CamelFolderThread
*thread_tree
;
6075 ml_sort_uids_by_tree (message_list
, uids
, cancellable
);
6077 thread_tree
= message_list_ref_thread_tree (message_list
);
6079 if (thread_tree
!= NULL
) {
6080 /* Make sure multiple threads will not access the same
6081 CamelFolderThread structure at the same time */
6082 g_mutex_lock (&message_list
->priv
->thread_tree_lock
);
6083 camel_folder_thread_messages_apply (thread_tree
, uids
);
6084 g_mutex_unlock (&message_list
->priv
->thread_tree_lock
);
6086 thread_tree
= camel_folder_thread_messages_new (
6087 folder
, uids
, regen_data
->thread_subject
);
6089 /* We will build the ETreeModel content from this
6090 * CamelFolderThread during regen post-processing.
6092 * We're committed at this point so keep our own
6093 * reference in case the MessageList's reference
6094 * gets invalidated before regen post-processing. */
6095 regen_data
->thread_tree
= thread_tree
;
6100 camel_folder_sort_uids (folder
, uids
);
6101 regen_data
->summary
= g_ptr_array_new ();
6103 camel_folder_summary_prepare_fetch_all (camel_folder_get_folder_summary (folder
), NULL
);
6105 for (ii
= 0; ii
< uids
->len
; ii
++) {
6108 uid
= g_ptr_array_index (uids
, ii
);
6109 info
= camel_folder_get_message_info (folder
, uid
);
6111 g_ptr_array_add (regen_data
->summary
, info
);
6116 if (searchuids
!= NULL
)
6117 camel_folder_search_free (folder
, searchuids
);
6118 else if (uids
!= NULL
)
6119 camel_folder_free_uids (folder
, uids
);
6121 g_object_unref (folder
);
6125 message_list_regen_done_cb (GObject
*source_object
,
6126 GAsyncResult
*result
,
6129 MessageList
*message_list
;
6130 GSimpleAsyncResult
*simple
;
6131 RegenData
*regen_data
;
6132 EActivity
*activity
;
6134 ETreeTableAdapter
*adapter
;
6135 gboolean was_searching
, is_searching
;
6137 GError
*local_error
= NULL
;
6139 message_list
= MESSAGE_LIST (source_object
);
6140 simple
= G_SIMPLE_ASYNC_RESULT (result
);
6141 regen_data
= g_simple_async_result_get_op_res_gpointer (simple
);
6143 /* Withdraw our RegenData from the private struct, if it hasn't
6144 * already been replaced. We have exclusive access to it now. */
6145 g_mutex_lock (&message_list
->priv
->regen_lock
);
6146 if (message_list
->priv
->regen_data
== regen_data
) {
6147 regen_data_unref (message_list
->priv
->regen_data
);
6148 message_list
->priv
->regen_data
= NULL
;
6149 e_tree_set_info_message (E_TREE (message_list
), NULL
);
6151 g_mutex_unlock (&message_list
->priv
->regen_lock
);
6153 activity
= regen_data
->activity
;
6155 if (g_simple_async_result_propagate_error (simple
, &local_error
) &&
6156 e_activity_handle_cancellation (activity
, local_error
)) {
6157 g_error_free (local_error
);
6160 /* FIXME This should be handed off to an EAlertSink. */
6161 } else if (local_error
!= NULL
) {
6162 g_warning ("%s: %s", G_STRFUNC
, local_error
->message
);
6163 g_error_free (local_error
);
6167 e_activity_set_state (activity
, E_ACTIVITY_COMPLETED
);
6169 tree
= E_TREE (message_list
);
6170 adapter
= e_tree_get_table_adapter (tree
);
6172 /* Show the cursor unless we're responding to a
6173 * "folder-changed" signal from our CamelFolder. */
6174 if (!regen_data
->folder_changed
)
6175 e_tree_show_cursor_after_reflow (tree
);
6177 g_signal_handlers_block_by_func (
6178 adapter
, ml_tree_sorting_changed
, message_list
);
6180 was_searching
= message_list_is_searching (message_list
);
6182 g_free (message_list
->search
);
6183 message_list
->search
= g_strdup (regen_data
->search
);
6185 is_searching
= message_list_is_searching (message_list
);
6187 if (regen_data
->group_by_threads
) {
6188 ETableItem
*table_item
= e_tree_get_item (E_TREE (message_list
));
6189 GPtrArray
*selected
;
6190 gchar
*saveuid
= NULL
;
6191 gboolean forcing_expand_state
;
6193 forcing_expand_state
=
6194 message_list
->expand_all
||
6195 message_list
->collapse_all
;
6197 if (message_list
->just_set_folder
) {
6198 message_list
->just_set_folder
= FALSE
;
6199 if (regen_data
->expand_state
!= NULL
) {
6200 /* Load state from disk rather than use
6201 * the memory data when changing folders. */
6202 xmlFreeDoc (regen_data
->expand_state
);
6203 regen_data
->expand_state
= NULL
;
6207 if (forcing_expand_state
) {
6210 if (message_list
->expand_all
)
6211 state
= 1; /* force expand */
6213 state
= -1; /* force collapse */
6215 e_tree_table_adapter_force_expanded_state (
6219 if (message_list
->cursor_uid
!= NULL
)
6220 saveuid
= find_next_selectable (message_list
);
6222 selected
= message_list_get_selected (message_list
);
6224 /* Show the cursor unless we're responding to a
6225 * "folder-changed" signal from our CamelFolder. */
6228 regen_data
->thread_tree
,
6229 regen_data
->folder_changed
);
6231 message_list_set_thread_tree (
6232 message_list
, regen_data
->thread_tree
);
6234 if (forcing_expand_state
) {
6235 if (message_list
->priv
->folder
!= NULL
&& tree
!= NULL
)
6236 save_tree_state (message_list
, regen_data
->folder
);
6238 /* Disable forced expand/collapse state. */
6239 e_tree_table_adapter_force_expanded_state (adapter
, 0);
6240 } else if (was_searching
&& !is_searching
) {
6241 /* Load expand state from disk */
6247 /* Load expand state from the previous state or disk */
6251 regen_data
->expand_state
);
6254 message_list
->expand_all
= 0;
6255 message_list
->collapse_all
= 0;
6257 /* restore cursor position only after the expand state is restored,
6258 thus the row numbers will actually match their real rows in UI */
6260 e_table_item_freeze (table_item
);
6262 message_list_set_selected (message_list
, selected
);
6263 g_ptr_array_unref (selected
);
6265 /* Show the cursor unless we're responding to a
6266 * "folder-changed" signal from our CamelFolder. */
6267 if (regen_data
->folder_changed
&& table_item
!= NULL
)
6268 table_item
->queue_show_cursor
= FALSE
;
6270 e_table_item_thaw (table_item
);
6272 if ((!saveuid
|| !g_hash_table_lookup (message_list
->uid_nodemap
, saveuid
)) &&
6273 message_list
->cursor_uid
&& g_hash_table_lookup (message_list
->uid_nodemap
, message_list
->cursor_uid
)) {
6274 /* this makes sure a visible node is selected, like when
6275 * collapsing all nodes and a children had been selected
6278 saveuid
= g_strdup (message_list
->cursor_uid
);
6281 if (message_list_selected_count (message_list
) > 1) {
6283 } else if (saveuid
) {
6286 node
= g_hash_table_lookup (
6287 message_list
->uid_nodemap
, saveuid
);
6289 g_free (message_list
->cursor_uid
);
6290 message_list
->cursor_uid
= NULL
;
6293 signals
[MESSAGE_SELECTED
], 0, NULL
);
6296 GNode
*parent
= node
;
6298 while ((parent
= parent
->parent
) != NULL
) {
6299 if (!e_tree_table_adapter_node_is_expanded (adapter
, parent
))
6303 e_table_item_freeze (table_item
);
6305 e_tree_set_cursor (E_TREE (message_list
), node
);
6307 /* Show the cursor unless we're responding to a
6308 * "folder-changed" signal from our CamelFolder. */
6309 if (regen_data
->folder_changed
&& table_item
!= NULL
)
6310 table_item
->queue_show_cursor
= FALSE
;
6312 e_table_item_thaw (table_item
);
6315 } else if (message_list
->cursor_uid
&& !g_hash_table_lookup (message_list
->uid_nodemap
, message_list
->cursor_uid
)) {
6316 g_free (message_list
->cursor_uid
);
6317 message_list
->cursor_uid
= NULL
;
6320 signals
[MESSAGE_SELECTED
], 0, NULL
);
6325 regen_data
->summary
,
6326 regen_data
->folder_changed
);
6329 row_count
= e_table_model_row_count (E_TABLE_MODEL (adapter
));
6331 if (regen_data
->select_all
) {
6332 message_list_select_all (message_list
);
6334 } else if (regen_data
->select_uid
!= NULL
) {
6335 message_list_select_uid (
6337 regen_data
->select_uid
,
6338 regen_data
->select_use_fallback
);
6340 } else if (message_list
->cursor_uid
== NULL
&& regen_data
->last_row
!= -1) {
6341 if (regen_data
->last_row
>= row_count
)
6342 regen_data
->last_row
= row_count
;
6344 if (regen_data
->last_row
>= 0) {
6347 node
= e_tree_table_adapter_node_at_row (
6348 adapter
, regen_data
->last_row
);
6350 select_node (message_list
, node
);
6354 if (gtk_widget_get_visible (GTK_WIDGET (message_list
))) {
6355 const gchar
*info_message
;
6356 gboolean have_search_expr
;
6358 /* space is used to indicate no search too */
6360 (message_list
->search
!= NULL
) &&
6361 (*message_list
->search
!= '\0') &&
6362 (strcmp (message_list
->search
, " ") != 0);
6364 if (row_count
> 0) {
6365 info_message
= NULL
;
6366 } else if (have_search_expr
) {
6368 _("No message satisfies your search criteria. "
6369 "Change search criteria by selecting a new "
6370 "Show message filter from the drop down list "
6371 "above or by running a new search either by "
6372 "clearing it with Search→Clear menu item or "
6373 "by changing the query above.");
6376 _("There are no messages in this folder.");
6379 e_tree_set_info_message (tree
, info_message
);
6382 g_signal_handlers_unblock_by_func (
6383 adapter
, ml_tree_sorting_changed
, message_list
);
6387 signals
[MESSAGE_LIST_BUILT
], 0);
6389 message_list
->priv
->any_row_changed
= FALSE
;
6390 message_list
->just_set_folder
= FALSE
;
6394 message_list_regen_idle_cb (gpointer user_data
)
6396 GSimpleAsyncResult
*simple
;
6397 RegenData
*regen_data
;
6398 GCancellable
*cancellable
;
6399 MessageList
*message_list
;
6400 ETreeTableAdapter
*adapter
;
6404 simple
= G_SIMPLE_ASYNC_RESULT (user_data
);
6405 regen_data
= g_simple_async_result_get_op_res_gpointer (simple
);
6406 cancellable
= e_activity_get_cancellable (regen_data
->activity
);
6408 message_list
= regen_data
->message_list
;
6410 g_mutex_lock (&message_list
->priv
->regen_lock
);
6412 /* Capture MessageList state to use for this regen. */
6414 regen_data
->group_by_threads
=
6415 message_list_get_group_by_threads (message_list
);
6416 regen_data
->thread_subject
=
6417 message_list_get_thread_subject (message_list
);
6419 searching
= message_list_is_searching (message_list
);
6421 adapter
= e_tree_get_table_adapter (E_TREE (message_list
));
6422 row_count
= e_table_model_row_count (E_TABLE_MODEL (adapter
));
6424 if (row_count
<= 0) {
6425 if (gtk_widget_get_visible (GTK_WIDGET (message_list
))) {
6428 txt
= g_strdup_printf (
6429 "%s...", _("Generating message list"));
6430 e_tree_set_info_message (E_TREE (message_list
), txt
);
6434 } else if (regen_data
->group_by_threads
&&
6435 !message_list
->just_set_folder
&&
6437 if (message_list
->priv
->any_row_changed
) {
6438 /* Something changed. If it was an expand
6439 * state change, then save the expand state. */
6440 message_list_save_state (message_list
);
6442 /* Remember the expand state and restore it
6444 regen_data
->expand_state
=
6445 e_tree_table_adapter_save_expanded_state_xml (
6449 /* Remember the expand state and restore it after regen. */
6450 regen_data
->expand_state
= e_tree_table_adapter_save_expanded_state_xml (adapter
);
6453 message_list
->priv
->regen_idle_id
= 0;
6455 g_mutex_unlock (&message_list
->priv
->regen_lock
);
6457 if (g_cancellable_is_cancelled (cancellable
)) {
6458 g_simple_async_result_complete (simple
);
6460 g_simple_async_result_run_in_thread (
6462 message_list_regen_thread
,
6471 mail_regen_cancel (MessageList
*message_list
)
6473 RegenData
*regen_data
= NULL
;
6475 g_mutex_lock (&message_list
->priv
->regen_lock
);
6477 if (message_list
->priv
->regen_data
!= NULL
)
6478 regen_data
= regen_data_ref (message_list
->priv
->regen_data
);
6480 if (message_list
->priv
->regen_idle_id
> 0) {
6481 g_source_remove (message_list
->priv
->regen_idle_id
);
6482 message_list
->priv
->regen_idle_id
= 0;
6485 g_mutex_unlock (&message_list
->priv
->regen_lock
);
6487 /* Cancel outside the lock, since this will emit a signal. */
6488 if (regen_data
!= NULL
) {
6489 e_activity_cancel (regen_data
->activity
);
6490 regen_data_unref (regen_data
);
6495 mail_regen_list (MessageList
*message_list
,
6496 const gchar
*search
,
6497 gboolean folder_changed
)
6499 GSimpleAsyncResult
*simple
;
6500 GCancellable
*cancellable
;
6501 RegenData
*new_regen_data
;
6502 RegenData
*old_regen_data
;
6503 gchar
*prefixes
, *tmp_search_copy
= NULL
;
6506 old_regen_data
= message_list_ref_regen_data (message_list
);
6508 if (old_regen_data
&& old_regen_data
->folder
== message_list
->priv
->folder
) {
6509 tmp_search_copy
= g_strdup (old_regen_data
->search
);
6510 search
= tmp_search_copy
;
6512 tmp_search_copy
= g_strdup (message_list
->search
);
6513 search
= tmp_search_copy
;
6517 regen_data_unref (old_regen_data
);
6518 } else if (search
&& !*search
) {
6522 /* Report empty search as NULL, not as one/two-space string. */
6523 if (search
&& (strcmp (search
, " ") == 0 || strcmp (search
, " ") == 0))
6526 /* Can't list messages in a folder until we have a folder. */
6527 if (message_list
->priv
->folder
== NULL
) {
6528 g_free (message_list
->search
);
6529 message_list
->search
= g_strdup (search
);
6530 g_free (tmp_search_copy
);
6534 g_mutex_lock (&message_list
->priv
->re_prefixes_lock
);
6536 g_strfreev (message_list
->priv
->re_prefixes
);
6537 prefixes
= g_settings_get_string (message_list
->priv
->mail_settings
, "composer-localized-re");
6538 message_list
->priv
->re_prefixes
= g_strsplit (prefixes
? prefixes
: "", ",", -1);
6541 g_strfreev (message_list
->priv
->re_separators
);
6542 message_list
->priv
->re_separators
= g_settings_get_strv (message_list
->priv
->mail_settings
, "composer-localized-re-separators");
6544 if (message_list
->priv
->re_separators
&& !*message_list
->priv
->re_separators
) {
6545 g_strfreev (message_list
->priv
->re_separators
);
6546 message_list
->priv
->re_separators
= NULL
;
6549 g_mutex_unlock (&message_list
->priv
->re_prefixes_lock
);
6551 g_mutex_lock (&message_list
->priv
->regen_lock
);
6553 old_regen_data
= message_list
->priv
->regen_data
;
6555 /* If a regen is scheduled but not yet started, just
6556 * apply the argument values without cancelling it. */
6557 if (message_list
->priv
->regen_idle_id
> 0) {
6558 g_return_if_fail (old_regen_data
!= NULL
);
6560 if (g_strcmp0 (search
, old_regen_data
->search
) != 0) {
6561 g_free (old_regen_data
->search
);
6562 old_regen_data
->search
= g_strdup (search
);
6565 /* Only turn off the folder_changed flag, do not turn it on, because otherwise
6566 the view may not scroll to the cursor position, due to claiming that
6567 the regen was done for folder-changed signal, while the initial regen
6568 request would be due to change of the folder in the view (or other similar
6570 if (!folder_changed
)
6571 old_regen_data
->folder_changed
= folder_changed
;
6573 /* Avoid cancelling on the way out. */
6574 old_regen_data
= NULL
;
6579 cancellable
= g_cancellable_new ();
6581 new_regen_data
= regen_data_new (message_list
, cancellable
);
6582 new_regen_data
->search
= g_strdup (search
);
6583 new_regen_data
->folder_changed
= folder_changed
;
6585 /* We generate the message list content in a worker thread, and
6586 * then supply our own GAsyncReadyCallback to redraw the widget. */
6588 simple
= g_simple_async_result_new (
6589 G_OBJECT (message_list
),
6590 message_list_regen_done_cb
,
6591 NULL
, mail_regen_list
);
6593 g_simple_async_result_set_check_cancellable (simple
, cancellable
);
6595 g_simple_async_result_set_op_res_gpointer (
6597 regen_data_ref (new_regen_data
),
6598 (GDestroyNotify
) regen_data_unref
);
6600 /* Set the RegenData immediately, but start the actual regen
6601 * operation from an idle callback. That way the caller has
6602 * the remainder of this main loop iteration to make further
6603 * MessageList changes without triggering additional regens. */
6605 message_list
->priv
->regen_data
= regen_data_ref (new_regen_data
);
6607 message_list
->priv
->regen_idle_id
=
6609 G_PRIORITY_DEFAULT_IDLE
,
6610 message_list_regen_idle_cb
,
6611 g_object_ref (simple
),
6612 (GDestroyNotify
) g_object_unref
);
6614 g_object_unref (simple
);
6616 regen_data_unref (new_regen_data
);
6618 g_object_unref (cancellable
);
6621 g_mutex_unlock (&message_list
->priv
->regen_lock
);
6623 /* Cancel outside the lock, since this will emit a signal. */
6624 if (old_regen_data
!= NULL
) {
6625 e_activity_cancel (old_regen_data
->activity
);
6626 regen_data_unref (old_regen_data
);
6629 g_free (tmp_search_copy
);
6633 message_list_contains_uid (MessageList
*message_list
,
6636 g_return_val_if_fail (IS_MESSAGE_LIST (message_list
), FALSE
);
6638 if (!uid
|| !*uid
|| !message_list
->priv
->folder
)
6641 return g_hash_table_lookup (message_list
->uid_nodemap
, uid
) != NULL
;