[geary/wip/765516-gtk-widget-conversation-viewer: 60/207] Reenable and update code for displayed attachments.



commit b5270e3740240f7be37dd65e439acb9a8168ceb3
Author: Michael James Gratton <mike vee net>
Date:   Mon Apr 18 11:01:46 2016 +1000

    Reenable and update code for displayed attachments.
    
    * src/client/conversation-viewer/conversation-message.vala: Add template
      child widgets for displayed attachment UI, add signal for when user
      activates an attachment.
      (ConversationMessage::ConversationMessage): Ensure the attachment UI
      is visible when there are displayed attachments.
      (ConversationMessage::start_loading): New method for async loading of
      message content, use just for attachments for now.
      (ConversationMessage::on_attachments_view_activated): Handle activation
      signal for specific attachments.
      (ConversationMessage::load_attachments): Load attachments from the
      message into the new UI.
      (ConversationMessage::load_attachment_icon): Load icons for the
      attachments UI from the attachment itself if an image, else from the
      icon theme.
    
    * src/client/application/geary-controller.vala: Hook up
      attachment_activated signal to conversation messages.
    
    * src/client/conversation-viewer/conversation-viewer.vala
      (ConversationViewer::add_message): Ensure message loading starts after
      new conversation messages are added to the window hierarchy.
    
    * ui/conversation-message.ui: Add an attachments box, with an icon view
      for displayed attachments.

 src/client/application/geary-controller.vala       |   12 +-
 .../conversation-viewer/conversation-message.vala  |  236 +++++++++++++-------
 .../conversation-viewer/conversation-viewer.vala   |    1 +
 ui/conversation-message.ui                         |   57 +++++
 4 files changed, 224 insertions(+), 82 deletions(-)
---
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index cd627ba..9f156f6 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -211,7 +211,6 @@ public class GearyController : Geary.BaseObject {
         main_window.conversation_viewer.reply_all_message.connect(on_reply_all_message);
         main_window.conversation_viewer.forward_message.connect(on_forward_message);
         main_window.conversation_viewer.mark_messages.connect(on_conversation_viewer_mark_messages);
-        main_window.conversation_viewer.open_attachment.connect(on_open_attachment);
         main_window.conversation_viewer.save_attachments.connect(on_save_attachments);
         main_window.conversation_viewer.save_buffer_to_file.connect(on_save_buffer_to_file);
         main_window.conversation_viewer.edit_draft.connect(on_edit_draft);
@@ -293,7 +292,6 @@ public class GearyController : Geary.BaseObject {
         main_window.conversation_viewer.reply_all_message.disconnect(on_reply_all_message);
         main_window.conversation_viewer.forward_message.disconnect(on_forward_message);
         main_window.conversation_viewer.mark_messages.disconnect(on_conversation_viewer_mark_messages);
-        main_window.conversation_viewer.open_attachment.disconnect(on_open_attachment);
         main_window.conversation_viewer.save_attachments.disconnect(on_save_attachments);
         main_window.conversation_viewer.save_buffer_to_file.disconnect(on_save_buffer_to_file);
         main_window.conversation_viewer.edit_draft.disconnect(on_edit_draft);
@@ -1889,8 +1887,8 @@ public class GearyController : Geary.BaseObject {
                 err.message);
         }
     }
