[geary/wip/765516-gtk-widget-conversation-viewer: 79/169] General clean up of conversation viewer code.
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/765516-gtk-widget-conversation-viewer: 79/169] General clean up of conversation viewer code.
- Date: Mon, 12 Sep 2016 13:19:00 +0000 (UTC)
commit b245090b8f5ab6c9163672b3ac1c6206fb2a4945
Author: Michael James Gratton <mike vee net>
Date: Tue Jun 21 13:52:20 2016 +1000
General clean up of conversation viewer code.
Ensure all public members have semi-decent documentaion comments. Make
public members private if they don't need to be public. Re-org non-public
members to be below public memebers in the source.
.../conversation-viewer/conversation-email.vala | 67 ++++--
.../conversation-viewer/conversation-message.vala | 61 ++++--
.../conversation-viewer/conversation-viewer.vala | 225 ++++++++++++--------
3 files changed, 229 insertions(+), 124 deletions(-)
---
diff --git a/src/client/conversation-viewer/conversation-email.vala
b/src/client/conversation-viewer/conversation-email.vala
index 1ac1a2f..7570b37 100644
--- a/src/client/conversation-viewer/conversation-email.vala
+++ b/src/client/conversation-viewer/conversation-email.vala
@@ -9,7 +9,7 @@
/**
* A widget for displaying an email in a conversation.
*
- * This widget corresponds to {@link Geary.Email}, displaying the
+ * This view corresponds to {@link Geary.Email}, displaying the
* email's primary message (a {@link Geary.RFC822.Message}), any
* sub-messages (also instances of {@link Geary.RFC822.Message}) and
* attachments. The RFC822 messages are themselves displayed by {@link
@@ -54,16 +54,16 @@ public class ConversationEmail : Gtk.Box {
private const string ACTION_VIEW_SOURCE = "view_source";
- // The email message being displayed
+ /** The specific email that is displayed by this view. */
public Geary.Email email { get; private set; }
- // Is the message body shown or not?
- public bool is_message_body_visible = false;
+ /** Determines if the email is showing a preview or the full message. */
+ public bool is_collapsed = true;
- // View displaying the email's primary message
+ /** The view displaying the email's primary message headers and body. */
public ConversationMessage primary_message { get; private set; }
- // Views for messages that are attachments
+ /** Views for attached messages. */
public Gee.List<ConversationMessage> attached_messages {
owned get { return this._attached_messages.read_only_view; }
}
@@ -125,44 +125,51 @@ public class ConversationEmail : Gtk.Box {
private Gtk.Menu attachments_menu;
- // Fired when the user clicks "reply" in the message menu.
+ /** Fired when the user clicks "reply" in the message menu. */
public signal void reply_to_message(Geary.Email message);
- // Fired when the user clicks "reply all" in the message menu.
+ /** Fired when the user clicks "reply all" in the message menu. */
public signal void reply_all_message(Geary.Email message);
- // Fired when the user clicks "forward" in the message menu.
+ /** Fired when the user clicks "forward" in the message menu. */
public signal void forward_message(Geary.Email message);
- // Fired when the user updates the message's flags.
+ /** Fired when the user updates the email's flags. */
public signal void mark_email(
Geary.Email email, Geary.NamedFlag? to_add, Geary.NamedFlag? to_remove
);
- // Fired when the user updates all message's flags from this down.
+ /** Fired when the user updates flags for this email and all emails down. */
public signal void mark_email_from(
Geary.Email email, Geary.NamedFlag? to_add, Geary.NamedFlag? to_remove
);
- // Fired on message image save action is activated
+ /** Fired when the user saves an inline displayed image. */
public signal void save_image(string? filename, Geary.Memory.Buffer buffer);
- // Fired on link activation in the web_view
+ /** Fired when the user clicks a link in the email. */
public signal void link_activated(string link);
- // Fired on attachment activation
+ /** Fired when the user activates an attachment. */
public signal void attachments_activated(Gee.Collection<AttachmentInfo> attachments);
- // Fired when the save attachments action is activated
+ /** Fired when the user saves an attachment. */
public signal void save_attachments(Gee.Collection<AttachmentInfo> attachments);
- // Fired the edit draft button is clicked.
+ /** Fired the edit draft button is clicked. */
public signal void edit_draft(Geary.Email email);
- // Fired when the view source action is activated
+ /** Fired when the view source action is activated. */
public signal void view_source(Geary.Email email);
+ /**
+ * Constructs a new view to display an email.
+ *
+ * This method sets up most of the user interface for displaying
+ * the complete email, but does not attempt any possibly
+ * long-running loading processes.
+ */
public ConversationEmail(Geary.Email email,
Geary.ContactStore contact_store,
bool is_draft) {
@@ -278,6 +285,13 @@ public class ConversationEmail : Gtk.Box {
update_email_state(false);
}
+ /**
+ * Starts loading the complete email.
+ *
+ * This method will load the avatar and message body for the
+ * primary message and any attached messages, as well as
+ * attachment names, types and icons.
+ */
public async void start_loading(Cancellable load_cancelled) {
yield primary_message.load_avatar(
GearyApplication.instance.controller.avatar_session,
@@ -294,8 +308,11 @@ public class ConversationEmail : Gtk.Box {
yield load_attachments(load_cancelled);
}
+ /**
+ * Shows the complete message: headers, body and attachments.
+ */
public void expand_email(bool include_transitions=true) {
- is_message_body_visible = true;
+ is_collapsed = false;
get_style_context().add_class("geary_show_body");
attachments_button.set_sensitive(true);
star_button.set_sensitive(true);
@@ -304,8 +321,11 @@ public class ConversationEmail : Gtk.Box {
primary_message.show_message_body(include_transitions);
}
+ /**
+ * Hides the complete message, just showing the header preview.
+ */
public void collapse_email() {
- is_message_body_visible = false;
+ is_collapsed = true;
get_style_context().remove_class("geary_show_body");
attachments_button.set_sensitive(false);
star_button.set_sensitive(false);
@@ -314,15 +334,24 @@ public class ConversationEmail : Gtk.Box {
primary_message.hide_message_body();
}
+ /**
+ * Updates the current email's flags and dependent UI state.
+ */
public void update_flags(Geary.Email email) {
this.email.set_flags(email.email_flags);
update_email_state();
}
+ /**
+ * Determines if the email is flagged as read on the client side only.
+ */
public bool is_manual_read() {
return get_style_context().has_class("geary_manual_read");
}
+ /**
+ * Displays the message as read, even if not reflected in its flags.
+ */
public void mark_manual_read() {
get_style_context().add_class("geary_manual_read");
}
diff --git a/src/client/conversation-viewer/conversation-message.vala
b/src/client/conversation-viewer/conversation-message.vala
index 8633066..4f923dd 100644
--- a/src/client/conversation-viewer/conversation-message.vala
+++ b/src/client/conversation-viewer/conversation-message.vala
@@ -9,22 +9,22 @@
/**
* A widget for displaying an {@link Geary.RFC822.Message}.
*
- * This widget corresponds to {@link Geary.RFC822.Message}, displaying
+ * This view corresponds to {@link Geary.RFC822.Message}, displaying
* both the message's headers and body. Any attachments and sub
* messages are handled by {@link ConversationEmail}, which typically
* embeds at least one instance of this class.
*/
[GtkTemplate (ui = "/org/gnome/Geary/conversation-message.ui")]
public class ConversationMessage : Gtk.Box {
-
- // Internal class to associate inline image buffers (replaced by rotated scaled versions of
- // them) so they can be saved intact if the user requires it
+ // Internal class to associate inline image buffers (replaced by
+ // rotated scaled versions of them) so they can be saved intact if
+ // the user requires it
private class ReplacedImage : Geary.BaseObject {
public string id;
public string filename;
public Geary.Memory.Buffer buffer;
-
+
public ReplacedImage(int replaced_number, string filename, Geary.Memory.Buffer buffer) {
id = "%X".printf(replaced_number);
this.filename = filename;
@@ -56,23 +56,25 @@ public class ConversationMessage : Gtk.Box {
private const string ACTION_SELECT_ALL = "select_all";
- // The message being displayed
+ /** The specific RFC822 message displayed by this view. */
public Geary.RFC822.Message message { get; private set; }
- // The HTML viewer to view the emails.
- public ConversationWebView web_view { get; private set; }
-
- // The allocation for the web view
+ /** Current allocated size of the HTML body view. */
public Gdk.Rectangle web_view_allocation { get; private set; }
- // Has the message body been been fully loaded?
+ /** Specifies if the message body been been fully loaded. */
public bool is_loading_complete = false;
+ /** Box containing the preview and full header widgets. */
[GtkChild]
- public Gtk.Box summary_box; // not yet supported: { get; private set; }
+ internal Gtk.Box summary_box;
+ /** Box that InfoBar widgets should be added to. */
[GtkChild]
- public Gtk.Box infobar_box; // not yet supported: { get; private set; }
+ internal Gtk.Box infobar_box;
+
+ /** HTML view that displays the message body. */
+ internal ConversationWebView web_view { get; private set; }
[GtkChild]
private Gtk.Revealer preview_revealer;
@@ -147,19 +149,26 @@ public class ConversationMessage : Gtk.Box {
// Message-specific actions
private SimpleActionGroup message_actions = new SimpleActionGroup();
- // Fired when an attachment is displayed inline
+ /** Fired when an attachment is added for inline display. */
public signal void attachment_displayed_inline(string id);
- // Fired when remote image load requested for sender
+ /** Fired when the user requests remote images be loaded. */
public signal void flag_remote_images();
- // Fired when remote image load requested for sender
+ /** Fired when the user requests remote images be always loaded. */
public signal void remember_remote_images();
- // Fired on message image save action is activated
+ /** Fired when the user saves an inline displayed image. */
public signal void save_image(string? filename, Geary.Memory.Buffer buffer);
+ /**
+ * Constructs a new view to display an RFC 823 message headers and body.
+ *
+ * This method sets up most of the user interface for displaying
+ * the message, but does not attempt any possibly long-running
+ * loading processes.
+ */
public ConversationMessage(Geary.RFC822.Message message,
Geary.ContactStore contact_store,
bool always_load_remote_images) {
@@ -244,6 +253,9 @@ public class ConversationMessage : Gtk.Box {
body_box.pack_start(web_view, true, true, 0);
}
+ /**
+ * Shows the complete message and hides the preview headers.
+ */
public void show_message_body(bool include_transitions=true) {
Gtk.RevealerTransitionType revealer = preview_revealer.get_transition_type();
if (!include_transitions) {
@@ -267,12 +279,18 @@ public class ConversationMessage : Gtk.Box {
body_revealer.set_transition_type(revealer);
}
+ /**
+ * Hides the complete message and shows the preview headers.
+ */
public void hide_message_body() {
preview_revealer.set_reveal_child(true);
header_revealer.set_reveal_child(false);
body_revealer.set_reveal_child(false);
}
+ /**
+ * Starts loading the avatar for the sender of the message.
+ */
public async void load_avatar(Soup.Session session, Cancellable load_cancellable) {
// Queued messages are cancelled in ConversationViewer.clear()
// rather than here using a callback on load_cancellable since
@@ -296,6 +314,9 @@ public class ConversationMessage : Gtk.Box {
}
}
+ /**
+ * Starts loading the message body in the HTML view.
+ */
public async void load_message_body(Cancellable load_cancelled) {
bool remote_images = false;
bool load_images = false;
@@ -359,6 +380,9 @@ public class ConversationMessage : Gtk.Box {
web_view.load_string(body_text, "text/html", "UTF8", "");
}
+ /**
+ * Highlights user search terms in the message view.
+ */
public void highlight_search_terms(Gee.Set<string> search_matches) {
// XXX Need to highlight subject, sender and recipient matches too
@@ -381,6 +405,9 @@ public class ConversationMessage : Gtk.Box {
web_view.set_highlight_text_matches(true);
}
+ /**
+ * Disables highlighting of any search terms in the message view.
+ */
public void unmark_search_terms() {
web_view.set_highlight_text_matches(false);
web_view.unmark_text_matches();
diff --git a/src/client/conversation-viewer/conversation-viewer.vala
b/src/client/conversation-viewer/conversation-viewer.vala
index a14ac5c..518b6cd 100644
--- a/src/client/conversation-viewer/conversation-viewer.vala
+++ b/src/client/conversation-viewer/conversation-viewer.vala
@@ -7,14 +7,25 @@
*/
/**
- * A Stack for managing the conversation pane and a {@link Geary.App.Conversation}.
+ * A widget for displaying conversations as a list of emails.
*
- * Unlike ConversationListStore (which sorts by date received), ConversationViewer sorts by the
- * {@link Geary.Email.date} field (the Date: header), as that's the date displayed to the user.
+ * The view displays the current selected {@link
+ * Geary.App.Conversation} from the conversation list. To do so, it
+ * listens to signals from both the list and the current conversation
+ * monitor, updating the email list as needed.
+ *
+ * Unlike ConversationListStore (which sorts by date received),
+ * ConversationViewer sorts by the {@link Geary.Email.date} field (the
+ * Date: header), as that's the date displayed to the user.
+ *
+ * In addition to the email list, docked composers, a progress spinner
+ * and user messages are also displayed, depending on its current
+ * state.
*/
-
[GtkTemplate (ui = "/org/gnome/Geary/conversation-viewer.ui")]
public class ConversationViewer : Gtk.Stack {
+
+ /** Fields that must be available for display as a conversation. */
public const Geary.Email.Field REQUIRED_FIELDS =
Geary.Email.Field.HEADER
| Geary.Email.Field.BODY
@@ -24,7 +35,7 @@ public class ConversationViewer : Gtk.Stack {
| Geary.Email.Field.DATE
| Geary.Email.Field.FLAGS
| Geary.Email.Field.PREVIEW;
-
+
private const int SELECT_CONVERSATION_TIMEOUT_MSEC = 100;
private enum ViewState {
@@ -32,7 +43,7 @@ public class ConversationViewer : Gtk.Stack {
CONVERSATION,
COMPOSE;
}
-
+
private enum SearchState {
// Search/find states.
NONE, // Not in search
@@ -52,22 +63,22 @@ public class ConversationViewer : Gtk.Stack {
COUNT;
}
- // Fired when an email is added to the view
+ /** Fired when an email view is added to the conversation list. */
public signal void email_row_added(ConversationEmail email);
- // Fired when an email is removed from the view
+ /** Fired when an email view is removed from the conversation list. */
public signal void email_row_removed(ConversationEmail email);
- // Fired when the user marks messages.
+ /** Fired when the user updates the flags for a set of emails. */
public signal void mark_emails(Gee.Collection<Geary.EmailIdentifier> emails,
Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove);
- // Fired when the viewer has been cleared.
+ /** Fired when the email list has been cleared. */
public signal void cleared();
-
- // Current conversation, or null if none.
+
+ /** Current conversation being displayed, or null if none. */
public Geary.App.Conversation? current_conversation = null;
-
+
// Stack pages
[GtkChild]
private Gtk.Image splash_page;
@@ -110,7 +121,10 @@ public class ConversationViewer : Gtk.Stack {
private Geary.State.Machine fsm;
private uint select_conversation_timeout_id = 0;
private bool loading_conversations = false;
-
+
+ /**
+ * Constructs a new conversation view instance.
+ */
public ConversationViewer() {
// Setup the conversation list box
conversation_listbox.set_sort_func((row1, row2) => {
@@ -131,10 +145,10 @@ public class ConversationViewer : Gtk.Stack {
// embedded composer and should not be activated.
ConversationEmail? msg = row.get_child() as ConversationEmail;
if (!row.get_style_context().has_class("geary_last") && msg != null) {
- if (msg.is_message_body_visible) {
- collapse_email(row);
- } else {
+ if (msg.is_collapsed) {
expand_email(row);
+ } else {
+ collapse_email(row);
}
}
});
@@ -194,11 +208,17 @@ public class ConversationViewer : Gtk.Stack {
do_conversation();
}
-
+
+ /**
+ * Returns the last email in the list by sort order, if any.
+ */
public Geary.Email? get_last_email() {
return emails.is_empty ? null : emails.last();
}
-
+
+ /**
+ * Returns the email with text currently selected, if any.
+ */
public Geary.Email? get_selected_email(out string? quote) {
// XXX check to see if there is a email with selected text,
// if so return that
@@ -206,56 +226,9 @@ public class ConversationViewer : Gtk.Stack {
return emails.is_empty ? null : emails.last();
}
- public void check_mark_read() {
- Gee.ArrayList<Geary.EmailIdentifier> email_ids =
- new Gee.ArrayList<Geary.EmailIdentifier>();
-
- Gtk.Adjustment adj = conversation_page.vadjustment;
- int top_bound = (int) adj.value;
- int bottom_bound = top_bound + (int) adj.page_size;
-
- const int TEXT_PADDING = 50;
- foreach (Geary.Email email in emails) {
- ConversationEmail conversation_email = conversation_email_for_id(email.id);
- ConversationMessage conversation_message =
- conversation_email.primary_message;
- // Don't bother with not-yet-loaded emails since the
- // size of the body will be off, affecting the visibility
- // of emails further down the conversation.
- if (email.email_flags.is_unread() &&
- conversation_message.is_loading_complete &&
- !conversation_email.is_manual_read()) {
- int body_top = 0;
- int body_left = 0;
- conversation_message.web_view.translate_coordinates(
- conversation_listbox,
- 0, 0,
- out body_left, out body_top
- );
- int body_bottom = body_top +
- conversation_message.web_view_allocation.height;
-
- // Only mark the email as read if it's actually visible
- if (body_bottom > top_bound &&
- body_top + TEXT_PADDING < bottom_bound) {
- email_ids.add(email.id);
-
- // Since it can take some time for the new flags
- // to round-trip back to ConversationViewer's
- // signal handlers, mark as manually read here
- conversation_email.mark_manual_read();
- }
- }
- }
-
- if (email_ids.size > 0) {
- Geary.EmailFlags flags = new Geary.EmailFlags();
- flags.add(Geary.EmailFlags.UNREAD);
- mark_emails(email_ids, null, flags);
- }
- }
-
- // Use this when an email has been marked read through manual (user) intervention
+ /**
+ * Displays an email as being read, regardless of its actual flags.
+ */
public void mark_manual_read(Geary.EmailIdentifier id) {
ConversationEmail? row = conversation_email_for_id(id);
if (row != null) {
@@ -263,13 +236,19 @@ public class ConversationViewer : Gtk.Stack {
}
}
+ /**
+ * Hides a specific email in the conversation.
+ */
public void blacklist_by_id(Geary.EmailIdentifier? id) {
if (id == null) {
return;
}
email_to_row.get(id).hide();
}
-
+
+ /**
+ * Re-displays a previously blacklisted email.
+ */
public void unblacklist_by_id(Geary.EmailIdentifier? id) {
if (id == null) {
return;
@@ -277,11 +256,17 @@ public class ConversationViewer : Gtk.Stack {
email_to_row.get(id).show();
}
+ /**
+ * Puts the view into conversation mode, showing the email list.
+ */
public void do_conversation() {
state = ViewState.CONVERSATION;
set_visible_child(loading_page);
}
-
+
+ /**
+ * Puts the view into composer mode, showing a full-height composer.
+ */
public void do_compose(ComposerWidget composer) {
state = ViewState.COMPOSE;
ComposerBox box = new ComposerBox(composer);
@@ -302,10 +287,13 @@ public class ConversationViewer : Gtk.Stack {
composer_page.pack_start(box);
set_visible_child(composer_page);
}
-
+
+ /**
+ * Puts the view into conversation mode, but with an embedded composer.
+ */
public void do_embedded_composer(ComposerWidget composer, Geary.Email referred) {
state = ViewState.CONVERSATION;
-
+
ComposerEmbed embed = new ComposerEmbed(
referred, composer, conversation_page
);
@@ -326,11 +314,84 @@ public class ConversationViewer : Gtk.Stack {
}
- public new void set_visible_child(Gtk.Widget widget) {
+ /**
+ * Shows the in-conversation search UI.
+ */
+ public void show_find_bar() {
+ fsm.issue(SearchEvent.OPEN_FIND_BAR);
+ conversation_find_bar.focus_entry();
+ }
+
+ /**
+ * Displays the next/previous match for an in-conversation search.
+ */
+ public void find(bool forward) {
+ if (!conversation_find_bar.visible)
+ show_find_bar();
+
+ conversation_find_bar.find(forward);
+ }
+
+ /**
+ * Sets the currently visible page of the stack.
+ */
+ private new void set_visible_child(Gtk.Widget widget) {
debug("Showing child: %s\n", widget.get_name());
base.set_visible_child(widget);
}
+ /**
+ * Finds any currently visible messages, marks them as being read.
+ */
+ private void check_mark_read() {
+ Gee.ArrayList<Geary.EmailIdentifier> email_ids =
+ new Gee.ArrayList<Geary.EmailIdentifier>();
+
+ Gtk.Adjustment adj = conversation_page.vadjustment;
+ int top_bound = (int) adj.value;
+ int bottom_bound = top_bound + (int) adj.page_size;
+
+ const int TEXT_PADDING = 50;
+ foreach (Geary.Email email in emails) {
+ ConversationEmail conversation_email = conversation_email_for_id(email.id);
+ ConversationMessage conversation_message =
+ conversation_email.primary_message;
+ // Don't bother with not-yet-loaded emails since the
+ // size of the body will be off, affecting the visibility
+ // of emails further down the conversation.
+ if (email.email_flags.is_unread() &&
+ conversation_message.is_loading_complete &&
+ !conversation_email.is_manual_read()) {
+ int body_top = 0;
+ int body_left = 0;
+ conversation_message.web_view.translate_coordinates(
+ conversation_listbox,
+ 0, 0,
+ out body_left, out body_top
+ );
+ int body_bottom = body_top +
+ conversation_message.web_view_allocation.height;
+
+ // Only mark the email as read if it's actually visible
+ if (body_bottom > top_bound &&
+ body_top + TEXT_PADDING < bottom_bound) {
+ email_ids.add(email.id);
+
+ // Since it can take some time for the new flags
+ // to round-trip back to ConversationViewer's
+ // signal handlers, mark as manually read here
+ conversation_email.mark_manual_read();
+ }
+ }
+ }
+
+ if (email_ids.size > 0) {
+ Geary.EmailFlags flags = new Geary.EmailFlags();
+ flags.add(Geary.EmailFlags.UNREAD);
+ mark_emails(email_ids, null, flags);
+ }
+ }
+
// Removes all displayed e-mails from the view.
private void clear() {
// Cancel any pending avatar loads here, rather than in
@@ -684,23 +745,11 @@ public class ConversationViewer : Gtk.Stack {
private void compress_emails() {
conversation_listbox.get_style_context().add_class("geary_compressed");
}
-
+
//private void decompress_emails() {
// conversation_listbox.get_style_context().remove_class("geary_compressed");
//}
-
- public void show_find_bar() {
- fsm.issue(SearchEvent.OPEN_FIND_BAR);
- conversation_find_bar.focus_entry();
- }
-
- public void find(bool forward) {
- if (!conversation_find_bar.visible)
- show_find_bar();
-
- conversation_find_bar.find(forward);
- }
-
+
private void on_update_flags(Geary.Email email) {
// Nothing to do if we aren't displaying this email.
if (!email_to_row.has_key(email.id)) {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]