Conversation view. Closes #3808
This commit is contained in:
parent
7bda84d331
commit
43a5b6152b
8 changed files with 233 additions and 168 deletions
|
|
@ -16,19 +16,21 @@ public class MainWindow : Gtk.Window {
|
|||
private MessageViewer message_viewer = new MessageViewer();
|
||||
private Geary.EngineAccount? account = null;
|
||||
private Geary.Folder? current_folder = null;
|
||||
private Geary.Conversations? current_conversations = null;
|
||||
private bool second_list_pass_required = false;
|
||||
private int window_width;
|
||||
private int window_height;
|
||||
private bool window_maximized;
|
||||
private Gtk.HPaned folder_paned = new Gtk.HPaned();
|
||||
private Gtk.HPaned messages_paned = new Gtk.HPaned();
|
||||
private Cancellable cancellable = new Cancellable();
|
||||
private Cancellable cancellable_folder = new Cancellable();
|
||||
private Cancellable cancellable_message = new Cancellable();
|
||||
|
||||
public MainWindow() {
|
||||
title = GearyApplication.NAME;
|
||||
|
||||
message_list_view = new MessageListView(message_list_store);
|
||||
message_list_view.message_selected.connect(on_message_selected);
|
||||
message_list_view.conversation_selected.connect(on_conversation_selected);
|
||||
|
||||
folder_list_view = new FolderListView(folder_list_store);
|
||||
folder_list_view.folder_selected.connect(on_folder_selected);
|
||||
|
|
@ -136,12 +138,12 @@ public class MainWindow : Gtk.Window {
|
|||
// message viewer
|
||||
Gtk.ScrolledWindow message_viewer_scrolled = new Gtk.ScrolledWindow(null, null);
|
||||
message_viewer_scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
|
||||
message_viewer_scrolled.add_with_viewport(message_viewer);
|
||||
message_viewer_scrolled.add(message_viewer);
|
||||
|
||||
// three-pane display: message list left of current message on bottom separated by
|
||||
// grippable
|
||||
messages_paned.pack1(message_list_scrolled, false, false);
|
||||
messages_paned.pack2(message_viewer_scrolled, true, false);
|
||||
messages_paned.pack2(message_viewer_scrolled, true, true);
|
||||
|
||||
// three-pane display: folder list on left and messages on right separated by grippable
|
||||
folder_paned.pack1(folder_list_scrolled, false, false);
|
||||
|
|
@ -166,61 +168,77 @@ public class MainWindow : Gtk.Window {
|
|||
}
|
||||
|
||||
private async void do_select_folder(Geary.Folder folder) throws Error {
|
||||
cancel();
|
||||
cancel_folder();
|
||||
message_list_store.clear();
|
||||
|
||||
if (current_folder != null) {
|
||||
current_folder.messages_appended.disconnect(on_folder_messages_appended);
|
||||
yield current_folder.close_async();
|
||||
}
|
||||
|
||||
current_folder = folder;
|
||||
current_folder.messages_appended.connect(on_folder_messages_appended);
|
||||
|
||||
yield current_folder.open_async(true, cancellable);
|
||||
yield current_folder.open_async(true, cancellable_folder);
|
||||
|
||||
current_conversations = new Geary.Conversations(current_folder,
|
||||
MessageListStore.REQUIRED_FIELDS);
|
||||
|
||||
current_conversations.monitor_new_messages(cancellable_folder);
|
||||
|
||||
current_conversations.scan_started.connect(on_scan_started);
|
||||
current_conversations.scan_error.connect(on_scan_error);
|
||||
current_conversations.scan_completed.connect(on_scan_completed);
|
||||
current_conversations.conversations_added.connect(on_conversations_added);
|
||||
current_conversations.conversation_appended.connect(on_conversation_appended);
|
||||
current_conversations.updated_placeholders.connect(on_updated_placeholders);
|
||||
|
||||
// Do a quick-list of the messages (which should return what's in the local store) if
|
||||
// supported by the Folder, followed by a complete list if needed
|
||||
second_list_pass_required =
|
||||
current_folder.get_supported_list_flags().is_all_set(Geary.Folder.ListFlags.FAST);
|
||||
current_folder.lazy_list_email(-1, FETCH_EMAIL_CHUNK_COUNT, MessageListStore.REQUIRED_FIELDS,
|
||||
current_folder.get_supported_list_flags() & Geary.Folder.ListFlags.FAST,
|
||||
on_list_email_ready, cancellable);
|
||||
|
||||
// Load all conversations from the DB.
|
||||
current_conversations.lazy_load(-1, -1, Geary.Folder.ListFlags.FAST, cancellable_folder);
|
||||
}
|
||||
|
||||
private void on_list_email_ready(Gee.List<Geary.Email>? email, Error? err) {
|
||||
if (email != null && email.size > 0) {
|
||||
int low = int.MAX;
|
||||
int high = int.MIN;
|
||||
foreach (Geary.Email envelope in email) {
|
||||
if (envelope.location.position < low)
|
||||
low = envelope.location.position;
|
||||
|
||||
if (envelope.location.position > high)
|
||||
high = envelope.location.position;
|
||||
|
||||
if (!message_list_store.has_envelope(envelope))
|
||||
message_list_store.append_envelope(envelope);
|
||||
}
|
||||
debug("Listed %d emails from position %d to %d", email.size, low, high);
|
||||
}
|
||||
public void on_scan_started(int low, int count) {
|
||||
debug("on scan started");
|
||||
}
|
||||
|
||||
public void on_scan_error(Error err) {
|
||||
debug("Scan error: %s", err.message);
|
||||
}
|
||||
|
||||
public void on_scan_completed() {
|
||||
debug("on scan completed");
|
||||
|
||||
if (err != null) {
|
||||
debug("Error while listing email: %s", err.message);
|
||||
|
||||
// TODO: Better error handling here
|
||||
return;
|
||||
do_fetch_previews.begin(cancellable_message);
|
||||
}
|
||||
|
||||
public void on_conversations_added(Gee.Collection<Geary.Conversation> conversations) {
|
||||
debug("on conversation added");
|
||||
foreach (Geary.Conversation c in conversations) {
|
||||
if (!message_list_store.has_conversation(c))
|
||||
message_list_store.append_conversation(c);
|
||||
}
|
||||
|
||||
// end of list, go get the previews for them
|
||||
if (email == null)
|
||||
do_fetch_previews.begin(cancellable);
|
||||
}
|
||||
|
||||
public void on_conversation_appended(Geary.Conversation conversation,
|
||||
Gee.Collection<Geary.Email> email) {
|
||||
message_list_store.update_conversation(conversation);
|
||||
}
|
||||
|
||||
public void on_updated_placeholders(Geary.Conversation conversation,
|
||||
Gee.Collection<Geary.Email> email) {
|
||||
message_list_store.update_conversation(conversation);
|
||||
}
|
||||
|
||||
private async void do_fetch_previews(Cancellable? cancellable) throws Error {
|
||||
int count = message_list_store.get_count();
|
||||
for (int ctr = 0; ctr < count; ctr++) {
|
||||
Geary.Email? email = message_list_store.get_message_at_index(ctr);
|
||||
Geary.Email? email = message_list_store.get_newest_message_at_index(ctr);
|
||||
if (email == null)
|
||||
continue;
|
||||
|
||||
Geary.Email? body = yield current_folder.fetch_email_async(email.id,
|
||||
Geary.Email.Field.HEADER | Geary.Email.Field.BODY | Geary.Email.Field.ENVELOPE |
|
||||
Geary.Email.Field.PROPERTIES, cancellable);
|
||||
|
|
@ -231,8 +249,8 @@ public class MainWindow : Gtk.Window {
|
|||
if (second_list_pass_required) {
|
||||
second_list_pass_required = false;
|
||||
debug("Doing second list pass now");
|
||||
current_folder.lazy_list_email(-1, FETCH_EMAIL_CHUNK_COUNT, MessageListStore.REQUIRED_FIELDS,
|
||||
Geary.Folder.ListFlags.NONE, on_list_email_ready, cancellable);
|
||||
current_conversations.lazy_load(-1, FETCH_EMAIL_CHUNK_COUNT, Geary.Folder.ListFlags.NONE,
|
||||
cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -244,25 +262,26 @@ public class MainWindow : Gtk.Window {
|
|||
}
|
||||
}
|
||||
|
||||
private void on_message_selected(Geary.Email? email) {
|
||||
if (email != null)
|
||||
do_select_message.begin(email, on_select_message_completed);
|
||||
private void on_conversation_selected(Geary.Conversation? conversation) {
|
||||
if (conversation != null)
|
||||
do_select_message.begin(conversation, on_select_message_completed);
|
||||
}
|
||||
|
||||
private async void do_select_message(Geary.Email email) throws Error {
|
||||
private async void do_select_message(Geary.Conversation conversation) throws Error {
|
||||
if (current_folder == null) {
|
||||
debug("Message %s selected with no folder selected", email.to_string());
|
||||
debug("Conversation selected with no folder selected");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
debug("Fetching email %s", email.to_string());
|
||||
|
||||
Geary.Email full_email = yield current_folder.fetch_email_async(email.id,
|
||||
MessageViewer.REQUIRED_FIELDS, cancellable);
|
||||
|
||||
cancel_message();
|
||||
message_viewer.clear();
|
||||
message_viewer.add_message(full_email);
|
||||
foreach (Geary.Email email in conversation.get_pool_sorted(compare_email)) {
|
||||
Geary.Email full_email = yield current_folder.fetch_email_async(email.id,
|
||||
MessageViewer.REQUIRED_FIELDS, cancellable_message);
|
||||
|
||||
message_viewer.add_message(full_email);
|
||||
}
|
||||
}
|
||||
|
||||
private void on_select_message_completed(Object? source, AsyncResult result) {
|
||||
|
|
@ -298,22 +317,6 @@ public class MainWindow : Gtk.Window {
|
|||
}
|
||||
}
|
||||
|
||||
private void on_folder_messages_appended() {
|
||||
int high = message_list_store.get_highest_folder_position();
|
||||
if (high < 0) {
|
||||
debug("Unable to find highest message position in %s", current_folder.to_string());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
debug("Message(s) appended to %s, fetching email at %d and above", current_folder.to_string(),
|
||||
high + 1);
|
||||
|
||||
// Want to get the one *after* the highest position in the message list
|
||||
current_folder.lazy_list_email(high + 1, -1, MessageListStore.REQUIRED_FIELDS,
|
||||
Geary.Folder.ListFlags.NONE, on_list_email_ready, cancellable);
|
||||
}
|
||||
|
||||
private async void search_folders_for_children(Gee.Collection<Geary.Folder> folders) {
|
||||
Gee.ArrayList<Geary.Folder> accumulator = new Gee.ArrayList<Geary.Folder>();
|
||||
foreach (Geary.Folder folder in folders) {
|
||||
|
|
@ -330,9 +333,17 @@ public class MainWindow : Gtk.Window {
|
|||
on_folders_added_removed(accumulator, null);
|
||||
}
|
||||
|
||||
private void cancel() {
|
||||
Cancellable old_cancellable = cancellable;
|
||||
cancellable = new Cancellable();
|
||||
private void cancel_folder() {
|
||||
Cancellable old_cancellable = cancellable_folder;
|
||||
cancellable_folder = new Cancellable();
|
||||
cancel_message();
|
||||
|
||||
old_cancellable.cancel();
|
||||
}
|
||||
|
||||
private void cancel_message() {
|
||||
Cancellable old_cancellable = cancellable_message;
|
||||
cancellable_message = new Cancellable();
|
||||
|
||||
old_cancellable.cancel();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
public class MessageListStore : Gtk.TreeStore {
|
||||
|
||||
public const Geary.Email.Field REQUIRED_FIELDS =
|
||||
Geary.Email.Field.ENVELOPE | Geary.Email.Field.PROPERTIES;
|
||||
|
||||
|
|
@ -23,7 +24,7 @@ public class MessageListStore : Gtk.TreeStore {
|
|||
public static Type[] get_types() {
|
||||
return {
|
||||
typeof (FormattedMessageData), // MESSAGE_DATA
|
||||
typeof (Geary.Email) // MESSAGE_OBJECT
|
||||
typeof (Geary.Conversation) // MESSAGE_OBJECT
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -47,39 +48,53 @@ public class MessageListStore : Gtk.TreeStore {
|
|||
}
|
||||
|
||||
// The Email should've been fetched with REQUIRED_FIELDS.
|
||||
public void append_envelope(Geary.Email envelope) {
|
||||
assert(envelope.fields.fulfills(REQUIRED_FIELDS));
|
||||
|
||||
public void append_conversation(Geary.Conversation conversation) {
|
||||
Gtk.TreeIter iter;
|
||||
append(out iter, null);
|
||||
|
||||
set(iter,
|
||||
Column.MESSAGE_DATA, new FormattedMessageData.from_email(envelope),
|
||||
Column.MESSAGE_OBJECT, envelope
|
||||
);
|
||||
Gee.SortedSet<Geary.Email>? pool = conversation.get_pool_sorted(compare_email);
|
||||
|
||||
envelope.location.position_deleted.connect(on_email_position_deleted);
|
||||
if (pool != null)
|
||||
set(iter,
|
||||
Column.MESSAGE_DATA, new FormattedMessageData.from_email(pool.first()),
|
||||
Column.MESSAGE_OBJECT, conversation
|
||||
);
|
||||
}
|
||||
|
||||
// The Email should've been fetched with REQUIRED_FIELDS.
|
||||
public bool has_envelope(Geary.Email envelope) {
|
||||
assert(envelope.fields.fulfills(REQUIRED_FIELDS));
|
||||
public void update_conversation(Geary.Conversation conversation) {
|
||||
Gtk.TreeIter iter;
|
||||
if (!find_conversation(conversation, out iter)) {
|
||||
// Unknown conversation, attempt to append it.
|
||||
append_conversation(conversation);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Gee.SortedSet<Geary.Email>? pool = conversation.get_pool_sorted(compare_email);
|
||||
|
||||
// Update the preview.
|
||||
set(iter, Column.MESSAGE_DATA, new FormattedMessageData.from_email(pool.first()));
|
||||
}
|
||||
|
||||
public bool has_conversation(Geary.Conversation conversation) {
|
||||
int count = get_count();
|
||||
for (int ctr = 0; ctr < count; ctr++) {
|
||||
Geary.Email? email = get_message_at_index(ctr);
|
||||
if (email == null)
|
||||
break;
|
||||
|
||||
if (email.location.position == envelope.location.position)
|
||||
if (conversation == get_conversation_at_index(ctr))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Geary.Email? get_message_at_index(int index) {
|
||||
return get_message_at(new Gtk.TreePath.from_indices(index, -1));
|
||||
public Geary.Conversation? get_conversation_at_index(int index) {
|
||||
return get_conversation_at(new Gtk.TreePath.from_indices(index, -1));
|
||||
}
|
||||
|
||||
public Geary.Email? get_newest_message_at_index(int index) {
|
||||
Geary.Conversation? c = get_conversation_at_index(index);
|
||||
Gee.SortedSet<Geary.Email>? pool = c.get_pool_sorted(compare_email);
|
||||
|
||||
return pool != null ? pool.first() : null;
|
||||
}
|
||||
|
||||
public void set_preview_at_index(int index, Geary.Email email) {
|
||||
|
|
@ -97,77 +112,43 @@ public class MessageListStore : Gtk.TreeStore {
|
|||
return iter_n_children(null);
|
||||
}
|
||||
|
||||
public Geary.Email? get_message_at(Gtk.TreePath path) {
|
||||
Gtk.TreeIter iter;
|
||||
public Geary.Conversation? get_conversation_at(Gtk.TreePath path) {
|
||||
Gtk.TreeIter iter;
|
||||
if (!get_iter(out iter, path))
|
||||
return null;
|
||||
|
||||
Geary.Email email;
|
||||
get(iter, Column.MESSAGE_OBJECT, out email);
|
||||
Geary.Conversation? conversation;
|
||||
get(iter, Column.MESSAGE_OBJECT, out conversation);
|
||||
|
||||
return email;
|
||||
return conversation;
|
||||
}
|
||||
|
||||
// Returns -1 if the list is empty.
|
||||
public int get_highest_folder_position() {
|
||||
Gtk.TreeIter iter;
|
||||
if (!get_iter_first(out iter))
|
||||
return -1;
|
||||
|
||||
int high = int.MIN;
|
||||
|
||||
// TODO: It would be more efficient to maintain highest and lowest values in a table or
|
||||
// as items are added and removed; this will do for now.
|
||||
do {
|
||||
Geary.Email email;
|
||||
get(iter, Column.MESSAGE_OBJECT, out email);
|
||||
|
||||
if (email.location.position > high)
|
||||
high = email.location.position;
|
||||
} while (iter_next(ref iter));
|
||||
|
||||
return high;
|
||||
}
|
||||
|
||||
private bool remove_at_position(int position) {
|
||||
Gtk.TreeIter iter;
|
||||
if (!get_iter_first(out iter))
|
||||
return false;
|
||||
|
||||
do {
|
||||
Geary.Email email;
|
||||
get(iter, Column.MESSAGE_OBJECT, out email);
|
||||
|
||||
if (email.location.position == position) {
|
||||
remove(iter);
|
||||
|
||||
email.location.position_deleted.disconnect(on_email_position_deleted);
|
||||
|
||||
return true;
|
||||
}
|
||||
} while (iter_next(ref iter));
|
||||
private bool find_conversation(Geary.Conversation conversation, out Gtk.TreeIter iter) {
|
||||
iter = Gtk.TreeIter();
|
||||
int count = get_count();
|
||||
for (int ctr = 0; ctr < count; ctr++) {
|
||||
if (conversation == get_conversation_at_index(ctr))
|
||||
return get_iter(out iter, new Gtk.TreePath.from_indices(ctr, -1));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int sort_by_date(Gtk.TreeModel model, Gtk.TreeIter aiter, Gtk.TreeIter biter) {
|
||||
Geary.Email aenvelope;
|
||||
get(aiter, Column.MESSAGE_OBJECT, out aenvelope);
|
||||
Geary.Conversation a, b;
|
||||
|
||||
Geary.Email benvelope;
|
||||
get(biter, Column.MESSAGE_OBJECT, out benvelope);
|
||||
get(aiter, Column.MESSAGE_OBJECT, out a);
|
||||
get(biter, Column.MESSAGE_OBJECT, out b);
|
||||
|
||||
int diff = aenvelope.date.value.compare(benvelope.date.value);
|
||||
if (diff != 0)
|
||||
return diff;
|
||||
Gee.SortedSet<Geary.Email>? apool = a.get_pool_sorted(compare_email);
|
||||
Gee.SortedSet<Geary.Email>? bpool = b.get_pool_sorted(compare_email);
|
||||
|
||||
// stabilize sort by using the mail's position, which is always unique in a folder
|
||||
return aenvelope.location.position - benvelope.location.position;
|
||||
}
|
||||
|
||||
private void on_email_position_deleted(int position) {
|
||||
if (!remove_at_position(position))
|
||||
debug("on_email_position_deleted: unable to find email at position %d", position);
|
||||
if (apool == null || apool.first() == null)
|
||||
return -1;
|
||||
else if (bpool == null || bpool.first() == null)
|
||||
return 1;
|
||||
|
||||
return compare_email(apool.first(), bpool.first());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
public class MessageListView : Gtk.TreeView {
|
||||
public signal void message_selected(Geary.Email? email);
|
||||
public signal void conversation_selected(Geary.Conversation? conversation);
|
||||
|
||||
public MessageListView(MessageListStore store) {
|
||||
set_model(store);
|
||||
|
|
@ -48,14 +48,14 @@ public class MessageListView : Gtk.TreeView {
|
|||
Gtk.TreeModel model;
|
||||
Gtk.TreePath? path = get_selection().get_selected_rows(out model).nth_data(0);
|
||||
if (path == null) {
|
||||
message_selected(null);
|
||||
conversation_selected(null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Geary.Email? email = get_store().get_message_at(path);
|
||||
if (email != null)
|
||||
message_selected(email);
|
||||
Geary.Conversation? conversation = get_store().get_conversation_at(path);
|
||||
if (conversation != null)
|
||||
conversation_selected(conversation);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public class MessageViewer : Gtk.EventBox {
|
||||
public class MessageViewer : Gtk.Viewport {
|
||||
public const Geary.Email.Field REQUIRED_FIELDS =
|
||||
Geary.Email.Field.HEADER
|
||||
| Geary.Email.Field.BODY
|
||||
|
|
@ -15,6 +15,7 @@ public class MessageViewer : Gtk.EventBox {
|
|||
|
||||
private const int HEADER_COL_SPACING = 10;
|
||||
private const int HEADER_ROW_SPACING = 3;
|
||||
private const int MESSAGE_BOX_MARGIN = 10;
|
||||
|
||||
// List of emails corresponding with VBox.
|
||||
private Gee.LinkedList<Geary.Email> messages = new Gee.LinkedList<Geary.Email>();
|
||||
|
|
@ -48,7 +49,7 @@ public class MessageViewer : Gtk.EventBox {
|
|||
border-color: #cccccc;
|
||||
border-style: solid;
|
||||
border-width: 1;
|
||||
-GtkWidget-separator-height: 1;
|
||||
-GtkWidget-separator-height: 2;
|
||||
}
|
||||
""";
|
||||
|
||||
|
|
@ -77,12 +78,10 @@ public class MessageViewer : Gtk.EventBox {
|
|||
}
|
||||
|
||||
// Only include to string if it's not just this account.
|
||||
// TODO: <ultiple accounts.
|
||||
// TODO: multiple accounts.
|
||||
string to = "";
|
||||
if (email.to != null) {
|
||||
Geary.RFC822.MailboxAddresses addr = new Geary.RFC822.MailboxAddresses.
|
||||
from_rfc822_string(email.to.to_string());
|
||||
if (!(addr.get_all().size == 1 && addr.get_all().get(0).address == username))
|
||||
if (!(email.to.get_all().size == 1 && email.to.get_all().get(0).address == username))
|
||||
to = email.to.to_string();
|
||||
}
|
||||
|
||||
|
|
@ -119,7 +118,12 @@ public class MessageViewer : Gtk.EventBox {
|
|||
debug("Could not get message text. %s", err.message);
|
||||
}
|
||||
|
||||
message_box.pack_start(container, false, false);
|
||||
Gtk.EventBox box = new Gtk.EventBox();
|
||||
box.add(container);
|
||||
box.margin = MESSAGE_BOX_MARGIN;
|
||||
|
||||
message_box.pack_end(box, false, false);
|
||||
message_box.show_all();
|
||||
|
||||
add_style();
|
||||
}
|
||||
|
|
@ -163,7 +167,8 @@ public class MessageViewer : Gtk.EventBox {
|
|||
|
||||
Gdk.RGBA color = Gdk.RGBA();
|
||||
color.parse(sample_view.style.base[0].to_string());
|
||||
override_background_color(Gtk.StateFlags.NORMAL, color);
|
||||
foreach (Gtk.Widget w in message_box.get_children())
|
||||
w.override_background_color(Gtk.StateFlags.NORMAL, color);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
14
src/client/util/util-email.vala
Normal file
14
src/client/util/util-email.vala
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* Copyright 2011 Yorba Foundation
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public int compare_email(Geary.Email aenvelope, Geary.Email benvelope) {
|
||||
int diff = aenvelope.date.value.compare(benvelope.date.value);
|
||||
if (diff != 0)
|
||||
return diff;
|
||||
|
||||
// stabilize sort by using the mail's position, which is always unique in a folder
|
||||
return aenvelope.location.position - benvelope.location.position;
|
||||
}
|
||||
|
|
@ -19,7 +19,8 @@ client_src = [
|
|||
'ui/message-list-view.vala',
|
||||
'ui/message-viewer.vala',
|
||||
|
||||
'util/util-keyring.vala'
|
||||
'util/util-keyring.vala',
|
||||
'util/util-email.vala'
|
||||
]
|
||||
|
||||
gsettings_schemas = [
|
||||
|
|
|
|||
|
|
@ -45,23 +45,35 @@ public abstract class Geary.Conversation : Object {
|
|||
*/
|
||||
public abstract Gee.Collection<Geary.ConversationNode>? get_replies(Geary.ConversationNode node);
|
||||
|
||||
/**
|
||||
* Returns all ConversationNodes in the conversation, which can then be sorted by the caller's
|
||||
* own requirements.
|
||||
*
|
||||
/**
|
||||
* Returns all emails in the conversation.
|
||||
* Only returns nodes that have an e-mail.
|
||||
*/
|
||||
public virtual Gee.Collection<Geary.ConversationNode>? get_pool() {
|
||||
Gee.HashSet<ConversationNode> pool = new Gee.HashSet<ConversationNode>();
|
||||
public virtual Gee.Set<Geary.Email>? get_pool() {
|
||||
Gee.HashSet<Email> pool = new Gee.HashSet<Email>();
|
||||
gather(pool, get_origin());
|
||||
|
||||
return (pool.size > 0) ? pool : null;
|
||||
}
|
||||
|
||||
private void gather(Gee.Set<ConversationNode> pool, ConversationNode? current) {
|
||||
/**
|
||||
* Returns all emails in the conversation, sorted by compare_func.
|
||||
* Only returns nodes that have an e-mail.
|
||||
*/
|
||||
public virtual Gee.SortedSet<Geary.Email>? get_pool_sorted(CompareFunc<Geary.Email>?
|
||||
compare_func = null) {
|
||||
Gee.TreeSet<Email> pool = new Gee.TreeSet<Email>(compare_func);
|
||||
gather(pool, get_origin());
|
||||
|
||||
return (pool.size > 0) ? pool : null;
|
||||
}
|
||||
|
||||
private void gather(Gee.Set<Email> pool, ConversationNode? current) {
|
||||
if (current == null)
|
||||
return;
|
||||
|
||||
pool.add(current);
|
||||
if (current.get_email() != null)
|
||||
pool.add(current.get_email());
|
||||
|
||||
Gee.Collection<Geary.ConversationNode>? children = get_replies(current);
|
||||
if (children != null) {
|
||||
|
|
|
|||
|
|
@ -166,6 +166,8 @@ public class Geary.Conversations : Object {
|
|||
private Gee.Map<Geary.RFC822.MessageID, Node> id_map = new Gee.HashMap<Geary.RFC822.MessageID,
|
||||
Node>(Geary.Hashable.hash_func, Geary.Equalable.equal_func);
|
||||
private Gee.Set<ImplConversation> conversations = new Gee.HashSet<ImplConversation>();
|
||||
private bool monitor_new = false;
|
||||
private Cancellable? cancellable_monitor = null;
|
||||
|
||||
public virtual signal void scan_started(int low, int count) {
|
||||
}
|
||||
|
|
@ -196,6 +198,9 @@ public class Geary.Conversations : Object {
|
|||
// Manually detach all the weak refs in the Conversation objects
|
||||
foreach (ImplConversation conversation in conversations)
|
||||
conversation.owner = null;
|
||||
|
||||
if (monitor_new)
|
||||
folder.messages_appended.disconnect(on_folder_messages_appended);
|
||||
}
|
||||
|
||||
protected virtual void notify_scan_started(int low, int count) {
|
||||
|
|
@ -247,6 +252,16 @@ public class Geary.Conversations : Object {
|
|||
folder.lazy_list_email(low, count, required_fields, flags, on_email_listed, cancellable);
|
||||
}
|
||||
|
||||
public bool monitor_new_messages(Cancellable? cancellable = null) {
|
||||
if (monitor_new)
|
||||
return false;
|
||||
|
||||
monitor_new = true;
|
||||
cancellable_monitor = cancellable;
|
||||
folder.messages_appended.connect(on_folder_messages_appended);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void on_email_listed(Gee.List<Geary.Email>? emails, Error? err) {
|
||||
if (err != null)
|
||||
notify_scan_error(err);
|
||||
|
|
@ -340,7 +355,7 @@ public class Geary.Conversations : Object {
|
|||
RFC822.MessageID ancestor_id = ancestors[ctr];
|
||||
|
||||
if (seen.contains(ancestor_id)) {
|
||||
warning("Loop detected in conversation: %s seen twice", ancestor_id.to_string());
|
||||
message("Loop detected in conversation: %s seen twice", ancestor_id.to_string());
|
||||
|
||||
continue;
|
||||
}
|
||||
|
|
@ -453,5 +468,31 @@ public class Geary.Conversations : Object {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void on_folder_messages_appended() {
|
||||
// Find highest position.
|
||||
// TODO: optimize.
|
||||
int high = -1;
|
||||
foreach (Conversation c in conversations)
|
||||
foreach (Email e in c.get_pool())
|
||||
if (e.location.position > high)
|
||||
high = e.location.position;
|
||||
|
||||
if (high < 0) {
|
||||
debug("Unable to find highest message position in %s", folder.to_string());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
debug("Message(s) appended to %s, fetching email at %d and above", folder.to_string(),
|
||||
high + 1);
|
||||
|
||||
// Want to get the one *after* the highest position in the list
|
||||
try {
|
||||
lazy_load(high + 1, -1, Folder.ListFlags.NONE, cancellable_monitor);
|
||||
} catch (Error e) {
|
||||
warning("Error getting new mail: %s", e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue