[geary/wip/765516-gtk-widget-conversation-viewer: 45/169] Reenable and update code for displayed attachments.
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/765516-gtk-widget-conversation-viewer: 45/169] Reenable and update code for displayed attachments.
- Date: Mon, 12 Sep 2016 13:16:08 +0000 (UTC)
commit 5fc350212cd2ae297e2a08bfe003c0c872851c80
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 1d5ac97..2af0caf 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -216,7 +216,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);
@@ -298,7 +297,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);
@@ -1975,8 +1973,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()),
@@ -1984,11 +1982,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.
@@ -2654,10 +2652,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 36e53ac..3b3364f 100644
--- a/src/client/conversation-viewer/conversation-viewer.vala
+++ b/src/client/conversation-viewer/conversation-viewer.vala
@@ -655,6 +655,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]