diff options
Diffstat (limited to 'main')
-rw-r--r-- | main/CMakeLists.txt | 2 | ||||
-rw-r--r-- | main/src/ui/application.vala | 1 | ||||
-rw-r--r-- | main/src/ui/conversation_content_view/file_preview_widget.vala | 90 | ||||
-rw-r--r-- | main/src/ui/conversation_content_view/file_widget.vala | 26 | ||||
-rw-r--r-- | main/src/ui/util/file_metadata_providers.vala | 27 |
5 files changed, 145 insertions, 1 deletions
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index fe7528cf..fa5f56e8 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -185,6 +185,7 @@ SOURCES src/ui/conversation_content_view/date_separator_populator.vala src/ui/conversation_content_view/file_default_widget.vala src/ui/conversation_content_view/file_image_widget.vala + src/ui/conversation_content_view/file_preview_widget.vala src/ui/conversation_content_view/file_widget.vala src/ui/conversation_content_view/item_actions.vala src/ui/conversation_content_view/message_widget.vala @@ -221,6 +222,7 @@ SOURCES src/ui/util/accounts_combo_box.vala src/ui/util/config.vala src/ui/util/data_forms.vala + src/ui/util/file_metadata_providers.vala src/ui/util/helper.vala src/ui/util/label_hybrid.vala src/ui/util/preference_group.vala diff --git a/main/src/ui/application.vala b/main/src/ui/application.vala index 918fb26c..3c816a77 100644 --- a/main/src/ui/application.vala +++ b/main/src/ui/application.vala @@ -69,6 +69,7 @@ public class Dino.Ui.Application : Adw.Application, Dino.Application { } } }); + stream_interactor.get_module(FileManager.IDENTITY).add_metadata_provider(new Util.AudioVideoFileMetadataProvider()); }); activate.connect(() => { diff --git a/main/src/ui/conversation_content_view/file_preview_widget.vala b/main/src/ui/conversation_content_view/file_preview_widget.vala new file mode 100644 index 00000000..e7cee93a --- /dev/null +++ b/main/src/ui/conversation_content_view/file_preview_widget.vala @@ -0,0 +1,90 @@ +using Gee; +using Gdk; +using Gtk; +using Xmpp; + +using Dino.Entities; + +namespace Dino.Ui { + + public class FilePreviewWidget : Box { + + private ScalingImage image; + FileDefaultWidget file_default_widget; + FileDefaultWidgetController file_default_widget_controller; + + public FilePreviewWidget() { + this.halign = Align.START; + + this.add_css_class("file-preview-widget"); + } + + public async void load_from_thumbnail(FileTransfer file_transfer, StreamInteractor stream_interactor, int MAX_WIDTH=600, int MAX_HEIGHT=300) throws GLib.Error { + Thread<ScalingImage?> thread = new Thread<ScalingImage?> (null, () => { + ScalingImage image = new ScalingImage() { halign=Align.START, visible = true, max_width = MAX_WIDTH, max_height = MAX_HEIGHT }; + Gdk.Pixbuf? pixbuf = null; + foreach (Xep.JingleContentThumbnails.Thumbnail thumbnail in file_transfer.thumbnails) { + pixbuf = ImageFileMetadataProvider.parse_thumbnail(thumbnail); + if (pixbuf != null) { + break; + } + } + if (pixbuf == null) { + warning("Can't load thumbnails of file %s", file_transfer.file_name); + Idle.add(load_from_thumbnail.callback); + throw new Error(-1, 0, "Error loading preview image"); + } + // TODO: should this be executed? If yes, before or after scaling + pixbuf = pixbuf.apply_embedded_orientation(); + + if (file_transfer.width > 0 && file_transfer.height > 0) { + pixbuf = pixbuf.scale_simple(file_transfer.width, file_transfer.height, InterpType.BILINEAR); + } else { + warning("Preview: Not scaling image, width: %d, height: %d\n", file_transfer.width, file_transfer.height); + } + if (pixbuf == null) { + warning("Can't scale thumbnail %s", file_transfer.file_name); + throw new Error(-1, 0, "Error scaling preview image"); + } + + image.load(pixbuf); + Idle.add(load_from_thumbnail.callback); + return image; + }); + yield; + this.image = thread.join(); + + file_default_widget = new FileDefaultWidget() { valign=Align.END, vexpand=false, visible=false }; + file_default_widget.image_stack.visible = false; + file_default_widget_controller = new FileDefaultWidgetController(file_default_widget); + file_default_widget_controller.set_file_transfer(file_transfer, stream_interactor); + + Overlay overlay = new Overlay(); + overlay.set_child(image); + overlay.add_overlay(file_default_widget); + overlay.set_measure_overlay(image, true); + overlay.set_clip_overlay(file_default_widget, true); + + EventControllerMotion this_motion_events = new EventControllerMotion(); + this.add_controller(this_motion_events); + this_motion_events.enter.connect(() => { + file_default_widget.visible = true; + }); + this_motion_events.leave.connect(() => { + if (file_default_widget.file_menu.popover != null && file_default_widget.file_menu.popover.visible) return; + + file_default_widget.visible = false; + }); + GestureClick gesture_click_controller = new GestureClick(); + gesture_click_controller.set_button(1); // listen for left clicks + this.add_controller(gesture_click_controller); + gesture_click_controller.pressed.connect((n_press, x, y) => { + // Check whether the click was inside the file menu. Otherwise, open the file. + this.file_default_widget.clicked(); + }); + + this.append(overlay); + } + } + +} diff --git a/main/src/ui/conversation_content_view/file_widget.vala b/main/src/ui/conversation_content_view/file_widget.vala index 02c9407a..69781f30 100644 --- a/main/src/ui/conversation_content_view/file_widget.vala +++ b/main/src/ui/conversation_content_view/file_widget.vala @@ -43,6 +43,7 @@ public class FileWidget : SizeRequestBox { enum State { IMAGE, + IMAGE_PREVIEW, DEFAULT } @@ -108,7 +109,26 @@ public class FileWidget : SizeRequestBox { } catch (Error e) { } } - if (!show_image() && state != State.DEFAULT) { + if (show_preview() && state != State.IMAGE_PREVIEW) { + var content_bak = content; + + FilePreviewWidget file_preview_widget = null; + try { + file_preview_widget = new FilePreviewWidget() { visible=true }; + yield file_preview_widget.load_from_thumbnail(file_transfer, stream_interactor); + + // If the widget changed in the meanwhile, stop + if (content != content_bak) return; + + if (content != null) this.remove(content); + content = file_preview_widget; + state = State.IMAGE_PREVIEW; + this.append(content); + return; + } catch (Error e) { } + } + + if (!show_image() && state != State.DEFAULT && state != State.IMAGE_PREVIEW) { if (content != null) this.remove(content); FileDefaultWidget default_file_widget = new FileDefaultWidget(); default_widget_controller = new FileDefaultWidgetController(default_file_widget); @@ -140,6 +160,10 @@ public class FileWidget : SizeRequestBox { return false; } + private bool show_preview() { + return !this.file_transfer.thumbnails.is_empty; + } + public override void dispose() { if (default_widget_controller != null) default_widget_controller.dispose(); default_widget_controller = null; diff --git a/main/src/ui/util/file_metadata_providers.vala b/main/src/ui/util/file_metadata_providers.vala new file mode 100644 index 00000000..9e8d9640 --- /dev/null +++ b/main/src/ui/util/file_metadata_providers.vala @@ -0,0 +1,27 @@ +using Dino.Entities; +using Xmpp; +using Xmpp.Xep; +using Gtk; + +namespace Dino.Ui.Util { + +public class AudioVideoFileMetadataProvider: Dino.FileMetadataProvider, Object { + public bool supports_file(File file) { + string? mime_type = file.query_info("*", FileQueryInfoFlags.NONE).get_content_type(); + if (mime_type == null) { + return false; + } + return mime_type.has_prefix("audio") || mime_type.has_prefix("video"); + } + + public async void fill_metadata(File file, Xep.FileMetadataElement.FileMetadata metadata) { + MediaFile media = MediaFile.for_file(file); + media.notify["prepared"].connect((object, pspec) => { + Idle.add(fill_metadata.callback); + }); + yield; + metadata.length = media.duration / 1000; + } +} + +}
\ No newline at end of file |