[evolution] Bug 621839 - Improve auto-selection of messages



commit 8dc6fd1344965e5bd0940e99be3af9be26e3f9bb
Author: Matthew Barnes <mbarnes redhat com>
Date:   Thu Jun 17 00:12:04 2010 -0400

    Bug 621839 - Improve auto-selection of messages

 mail/e-mail-reader.c                |    2 +-
 mail/message-list.c                 |  163 ++++++++++++++++++++++++++++------
 mail/message-list.h                 |    4 +-
 modules/mail/e-mail-shell-content.c |   27 ++----
 4 files changed, 145 insertions(+), 51 deletions(-)
---
diff --git a/mail/e-mail-reader.c b/mail/e-mail-reader.c
index 9c300fc..502ee0d 100644
--- a/mail/e-mail-reader.c
+++ b/mail/e-mail-reader.c
@@ -2111,7 +2111,7 @@ mail_reader_set_message (EMailReader *reader,
 
 	message_list = e_mail_reader_get_message_list (reader);
 
-	message_list_select_uid (MESSAGE_LIST (message_list), uid);
+	message_list_select_uid (MESSAGE_LIST (message_list), uid, FALSE);
 }
 
 static void
diff --git a/mail/message-list.c b/mail/message-list.c
index 29776a2..af5cd4b 100644
--- a/mail/message-list.c
+++ b/mail/message-list.c
@@ -114,6 +114,12 @@ struct _MessageListPrivate {
 
 	GtkTargetList *copy_target_list;
 	GtkTargetList *paste_target_list;
+
+	/* This aids in automatic message selection. */
+	time_t latest_read_date;
+	const gchar *latest_read_uid;
+	time_t latest_unread_date;
+	const gchar *latest_unread_uid;
 };
 
 enum {
@@ -667,19 +673,47 @@ message_list_can_select(MessageList *ml, MessageListSelectDirection direction, g
  * Selects the message with the given UID.
  **/
 void
-message_list_select_uid (MessageList *message_list, const gchar *uid)
+message_list_select_uid (MessageList *message_list,
+                         const gchar *uid,
+                         gboolean with_fallback)
 {
-	ETreePath node;
+	MessageListPrivate *priv;
+	GHashTable *uid_nodemap;
+	ETreePath node = NULL;
+
+	g_return_if_fail (IS_MESSAGE_LIST (message_list));
+
+	priv = message_list->priv;
+	uid_nodemap = message_list->uid_nodemap;
 
 	if (message_list->folder == NULL)
 		return;
 
+	/* Try to find the requested message UID. */
+	if (uid != NULL)
+		node = g_hash_table_lookup (uid_nodemap, uid);
+
+	/* If we're busy or waiting to regenerate the message list, cache
+	 * the UID so we can try again when we're done.  Otherwise if the
+	 * requested message UID was not found and 'with_fallback' is set,
+	 * try a couple fallbacks:
+	 *
+	 * 1) Most recently received unread message in the list.
+	 * 2) Most recently received read message in the list.
+	 */
 	if (message_list->regen || message_list->regen_timeout_id) {
-		g_free(message_list->pending_select_uid);
-		message_list->pending_select_uid = g_strdup(uid);
+		g_free (message_list->pending_select_uid);
+		message_list->pending_select_uid = g_strdup (uid);
+		message_list->pending_select_fallback = with_fallback;
+	} else if (with_fallback) {
+		if (node == NULL && priv->latest_unread_uid != NULL)
+			node = g_hash_table_lookup (
+				uid_nodemap, priv->latest_unread_uid);
+		if (node == NULL && priv->latest_read_uid != NULL)
+			node = g_hash_table_lookup (
+				uid_nodemap, priv->latest_read_uid);
 	}
 
-	node = g_hash_table_lookup (message_list->uid_nodemap, uid);
 	if (node) {
 		ETree *tree;
 		ETreePath old_cur;
@@ -691,11 +725,17 @@ message_list_select_uid (MessageList *message_list, const gchar *uid)
 		e_tree_set_cursor (tree, node);
 
 		if (old_cur == node)
-			g_signal_emit (message_list, message_list_signals[MESSAGE_SELECTED], 0, message_list->cursor_uid);
+			g_signal_emit (
+				message_list,
+				message_list_signals[MESSAGE_SELECTED],
+				0, message_list->cursor_uid);
 	} else {
 		g_free (message_list->cursor_uid);
 		message_list->cursor_uid = NULL;
-		g_signal_emit (GTK_OBJECT (message_list), message_list_signals[MESSAGE_SELECTED], 0, NULL);
+		g_signal_emit (
+			message_list,
+			message_list_signals[MESSAGE_SELECTED],
+			0, NULL);
 	}
 }
 
@@ -2836,6 +2876,11 @@ clear_tree (MessageList *ml, gboolean tfree)
 	g_hash_table_destroy (ml->uid_nodemap);
 	ml->uid_nodemap = g_hash_table_new (g_str_hash, g_str_equal);
 
+	ml->priv->latest_read_date = 0;
+	ml->priv->latest_read_uid = NULL;
+	ml->priv->latest_unread_date = 0;
+	ml->priv->latest_unread_uid = NULL;
+
 	if (ml->tree_root) {
 		/* we should be frozen already */
 		e_tree_memory_node_remove (E_TREE_MEMORY(etm), ml->tree_root);
@@ -2970,6 +3015,70 @@ find_next_selectable (MessageList *ml)
 	return NULL;
 }
 
+static ETreePath *
+ml_uid_nodemap_insert (MessageList *message_list,
+                       CamelMessageInfo *info,
+                       ETreePath *parent_node,
+                       gint row)
+{
+	ETreeMemory *tree;
+	ETreePath *node;
+	const gchar *uid;
+	time_t date;
+	guint flags;
+
+	if (parent_node == NULL)
+		parent_node = message_list->tree_root;
+
+	tree = E_TREE_MEMORY (message_list->model);
+	node = e_tree_memory_node_insert (tree, parent_node, row, info);
+
+	uid = camel_message_info_uid (info);
+	flags = camel_message_info_flags (info);
+	date = camel_message_info_date_received (info);
+
+	camel_folder_ref_message_info (message_list->folder, info);
+	g_hash_table_insert (message_list->uid_nodemap, (gpointer) uid, node);
+
+	/* Track the latest seen and unseen messages shown, used in
+	 * fallback heuristics for automatic message selection. */
+	if (flags & CAMEL_MESSAGE_SEEN) {
+		if (date > message_list->priv->latest_read_date) {
+			message_list->priv->latest_read_date = date;
+			message_list->priv->latest_read_uid = uid;
+		}
+	} else {
+		if (date > message_list->priv->latest_unread_date) {
+			message_list->priv->latest_unread_date = date;
+			message_list->priv->latest_unread_uid = uid;
+		}
+	}
+
+	return node;
+}
+
+static void
+ml_uid_nodemap_remove (MessageList *message_list,
+                       CamelMessageInfo *info)
+{
+	const gchar *uid;
+
+	uid = camel_message_info_uid (info);
+
+	if (uid == message_list->priv->latest_read_uid) {
+		message_list->priv->latest_read_date = 0;
+		message_list->priv->latest_read_uid = NULL;
+	}
+
+	if (uid == message_list->priv->latest_unread_uid) {
+		message_list->priv->latest_unread_date = 0;
+		message_list->priv->latest_unread_uid = NULL;
+	}
+
+	g_hash_table_remove (message_list->uid_nodemap, uid);
+	camel_folder_free_message_info (message_list->folder, info);
+}
+
 /* only call if we have a tree model */
 /* builds the tree structure */
 
@@ -3067,7 +3176,6 @@ build_tree (MessageList *ml, CamelFolderThread *thread, CamelFolderChangeInfo *c
 static void
 build_subtree (MessageList *ml, ETreePath parent, CamelFolderThreadNode *c, gint *row)
 {
-	ETreeModel *tree = ml->model;
 	ETreePath node;
 
 	while (c) {
@@ -3078,9 +3186,8 @@ build_subtree (MessageList *ml, ETreePath parent, CamelFolderThreadNode *c, gint
 			continue;
 		}
 
-		node = e_tree_memory_node_insert(E_TREE_MEMORY(tree), parent, -1, (gpointer)c->message);
-		g_hash_table_insert(ml->uid_nodemap, (gpointer)camel_message_info_uid(c->message), node);
-		camel_folder_ref_message_info(ml->folder, (CamelMessageInfo *)c->message);
+		node = ml_uid_nodemap_insert (
+			ml, (CamelMessageInfo *) c->message, parent, -1);
 
 		if (c->child) {
 			build_subtree(ml, node, c->child, row);
@@ -3149,16 +3256,17 @@ tree_equal(ETreeModel *etm, ETreePath ap, CamelFolderThreadNode *bp)
 static void
 add_node_diff(MessageList *ml, ETreePath parent, ETreePath path, CamelFolderThreadNode *c, gint *row, gint myrow)
 {
-	ETreeModel *etm = ml->model;
+	CamelMessageInfo *info;
 	ETreePath node;
 
 	g_return_if_fail (c->message != NULL);
 
-	/* we just update the hashtable key, umm, does this leak the info on the message node? */
-	g_hash_table_remove(ml->uid_nodemap, camel_message_info_uid(c->message));
-	node = e_tree_memory_node_insert(E_TREE_MEMORY(etm), parent, myrow, (gpointer)c->message);
-	g_hash_table_insert(ml->uid_nodemap, (gpointer)camel_message_info_uid(c->message), node);
-	camel_folder_ref_message_info(ml->folder, (CamelMessageInfo *)c->message);
+	/* XXX Casting away constness. */
+	info = (CamelMessageInfo *) c->message;
+
+	/* we just update the hashtable key */
+	ml_uid_nodemap_remove (ml, info);
+	node = ml_uid_nodemap_insert (ml, info, parent, myrow);
 	(*row)++;
 
 	if (c->child) {
@@ -3192,8 +3300,7 @@ remove_node_diff(MessageList *ml, ETreePath node, gint depth)
 		e_tree_memory_node_remove(E_TREE_MEMORY(etm), node);
 
 	g_return_if_fail (info);
-	g_hash_table_remove(ml->uid_nodemap, camel_message_info_uid(info));
-	camel_folder_free_message_info(ml->folder, info);
+	ml_uid_nodemap_remove (ml, info);
 }
 
 /* applies a new tree structure to an existing tree, but only by changing things
@@ -3341,12 +3448,9 @@ build_flat (MessageList *ml, GPtrArray *summary, CamelFolderChangeInfo *changes)
 		e_tree_memory_freeze(E_TREE_MEMORY(etm));
 		clear_tree (ml, FALSE);
 		for (i = 0; i < summary->len; i++) {
-			ETreePath node;
 			CamelMessageInfo *info = summary->pdata[i];
 
-			node = e_tree_memory_node_insert(E_TREE_MEMORY(etm), ml->tree_root, -1, info);
-			g_hash_table_insert(ml->uid_nodemap, (gpointer)camel_message_info_uid(info), node);
-			camel_folder_ref_message_info(ml->folder, info);
+			ml_uid_nodemap_insert (ml, info, NULL, -1);
 		}
 		e_tree_memory_thaw(E_TREE_MEMORY(etm));
 #ifdef BROKEN_ETREE
@@ -3418,8 +3522,7 @@ build_flat_diff(MessageList *ml, CamelFolderChangeInfo *changes)
 		if (node) {
 			info = e_tree_memory_node_get_data(E_TREE_MEMORY(ml->model), node);
 			e_tree_memory_node_remove(E_TREE_MEMORY(ml->model), node);
-			camel_folder_free_message_info(ml->folder, info);
-			g_hash_table_remove(ml->uid_nodemap, changes->uid_removed->pdata[i]);
+			ml_uid_nodemap_remove (ml, info);
 		}
 	}
 
@@ -3429,8 +3532,7 @@ build_flat_diff(MessageList *ml, CamelFolderChangeInfo *changes)
 		info = camel_folder_get_message_info (ml->folder, changes->uid_added->pdata[i]);
 		if (info) {
 			d(printf(" %s\n", (gchar *)changes->uid_added->pdata[i]));
-			node = e_tree_memory_node_insert (E_TREE_MEMORY (ml->model), ml->tree_root, -1, info);
-			g_hash_table_insert (ml->uid_nodemap, (gpointer)camel_message_info_uid (info), node);
+			ml_uid_nodemap_insert (ml, info, NULL, -1);
 		}
 	}
 
@@ -4432,10 +4534,13 @@ regen_list_done (struct _regen_list_msg *m)
 	g_mutex_unlock (m->ml->regen_lock);
 
 	if (m->ml->regen == NULL && m->ml->pending_select_uid) {
-		gchar *uid = m->ml->pending_select_uid;
+		gchar *uid;
+		gboolean with_fallback;
 
+		uid = m->ml->pending_select_uid;
 		m->ml->pending_select_uid = NULL;
-		message_list_select_uid(m->ml, uid);
+		with_fallback = m->ml->pending_select_fallback;
+		message_list_select_uid (m->ml, uid, with_fallback);
 		g_free(uid);
 	} else if (m->ml->regen == NULL && m->ml->cursor_uid == NULL && m->last_row != -1) {
 		ETreeTableAdapter *etta = e_tree_get_table_adapter (tree);
diff --git a/mail/message-list.h b/mail/message-list.h
index 0b46e8e..bc2bc06 100644
--- a/mail/message-list.h
+++ b/mail/message-list.h
@@ -137,6 +137,7 @@ struct _MessageList {
 	GList *regen;
 	GMutex *regen_lock; /* when writing to the regen, guard with this lock too */
 	gchar *pending_select_uid; /* set if we were busy regnerating while we had a select come in */
+	gboolean pending_select_fallback;
 	guint regen_timeout_id;
 	gpointer regen_timeout_msg;
 
@@ -191,7 +192,8 @@ gboolean	message_list_can_select		(MessageList *message_list,
 						 guint32 flags,
 						 guint32 mask);
 void		message_list_select_uid		(MessageList *message_list,
-						 const gchar *uid);
+						 const gchar *uid,
+						 gboolean with_fallback);
 void		message_list_select_next_thread	(MessageList *message_list);
 void		message_list_select_all		(MessageList *message_list);
 void		message_list_select_thread	(MessageList *message_list);
diff --git a/modules/mail/e-mail-shell-content.c b/modules/mail/e-mail-shell-content.c
index 135888d..340e323 100644
--- a/modules/mail/e-mail-shell-content.c
+++ b/modules/mail/e-mail-shell-content.c
@@ -123,7 +123,6 @@ mail_shell_content_message_list_built_cb (EMailShellContent *mail_shell_content,
 	EShellWindow *shell_window;
 	EShellContent *shell_content;
 	GKeyFile *key_file;
-	gchar *uid;
 
 	g_signal_handler_disconnect (
 		message_list, priv->message_list_built_id);
@@ -136,40 +135,28 @@ mail_shell_content_message_list_built_cb (EMailShellContent *mail_shell_content,
 	key_file = e_shell_view_get_state_key_file (shell_view);
 
 	if (message_list->cursor_uid != NULL)
-		uid = NULL;
+		;  /* do nothing */
 
 	else if (message_list->folder_uri == NULL)
-		uid = NULL;
+		;  /* do nothing */
 
-	else if (e_shell_window_get_safe_mode (shell_window)) {
+	else if (e_shell_window_get_safe_mode (shell_window))
 		e_shell_window_set_safe_mode (shell_window, FALSE);
-		uid = NULL;
 
-	} else {
+	else {
 		const gchar *folder_uri;
 		const gchar *key;
 		gchar *group_name;
+		gchar *uid;
 
 		key = STATE_KEY_SELECTED_MESSAGE;
 		folder_uri = message_list->folder_uri;
 		group_name = g_strdup_printf ("Folder %s", folder_uri);
 		uid = g_key_file_get_string (key_file, group_name, key, NULL);
 		g_free (group_name);
-	}
-
-	if (uid != NULL) {
-		CamelFolder *folder;
-		CamelMessageInfo *info;
 
-		folder = message_list->folder;
-		info = camel_folder_get_message_info (folder, uid);
-		if (info != NULL) {
-			EMailReader *reader;
-
-			reader = E_MAIL_READER (mail_shell_content);
-			e_mail_reader_set_message (reader, uid);
-			camel_folder_free_message_info (folder, info);
-		}
+		/* Use selection fallbacks if UID is not found. */
+		message_list_select_uid (message_list, uid, TRUE);
 
 		g_free (uid);
 	}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]