[geary/wip/765516-gtk-widget-conversation-viewer: 98/142] Enable popover for conversation message header addresses.



commit 2b1c3ecdba4047d80fd7f3759d70903392445178
Author: Michael James Gratton <mike vee net>
Date:   Wed Jul 27 00:58:55 2016 +1000

    Enable popover for conversation message header addresses.
    
    * src/client/conversation-viewer/conversation-email.vala: Hook up new
      ConversationMessage.link_activated signal, ensure sub-messages also
      have their signals hooked up.
    
    * src/client/conversation-viewer/conversation-message.vala: Add a new
      AddressFlowBoxChild inner class for message header addresses. Add new
      signal for emitting link clicks for both actions and the web view. Make
      link-related actions require a string target, update menu construction
      to supply them to the context menu and callbacks expect them. Add a
      popover for header addresses for copying/pasting each address.

 .../conversation-viewer/conversation-email.vala    |   70 +++++----
 .../conversation-viewer/conversation-message.vala  |  173 ++++++++++++--------
 2 files changed, 148 insertions(+), 95 deletions(-)
---
diff --git a/src/client/conversation-viewer/conversation-email.vala 
b/src/client/conversation-viewer/conversation-email.vala
index 6acc4f7..b3fe8ac 100644
--- a/src/client/conversation-viewer/conversation-email.vala
+++ b/src/client/conversation-viewer/conversation-email.vala
@@ -312,47 +312,42 @@ public class ConversationEmail : Gtk.Box {
             return;
         }
 
