[fractal] attachment-dialog: Use MediaContentViewer
- From: Marge Bot <marge-bot src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal] attachment-dialog: Use MediaContentViewer
- Date: Wed, 27 Apr 2022 13:17:43 +0000 (UTC)
commit 74b5b025e2e6fbdc0c140f7fb0d97883372c43a5
Author: Kévin Commaille <zecakeh tedomum fr>
Date: Sun Apr 24 16:07:09 2022 +0200
attachment-dialog: Use MediaContentViewer
Preview more file types
Part-of: <https://gitlab.gnome.org/GNOME/fractal/-/merge_requests/1085>
data/resources/ui/attachment-dialog.ui | 19 +-
.../content/room_history/attachment_dialog.rs | 29 +--
src/session/content/room_history/mod.rs | 280 ++++++++++-----------
src/session/room/mod.rs | 10 +-
4 files changed, 158 insertions(+), 180 deletions(-)
---
diff --git a/data/resources/ui/attachment-dialog.ui b/data/resources/ui/attachment-dialog.ui
index 674aea5d5..8fce210a0 100644
--- a/data/resources/ui/attachment-dialog.ui
+++ b/data/resources/ui/attachment-dialog.ui
@@ -29,24 +29,7 @@
</object>
</property>
<property name="child">
- <object class="GtkStack" id="stack">
- <child>
- <object class="AdwStatusPage">
- <property name="icon-name">face-sick-symbolic</property>
- <property name="title" translatable="yes">No Preview Available</property>
- <style>
- <class name="compact"/>
- </style>
- </object>
- </child>
- <child>
- <object class="GtkStackPage">
- <property name="name">preview</property>
- <property name="child">
- <object class="GtkImage" id="preview"/>
- </property>
- </object>
- </child>
+ <object class="ComponentsMediaContentViewer" id="media">
</object>
</property>
<child>
diff --git a/src/session/content/room_history/attachment_dialog.rs
b/src/session/content/room_history/attachment_dialog.rs
index 4e39c4bd2..6168aeee9 100644
--- a/src/session/content/room_history/attachment_dialog.rs
+++ b/src/session/content/room_history/attachment_dialog.rs
@@ -1,20 +1,16 @@
use gtk::{gdk, gio, glib, prelude::*, subclass::prelude::*, CompositeTemplate};
use once_cell::sync::Lazy;
-mod imp {
- use std::cell::RefCell;
+use crate::components::MediaContentViewer;
+mod imp {
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[template(resource = "/org/gnome/Fractal/attachment-dialog.ui")]
pub struct AttachmentDialog {
- pub file: RefCell<Option<gio::File>>,
- pub texture: RefCell<Option<gdk::Texture>>,
#[template_child]
- pub preview: TemplateChild<gtk::Image>,
- #[template_child]
- pub stack: TemplateChild<gtk::Stack>,
+ pub media: TemplateChild<MediaContentViewer>,
}
#[glib::object_subclass]
@@ -59,16 +55,17 @@ glib::wrapper! {
}
impl AttachmentDialog {
- pub fn new(window: >k::Window) -> Self {
- glib::Object::new(&[("transient-for", window)]).unwrap()
+ pub fn for_image(transient_for: >k::Window, title: &str, image: &gdk::Texture) -> Self {
+ let obj: Self = glib::Object::new(&[("transient-for", transient_for), ("title", &title)])
+ .expect("Failed to create AttachmentDialog");
+ obj.imp().media.view_image(image);
+ obj
}
- pub fn set_texture(&self, texture: &gdk::Texture) {
- let priv_ = self.imp();
- priv_.stack.set_visible_child_name("preview");
-
- priv_
- .preview
- .set_paintable(Some(texture.upcast_ref::<gdk::Paintable>()));
+ pub fn for_file(transient_for: >k::Window, title: &str, file: &gio::File) -> Self {
+ let obj: Self = glib::Object::new(&[("transient-for", transient_for), ("title", &title)])
+ .expect("Failed to create AttachmentDialog");
+ obj.imp().media.view_file(file.to_owned());
+ obj
}
}
diff --git a/src/session/content/room_history/mod.rs b/src/session/content/room_history/mod.rs
index c838267ec..949189a16 100644
--- a/src/session/content/room_history/mod.rs
+++ b/src/session/content/room_history/mod.rs
@@ -21,6 +21,7 @@ use gtk::{
subclass::prelude::*,
CompositeTemplate,
};
+use log::warn;
use matrix_sdk::ruma::events::room::message::{
EmoteMessageEventContent, FormattedBody, MessageType, RoomMessageEventContent,
TextMessageEventContent,
@@ -33,7 +34,7 @@ use self::{
state_row::StateRow, verification_info_bar::VerificationInfoBar,
};
use crate::{
- components::{CustomEntry, DragOverlay, Pill, ReactionChooser, RoomTitle},
+ components::{CustomEntry, DragOverlay, Pill, ReactionChooser, RoomTitle, Toast},
i18n::gettext_f,
session::{
content::{MarkdownPopover, RoomDetails},
@@ -41,16 +42,10 @@ use crate::{
user::UserExt,
},
spawn,
+ utils::filename_for_mime,
+ window::Window,
};
-const MIME_TYPES: &[&str] = &[
- "image/png",
- "image/jpeg",
- "image/tiff",
- "image/svg+xml",
- "image/bmp",
-];
-
mod imp {
use std::cell::{Cell, RefCell};
@@ -328,19 +323,15 @@ mod imp {
self.message_entry.add_controller(&key_events);
self.message_entry
.connect_paste_clipboard(clone!(@weak obj => move |entry| {
- spawn!(
- glib::PRIORITY_DEFAULT_IDLE,
- clone!(@weak obj => async move {
- obj.read_clipboard().await;
- }));
- let clip = obj.clipboard();
+ let formats = obj.clipboard().formats();
- // TODO Check if this is the most general condition on which
- // the clipboard contains more than text.
- let formats = clip.formats();
- let contains_mime = MIME_TYPES.iter().any(|mime| formats.contain_mime_type(mime));
- if formats.contains_type(gio::File::static_type()) || contains_mime {
+ // We only handle files and supported images.
+ if formats.contains_type(gio::File::static_type()) ||
formats.contains_type(gdk::Texture::static_type()) {
entry.stop_signal_emission_by_name("paste-clipboard");
+ spawn!(
+ clone!(@weak obj => async move {
+ obj.read_clipboard().await;
+ }));
}
}));
@@ -398,45 +389,53 @@ glib::wrapper! {
impl RoomHistory {
async fn read_clipboard(&self) {
let clipboard = self.clipboard();
-
- // Check if there is a png/jpg in the clipboard.
- let res = clipboard
- .read_future(MIME_TYPES, glib::PRIORITY_DEFAULT)
- .await;
- let body = match clipboard.read_text_future().await {
- Ok(Some(body)) => std::path::Path::new(&body)
- .file_name()
- .unwrap()
- .to_str()
- .unwrap()
- .to_string(),
- _ => gettext("Image"),
- };
- if let Ok((stream, mime)) = res {
- log::debug!("Found a {} in the clipboard", &mime);
- if let Ok(bytes) = read_stream(&stream).await {
- let mime = mime::Mime::from_str(&mime).unwrap();
- self.open_attach_dialog(bytes, mime, &body);
-
- return;
+ let formats = clipboard.formats();
+
+ if formats.contains_type(gdk::Texture::static_type()) {
+ // There is an image in the clipboard.
+ match clipboard
+ .read_value_future(gdk::Texture::static_type(), glib::PRIORITY_DEFAULT)
+ .await
+ {
+ Ok(value) => match value.get::<gdk::Texture>() {
+ Ok(texture) => {
+ self.send_image(texture).await;
+ return;
+ }
+ Err(error) => warn!("Could not get GdkTexture from value: {error:?}"),
+ },
+ Err(error) => warn!("Could not get GdkTexture from the clipboard: {error:?}"),
}
- }
- // Check if there is a file in the clipboard.
- let res = clipboard
- .read_value_future(gio::File::static_type(), glib::PRIORITY_DEFAULT)
- .await;
- if let Ok(value) = res {
- if let Ok(file) = value.get::<gio::File>() {
- log::debug!("Found a file in the clipboard");
-
- // Under some circumstances, the file will be
- // under a path we don't have access to.
- if !file.query_exists(gio::Cancellable::NONE) {
- return;
- }
+ if let Some(window) = self
+ .root()
+ .as_ref()
+ .and_then(|root| root.downcast_ref::<Window>())
+ {
+ window.add_toast(&Toast::new(&gettext("Error getting image from clipboard")));
+ }
+ } else if formats.contains_type(gio::File::static_type()) {
+ // There is a file in the clipboard.
+ match clipboard
+ .read_value_future(gio::File::static_type(), glib::PRIORITY_DEFAULT)
+ .await
+ {
+ Ok(value) => match value.get::<gio::File>() {
+ Ok(file) => {
+ self.send_file(file).await;
+ return;
+ }
+ Err(error) => warn!("Could not get file from value: {error:?}"),
+ },
+ Err(error) => warn!("Could not get file from the clipboard: {error:?}"),
+ }
- self.read_file(&file).await;
+ if let Some(window) = self
+ .root()
+ .as_ref()
+ .and_then(|root| root.downcast_ref::<Window>())
+ {
+ window.add_toast(&Toast::new(&gettext("Error getting file from clipboard")));
}
}
}
@@ -774,47 +773,30 @@ impl RoomHistory {
);
target.connect_drop(
- glib::clone!(@weak self as obj => @default-return false, move |target, value, _, _| {
- let drop = target.current_drop().unwrap();
-
- // We first try to read if we get a serialized image. In general
- // we get files, but this is useful when reading a drag-n-drop
- // from another sandboxed app.
- let formats = drop.formats();
- for mime in MIME_TYPES {
- if formats.contain_mime_type(mime) {
- log::debug!("Received drag & drop with mime type: {}", mime);
- drop.read_async(&[mime], glib::PRIORITY_DEFAULT, gio::Cancellable::NONE,
glib::clone!(@weak obj => move |res| {
- if let Ok((stream, mime)) = res {
- crate::spawn!(glib::clone!(@weak obj => async move {
- if let Ok(bytes) = read_stream(&stream).await {
- // TODO Get the actual name of the file by reading
- // the text/plain mime type.
- let body = gettext("Image");
- let mime = mime::Mime::from_str(&mime).unwrap();
- obj.open_attach_dialog(bytes, mime, &body);
- }
- }));
- }
+ clone!(@weak self as obj => @default-return false, move |_, value, _, _| {
+ match value.get::<gio::File>() {
+ Ok(file) => {
+ spawn!(clone!(@weak obj => async move {
+ obj.send_file(file).await;
}));
-
- return true;
+ true
}
- }
+ Err(error) => {
+ warn!("Could not get file from drop: {error:?}");
+
+ if let Some(window) = obj
+ .root()
+ .as_ref()
+ .and_then(|root| root.downcast_ref::<Window>())
+ {
+ window.add_toast(
+ &Toast::new(&gettext("Error getting file from drop"))
+ );
+ }
- if let Ok(file) = value.get::<gio::File>() {
- if !file.query_exists(gio::Cancellable::NONE) {
- log::debug!("Received drag & drop file, but don't have permissions: {:?}",
file.path());
- return false;
+ false
}
- log::debug!("Received drag & drop file: {:?}", file.path());
- crate::spawn!(glib::clone!(@weak obj, @strong file => async move {
- obj.read_file(&file).await;
- }));
-
- return true;
}
- false
}),
);
@@ -866,22 +848,21 @@ impl RoomHistory {
Ok(())
}
- fn open_attach_dialog(&self, bytes: Vec<u8>, mime: mime::Mime, title: &str) {
+ async fn send_image(&self, image: gdk::Texture) {
let window = self.root().unwrap().downcast::<gtk::Window>().unwrap();
- let dialog = AttachmentDialog::new(&window);
- let gbytes = glib::Bytes::from_owned(bytes);
- if let Ok(texture) = gdk::Texture::from_bytes(&gbytes) {
- dialog.set_texture(&texture);
- }
+ let filename = filename_for_mime(Some(mime::IMAGE_PNG.as_ref()), None);
+ let dialog = AttachmentDialog::for_image(&window, &filename, &image);
- dialog.set_title(Some(title));
- let title = title.to_string();
dialog.connect_local(
"send",
false,
- glib::clone!(@weak self as obj => @default-return None, move |_| {
+ clone!(@weak self as obj, @strong image => @default-return None, move |_| {
if let Some(room) = obj.room() {
- room.send_attachment(&gbytes, mime.clone(), &title);
+ room.send_attachment(
+ image.save_to_png_bytes().to_vec(),
+ mime::IMAGE_PNG,
+ &filename,
+ );
}
None
@@ -908,7 +889,7 @@ impl RoomHistory {
let file = dialog.file().unwrap();
crate::spawn!(glib::clone!(@weak obj, @strong file => async move {
- obj.read_file(&file).await;
+ obj.send_file(file).await;
}));
}
}),
@@ -917,32 +898,65 @@ impl RoomHistory {
dialog.show();
}
- async fn read_file(&self, file: &gio::File) {
- let filename = file
- .basename()
- .unwrap()
- .into_os_string()
- .to_str()
- .unwrap()
- .to_string();
+ async fn send_file(&self, file: gio::File) {
+ let attributes: &[&str] = &[
+ *gio::FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+ *gio::FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
+ ];
// Read mime type.
- let mime = if let Ok(file_info) = file.query_info(
- "standard::content-type",
- gio::FileQueryInfoFlags::NONE,
- gio::Cancellable::NONE,
- ) {
- file_info
- .content_type()
- .map_or("text/plain".to_string(), |x| x.to_string())
- } else {
- "text/plain".to_string()
- };
- let mime = mime::Mime::from_str(&mime).unwrap();
+ let info = file
+ .query_info_future(
+ &attributes.join(","),
+ gio::FileQueryInfoFlags::NONE,
+ glib::PRIORITY_DEFAULT,
+ )
+ .await
+ .ok();
+
+ let mime = info
+ .as_ref()
+ .and_then(|info| info.content_type())
+ .and_then(|content_type| mime::Mime::from_str(&content_type).ok())
+ .unwrap_or(mime::APPLICATION_OCTET_STREAM);
+ let filename = info.map(|info| info.display_name()).map_or_else(
+ || filename_for_mime(Some(mime.as_ref()), None),
+ |name| name.to_string(),
+ );
match file.load_contents_future().await {
- Ok((bytes, _tag)) => self.open_attach_dialog(bytes, mime, &filename),
- Err(err) => log::debug!("Could not read file: {}", err),
+ Ok((bytes, _tag)) => {
+ let window = self.root().unwrap().downcast::<gtk::Window>().unwrap();
+ let dialog = AttachmentDialog::for_file(&window, &filename, &file);
+
+ dialog.connect_local(
+ "send",
+ false,
+ clone!(@weak self as obj => @default-return None, move |_| {
+ if let Some(room) = obj.room() {
+ room.send_attachment(
+ bytes.clone(),
+ mime.clone(),
+ &filename,
+ );
+ }
+
+ None
+ }),
+ );
+ dialog.present();
+ }
+ Err(err) => {
+ warn!("Could not read file: {}", err);
+
+ if let Some(window) = self
+ .root()
+ .as_ref()
+ .and_then(|root| root.downcast_ref::<Window>())
+ {
+ window.add_toast(&Toast::new(&gettext("Error reading file")));
+ }
+ }
}
}
@@ -962,19 +976,3 @@ impl Default for RoomHistory {
Self::new()
}
}
-
-async fn read_stream(stream: &gio::InputStream) -> Result<Vec<u8>, glib::Error> {
- let mut buffer = Vec::<u8>::with_capacity(4096);
-
- loop {
- let bytes = stream
- .read_bytes_future(4096, glib::PRIORITY_DEFAULT)
- .await?;
- if bytes.is_empty() {
- break;
- }
- buffer.extend_from_slice(&bytes);
- }
-
- Ok(buffer)
-}
diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs
index c0d586900..2701001a4 100644
--- a/src/session/room/mod.rs
+++ b/src/session/room/mod.rs
@@ -10,7 +10,7 @@ mod reaction_list;
mod room_type;
mod timeline;
-use std::{cell::RefCell, convert::TryInto, ops::Deref, path::PathBuf, sync::Arc};
+use std::{cell::RefCell, convert::TryInto, path::PathBuf, sync::Arc};
use gettextrs::gettext;
use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
@@ -1513,21 +1513,21 @@ impl Room {
Some(())
}
- pub fn send_attachment(&self, bytes: &glib::Bytes, mime: mime::Mime, body: &str) {
+ pub fn send_attachment(&self, bytes: Vec<u8>, mime: mime::Mime, body: &str) {
let matrix_room = self.matrix_room();
if let MatrixRoom::Joined(matrix_room) = matrix_room {
let body = body.to_string();
- spawn_tokio!(glib::clone!(@strong bytes => async move {
+ spawn_tokio!(async move {
let config = AttachmentConfig::default();
- let mut cursor = std::io::Cursor::new(bytes.deref());
+ let mut cursor = std::io::Cursor::new(&bytes);
matrix_room
// TODO This should be added to pending messages instead of
// sending it directly.
.send_attachment(&body, &mime, &mut cursor, config)
.await
.unwrap();
- }));
+ });
}
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]