-    
-    private void on_open_attachment(Geary.Attachment attachment) {
+
+    private void on_attachment_activated(Geary.Attachment attachment) {
         if (GearyApplication.instance.config.ask_open_attachment) {
             QuestionDialog ask_to_open = new QuestionDialog.with_checkbox(main_window,
                 _("Are you sure you want to open \"%s\"?").printf(attachment.file.get_basename()),
@@ -1898,11 +1896,11 @@ public class GearyController : Geary.BaseObject {
                 Stock._OPEN_BUTTON, Stock._CANCEL, _("Don't _ask me again"), false);
             if (ask_to_open.run() != Gtk.ResponseType.OK)
                 return;
-            
+
             // only save checkbox state if OK was selected
             GearyApplication.instance.config.ask_open_attachment = !ask_to_open.is_checked;
         }
-        
+
         // Open the attachment if we know what to do with it.
         if (!open_uri(attachment.file.get_uri())) {
             // Failing that, trigger a save dialog.
@@ -2590,10 +2588,12 @@ public class GearyController : Geary.BaseObject {
 
     private void on_message_added(ConversationMessage message) {
         message.link_activated.connect(on_link_activated);
+        message.attachment_activated.connect(on_attachment_activated);
     }
 
     private void on_message_removed(ConversationMessage message) {
         message.link_activated.disconnect(on_link_activated);
+        message.attachment_activated.disconnect(on_attachment_activated);
     }
 
     private void on_link_activated(string link) {
diff --git a/src/client/conversation-viewer/conversation-message.vala 
b/src/client/conversation-viewer/conversation-message.vala
index 1b4d1a4..38c5417 100644
--- a/src/client/conversation-viewer/conversation-message.vala
+++ b/src/client/conversation-viewer/conversation-message.vala
@@ -38,7 +38,8 @@ public class ConversationMessage : Gtk.Box {
         "image/x-xbitmap",
         "image/x-xbm"
     };
-    private const int ATTACHMENT_PREVIEW_SIZE = 50;
+    private const int ATTACHMENT_ICON_SIZE = 32;
+    private const int ATTACHMENT_PREVIEW_SIZE = 64;
     private const string REPLACED_IMAGE_CLASS = "replaced_inline_image";
     private const string DATA_IMAGE_CLASS = "data_inline_image";
     private const int MAX_INLINE_IMAGE_MAJOR_DIM = 1024;
@@ -103,6 +104,11 @@ public class ConversationMessage : Gtk.Box {
     public Gtk.Box body_box;
 
     [GtkChild]
+    private Gtk.Box attachments_box;
+    [GtkChild]
+    private Gtk.ListStore attachments_model;
+
+    [GtkChild]
     private Gtk.Popover link_popover;
     [GtkChild]
     private Gtk.Label good_link_label;
@@ -127,6 +133,9 @@ public class ConversationMessage : Gtk.Box {
     // Fired on link activation in the web_view
     public signal void link_activated(string link);
 
+    // Fired on attachment activation
+    public signal void attachment_activated(Geary.Attachment attachment);
+
 
     public ConversationMessage(Geary.Email email, Geary.Folder containing_folder) {
         this.email = email;
@@ -195,13 +204,13 @@ public class ConversationMessage : Gtk.Box {
         //  get_style_context().add_class("sent");
         // }
 
-        // Set attachment icon and add the attachments container if there are displayed attachments.
+        // Add the attachments container if there are displayed attachments.
         int displayed = displayed_attachments(email);
-        attachment_icon.set_visible(displayed > 0);
-        // if (displayed > 0) {
-        //     insert_attachments(div_message, email.attachments);
-        // }
-        
+        if (displayed > 0) {
+            attachment_icon.set_visible(true);
+            body_box.pack_start(attachments_box, false, false, 0);
+        }
+
         // // Look for any attached emails
         // Gee.List<Geary.RFC822.Message> sub_messages = message.get_sub_messages();
         // foreach (Geary.RFC822.Message sub_message in sub_messages) {
@@ -234,6 +243,10 @@ public class ConversationMessage : Gtk.Box {
         update_message_state(false);
     }
 
+    public async void start_loading(Cancellable load_cancelled) {
+        yield load_attachments(email.attachments, load_cancelled);
+    }
+
     public void show_message_body(bool include_transitions=true) {
         is_message_body_visible = true;
         get_style_context().add_class("geary_show_body");
@@ -646,18 +659,25 @@ public class ConversationMessage : Gtk.Box {
     //     get_viewer().mark_read();
     // }
 
-    // private void on_attachment_clicked(string attachment_id) {
-    //     Geary.Attachment? attachment = null;
-    //     try {
-    //         attachment = email.get_attachment(attachment_id);
-    //     } catch (Error error) {
-    //         warning("Error opening attachment: %s", error.message);
-    //     }
-        
-    //     if (attachment != null) {
-    //         get_viewer().open_attachment(attachment);
-    //  }
-    // }
+    [GtkCallback]
+    private void on_attachments_view_activated(Gtk.IconView view, Gtk.TreePath path) {
+        Gtk.TreeIter iter;
+        Value attachment_id;
+
+        attachments_model.get_iter(out iter, path);
+        attachments_model.get_value(iter, 2, out attachment_id);
+
+        Geary.Attachment? attachment = null;
+        try {
+            attachment = email.get_attachment(attachment_id.get_string());
+        } catch (Error error) {
+            warning("Error getting attachment: %s", error.message);
+        }
+
+        if (attachment != null) {
+            attachment_activated(attachment);
+        }
+    }
 
     // private void on_data_image_menu(WebKit.DOM.Element element, WebKit.DOM.Event event) {
     //     event.stop_propagation();
@@ -1045,64 +1065,46 @@ public class ConversationMessage : Gtk.Box {
                 assert_not_reached();
         }
     }
-    
-    // private void insert_attachments(WebKit.DOM.HTMLElement email_container,
-    //     Gee.List<Geary.Attachment> attachments) {
-
-    //     // <div class="attachment_container">
-    //     //     <div class="top_border"></div>
-    //     //     <table class="attachment" data-attachment-id="">
-    //     //         <tr>
-    //     //             <td class="preview">
-    //     //                 <img src="" />
-    //     //             </td>
-    //     //             <td class="info">
-    //     //                 <div class="filename"></div>
-    //     //                 <div class="filesize"></div>
-    //     //             </td>
-    //     //         </tr>
-    //     //     </table>
-    //     // </div>
 
-    //     try {
-    //         // Prepare the dom for our attachments.
-    //         WebKit.DOM.Document document = web_view.get_dom_document();
-    //         WebKit.DOM.HTMLElement attachment_container =
-    //             Util.DOM.clone_select(document, "#attachment_template");
-    //         WebKit.DOM.HTMLElement attachment_template =
-    //             Util.DOM.select(attachment_container, ".attachment");
-    //         attachment_container.remove_attribute("id");
-    //         attachment_container.remove_child(attachment_template);
-
-    //         // Create an attachment table for each attachment.
-    //         foreach (Geary.Attachment attachment in attachments) {
-    //             if (!should_show_attachment(attachment)) {
-    //                 continue;
-    //             }
-    //             // Generate the attachment table.
-    //             WebKit.DOM.HTMLElement attachment_table = Util.DOM.clone_node(attachment_template);
-    //             string filename = !attachment.has_supplied_filename ? _("none") : 
attachment.file.get_basename();
-    //             Util.DOM.select(attachment_table, ".info .filename")
-    //                 .set_inner_text(filename);
-    //             Util.DOM.select(attachment_table, ".info .filesize")
-    //                 .set_inner_text(Files.get_filesize_as_string(attachment.filesize));
-    //             attachment_table.set_attribute("data-attachment-id", attachment.id);
-
-    //             // Set the image preview and insert it into the container.
-    //             WebKit.DOM.HTMLImageElement img =
-    //                 Util.DOM.select(attachment_table, ".preview img") as WebKit.DOM.HTMLImageElement;
-    //             web_view.set_attachment_src(img, attachment.content_type, attachment.file.get_path(),
-    //                 ATTACHMENT_PREVIEW_SIZE);
-    //             attachment_container.append_child(attachment_table);
-    //         }
+    private async void load_attachments(Gee.List<Geary.Attachment> attachments,
+                                        Cancellable load_cancelled) {
+        foreach (Geary.Attachment attachment in attachments) {
+            if (should_show_attachment(attachment)) {
+                Gdk.Pixbuf? icon =
+                    yield load_attachment_icon(attachment, load_cancelled);
+                string file_name = null;
+                if (attachment.has_supplied_filename) {
+                    file_name = attachment.file.get_basename();
+                }
+                // XXX Geary.ImapDb.Attachment will use "none" when
+                // saving attachments with no filename to disk, this
+                // seems to be getting saved to be the filename and
+                // passed back, breaking the has_supplied_filename
+                // test - so check for it here.
+                if (file_name == null ||
+                    file_name == "" ||
+                    file_name == "none") {
+                    // XXX Check for unknown types here and try to guess
+                    // using attachment data.
+                    file_name = ContentType.get_description(
+                        attachment.content_type.get_mime_type()
+                    );
+                }
+                string file_size = Files.get_filesize_as_string(attachment.filesize);
+
+                Gtk.TreeIter iter;
+                attachments_model.append(out iter);
+                attachments_model.set(
+                    iter,
+                    0, icon,
+                    1, Markup.printf_escaped("%s\n%s", file_name, file_size),
+                    2, attachment.id,
+                    -1
+                );
+            }
+        }
+    }
 
-    //         // Append the attachments to the email.
-    //         email_container.append_child(attachment_container);
-    //     } catch (Error error) {
-    //         debug("Failed to insert attachments: %s", error.message);
-    //     }
-    // }
-    
     // private bool in_drafts_folder() {
     //     return containing_folder.special_folder_type == Geary.SpecialFolderType.DRAFTS;
     // }
@@ -1119,7 +1121,89 @@ public class ConversationMessage : Gtk.Box {
         
         return false;
     }
-    
+
+    private async Gdk.Pixbuf? load_attachment_icon(Geary.Attachment attachment,
+                                                   Cancellable load_cancelled) {
+        Geary.Mime.ContentType content_type = attachment.content_type;
+        Gdk.Pixbuf? pixbuf = null;
+
+        // Due to Bug 65167, for retina/highdpi displays with
+        // window_scale == 2, GtkCellRendererPixbuf will draw the
+        // pixbuf twice as large and blurry, so clamp it to 1 for now
+        // - this at least gives is the correct size icons, but still
+        // blurry.
+        //int window_scale = get_window().get_scale_factor();
+        int window_scale = 1;
+        try {
+            // If the file is an image, use it. Otherwise get the icon
+            // for this mime_type.
+            if (content_type.has_media_type("image")) {
+                // Get a thumbnail for the image.
+                // TODO Generate and save the thumbnail when
+                // extracting the attachments rather than when showing
+                // them in the viewer.
+                int preview_size = ATTACHMENT_PREVIEW_SIZE * window_scale;
+                InputStream stream = yield attachment.file.read_async(
+                    Priority.DEFAULT,
+                    load_cancelled
+                );
+                pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async(
+                    stream, preview_size, preview_size, true, load_cancelled
+                );
+                pixbuf = pixbuf.apply_embedded_orientation();
+            } else {
+                // Load the icon for this mime type.
+                string gio_content_type =
+                   ContentType.from_mime_type(content_type.get_mime_type());
+                Icon icon = ContentType.get_icon(gio_content_type);
+                Gtk.IconTheme theme = Gtk.IconTheme.get_default();
+
+                // XXX GTK 3.14 We should be able to replace the
+                // ThemedIcon/LoadableIcon/other cases below with
+                // simply this:
+                // Gtk.IconInfo? icon_info = theme.lookup_by_gicon_for_scale(
+                //     icon, ATTACHMENT_ICON_SIZE, window_scale
+                // );
+                // pixbuf = yield icon_info.load_icon_async(load_cancelled);
+
+                if (icon is ThemedIcon) {
+                    Gtk.IconInfo? icon_info = null;
+                    foreach (string name in ((ThemedIcon) icon).names) {
+                        icon_info = theme.lookup_icon_for_scale(
+                            name, ATTACHMENT_ICON_SIZE, window_scale, 0
+                        );
+                        if (icon_info != null) {
+                            break;
+                        }
+                    }
+                    if (icon_info == null) {
+                        icon_info = theme.lookup_icon_for_scale(
+                            "x-office-document", ATTACHMENT_ICON_SIZE, window_scale, 0
+                        );
+                    }
+                    pixbuf = yield icon_info.load_icon_async(load_cancelled);
+                } else if (icon is LoadableIcon) {
+                    InputStream stream = yield ((LoadableIcon) icon).load_async(
+                        ATTACHMENT_ICON_SIZE, load_cancelled
+                    );
+                    int icon_size = ATTACHMENT_ICON_SIZE * window_scale;
+                    pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async(
+                        stream, icon_size, icon_size, true, load_cancelled
+                    );
+                } else {
+                    warning("Unsupported attachment icon type: %s\n",
+                            icon.get_type().name());
+                }
+            }
+        } catch (Error error) {
+            warning("Failed to load icon for attachment '%s': %s",
+                    attachment.id,
+                    error.message);
+        }
+
+        return pixbuf;
+    }
+
     /*
      * Test whether text looks like a URI that leads somewhere other than href.  The text
      * will have a scheme prepended if it doesn't already have one, and the short versions
diff --git a/src/client/conversation-viewer/conversation-viewer.vala 
b/src/client/conversation-viewer/conversation-viewer.vala
index ffe0d0e..3345f13 100644
--- a/src/client/conversation-viewer/conversation-viewer.vala
+++ b/src/client/conversation-viewer/conversation-viewer.vala
@@ -656,6 +656,7 @@ public class ConversationViewer : Gtk.Stack {
             show_message(row, false);
         }
 
+        message.start_loading.begin(cancellable_fetch);
         message_added(message);
         
         // Update the search results
diff --git a/ui/conversation-message.ui b/ui/conversation-message.ui
index 286581a..c710aa3 100644
--- a/ui/conversation-message.ui
+++ b/ui/conversation-message.ui
@@ -700,4 +700,61 @@
       <class name="tooltip"/>
     </style>
   </object>
+  <object class="GtkListStore" id="attachments_model">
+    <columns>
+      <!-- column-name icon -->
+      <column type="GdkPixbuf"/>
+      <!-- column-name label -->
+      <column type="gchararray"/>
+      <!-- column-name attachment_id -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkBox" id="attachments_box">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="orientation">vertical</property>
+    <child>
+      <object class="GtkSeparator">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">False</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkIconView" id="attachments_view">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="margin">0</property>
+        <property name="selection_mode">multiple</property>
+        <property name="item_orientation">horizontal</property>
+        <property name="model">attachments_model</property>
+        <property name="spacing">6</property>
+        <signal name="item-activated" handler="on_attachments_view_activated" swapped="no"/>
+        <child>
+          <object class="GtkCellRendererPixbuf" id="icon"/>
+          <attributes>
+            <attribute name="pixbuf">0</attribute>
+          </attributes>
+        </child>
+        <child>
+          <object class="GtkCellRendererText" id="file_name">
+            <property name="xpad">6</property>
+          </object>
+          <attributes>
+            <attribute name="text">1</attribute>
+          </attributes>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">True</property>
+        <property name="fill">True</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+  </object>
 </interface>


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