[geary/wip/713150-conversations] Further simplifications to ConversationMonitor
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/713150-conversations] Further simplifications to ConversationMonitor
- Date: Thu, 19 Mar 2015 01:07:47 +0000 (UTC)
commit 2445fefe0a110b66a9aeda6fa967ecd4fc960c32
Author: Jim Nelson <jim yorba org>
Date: Wed Mar 18 15:29:02 2015 -0700
Further simplifications to ConversationMonitor
Also, client now accurately sets min_window_count to the number
of conversations required to generate a scrollbar w/o estimating.
src/CMakeLists.txt | 2 +-
src/client/application/geary-controller.vala | 45 ++++++++++++--
.../conversation-list-cell-dimensions.vala | 63 ++++++++++++++++++++
.../formatted-conversation-data.vala | 45 ++++++++------
src/engine/app/app-conversation-monitor.vala | 36 +++++++-----
.../app-local-load-operation.vala | 15 -----
6 files changed, 152 insertions(+), 54 deletions(-)
---
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ec586c9..db6c896 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -56,7 +56,6 @@ engine/app/conversation-monitor/app-conversation-operation.vala
engine/app/conversation-monitor/app-external-append-operation.vala
engine/app/conversation-monitor/app-external-remove-operation.vala
engine/app/conversation-monitor/app-fill-window-operation.vala
-engine/app/conversation-monitor/app-local-load-operation.vala
engine/app/conversation-monitor/app-remove-operation.vala
engine/app/conversation-monitor/app-terminate-operation.vala
@@ -357,6 +356,7 @@ client/composer/email-entry.vala
client/composer/scrollable-overlay.vala
client/composer/webview-edit-fixer.vala
+client/conversation-list/conversation-list-cell-dimensions.vala
client/conversation-list/conversation-list-cell-renderer.vala
client/conversation-list/conversation-list-store.vala
client/conversation-list/conversation-list-view.vala
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index aa3923e..db5cc7b 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -57,8 +57,8 @@ public class GearyController : Geary.BaseObject {
public const string PROP_CURRENT_CONVERSATION ="current-conversations";
- public const int MIN_CONVERSATION_COUNT = 50;
- public const int LOAD_MORE_CONVERSATION_COUNT = 10;
+ private const int INITIAL_CONVERSATION_COUNT = 0;
+ private const int LOAD_MORE_CONVERSATION_COUNT = 10;
private const string DELETE_MESSAGE_TOOLTIP_SINGLE = _("Delete conversation (Shift+Delete)");
private const string DELETE_MESSAGE_TOOLTIP_MULTIPLE = _("Delete conversations (Shift+Delete)");
@@ -98,6 +98,8 @@ public class GearyController : Geary.BaseObject {
public LoginDialog? login_dialog { get; private set; default = null; }
+ public ConversationListCellDimensions cell_dimensions = new ConversationListCellDimensions();
+
private Geary.Account? current_account = null;
private Gee.HashMap<Geary.Account, Geary.App.EmailStore> email_stores
= new Gee.HashMap<Geary.Account, Geary.App.EmailStore>();
@@ -164,6 +166,11 @@ public class GearyController : Geary.BaseObject {
public signal void search_text_changed(string keywords);
public GearyController() {
+ // when the ConversationListStore cell dimensions are updated, ensure that the
+ // ConversationMonitor's window count is set to a number that will load just enough
+ // conversations to create scrollbars
+ cell_dimensions.notify[ConversationListCellDimensions.PROP_CELL_HEIGHT].connect(
+ update_min_window_count);
}
~GearyController() {
@@ -206,7 +213,7 @@ public class GearyController : Geary.BaseObject {
main_window.conversation_list_view.load_more.connect(on_load_more);
main_window.conversation_list_view.mark_conversations.connect(on_mark_conversations);
main_window.conversation_list_view.visible_conversations_changed.connect(on_visible_conversations_changed);
- main_window.conversation_list_height_changed.connect(on_check_conversation_list_scrollbars);
+ main_window.conversation_list_height_changed.connect(on_conversation_list_view_height_changed);
main_window.folder_list.folder_selected.connect(on_folder_selected);
main_window.folder_list.copy_conversation.connect(on_copy_conversation);
main_window.folder_list.move_conversation.connect(on_move_conversation);
@@ -283,7 +290,7 @@ public class GearyController : Geary.BaseObject {
main_window.conversation_list_view.load_more.disconnect(on_load_more);
main_window.conversation_list_view.mark_conversations.disconnect(on_mark_conversations);
main_window.conversation_list_view.visible_conversations_changed.disconnect(on_visible_conversations_changed);
- main_window.conversation_list_height_changed.disconnect(on_check_conversation_list_scrollbars);
+ main_window.conversation_list_height_changed.disconnect(on_conversation_list_view_height_changed);
main_window.folder_list.folder_selected.disconnect(on_folder_selected);
main_window.folder_list.copy_conversation.disconnect(on_copy_conversation);
main_window.folder_list.move_conversation.disconnect(on_move_conversation);
@@ -1447,7 +1454,7 @@ public class GearyController : Geary.BaseObject {
update_ui();
current_conversations = new Geary.App.ConversationMonitor(current_folder,
Geary.Folder.OpenFlags.NO_DELAY,
- ConversationListStore.REQUIRED_FIELDS, MIN_CONVERSATION_COUNT);
+ ConversationListStore.REQUIRED_FIELDS, INITIAL_CONVERSATION_COUNT);
if (inboxes.values.contains(current_folder)) {
// Inbox selected, clear new messages if visible
@@ -1465,6 +1472,10 @@ public class GearyController : Geary.BaseObject {
current_conversations.conversation_removed.connect(on_conversation_count_changed);
current_conversations.conversation_removed.connect(on_conversation_removed);
+ // update current_conversations min_window_count for enough conversations to create
+ // scrollbars
+ update_min_window_count();
+
if (!current_conversations.is_monitoring)
yield current_conversations.start_monitoring_async(conversation_cancellable);
@@ -1477,6 +1488,11 @@ public class GearyController : Geary.BaseObject {
debug("Scan error: %s", err.message);
}
+ private void on_conversation_list_view_height_changed() {
+ update_min_window_count();
+ on_check_conversation_list_scrollbars();
+ }
+
private void on_check_conversation_list_scrollbars() {
if (scrollbar_check_id != 0)
Source.remove(scrollbar_check_id);
@@ -1497,6 +1513,11 @@ public class GearyController : Geary.BaseObject {
if (main_window.conversation_list_has_scrollbar())
return false;
+ // if conversation count is below min_window_count, give the conversation monitor a chance
+ // to keep filling until it reaches that level
+ if (current_conversations.get_conversation_count() < current_conversations.min_window_count)
+ return false;
+
debug("Not enough conversations (%d/%d) for scrollbars, loading more from folder %s",
current_conversations.get_conversation_count(), current_conversations.min_window_count,
current_folder.to_string());
@@ -1545,6 +1566,20 @@ public class GearyController : Geary.BaseObject {
main_window.folder_list.select_folder(folder);
}
+ // Update the ConversationMonitor's min_window_count to be at least enough to create scrollbars
+ // when all conversations are loaded
+ private void update_min_window_count() {
+ if (!cell_dimensions.valid || current_conversations == null)
+ return;
+
+ // determine the minimum (floor) of the window count that is enough to show a scrollbar
+ int list_height = main_window.conversation_list_view.get_allocated_height();
+ int cells = (list_height / cell_dimensions.cell_height) + 1;
+
+ if (current_conversations.min_window_count < cells)
+ current_conversations.min_window_count = cells;
+ }
+
private void on_load_more() {
if (current_conversations == null || current_conversations.all_mail_loaded)
return;
diff --git a/src/client/conversation-list/conversation-list-cell-dimensions.vala
b/src/client/conversation-list/conversation-list-cell-dimensions.vala
new file mode 100644
index 0000000..052952e
--- /dev/null
+++ b/src/client/conversation-list/conversation-list-cell-dimensions.vala
@@ -0,0 +1,63 @@
+/* Copyright 2015 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 class ConversationListCellDimensions : Geary.BaseObject {
+ public const string PROP_CELL_HEIGHT = "cell-height";
+ public const string PROP_PREVIEW_HEIGHT = "preview-height";
+ public const string PROP_VALID = "valid";
+
+ /**
+ * The height in pixels of each cell in the { link ConversationListStore}.
+ *
+ * { link update} ensures that the notify signal for this property is only fired when the
+ * value changes.
+ *
+ * Defaults to -1.
+ */
+ public int cell_height { get; private set; default = -1; }
+
+ /**
+ * The height in pixels of each cell in the { link ConversationListStore}.
+ *
+ * { link update} ensures that the notify signal for this property is only fired when the
+ * value changes.
+ *
+ * Defaults to -1.
+ */
+ public int preview_height { get; private set; default = -1; }
+
+ /**
+ * Returns true when { link cell_height} and { link preview_height} are valid (non-negative)
+ * values.
+ *
+ * This does not actually check if the values are valid for screen or window dimensions, etc.
+ */
+ public bool valid { get; private set; default = false; }
+
+ public ConversationListCellDimensions() {
+ }
+
+ /**
+ * Update the dimensions of cells in the { link ConversationListStore}.
+ *
+ * @see cell_height
+ * @see preview_height
+ */
+ public void update(int cell_height, int preview_height) {
+ // don't fire notify signal(s) until all sets are complete
+ freeze_notify();
+
+ if (this.cell_height != cell_height)
+ this.cell_height = cell_height;
+
+ if (this.preview_height != preview_height)
+ this.preview_height = preview_height;
+
+ valid = this.cell_height >= 0 && this.preview_height >= 0;
+
+ thaw_notify();
+ }
+}
diff --git a/src/client/conversation-list/formatted-conversation-data.vala
b/src/client/conversation-list/formatted-conversation-data.vala
index 4cd56d2..69912e8 100644
--- a/src/client/conversation-list/formatted-conversation-data.vala
+++ b/src/client/conversation-list/formatted-conversation-data.vala
@@ -72,9 +72,6 @@ public class FormattedConversationData : Geary.BaseObject {
}
}
- private static int cell_height = -1;
- private static int preview_height = -1;
-
public bool is_unread { get; set; }
public bool is_flagged { get; set; }
public string date { get; private set; }
@@ -87,6 +84,7 @@ public class FormattedConversationData : Geary.BaseObject {
private Gee.List<Geary.RFC822.MailboxAddress>? account_owner_emails = null;
private bool use_to = true;
private CountBadge count_badge = new CountBadge(2);
+ private ConversationListCellDimensions cell_dimensions;
// Creates a formatted message data from an e-mail.
public FormattedConversationData(Geary.App.Conversation conversation, Geary.Email preview,
@@ -97,6 +95,10 @@ public class FormattedConversationData : Geary.BaseObject {
this.account_owner_emails = account_owner_emails;
use_to = (folder != null) && folder.special_folder_type.is_outgoing();
+ // take a reference to the global ConversationListCellDimensions object, which is held by
+ // the GearyController
+ cell_dimensions = GearyApplication.instance.controller.cell_dimensions;
+
// Load preview-related data.
update_date_string();
this.subject = EmailUtil.strip_subject_prefixes(preview);
@@ -109,6 +111,20 @@ public class FormattedConversationData : Geary.BaseObject {
this.num_emails = conversation.get_count();
}
+ // Creates an example message (used interally for styling calculations.)
+ public FormattedConversationData.create_example() {
+ this.is_unread = false;
+ this.is_flagged = false;
+ this.date = STYLE_EXAMPLE;
+ this.subject = STYLE_EXAMPLE;
+ this.body = STYLE_EXAMPLE + "\n" + STYLE_EXAMPLE;
+ this.num_emails = 1;
+
+ // take a reference to the global ConversationListCellDimensions object, which is held by
+ // the GearyController
+ cell_dimensions = GearyApplication.instance.controller.cell_dimensions;
+ }
+
public bool update_date_string() {
// get latest email *in folder* for the conversation's date, fall back on out-of-folder
Geary.Email? latest =
conversation.get_latest_recv_email(Geary.App.Conversation.Location.IN_FOLDER_OUT_OF_FOLDER);
@@ -127,16 +143,6 @@ public class FormattedConversationData : Geary.BaseObject {
return true;
}
- // Creates an example message (used interally for styling calculations.)
- public FormattedConversationData.create_example() {
- this.is_unread = false;
- this.is_flagged = false;
- this.date = STYLE_EXAMPLE;
- this.subject = STYLE_EXAMPLE;
- this.body = STYLE_EXAMPLE + "\n" + STYLE_EXAMPLE;
- this.num_emails = 1;
- }
-
private uint8 gdk_to_rgb(double gdk) {
return (uint8) (gdk.clamp(0.0, 1.0) * 255.0);
}
@@ -232,7 +238,8 @@ public class FormattedConversationData : Geary.BaseObject {
// Must call calculate_sizes() first.
public void get_size(Gtk.Widget widget, Gdk.Rectangle? cell_area, out int x_offset,
out int y_offset, out int width, out int height) {
- assert(cell_height != -1); // ensures calculate_sizes() was called.
+ // ensure calculate_sizes() was called
+ assert(cell_dimensions.valid);
x_offset = 0;
y_offset = 0;
@@ -240,7 +247,7 @@ public class FormattedConversationData : Geary.BaseObject {
// conversation list to be shown as "squished":
// https://bugzilla.gnome.org/show_bug.cgi?id=713954
width = 1;
- height = cell_height;
+ height = cell_dimensions.cell_height;
}
// Can be used for rendering or calculating height.
@@ -300,8 +307,7 @@ public class FormattedConversationData : Geary.BaseObject {
}
if (recalc_dims) {
- FormattedConversationData.preview_height = preview_height;
- FormattedConversationData.cell_height = y + preview_height;
+ cell_dimensions.update(y + preview_height, preview_height);
} else {
int unread_y = display_preview ? cell_area.y + LINE_SPACING * 2 : cell_area.y +
LINE_SPACING;
@@ -393,6 +399,9 @@ public class FormattedConversationData : Geary.BaseObject {
private Pango.Rectangle render_preview(Gtk.Widget widget, Gdk.Rectangle? cell_area,
Cairo.Context? ctx, int y, bool selected, int counter_width = 0) {
+ // the dimensions should have been calculated before calling this
+ assert(cell_dimensions.valid);
+
double dim = selected ? DIM_TEXT_AMOUNT : DIM_PREVIEW_TEXT_AMOUNT;
string preview_markup = "<span foreground='%s'>%s</span>".printf(
rgba_to_markup(dim_rgba(get_foreground_rgba(widget, selected), dim)),
@@ -409,7 +418,7 @@ public class FormattedConversationData : Geary.BaseObject {
layout_preview.set_ellipsize(Pango.EllipsizeMode.END);
if (ctx != null && cell_area != null) {
layout_preview.set_width((cell_area.width - TEXT_LEFT - counter_width - LINE_SPACING) *
Pango.SCALE);
- layout_preview.set_height(preview_height * Pango.SCALE);
+ layout_preview.set_height(cell_dimensions.preview_height * Pango.SCALE);
ctx.move_to(cell_area.x + TEXT_LEFT, y);
Pango.cairo_show_layout(ctx, layout_preview);
diff --git a/src/engine/app/app-conversation-monitor.vala b/src/engine/app/app-conversation-monitor.vala
index d69c558..c74695a 100644
--- a/src/engine/app/app-conversation-monitor.vala
+++ b/src/engine/app/app-conversation-monitor.vala
@@ -4,6 +4,16 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
+/**
+ * A helper class that automatically loads and sorts { link Email} in a { link Folder} into
+ * { link Conversation}s ordered by date received (descending).
+ *
+ * Before starting, the application should subscribe to ConversationMonitor's various signals to
+ * be informed of conversations being added and removed from the folder. The application can
+ * specify the minimum number of conversations to be held by ConversationMonitor by adjusting the
+ * { link min_window_count} property.
+ */
+
public class Geary.App.ConversationMonitor : BaseObject {
/**
* These are the fields Conversations require to thread emails together. These fields will
@@ -12,8 +22,13 @@ public class Geary.App.ConversationMonitor : BaseObject {
public const Geary.Email.Field REQUIRED_FIELDS = Geary.Email.Field.REFERENCES |
Geary.Email.Field.FLAGS | Geary.Email.Field.DATE;
+ // An approximate multipler of messages-in-folder-to-conversation ... because the Engine doesn't
+ // offer a way to directly load conversations in a folder as a vector unto itself, this class
+ // loads a number of messages equal to the number of needed conversations times this multiplier
+ private const int MSG_CONV_MULTIPLIER = 2;
+
// # of messages to load at a time as we attempt to fill the min window.
- private const int MIN_FILL_MESSAGE_COUNT = 5;
+ private const int MIN_FILL_MESSAGE_COUNT = 5 * MSG_CONV_MULTIPLIER;
private const Geary.SpecialFolderType[] BLACKLISTED_FOLDER_TYPES = {
Geary.SpecialFolderType.SPAM,
@@ -323,9 +338,8 @@ public class Geary.App.ConversationMonitor : BaseObject {
yield operation_queue.stop_processing_async(cancellable_monitor);
operation_queue.clear();
- // Add the necessary initial operations ahead of anything the Folder might notify us of
- // (additions, removals, etc.)
- operation_queue.add(new LocalLoadOperation(this));
+ // Add the initial local load ahead of anything the Folder might notify us of
+ // (additions, removals, etc.) to prime the pump
operation_queue.add(new FillWindowOperation(this, false));
connect_to_folder();
@@ -348,15 +362,6 @@ public class Geary.App.ConversationMonitor : BaseObject {
return true;
}
- internal async void local_load_async() {
- debug("ConversationMonitor seeding with local email for %s", folder.to_string());
- yield load_by_id_async(null, min_window_count, required_fields, Folder.ListFlags.LOCAL_ONLY,
- cancellable_monitor);
- debug("ConversationMonitor seeded for %s", folder.to_string());
-
- operation_queue.add(new FillWindowOperation(this, false));
- }
-
/**
* Halt monitoring of the Folder and, if specified, close it. Note that the Cancellable
* supplied to start_monitoring_async() is used during monitoring but *not* for this method.
@@ -834,7 +839,7 @@ public class Geary.App.ConversationMonitor : BaseObject {
Geary.EmailIdentifier? low_id = yield get_lowest_email_id_async(null);
if (low_id != null && !is_insert) {
// Load at least as many messages as remaining conversations.
- int num_to_load = Numeric.int_floor(min_window_count - conversations.size,
+ int num_to_load = Numeric.int_floor((min_window_count - conversations.size) *
MSG_CONV_MULTIPLIER,
MIN_FILL_MESSAGE_COUNT);
yield load_by_id_async(low_id, num_to_load, Email.Field.NONE, flags, cancellable_monitor);
@@ -844,7 +849,8 @@ public class Geary.App.ConversationMonitor : BaseObject {
} else {
// No existing messages or an insert invalidated our existing list,
// need to start from scratch.
- yield load_by_id_async(null, min_window_count, Email.Field.NONE, flags, cancellable_monitor);
+ yield load_by_id_async(null, min_window_count * MSG_CONV_MULTIPLIER, Email.Field.NONE,
+ flags, cancellable_monitor);
// in this case, it's possible no new email is loaded, but that shouldn't stop a
// reschedule
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]