session: Add action to copy an image in event's context menu

This commit is contained in:
Kévin Commaille 2022-11-05 12:36:05 +01:00
parent 4a31e667a3
commit b73c149a22
No known key found for this signature in database
GPG Key ID: DD507DAE96E8245C
10 changed files with 98 additions and 11 deletions

View File

@ -41,7 +41,7 @@
<attribute name="hidden-when">action-missing</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Copy Image</attribute>
<attribute name="label" translatable="yes">_Copy Thumbnail</attribute>
<attribute name="action">event.copy-image</attribute>
<attribute name="hidden-when">action-missing</attribute>
</item>

View File

@ -359,6 +359,11 @@ impl ImagePaintable {
pub fn is_animation(&self) -> bool {
self.imp().frames.borrow().is_some()
}
/// Get the current frame of this `ImagePaintable`, if any.
pub fn current_frame(&self) -> Option<gdk::Texture> {
self.imp().frame.borrow().clone()
}
}
fn texture_from_data(

View File

@ -303,4 +303,15 @@ impl MediaContentViewer {
location.set_location(geo_uri);
self.show_viewer();
}
/// Get the texture displayed by this widget, if any.
pub fn texture(&self) -> Option<gdk::Texture> {
self.imp()
.viewer
.child()
.and_then(|w| w.downcast::<gtk::Picture>().ok())
.and_then(|p| p.paintable())
.and_then(|p| p.downcast::<ImagePaintable>().ok())
.and_then(|p| p.current_frame())
}
}

View File

@ -11,8 +11,8 @@ use crate::{
message_row::MessageRow, DividerRow, RoomHistory, StateRow, TypingRow,
},
room::{
Event, EventActions, PlaceholderKind, SupportedEvent, TimelineDayDivider, TimelineItem,
TimelineNewMessagesDivider, TimelinePlaceholder,
Event, EventActions, EventTexture, PlaceholderKind, SupportedEvent, TimelineDayDivider,
TimelineItem, TimelineNewMessagesDivider, TimelinePlaceholder,
},
},
};
@ -345,4 +345,11 @@ impl ItemRow {
}
}
impl EventActions for ItemRow {}
impl EventActions for ItemRow {
fn texture(&self) -> Option<EventTexture> {
self.child()
.and_then(|w| w.downcast::<MessageRow>().ok())
.and_then(|r| r.texture())
.map(EventTexture::Thumbnail)
}
}

View File

@ -1,6 +1,6 @@
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{glib, glib::clone};
use gtk::{gdk, glib, glib::clone};
use log::warn;
use matrix_sdk::ruma::events::{
room::message::{MessageType, Relation},
@ -137,6 +137,17 @@ impl MessageContent {
build_content(self, event, format);
}
}
/// Get the texture displayed by this widget, if any.
pub fn texture(&self) -> Option<gdk::Texture> {
let mut content = self.child()?;
if let Some(reply) = content.downcast_ref::<MessageReply>() {
content = reply.content().child()?;
}
content.downcast_ref::<MessageMedia>()?.texture()
}
}
/// Build the content widget of `event` as a child of `parent`.

View File

@ -1,7 +1,7 @@
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{
gio,
gdk, gio,
glib::{self, clone},
CompositeTemplate,
};
@ -476,4 +476,15 @@ impl MessageMedia {
})
);
}
/// Get the texture displayed by this widget, if any.
pub fn texture(&self) -> Option<gdk::Texture> {
self.imp()
.media
.child()
.and_then(|w| w.downcast::<gtk::Picture>().ok())
.and_then(|p| p.paintable())
.and_then(|p| p.downcast::<ImagePaintable>().ok())
.and_then(|p| p.current_frame())
}
}

View File

@ -10,7 +10,7 @@ mod text;
use adw::{prelude::*, subclass::prelude::*};
use gtk::{
glib,
gdk, glib,
glib::{clone, signal::SignalHandlerId},
CompositeTemplate,
};
@ -196,4 +196,9 @@ impl MessageRow {
fn update_content(&self, event: &SupportedEvent) {
self.imp().content.update_for_event(event);
}
/// Get the texture displayed by this widget, if any.
pub fn texture(&self) -> Option<gdk::Texture> {
self.imp().content.texture()
}
}

View File

@ -3,7 +3,7 @@ use gtk::{gdk, gio, glib, glib::clone, CompositeTemplate};
use log::warn;
use matrix_sdk::ruma::events::{room::message::MessageType, AnyMessageLikeEventContent};
use super::room::EventActions;
use super::room::{EventActions, EventTexture};
use crate::{
components::{ContentType, ImagePaintable, MediaContentViewer},
session::room::SupportedEvent,
@ -293,4 +293,8 @@ impl MediaViewer {
}
}
impl EventActions for MediaViewer {}
impl EventActions for MediaViewer {
fn texture(&self) -> Option<EventTexture> {
self.imp().media.texture().map(EventTexture::Original)
}
}

View File

@ -1,5 +1,5 @@
use gettextrs::gettext;
use gtk::{gio, glib, glib::clone, prelude::*};
use gtk::{gdk, gio, glib, glib::clone, prelude::*};
use log::error;
use matrix_sdk::ruma::events::{room::message::MessageType, AnyMessageLikeEventContent};
use once_cell::sync::Lazy;
@ -213,6 +213,27 @@ where
}
MessageType::Image(_) => {
// Copy the texture to the clipboard.
gtk_macros::action!(
&action_group,
"copy-image",
clone!(@weak self as widget, @weak event => move |_, _| {
let texture = widget.texture().expect("A widget with an image should have a texture");
match texture {
EventTexture::Original(texture) => {
widget.clipboard().set_texture(&texture);
toast!(widget, gettext("Image copied to clipboard"));
}
EventTexture::Thumbnail(texture) => {
widget.clipboard().set_texture(&texture);
toast!(widget, gettext("Thumbnail copied to clipboard"));
}
}
})
);
// Save the image to a file.
gtk_macros::action!(
&action_group,
"save-image",
@ -294,4 +315,16 @@ where
})
);
}
/// Get the texture displayed by this widget, if any.
fn texture(&self) -> Option<EventTexture>;
}
/// A texture from an event.
pub enum EventTexture {
/// The texture is the original image.
Original(gdk::Texture),
/// The texture is a thumbnail of the image.
Thumbnail(gdk::Texture),
}

View File

@ -46,7 +46,7 @@ use ruma::events::{typing::TypingEventContent, MessageLikeEventContent, SyncEphe
pub use self::{
event::*,
event_actions::EventActions,
event_actions::{EventActions, EventTexture},
highlight_flags::HighlightFlags,
member::{Member, Membership},
member_list::MemberList,