[geary/wip/765516-gtk-widget-conversation-viewer] Replace Gtk.IconView with FlowBox for displaying email attachments.
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/765516-gtk-widget-conversation-viewer] Replace Gtk.IconView with FlowBox for displaying email attachments.
- Date: Tue, 16 Aug 2016 15:39:12 +0000 (UTC)
commit 36227c59da273f8a3d8031737707abeb0ca3778f
Author: Michael James Gratton <mike vee net>
Date: Tue Aug 16 18:08:21 2016 +1000
Replace Gtk.IconView with FlowBox for displaying email attachments.
po/POTFILES.in | 1 +
src/client/application/geary-controller.vala | 50 ++--
.../conversation-viewer/conversation-email.vala | 423 +++++++++-----------
ui/CMakeLists.txt | 1 +
ui/conversation-email-attachment-view.ui | 50 +++
ui/conversation-email.ui | 119 ++++--
6 files changed, 363 insertions(+), 281 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 99797ad..1b43efc 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -390,6 +390,7 @@ src/mailer/main.vala
[type: gettext/glade]ui/composer_accelerators.ui
[type: gettext/glade]ui/composer.glade
[type: gettext/glade]ui/conversation-email.ui
+[type: gettext/glade]ui/conversation-email-attachment-view.ui
[type: gettext/glade]ui/conversation-email-menus.ui
[type: gettext/glade]ui/conversation-message.ui
[type: gettext/glade]ui/conversation-message-menus.ui
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index c0dd4b8..afb2313 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -2041,8 +2041,7 @@ public class GearyController : Geary.BaseObject {
}
}
- private void on_attachments_activated(
- Gee.Collection<ConversationEmail.AttachmentInfo> attachments) {
+ private void on_attachments_activated(Gee.Collection<Geary.Attachment> attachments) {
if (GearyApplication.instance.config.ask_open_attachment) {
QuestionDialog ask_to_open = new QuestionDialog.with_checkbox(main_window,
_("Are you sure you want to open these attachments?"),
@@ -2055,9 +2054,17 @@ public class GearyController : Geary.BaseObject {
GearyApplication.instance.config.ask_open_attachment = !ask_to_open.is_checked;
}
- foreach (ConversationEmail.AttachmentInfo info in attachments) {
- if (info.app == null) {
- string content_type = info.attachment.content_type.get_mime_type();
+ foreach (Geary.Attachment attachment in attachments) {
+ string gio_content_type = ContentType.from_mime_type(
+ attachment.content_type.get_mime_type()
+ );
+ AppInfo? app = null;
+ if (!ContentType.can_be_executable(gio_content_type) &&
+ !ContentType.is_unknown(gio_content_type)) {
+ app = AppInfo.get_default_for_type(gio_content_type, false);
+ }
+ if (app == null) {
+ string content_type = attachment.content_type.get_mime_type();
Gtk.AppChooserDialog app_chooser =
new Gtk.AppChooserDialog.for_content_type(
this.main_window,
@@ -2065,21 +2072,18 @@ public class GearyController : Geary.BaseObject {
content_type
);
if (app_chooser.run() == Gtk.ResponseType.OK) {
- info.app = app_chooser.get_app_info();
+ app = app_chooser.get_app_info();
}
app_chooser.hide();
}
- if (info.app != null) {
+ if (app != null) {
List<File> files = new List<File>();
- files.append(info.attachment.file);
+ files.append(attachment.file);
try {
- info.app.launch(files, null);
+ app.launch(files, null);
} catch (Error error) {
- warning(
- "Failed to launch %s: %s\n",
- info.app.get_name(),
- error.message
- );
+ warning("Failed to launch %s: %s\n",
+ app.get_name(), error.message);
}
}
}
@@ -2102,11 +2106,7 @@ public class GearyController : Geary.BaseObject {
: Gtk.FileChooserConfirmation.SELECT_AGAIN;
}
- private void on_save_attachments(
- Gee.Collection<ConversationEmail.AttachmentInfo> attachments) {
- if (attachments.size == 0)
- return;
-
+ private void on_save_attachments(Gee.Collection<Geary.Attachment> attachments) {
Gtk.FileChooserAction action = (attachments.size == 1)
? Gtk.FileChooserAction.SAVE
: Gtk.FileChooserAction.SELECT_FOLDER;
@@ -2115,10 +2115,10 @@ public class GearyController : Geary.BaseObject {
if (last_save_directory != null)
dialog.set_current_folder(last_save_directory.get_path());
if (attachments.size == 1) {
- Gee.Iterator<ConversationEmail.AttachmentInfo> it = attachments.iterator();
+ Gee.Iterator<Geary.Attachment> it = attachments.iterator();
it.next();
- ConversationEmail.AttachmentInfo info = it.get();
- dialog.set_current_name(info.attachment.file.get_basename());
+ Geary.Attachment attachment = it.get();
+ dialog.set_current_name(attachment.file.get_basename());
dialog.set_do_overwrite_confirmation(true);
// use custom overwrite confirmation so it looks consistent whether one or many
// attachments are being saved
@@ -2143,9 +2143,9 @@ public class GearyController : Geary.BaseObject {
debug("Saving attachments to %s", destination.get_path());
// Save each one, checking for overwrite only if multiple attachments are being written
- foreach (ConversationEmail.AttachmentInfo info in attachments) {
- File source_file = info.attachment.file;
- File dest_file = (attachments.size == 1) ? destination :
destination.get_child(info.attachment.file.get_basename());
+ foreach (Geary.Attachment attachment in attachments) {
+ File source_file = attachment.file;
+ File dest_file = (attachments.size == 1) ? destination :
destination.get_child(attachment.file.get_basename());
if (attachments.size > 1 && dest_file.query_exists() && !do_overwrite_confirmation(dest_file))
return;
diff --git a/src/client/conversation-viewer/conversation-email.vala
b/src/client/conversation-viewer/conversation-email.vala
index 3582b37..14f136a 100644
--- a/src/client/conversation-viewer/conversation-email.vala
+++ b/src/client/conversation-viewer/conversation-email.vala
@@ -17,10 +17,10 @@
*/
[GtkTemplate (ui = "/org/gnome/Geary/conversation-email.ui")]
public class ConversationEmail : Gtk.Box {
-
// This isn't a Gtk.Grid since when added to a Gtk.ListBoxRow the
// hover style isn't applied to it.
+
/**
* Iterator that returns all message views in an email view.
*/
@@ -88,18 +88,117 @@ public class ConversationEmail : Gtk.Box {
}
- /**
- * Information related to a specific attachment.
- */
- public class AttachmentInfo : GLib.Object {
- // Extends GObject since we put it in a ListStore
+
+ // Displays an attachment's icon and details
+ [GtkTemplate (ui = "/org/gnome/Geary/conversation-email-attachment-view.ui")]
+ private class AttachmentView : Gtk.Grid {
public Geary.Attachment attachment { get; private set; }
- public AppInfo? app { get; internal set; default = null; }
+ [GtkChild]
+ private Gtk.Image icon;
- internal AttachmentInfo(Geary.Attachment attachment) {
+ [GtkChild]
+ private Gtk.Label filename;
+
+ [GtkChild]
+ private Gtk.Label description;
+
+ private string gio_content_type;
+
+ public AttachmentView(Geary.Attachment attachment) {
this.attachment = attachment;
+ string mime_content_type = attachment.content_type.get_mime_type();
+ this.gio_content_type = ContentType.from_mime_type(
+ mime_content_type
+ );
+
+ string file_name = null;
+ if (attachment.has_supplied_filename) {
+ file_name = attachment.file.get_basename();
+ }
+ string file_desc = ContentType.get_description(gio_content_type);
+ if (ContentType.is_unknown(gio_content_type)) {
+ // Translators: This is the file type displayed for
+ // attachments with unknown file types.
+ file_desc = _("Unknown");
+ }
+ string file_size = Files.get_filesize_as_string(attachment.filesize);
+
+ // 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 = file_desc;
+ file_desc = file_size;
+ } else {
+ // Translators: The first argument will be a
+ // description of the document type, the second will
+ // be a human-friendly size string. For example:
+ // Document (100.9MB)
+ file_desc = _("%s (%s)".printf(file_desc, file_size));
+ }
+ this.filename.set_text(file_name);
+ this.description.set_text(file_desc);
+ }
+
+ internal async void load_icon(Cancellable load_cancelled) {
+ Gdk.Pixbuf? pixbuf = null;
+
+ // XXX We need to hook up to GtkWidget::style-set and
+ // reload the icon when the theme changes.
+
+ int window_scale = get_scale_factor();
+ try {
+ // If the file is an image, use it. Otherwise get the
+ // icon for this mime_type.
+ if (this.attachment.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 this.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
+ Icon icon = ContentType.get_icon(this.gio_content_type);
+ Gtk.IconTheme theme = Gtk.IconTheme.get_default();
+ Gtk.IconLookupFlags flags = Gtk.IconLookupFlags.DIR_LTR;
+ if (get_direction() == Gtk.TextDirection.RTL) {
+ flags = Gtk.IconLookupFlags.DIR_RTL;
+ }
+ Gtk.IconInfo? icon_info = theme.lookup_by_gicon_for_scale(
+ icon, ATTACHMENT_ICON_SIZE, window_scale, flags
+ );
+ if (icon_info != null) {
+ pixbuf = yield icon_info.load_icon_async(load_cancelled);
+ }
+ }
+ } catch (Error error) {
+ debug("Failed to load icon for attachment '%s': %s",
+ this.attachment.id,
+ error.message);
+ }
+
+ if (pixbuf != null) {
+ Cairo.Surface surface = Gdk.cairo_surface_create_from_pixbuf(
+ pixbuf, window_scale, get_window()
+ );
+ this.icon.set_from_surface(surface);
+ }
}
}
@@ -118,6 +217,7 @@ public class ConversationEmail : Gtk.Box {
private const string ACTION_REPLY_ALL = "reply_all";
private const string ACTION_SAVE_ATTACHMENTS = "save_attachments";
private const string ACTION_SAVE_ALL_ATTACHMENTS = "save_all_attachments";
+ private const string ACTION_SELECT_ALL_ATTACHMENTS = "select_all_attachments";
private const string ACTION_STAR = "star";
private const string ACTION_UNSTAR = "unstar";
private const string ACTION_VIEW_SOURCE = "view_source";
@@ -168,12 +268,8 @@ public class ConversationEmail : Gtk.Box {
// A subset of the message's attachments that are displayed in the
// attachments view
- Gee.List<AttachmentInfo> displayed_attachments =
- new Gee.LinkedList<AttachmentInfo>();
-
- // A subset of the message's attachments selected by the user
- Gee.Set<AttachmentInfo> selected_attachments =
- new Gee.HashSet<AttachmentInfo>();
+ Gee.Collection<Geary.Attachment> displayed_attachments =
+ new Gee.LinkedList<Geary.Attachment>();
// Message-specific actions
private SimpleActionGroup message_actions = new SimpleActionGroup();
@@ -206,13 +302,14 @@ public class ConversationEmail : Gtk.Box {
private Gtk.Grid attachments;
[GtkChild]
- private Gtk.IconView attachments_view;
+ private Gtk.FlowBox attachments_view;
[GtkChild]
- private Gtk.ListStore attachments_model;
+ private Gtk.Button select_all_attachments;
private Gtk.Menu attachments_menu;
+
/** Fired when the user clicks "reply" in the message menu. */
public signal void reply_to_message();
@@ -239,10 +336,14 @@ public class ConversationEmail : Gtk.Box {
public signal void link_activated(string link);
/** Fired when the user activates an attachment. */
- public signal void attachments_activated(Gee.Collection<AttachmentInfo> attachments);
+ public signal void attachments_activated(
+ Gee.Collection<Geary.Attachment> attachments
+ );
/** Fired when the user saves an attachment. */
- public signal void save_attachments(Gee.Collection<AttachmentInfo> attachments);
+ public signal void save_attachments(
+ Gee.Collection<Geary.Attachment> attachments
+ );
/** Fired the edit draft button is clicked. */
public signal void edit_draft();
@@ -253,6 +354,7 @@ public class ConversationEmail : Gtk.Box {
/** Fired when the user selects text in a message. */
internal signal void body_selection_changed(bool has_selection);
+
/**
* Constructs a new view to display an email.
*
@@ -281,8 +383,8 @@ public class ConversationEmail : Gtk.Box {
add_action(ACTION_MARK_UNREAD_DOWN).activate.connect(() => {
mark_email_from_here(Geary.EmailFlags.UNREAD, null);
});
- add_action(ACTION_OPEN_ATTACHMENTS).activate.connect(() => {
- attachments_activated(selected_attachments);
+ add_action(ACTION_OPEN_ATTACHMENTS, false).activate.connect(() => {
+ attachments_activated(get_selected_attachments());
});
add_action(ACTION_REPLY_ALL).activate.connect(() => {
reply_all_message();
@@ -290,11 +392,14 @@ public class ConversationEmail : Gtk.Box {
add_action(ACTION_REPLY_SENDER).activate.connect(() => {
reply_to_message();
});
- add_action(ACTION_SAVE_ATTACHMENTS).activate.connect(() => {
- save_attachments(selected_attachments);
+ add_action(ACTION_SAVE_ATTACHMENTS, false).activate.connect(() => {
+ save_attachments(get_selected_attachments());
});
- add_action(ACTION_SAVE_ALL_ATTACHMENTS).activate.connect(() => {
- save_attachments(displayed_attachments);
+ add_action(ACTION_SAVE_ALL_ATTACHMENTS, false).activate.connect(() => {
+ save_attachments(this.displayed_attachments);
+ });
+ add_action(ACTION_SELECT_ALL_ATTACHMENTS, false).activate.connect(() => {
+ this.attachments_view.select_all();
});
add_action(ACTION_STAR).activate.connect(() => {
mark_email(Geary.EmailFlags.FLAGGED, null);
@@ -465,8 +570,9 @@ public class ConversationEmail : Gtk.Box {
return new MessageViewIterator(this);
}
- private SimpleAction add_action(string name) {
+ private SimpleAction add_action(string name, bool enabled = true) {
SimpleAction action = new SimpleAction(name, null);
+ action.set_enabled(enabled);
message_actions.add_action(action);
return action;
}
@@ -527,8 +633,63 @@ public class ConversationEmail : Gtk.Box {
}
}
+ private async void load_attachments(Cancellable load_cancelled) {
+ // Do we have any attachments to be displayed? This relies on
+ // the primary and any attached message bodies having being
+ // already loaded, so that we know which attachments have been
+ // shown inline and hence do not need to be included here.
+ foreach (Geary.Attachment attachment in email.attachments) {
+ if (!(attachment.content_id in inlined_content_ids)) {
+ Geary.Mime.DispositionType? disposition = null;
+ if (attachment.content_disposition != null) {
+ disposition = attachment.content_disposition.disposition_type;
+ }
+ // Display both any attachment and inline parts that
+ // have already not been inlined. Although any inline
+ // parts should be referred to by other content in a
+ // multipart/related or multipart/alternative
+ // container, or inlined if in a multipart/mixed
+ // container, this cannot be not guaranteed. C.f. Bug
+ // 769868.
+ if (disposition != null &&
+ disposition == Geary.Mime.DispositionType.ATTACHMENT ||
+ disposition == Geary.Mime.DispositionType.INLINE) {
+ this.displayed_attachments.add(attachment);
+ }
+ }
+ }
+
+ if (!this.displayed_attachments.is_empty) {
+ this.attachments_button.show();
+ this.attachments_button.set_sensitive(!this.is_collapsed);
+ this.primary_message.body.add(this.attachments);
+
+ if (this.displayed_attachments.size > 1) {
+ this.select_all_attachments.show();
+ set_action_enabled(ACTION_SELECT_ALL_ATTACHMENTS, true);
+ }
+
+ foreach (Geary.Attachment attachment in this.displayed_attachments) {
+ AttachmentView view = new AttachmentView(attachment);
+ this.attachments_view.add(view);
+ yield view.load_icon(load_cancelled);
+ }
+ }
+ }
+
+ internal Gee.Collection<Geary.Attachment> get_selected_attachments() {
+ Gee.LinkedList<Geary.Attachment> selected =
+ new Gee.LinkedList<Geary.Attachment>();
+ foreach (Gtk.FlowBoxChild child in
+ this.attachments_view.get_selected_children()) {
+ selected.add(((AttachmentView) child.get_child()).attachment);
+ }
+ return selected;
+ }
+
private void print() {
- // XXX this isn't anywhere near good enough
+ // XXX This isn't anywhere near good enough - headers aren't
+ // being printed.
primary_message.web_view.get_main_frame().print();
}
@@ -573,209 +734,23 @@ public class ConversationEmail : Gtk.Box {
}
[GtkCallback]
- private void on_attachments_view_activated(Gtk.IconView view, Gtk.TreePath path) {
- AttachmentInfo attachment_info = attachment_info_for_view_path(path);
+ private void on_attachments_child_activated(Gtk.FlowBox view,
+ Gtk.FlowBoxChild child) {
attachments_activated(
- Geary.iterate<AttachmentInfo>(attachment_info).to_array_list()
+ Geary.iterate<Geary.Attachment>(
+ ((AttachmentView) child.get_child()).attachment
+ ).to_array_list()
);
}
[GtkCallback]
- private void on_attachments_view_selection_changed() {
- selected_attachments.clear();
- List<Gtk.TreePath> selected = attachments_view.get_selected_items();
- selected.foreach((path) => {
- selected_attachments.add(attachment_info_for_view_path(path));
- });
- }
-
- [GtkCallback]
- private bool on_attachments_view_button_press_event(Gdk.EventButton event) {
- if (event.button != Gdk.BUTTON_SECONDARY) {
- return false;
- }
-
- Gtk.TreePath path = attachments_view.get_path_at_pos(
- (int) event.x, (int) event.y
- );
- AttachmentInfo attachment = attachment_info_for_view_path(path);
- if (!selected_attachments.contains(attachment)) {
- attachments_view.unselect_all();
- attachments_view.select_path(path);
- }
- attachments_menu.popup(null, null, null, event.button, event.time);
- return false;
- }
-
- private AttachmentInfo attachment_info_for_view_path(Gtk.TreePath path) {
- Gtk.TreeIter iter;
- attachments_model.get_iter(out iter, path);
- Value info_value;
- attachments_model.get_value(iter, 2, out info_value);
- AttachmentInfo info = (AttachmentInfo) info_value.dup_object();
- info_value.unset();
- return info;
- }
-
- private async void load_attachments(Cancellable load_cancelled) {
- // Do we have any attachments to be displayed?
- foreach (Geary.Attachment attachment in email.attachments) {
- if (!(attachment.content_id in inlined_content_ids)) {
- Geary.Mime.DispositionType? disposition = null;
- if (attachment.content_disposition != null) {
- disposition = attachment.content_disposition.disposition_type;
- }
- // Display both any attachment and inline parts that
- // have already not been inlined. Although any inline
- // parts should be referred to by other content in a
- // multipart/related or multipart/alternative
- // container, or inlined if in a multipart/mixed
- // container, this cannot be not guaranteed. C.f. Bug
- // 769868.
- if (disposition != null &&
- disposition == Geary.Mime.DispositionType.ATTACHMENT ||
- disposition == Geary.Mime.DispositionType.INLINE) {
- displayed_attachments.add(new AttachmentInfo(attachment));
- }
- }
- }
-
- if (displayed_attachments.is_empty) {
- set_action_enabled(ACTION_OPEN_ATTACHMENTS, false);
- set_action_enabled(ACTION_SAVE_ATTACHMENTS, false);
- set_action_enabled(ACTION_SAVE_ALL_ATTACHMENTS, false);
- return;
- }
-
- // Show attachment widgets. Would like to do this in the
- // ctor but we don't know at that point if any attachments
- // will be displayed inline.
- this.attachments_button.show();
- this.attachments_button.set_sensitive(!this.is_collapsed);
- this.primary_message.body.add(this.attachments);
-
- // Add each displayed attachment to the icon view
- foreach (AttachmentInfo attachment_info in displayed_attachments) {
- Geary.Attachment attachment = attachment_info.attachment;
-
- attachment_info.app = AppInfo.get_default_for_type(
- attachment.content_type.get_mime_type(), false
- );
-
- 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_info,
- -1
- );
- }
- }
-
- 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_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 {
- debug("Unsupported attachment icon type: %s\n",
- icon.get_type().name());
- }
- }
- } catch (Error error) {
- debug("Failed to load icon for attachment '%s': %s",
- attachment.id,
- error.message);
- }
-
- return pixbuf;
+ private void on_attachments_selected_changed(Gtk.FlowBox view) {
+ uint len = view.get_selected_children().length();
+ bool not_empty = len > 0;
+ set_action_enabled(ACTION_OPEN_ATTACHMENTS, not_empty);
+ set_action_enabled(ACTION_SAVE_ATTACHMENTS, not_empty);
+ set_action_enabled(ACTION_SELECT_ALL_ATTACHMENTS,
+ len < this.displayed_attachments.size);
}
}
diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index c631a9c..72d6302 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -9,6 +9,7 @@ set(RESOURCE_LIST
STRIPBLANKS "composer.glade"
STRIPBLANKS "composer_accelerators.ui"
STRIPBLANKS "conversation-email.ui"
+ STRIPBLANKS "conversation-email-attachment-view.ui"
STRIPBLANKS "conversation-email-menus.ui"
STRIPBLANKS "conversation-message.ui"
STRIPBLANKS "conversation-message-menus.ui"
diff --git a/ui/conversation-email-attachment-view.ui b/ui/conversation-email-attachment-view.ui
new file mode 100644
index 0000000..92a5756
--- /dev/null
+++ b/ui/conversation-email-attachment-view.ui
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <template class="ConversationEmailAttachmentView" parent="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkImage" id="icon">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="pixel_size">32</property>
+ <property name="icon_name">x-office-document</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="height">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="filename">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">end</property>
+ <property name="label">filename.ext</property>
+ <property name="ellipsize">middle</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="description">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="label">type (size)</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/ui/conversation-email.ui b/ui/conversation-email.ui
index 4ce1e99..cad69a1 100644
--- a/ui/conversation-email.ui
+++ b/ui/conversation-email.ui
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
- <requires lib="gtk+" version="3.12"/>
+ <requires lib="gtk+" version="3.14"/>
<template class="ConversationEmail" parent="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -109,17 +109,8 @@
</packing>
</child>
</object>
- <object class="GtkListStore" id="attachments_model">
- <columns>
- <!-- column-name icon -->
- <column type="GdkPixbuf"/>
- <!-- column-name label -->
- <column type="gchararray"/>
- <!-- column-name attachment_info -->
- <column type="GObject"/>
- </columns>
- </object>
<object class="GtkGrid" id="attachments">
+ <property name="name">box</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
@@ -136,41 +127,105 @@
</packing>
</child>
<child>
- <object class="GtkIconView" id="attachments_view">
+ <object class="GtkFlowBox" id="attachments_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="margin">6</property>
+ <property name="margin_left">6</property>
+ <property name="margin_right">6</property>
+ <property name="margin_top">6</property>
+ <property name="margin_bottom">6</property>
<property name="hexpand">True</property>
+ <property name="homogeneous">True</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <property name="max_children_per_line">4</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="button-press-event" handler="on_attachments_view_button_press_event" swapped="no"/>
- <signal name="item-activated" handler="on_attachments_view_activated" swapped="no"/>
- <signal name="selection-changed" handler="on_attachments_view_selection_changed" swapped="no"/>
+ <property name="activate_on_single_click">False</property>
+ <signal name="child-activated" handler="on_attachments_child_activated" swapped="no"/>
+ <signal name="selected-children-changed" handler="on_attachments_selected_changed" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkActionBar" id="attachments_actions">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <style>
+ <class name="background"/>
+ </style>
+ <child>
+ <object class="GtkButton" id="open_attachments">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Open selected attachments</property>
+ <property name="action_name">eml.open_attachments</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-open-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ </packing>
+ </child>
<child>
- <object class="GtkCellRendererPixbuf" id="icon"/>
- <attributes>
- <attribute name="pixbuf">0</attribute>
- </attributes>
+ <object class="GtkButton" id="save_attachments">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Save selected attachments</property>
+ <property name="action_name">eml.save_attachments</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-save-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ </packing>
</child>
<child>
- <object class="GtkCellRendererText" id="file_name">
- <property name="xpad">6</property>
+ <object class="GtkButton" id="select_all_attachments">
+ <property name="visible">False</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Select all attachments</property>
+ <property name="action_name">eml.select_all_attachments</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">edit-select-all-symbolic</property>
+ </object>
+ </child>
</object>
- <attributes>
- <attribute name="text">1</attribute>
- </attributes>
+ <packing>
+ </packing>
</child>
- <style>
- <class name="geary-attachments"/>
- </style>
</object>
<packing>
<property name="left_attach">0</property>
- <property name="top_attach">1</property>
+ <property name="top_attach">2</property>
</packing>
</child>
+ <style>
+ <class name="view"/>
+ </style>
+ </object>
+ <object class="GtkListStore" id="attachments_model">
+ <columns>
+ <!-- column-name icon -->
+ <column type="GdkPixbuf"/>
+ <!-- column-name label -->
+ <column type="gchararray"/>
+ <!-- column-name attachment_info -->
+ <column type="GObject"/>
+ </columns>
</object>
<object class="GtkInfoBar" id="draft_infobar">
<property name="app_paintable">True</property>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]