diff options
author | fiaxh <git@mx.ax.lt> | 2017-08-30 00:03:37 +0200 |
---|---|---|
committer | fiaxh <git@mx.ax.lt> | 2017-08-31 18:54:38 +0200 |
commit | a257b163376174e4f5efcbc82c9fdd56463c3191 (patch) | |
tree | 5aedafe95426e732d4b4790edc4e23de25844e5d /main | |
parent | 9b5bd0ccf0cdeb49f900450df8eea41c6a0dea75 (diff) | |
download | dino-a257b163376174e4f5efcbc82c9fdd56463c3191.tar.gz dino-a257b163376174e4f5efcbc82c9fdd56463c3191.zip |
Download & inline display images
Diffstat (limited to 'main')
-rw-r--r-- | main/CMakeLists.txt | 3 | ||||
-rw-r--r-- | main/data/conversation_summary/image_toolbar.ui | 64 | ||||
-rw-r--r-- | main/src/ui/add_conversation/list_row.vala | 2 | ||||
-rw-r--r-- | main/src/ui/avatar_generator.vala | 7 | ||||
-rw-r--r-- | main/src/ui/conversation_summary/conversation_item_skeleton.vala | 9 | ||||
-rw-r--r-- | main/src/ui/conversation_summary/conversation_view.vala | 39 | ||||
-rw-r--r-- | main/src/ui/conversation_summary/file_populator.vala | 168 | ||||
-rw-r--r-- | main/src/ui/conversation_summary/image_display.vala | 0 | ||||
-rw-r--r-- | main/src/ui/conversation_summary/message_populator.vala | 7 | ||||
-rw-r--r-- | main/src/ui/util/helper.vala | 2 |
10 files changed, 281 insertions, 20 deletions
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index c9fe88f1..44cf3c6a 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -32,6 +32,7 @@ set(RESOURCE_LIST conversation_selector/view.ui conversation_selector/chat_row_tooltip.ui conversation_selector/conversation_row.ui + conversation_summary/image_toolbar.ui conversation_summary/message_item.ui conversation_summary/view.ui manage_accounts/account_row.ui @@ -101,6 +102,8 @@ SOURCES src/ui/conversation_summary/conversation_item_skeleton.vala src/ui/conversation_summary/conversation_view.vala src/ui/conversation_summary/default_message_display.vala + src/ui/conversation_summary/file_populator.vala + src/ui/conversation_summary/image_display.vala src/ui/conversation_summary/message_populator.vala src/ui/conversation_summary/message_textview.vala src/ui/conversation_summary/slashme_message_display.vala diff --git a/main/data/conversation_summary/image_toolbar.ui b/main/data/conversation_summary/image_toolbar.ui new file mode 100644 index 00000000..791dc2b9 --- /dev/null +++ b/main/data/conversation_summary/image_toolbar.ui @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <object class="GtkBox" id="main"> + <property name="valign">end</property> + <property name="margin">10</property> + <property name="visible">True</property> + <child> + <object class="GtkToolItem"> + <property name="visible">True</property> + <child> + <object class="GtkBox" id="url_box"> + <property name="visible">True</property> + <child> + <object class="GtkLabel" id="url_label"> + <property name="max_width_chars">10</property> + <property name="ellipsize">middle</property> + <property name="hexpand">True</property> + <property name="margin-left">5</property> + <property name="visible">True</property> + </object> + </child> + <child> + <object class="GtkButton" id="copy_button"> + <property name="visible">True</property> + <property name="relief">none</property> + <child> + <object class="GtkImage" id="copy_image"> + <property name="icon-name">edit-copy-symbolic</property> + <property name="icon-size">1</property> + <property name="visible">True</property> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkToolItem"> + <property name="visible">True</property> + <child> + <object class="GtkToolItem"> + <property name="visible">True</property> + <child> + <object class="GtkButton" id="open_button"> + <property name="margin-left">5</property> + <property name="relief">none</property> + <property name="visible">True</property> + <child> + <object class="GtkImage" id="open_image"> + <property name="icon-name">view-fullscreen-symbolic</property> + <property name="icon-size">1</property> + <property name="visible">True</property> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </object> +</interface> diff --git a/main/src/ui/add_conversation/list_row.vala b/main/src/ui/add_conversation/list_row.vala index 5b600786..1f69074d 100644 --- a/main/src/ui/add_conversation/list_row.vala +++ b/main/src/ui/add_conversation/list_row.vala @@ -32,7 +32,7 @@ public class ListRow : ListBoxRow { via_label.visible = false; } name_label.label = display_name; - image.set_from_pixbuf((new AvatarGenerator(35, 35)).draw_jid(stream_interactor, jid, account)); + image.set_from_pixbuf((new AvatarGenerator(35, 35, image.scale_factor)).draw_jid(stream_interactor, jid, account)); } } diff --git a/main/src/ui/avatar_generator.vala b/main/src/ui/avatar_generator.vala index 10406699..7e44f7e2 100644 --- a/main/src/ui/avatar_generator.vala +++ b/main/src/ui/avatar_generator.vala @@ -32,7 +32,7 @@ public class AvatarGenerator { if (real_jid != null && stream_interactor.get_module(AvatarManager.IDENTITY).get_avatar(account, real_jid) != null) { jid = real_jid; } - return crop_corners(draw_tile(jid, account, width * scale_factor, height * scale_factor)); + return crop_corners(draw_tile(jid, account, width * scale_factor, height * scale_factor), 3 * scale_factor); } public Pixbuf draw_message(StreamInteractor stream_interactor, Message message) { @@ -52,7 +52,7 @@ public class AvatarGenerator { public Pixbuf draw_text(string text) { Pixbuf pixbuf = draw_colored_rectangle_text(COLOR_GREY, text, width, height); - return crop_corners(pixbuf); + return crop_corners(pixbuf, 3 * scale_factor); } public AvatarGenerator set_greyscale(bool greyscale) { @@ -207,8 +207,7 @@ public class AvatarGenerator { return pixbuf_get_from_surface(context.get_target(), 0, 0, pixbuf.width, pixbuf.height); } - private Pixbuf crop_corners(Pixbuf pixbuf, double radius = 3) { - radius *= scale_factor; + public static Pixbuf crop_corners(Pixbuf pixbuf, double radius = 3) { Context ctx = new Context(new ImageSurface(Format.ARGB32, pixbuf.width, pixbuf.height)); cairo_set_source_pixbuf(ctx, pixbuf, 0, 0); double degrees = Math.PI / 180.0; diff --git a/main/src/ui/conversation_summary/conversation_item_skeleton.vala b/main/src/ui/conversation_summary/conversation_item_skeleton.vala index f9340c84..1eb76840 100644 --- a/main/src/ui/conversation_summary/conversation_item_skeleton.vala +++ b/main/src/ui/conversation_summary/conversation_item_skeleton.vala @@ -84,6 +84,7 @@ public class ConversationItemSkeleton : Grid { private void update_received() { bool all_received = true; bool all_read = true; + bool all_sent = true; foreach (Plugins.MetaConversationItem item in items) { if (item.mark == Message.Marked.WONTSEND) { received_image.visible = true; @@ -96,6 +97,9 @@ public class ConversationItemSkeleton : Grid { all_read = false; if (item.mark != Message.Marked.RECEIVED) { all_received = false; + if (item.mark == Message.Marked.UNSENT) { + all_sent = false; + } } } } @@ -105,6 +109,9 @@ public class ConversationItemSkeleton : Grid { } else if (all_received) { received_image.visible = true; received_image.set_from_icon_name("dino-tick-symbolic", IconSize.SMALL_TOOLBAR); + } else if (!all_sent) { + received_image.visible = true; + received_image.set_from_icon_name("image-loading-symbolic", IconSize.SMALL_TOOLBAR); } else if (received_image.visible) { received_image.set_from_icon_name("image-loading-symbolic", IconSize.SMALL_TOOLBAR); } @@ -130,7 +137,7 @@ public class ConversationItemSkeleton : Grid { return format_time(datetime, /* xgettext:no-c-format */ /* Month, day and time in 24h format (w/o seconds) */ _("%b %d, %H∶%M"), /* xgettext:no-c-format */ /* Month, day and time in 12h format (w/o seconds) */ _("%b %d, %l∶%M %p")); - } else if (datetime.get_day_of_month() != new DateTime.now_utc().get_day_of_month()) { + } else if (datetime.get_day_of_month() != now.get_day_of_month()) { return format_time(datetime, /* xgettext:no-c-format */ /* Day of week and time in 24h format (w/o seconds) */ _("%a, %H∶%M"), /* xgettext:no-c-format */ /* Day of week and time in 12h format (w/o seconds) */_("%a, %l∶%M %p")); diff --git a/main/src/ui/conversation_summary/conversation_view.vala b/main/src/ui/conversation_summary/conversation_view.vala index b090e5d7..370f7943 100644 --- a/main/src/ui/conversation_summary/conversation_view.vala +++ b/main/src/ui/conversation_summary/conversation_view.vala @@ -39,6 +39,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { Application app = GLib.Application.get_default() as Application; app.plugin_registry.register_conversation_item_populator(new ChatStatePopulator(stream_interactor)); + app.plugin_registry.register_conversation_item_populator(new FilePopulator(stream_interactor)); Timeout.add_seconds(60, () => { foreach (ConversationItemSkeleton item_skeleton in item_skeletons) { @@ -59,30 +60,33 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { animate = false; Timeout.add(20, () => { animate = true; return false; }); - message_item_populator.init(conversation, this); - message_item_populator.populate_number(conversation, new DateTime.now_utc(), 50); - Dino.Application app = Dino.Application.get_default(); foreach (Plugins.ConversationItemPopulator populator in app.plugin_registry.conversation_item_populators) { populator.init(conversation, this, Plugins.WidgetType.GTK); } + message_item_populator.init(conversation, this); + message_item_populator.populate_number(conversation, new DateTime.now_utc(), 50); stack.set_visible_child_name("main"); } public void insert_item(Plugins.MetaConversationItem item) { - meta_items.add(item); - if (!item.can_merge || !merge_back(item)) { - insert_new(item); + lock (meta_items) { + meta_items.add(item); + if (!item.can_merge || !merge_back(item)) { + insert_new(item); + } } } public void remove_item(Plugins.MetaConversationItem item) { - main.remove(widgets[item]); - widgets.unset(item); - meta_items.remove(item); - item_skeletons.remove(item_item_skeletons[item]); - item_item_skeletons.unset(item); + lock (meta_items) { + main.remove(widgets[item]); + widgets.unset(item); + meta_items.remove(item); + item_skeletons.remove(item_item_skeletons[item]); + item_item_skeletons.unset(item); + } } private bool merge_back(Plugins.MetaConversationItem item) { @@ -125,6 +129,19 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { widgets[item] = insert; force_alloc_width(insert, main.get_allocated_width()); main.reorder_child(insert, index); + + if (index == 0) { + Dino.Application app = Dino.Application.get_default(); + if (item_skeletons.size == 1) { + foreach (Plugins.ConversationItemPopulator populator in app.plugin_registry.conversation_item_populators) { + populator.populate_between_widgets(conversation, item.sort_time, new DateTime.now_utc()); + } + } else { + foreach (Plugins.ConversationItemPopulator populator in app.plugin_registry.conversation_item_populators) { + populator.populate_between_widgets(conversation, item.sort_time, meta_items.higher(item).sort_time); + } + } + } } private void on_upper_notify() { diff --git a/main/src/ui/conversation_summary/file_populator.vala b/main/src/ui/conversation_summary/file_populator.vala new file mode 100644 index 00000000..bdaeaa41 --- /dev/null +++ b/main/src/ui/conversation_summary/file_populator.vala @@ -0,0 +1,168 @@ +using Gee; +using Gtk; + +using Dino.Entities; +using Xmpp; + +namespace Dino.Ui.ConversationSummary { + +class FilePopulator : Plugins.ConversationItemPopulator, Object { + + public string id { get { return "file"; } } + + private StreamInteractor? stream_interactor; + private Conversation? current_conversation; + private Plugins.ConversationItemCollection? item_collection; + + public FilePopulator(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + + stream_interactor.get_module(FileManager.IDENTITY).received_file.connect((file_transfer) => { + if (current_conversation != null && current_conversation.account.equals(file_transfer.account) && current_conversation.counterpart.equals_bare(file_transfer.counterpart)) { + insert_file(file_transfer); + } + }); + } + + public void init(Conversation conversation, Plugins.ConversationItemCollection item_collection, Plugins.WidgetType type) { + current_conversation = conversation; + this.item_collection = item_collection; + } + + public void close(Conversation conversation) { } + + public void populate_timespan(Conversation conversation, DateTime from, DateTime to) { } + + public void populate_between_widgets(Conversation conversation, DateTime from, DateTime to) { + Gee.List<FileTransfer> transfers = stream_interactor.get_module(FileManager.IDENTITY).get_file_transfers(conversation.account, conversation.counterpart, from, to); + foreach (FileTransfer transfer in transfers) { + insert_file(transfer); + } + } + + private void insert_file(FileTransfer transfer) { + if (transfer.mime_type.has_prefix("image")) { + item_collection.insert_item(new ImageItem(stream_interactor, transfer)); + } + } +} + +public class ImageItem : Plugins.MetaConversationItem { + public override Jid? jid { get; set; } + public override DateTime? sort_time { get; set; } + public override DateTime? display_time { get; set; } + public override Encryption? encryption { get; set; } + public override Entities.Message.Marked? mark { get; set; } + + public override bool can_merge { get; set; default=true; } + public override bool requires_avatar { get; set; default=true; } + public override bool requires_header { get; set; default=true; } + + private const int MAX_HEIGHT = 300; + private const int MAX_WIDTH = 600; + + private StreamInteractor stream_interactor; + private FileTransfer file_transfer; + + public ImageItem(StreamInteractor stream_interactor, FileTransfer file_transfer) { + this.stream_interactor = stream_interactor; + this.file_transfer = file_transfer; + + this.jid = file_transfer.direction == FileTransfer.DIRECTION_SENT ? new Jid.with_resource(file_transfer.account.bare_jid.to_string(), file_transfer.account.resourcepart) : file_transfer.counterpart; + this.sort_time = file_transfer.time; + this.display_time = file_transfer.time; + this.encryption = file_transfer.encryption; + this.mark = file_to_message_state(file_transfer.state); + file_transfer.notify["state"].connect_after(() => { + this.mark = file_to_message_state(file_transfer.state); + }); + } + + public override Object get_widget(Plugins.WidgetType widget_type) { + Image image = new Image() { halign=Align.START, visible = true }; + Gdk.Pixbuf pixbuf = new Gdk.Pixbuf.from_stream(file_transfer.input_stream); + + int max_scaled_height = MAX_HEIGHT * image.scale_factor; + if (pixbuf.height > max_scaled_height) { + pixbuf = pixbuf.scale_simple((int) ((double) max_scaled_height / pixbuf.height * pixbuf.width), max_scaled_height, Gdk.InterpType.BILINEAR); + } + int max_scaled_width = MAX_WIDTH * image.scale_factor; + if (pixbuf.width > max_scaled_width) { + pixbuf = pixbuf.scale_simple(max_scaled_width, (int) ((double) max_scaled_width / pixbuf.width * pixbuf.height), Gdk.InterpType.BILINEAR); + } + pixbuf = AvatarGenerator.crop_corners(pixbuf, 3 * image.get_scale_factor()); + Util.image_set_from_scaled_pixbuf(image, pixbuf); + Util.force_css(image, "* { box-shadow: 0px 0px 2px 0px rgba(0,0,0,0.1); margin: 2px; border-radius: 3px; }"); + + Builder builder = new Builder.from_resource("/im/dino/conversation_summary/image_toolbar.ui"); + Widget toolbar = builder.get_object("main") as Widget; + Util.force_background(toolbar, "rgba(0, 0, 0, 0.5)"); + Util.force_css(toolbar, "* { padding: 3px; border-radius: 3px; }"); + + Label url_label = builder.get_object("url_label") as Label; + Util.force_color(url_label, "#eee"); + file_transfer.notify["info"].connect_after(() => { update_info(url_label, file_transfer.info); }); + update_info(url_label, file_transfer.info); + Box url_box = builder.get_object("url_box") as Box; + + Image copy_image = builder.get_object("copy_image") as Image; + Util.force_css(copy_image, "*:not(:hover) { color: #eee; }"); + Button copy_button = builder.get_object("copy_button") as Button; + Util.force_css(copy_button, "*:hover { background-color: rgba(255,255,255,0.3); border-color: transparent; }"); + copy_button.clicked.connect(() => { + if (file_transfer.info != null) Clipboard.get_default(Gdk.Display.get_default()).set_text(file_transfer.info, file_transfer.info.length); + }); + + Image open_image = builder.get_object("open_image") as Image; + Util.force_css(open_image, "*:not(:hover) { color: #eee; }"); + Button open_button = builder.get_object("open_button") as Button; + Util.force_css(open_button, "*:hover { background-color: rgba(255,255,255,0.3); border-color: transparent; }"); + open_button.clicked.connect(() => { + try{ + AppInfo.launch_default_for_uri(file_transfer.info, null); + } catch (Error err) { + print("Tryed to open " + file_transfer.info); + } + }); + + Revealer toolbar_revealer = new Revealer() { transition_type=RevealerTransitionType.CROSSFADE, transition_duration=400, visible=true }; + toolbar_revealer.add(toolbar); + + Grid grid = new Grid() { visible=true }; + grid.attach(toolbar_revealer, 0, 0, 1, 1); + grid.attach(image, 0, 0, 1, 1); + + EventBox event_box = new EventBox() { halign=Align.START, visible=true }; + event_box.add(grid); + event_box.enter_notify_event.connect(() => { toolbar_revealer.reveal_child = true; return false; }); + event_box.leave_notify_event.connect(() => { toolbar_revealer.reveal_child = false; return false; }); + + return event_box; + } + + private void update_info(Label url_label, string? info) { + string url = info ?? ""; + if (url.has_prefix("https://")) url = url.substring(8); + if (url.has_prefix("http://")) url = url.substring(7); + if (url.has_prefix("www.")) url = url.substring(4); + string[] slash_split = url.split("/"); + if (slash_split.length > 2) url = slash_split[0] + "/…/" + slash_split[slash_split.length - 1]; + url_label.label = url; + } + + private Entities.Message.Marked file_to_message_state(FileTransfer.State state) { + switch (state) { + case FileTransfer.State.IN_PROCESS: + return Entities.Message.Marked.UNSENT; + case FileTransfer.State.COMPLETE: + return Entities.Message.Marked.NONE; + case FileTransfer.State.NOT_STARTED: + return Entities.Message.Marked.UNSENT; + case FileTransfer.State.FAILED: + return Entities.Message.Marked.WONTSEND; + } + assert_not_reached(); + } +} + +} diff --git a/main/src/ui/conversation_summary/image_display.vala b/main/src/ui/conversation_summary/image_display.vala new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/main/src/ui/conversation_summary/image_display.vala diff --git a/main/src/ui/conversation_summary/message_populator.vala b/main/src/ui/conversation_summary/message_populator.vala index 2c3eccd2..f6d55a92 100644 --- a/main/src/ui/conversation_summary/message_populator.vala +++ b/main/src/ui/conversation_summary/message_populator.vala @@ -47,14 +47,17 @@ public class MessagePopulator : Object { if (!conversation.equals(current_conversation)) return; Plugins.MessageDisplayProvider? best_provider = null; - int priority = -1; + double priority = -1; Application app = GLib.Application.get_default() as Application; foreach (Plugins.MessageDisplayProvider provider in app.plugin_registry.message_displays) { if (provider.can_display(message) && provider.priority > priority) { best_provider = provider; + priority = provider.priority; } } - Plugins.MetaConversationItem meta_item = best_provider.get_item(message, conversation); + Plugins.MetaConversationItem? meta_item = best_provider.get_item(message, conversation); + if (meta_item == null) return; + meta_item.mark = message.marked; message.notify["marked"].connect(() => { meta_item.mark = message.marked; diff --git a/main/src/ui/util/helper.vala b/main/src/ui/util/helper.vala index a2dde504..c94752ed 100644 --- a/main/src/ui/util/helper.vala +++ b/main/src/ui/util/helper.vala @@ -85,7 +85,7 @@ public static string get_message_display_name(StreamInteractor stream_interactor } public static void image_set_from_scaled_pixbuf(Image image, Gdk.Pixbuf pixbuf, int scale = 0) { - if (scale == 0) scale = image.get_scale_factor(); + if (scale == 0) scale = image.scale_factor; image.set_from_surface(Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale, image.get_window())); } |