aboutsummaryrefslogtreecommitdiff
path: root/main/src
diff options
context:
space:
mode:
Diffstat (limited to 'main/src')
-rw-r--r--main/src/ui/application.vala1
-rw-r--r--main/src/ui/conversation_content_view/file_preview_widget.vala90
-rw-r--r--main/src/ui/conversation_content_view/file_widget.vala26
-rw-r--r--main/src/ui/util/file_metadata_providers.vala27
4 files changed, 143 insertions, 1 deletions
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