-        primary_message = new ConversationMessage(
+        this.primary_message = new ConversationMessage(
             message,
             contact_store,
             email.load_remote_images().is_certain()
         );
-        primary_message.flag_remote_images.connect(on_flag_remote_images);
-        primary_message.remember_remote_images.connect(on_remember_remote_images);
-        primary_message.attachment_displayed_inline.connect((id) => {
-                inlined_content_ids.add(id);
-            });
-        primary_message.web_view.link_selected.connect((link) => {
-                link_activated(link);
-            });
-        primary_message.web_view.selection_changed.connect(() => {
-                on_message_selection_changed(primary_message);
-            });
-        primary_message.save_image.connect((filename, buffer) => {
-                save_image(filename, buffer);
-            });
-        primary_message.summary_box.pack_start(action_box, false, false, 0);
+        connect_message_view_signals(this.primary_message);
+        this.primary_message.summary_box.pack_start(
+            this.action_box, false, false, 0
+        );
 
         Gtk.Builder builder = new Gtk.Builder.from_resource(
             "/org/gnome/Geary/conversation-email-menus.ui"
         );
-        email_menubutton.set_menu_model((MenuModel) builder.get_object("email_menu"));
-        email_menubutton.set_sensitive(false);
+        this.email_menubutton.set_menu_model(
+            (MenuModel) builder.get_object("email_menu")
+        );
+        this.email_menubutton.set_sensitive(false);
 
-        attachments_menu = new Gtk.Menu.from_model(
+        this.attachments_menu = new Gtk.Menu.from_model(
             (MenuModel) builder.get_object("attachments_menu")
         );
-        attachments_menu.attach_to_widget(this, null);
+        this.attachments_menu.attach_to_widget(this, null);
 
-        primary_message.infobar_box.pack_start(draft_infobar, false, false, 0);
+        this.primary_message.infobar_box.pack_start(
+            this.draft_infobar, false, false, 0
+        );
         if (is_draft) {
-            draft_infobar.show();
-            draft_infobar.response.connect((infobar, response_id) => {
+            this.draft_infobar.show();
+            this.draft_infobar.response.connect((infobar, response_id) => {
                     if (response_id == 1) { edit_draft(); }
                 });
         }
 
-        primary_message.infobar_box.pack_start(not_saved_infobar, false, false, 0);
+        this.primary_message.infobar_box.pack_start(
+            this.not_saved_infobar, false, false, 0
+        );
 
         // if (email.from != null && email.from.contains_normalized(current_account_information.email)) {
         //  // XXX set a RO property?
@@ -362,15 +357,15 @@ public class ConversationEmail : Gtk.Box {
         // Add sub_messages container and message viewers if there are any
         Gee.List<Geary.RFC822.Message> sub_messages = message.get_sub_messages();
         if (sub_messages.size > 0) {
-            primary_message.body_box.pack_start(sub_messages_box, false, false, 0);
+            this.primary_message.body_box.pack_start(
+                this.sub_messages_box, false, false, 0
+            );
         }
         foreach (Geary.RFC822.Message sub_message in sub_messages) {
             ConversationMessage attached_message =
                 new ConversationMessage(sub_message, contact_store, false);
-                attached_message.web_view.selection_changed.connect(() => {
-                        on_message_selection_changed(attached_message);
-                    });
-            sub_messages_box.pack_start(attached_message, false, false, 0);
+            connect_message_view_signals(attached_message);
+            this.sub_messages_box.pack_start(attached_message, false, false, 0);
             this._attached_messages.add(attached_message);
         }
 
@@ -482,6 +477,23 @@ public class ConversationEmail : Gtk.Box {
         }
     }
 
+    private void connect_message_view_signals(ConversationMessage view) {
+        view.flag_remote_images.connect(on_flag_remote_images);
+        view.remember_remote_images.connect(on_remember_remote_images);
+        view.attachment_displayed_inline.connect((id) => {
+                inlined_content_ids.add(id);
+            });
+        view.link_activated.connect((link) => {
+                link_activated(link);
+            });
+        view.save_image.connect((filename, buffer) => {
+                save_image(filename, buffer);
+            });
+        view.web_view.selection_changed.connect(() => {
+                on_message_selection_changed(view);
+            });
+    }
+
     private void update_email_state() {
         Geary.EmailFlags flags = email.email_flags;
         Gtk.StyleContext style = get_style_context();
diff --git a/src/client/conversation-viewer/conversation-message.vala 
b/src/client/conversation-viewer/conversation-message.vala
index 0196c43..690053f 100644
--- a/src/client/conversation-viewer/conversation-message.vala
+++ b/src/client/conversation-viewer/conversation-message.vala
@@ -17,6 +17,42 @@
 [GtkTemplate (ui = "/org/gnome/Geary/conversation-message.ui")]
 public class ConversationMessage : Gtk.Box {
 
+    // Widget used to display sender/recipient email addresses in
+    // message header Gtk.FlowBox instances.
+    private class AddressFlowBoxChild : Gtk.FlowBoxChild {
+
+        public Geary.RFC822.MailboxAddress address { get; private set; }
+
+        public AddressFlowBoxChild(Geary.RFC822.MailboxAddress address,
+                                   string dim_color, string weight) {
+            this.address = address;
+
+            Gtk.Label label = new Gtk.Label(null);
+            //label.set_halign(Gtk.Align.START);
+            //label.set_valign(Gtk.Align.BASELINE);
+            //label.set_xalign(0.0f);
+
+            string name = Geary.HTML.escape_markup(address.name);
+            string addr = Geary.HTML.escape_markup(address.address);
+            if (!Geary.String.is_empty(address.name) && name != addr) {
+                label.set_markup(
+                    "<span weight=\"%s\">%s</span> <span color=\"%s\">%s</span>"
+                    .printf(weight, name, dim_color, addr)
+                    );
+            } else {
+                label.set_markup(
+                    "<span weight=\"%s\">%s</span>".printf(weight, addr)
+                    );
+            }
+
+            add(label);
+            set_halign(Gtk.Align.START);
+            //child.set_valign(Gtk.Align.START);
+            show_all();
+        }
+
+    }
+
     // 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
@@ -151,6 +187,9 @@ public class ConversationMessage : Gtk.Box {
     /** Fired when the user requests remote images be loaded. */
     public signal void flag_remote_images();
 
+    /** Fired when the user clicks a link in the email. */
+    public signal void link_activated(string link);
+
     /** Fired when the user requests remote images be always loaded. */
     public signal void remember_remote_images();
 
@@ -174,21 +213,22 @@ public class ConversationMessage : Gtk.Box {
 
         // Actions
 
-        add_action(ACTION_COPY_EMAIL, false).activate.connect(on_copy_email_address);
-        add_action(ACTION_COPY_LINK, false).activate.connect(on_copy_link);
+        add_action(ACTION_COPY_EMAIL, true, VariantType.STRING)
+            .activate.connect(on_copy_email_address);
+        add_action(ACTION_COPY_LINK, true, VariantType.STRING)
+            .activate.connect(on_copy_link);
         add_action(ACTION_COPY_SELECTION, false).activate.connect(() => {
                 web_view.copy_clipboard();
             });
         add_action(ACTION_OPEN_INSPECTOR, Args.inspector).activate.connect(() => {
                 web_view.web_inspector.inspect_node(context_menu_element);
             });
-        add_action(ACTION_OPEN_LINK, false).activate.connect(() => {
-                context_menu_element.click();
-            });
+        add_action(ACTION_OPEN_LINK, true, VariantType.STRING)
+            .activate.connect(on_open_link);
         add_action(ACTION_SELECT_ALL, true).activate.connect(() => {
                 web_view.select_all();
             });
-        add_action(ACTION_SAVE_IMAGE, false).activate.connect(on_save_image);
+        add_action(ACTION_SAVE_IMAGE, true).activate.connect(on_save_image);
 
         insert_action_group("msg", message_actions);
 
@@ -235,13 +275,11 @@ public class ConversationMessage : Gtk.Box {
             set_flowbox_addresses(this.from, this.message.from, "bold");
         } else {
             Gtk.Label label = new Gtk.Label(null);
-            label.set_markup(format_sender_preview(message.from));
+            label.set_markup(format_sender_preview(null));
 
-            // XXX This is copied from set_flowbox_addresses below
             Gtk.FlowBoxChild child = new Gtk.FlowBoxChild();
             child.add(label);
             child.set_halign(Gtk.Align.START);
-            //child.set_valign(Gtk.Align.START);
             child.show_all();
 
             this.from.add(child);
@@ -262,6 +300,9 @@ public class ConversationMessage : Gtk.Box {
         // Suppress default context menu.
         this.web_view.context_menu.connect(() => { return true; });
         this.web_view.hovering_over_link.connect(on_hovering_over_link);
+        this.web_view.link_selected.connect((link) => {
+                link_activated(link);
+            });
         this.web_view.selection_changed.connect(on_selection_changed);
         this.web_view.show();
 
@@ -473,8 +514,8 @@ public class ConversationMessage : Gtk.Box {
         return quote;
     }
 
-    private SimpleAction add_action(string name, bool enabled) {
-        SimpleAction action = new SimpleAction(name, null);
+    private SimpleAction add_action(string name, bool enabled, VariantType? type = null) {
+        SimpleAction action = new SimpleAction(name, type);
         action.set_enabled(enabled);
         message_actions.add_action(action);
         return action;
@@ -487,6 +528,23 @@ public class ConversationMessage : Gtk.Box {
         }
     }
 
+    private Menu set_action_param_string(MenuModel existing, string value) {
+        Menu menu = new Menu();
+        for (int i = 0; i < existing.get_n_items(); i++) {
+            MenuItem item = new MenuItem.from_model(existing, i);
+            Variant action = item.get_attribute_value(
+                Menu.ATTRIBUTE_ACTION, VariantType.STRING
+            );
+            item.set_action_and_target(
+                action.get_string(),
+                VariantType.STRING.dup_string(),
+                value
+            );
+            menu.append_item(item);
+        }
+        return menu;
+    }
+
     private void set_header_addresses(Gtk.Box header,
                                       Geary.RFC822.MailboxAddresses? addresses) {
         if (addresses != null && addresses.size > 0) {
@@ -504,34 +562,23 @@ public class ConversationMessage : Gtk.Box {
         string dim_color = GtkUtil.pango_color_from_theme(
             address_box.get_style_context(), "insensitive_fg_color"
         );
-        foreach (Geary.RFC822.MailboxAddress addr in addresses) {
-            Gtk.Label label = new Gtk.Label(null);
-            //label.set_halign(Gtk.Align.START);
-            //label.set_valign(Gtk.Align.BASELINE);
-            //label.set_ellipsize(Pango.EllipsizeMode.END);
-            //label.set_xalign(0.0f);
+        foreach (Geary.RFC822.MailboxAddress address in addresses) {
+            address_box.add(new AddressFlowBoxChild(address, dim_color, weight));
+        }
 
-            string name = Geary.HTML.escape_markup(addr.name);
-            string address = Geary.HTML.escape_markup(addr.address);
-            if (!Geary.String.is_empty(addr.name) && name != address) {
-                label.set_markup(
-                    "<span weight=\"%s\">%s</span> <span color=\"%s\">%s</span>"
-                    .printf(weight, name, dim_color, address)
-                    );
-            } else {
-                label.set_markup(
-                    "<span weight=\"%s\">%s</span>".printf(weight, address)
+        address_box.child_activated.connect((box, child) => {
+                AddressFlowBoxChild address_child = child as AddressFlowBoxChild;
+                if (address_child != null) {
+                    Menu menu = set_action_param_string(
+                        context_menu_email,
+                        "mailto:"; + address_child.address.address
                     );
-            }
-
-            Gtk.FlowBoxChild child = new Gtk.FlowBoxChild();
-            child.add(label);
-            child.set_halign(Gtk.Align.START);
-            //child.set_valign(Gtk.Align.START);
-            child.show_all();
-
-            address_box.add(child);
-        }
+                    Gtk.Popover popover =
+                        new Gtk.Popover.from_model(child, menu);
+                    popover.set_position(Gtk.PositionType.BOTTOM);
+                    popover.show();
+                }
+            });
     }
 
     private string format_sender_preview(Geary.RFC822.MailboxAddresses? addresses) {
@@ -552,7 +599,7 @@ public class ConversationMessage : Gtk.Box {
                 } else {
                     value += "<span weight=\"bold\">%s</span>".printf(address);
                 }
-                
+
                 if (++i < list.size)
                     value += ", ";
             }
@@ -1052,12 +1099,14 @@ public class ConversationMessage : Gtk.Box {
         // have a single menu model and disable the parts we don't
         // need.
         Menu model = new Menu();
-        if (hover_url != null) {
-            if (hover_url.has_prefix(Geary.ComposedEmail.MAILTO_SCHEME)) {
-                model.append_section(null, context_menu_email);
-            } else {
-                model.append_section(null, context_menu_link);
-            }
+        if (this.hover_url != null) {
+            MenuModel link_menu =
+                this.hover_url.has_prefix(Geary.ComposedEmail.MAILTO_SCHEME)
+                ? context_menu_email
+                : context_menu_link;
+            model.append_section(
+                null, set_action_param_string(link_menu, this.hover_url)
+            );
         }
         if (context_menu_element.local_name.down() == "img") {
             ReplacedImage image = get_replaced_image();
@@ -1122,23 +1171,12 @@ public class ConversationMessage : Gtk.Box {
     }
 
     private void on_hovering_over_link(string? title, string? url) {
-        if (url != null) {
-            hover_url = Uri.unescape_string(url);
-            bool is_email = hover_url.has_prefix(Geary.ComposedEmail.MAILTO_SCHEME);
-            set_action_enabled(ACTION_OPEN_LINK, true);
-            set_action_enabled(ACTION_COPY_LINK, !is_email);
-            set_action_enabled(ACTION_COPY_EMAIL, is_email);
-        } else {
-            hover_url = null;
-            set_action_enabled(ACTION_OPEN_LINK, false);
-            set_action_enabled(ACTION_COPY_LINK, false);
-            set_action_enabled(ACTION_COPY_EMAIL, false);
-        }
+        this.hover_url = (url != null) ? Uri.unescape_string(url) : null;
 
         // Use tooltip on the containing box since the web_view
         // doesn't want to pay ball.
-        body_box.set_tooltip_text(hover_url);
-        body_box.trigger_tooltip_query();
+        this.body_box.set_tooltip_text(this.hover_url);
+        this.body_box.trigger_tooltip_query();
     }
 
     private void on_selection_changed() {
@@ -1170,22 +1208,25 @@ public class ConversationMessage : Gtk.Box {
         remote_images_infobar.hide();
     }
 
-    private void on_copy_link() {
-        // Put the current link in clipboard.
+    private void on_copy_link(Variant? param) {
         Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
-        clipboard.set_text(hover_url, -1);
+        clipboard.set_text(param.get_string(), -1);
         clipboard.store();
     }
 
-    private void on_copy_email_address() {
-        // Put the current email address in clipboard.
+    private void on_copy_email_address(Variant? param) {
+        string mailto = param.get_string().substring(
+            Geary.ComposedEmail.MAILTO_SCHEME.length, -1
+        );
         Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
-        if (hover_url.has_prefix(Geary.ComposedEmail.MAILTO_SCHEME)) {
-            clipboard.set_text(hover_url.substring(Geary.ComposedEmail.MAILTO_SCHEME.length, -1), -1);
-        }
+        clipboard.set_text(mailto, -1);
         clipboard.store();
     }
 
+    private void on_open_link(Variant? param) {
+        link_activated(param.get_string());
+    }
+
     private void on_save_image() {
         ReplacedImage? replaced_image = get_replaced_image();
         save_image(replaced_image.filename, replaced_image.buffer);


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