fractal/session/view/content/room_history/message_toolbar/
attachment_dialog.rs1use adw::{prelude::*, subclass::prelude::*};
2use futures_channel::oneshot;
3use gtk::{gdk, gio, glib, glib::clone, CompositeTemplate};
4use tracing::error;
5
6use crate::{components::MediaContentViewer, spawn};
7
8mod imp {
9 use std::cell::RefCell;
10
11 use super::*;
12
13 #[derive(Debug, Default, CompositeTemplate)]
14 #[template(
15 resource = "/org/gnome/Fractal/ui/session/view/content/room_history/message_toolbar/attachment_dialog.ui"
16 )]
17 pub struct AttachmentDialog {
18 #[template_child]
19 cancel_button: TemplateChild<gtk::Button>,
20 #[template_child]
21 send_button: TemplateChild<gtk::Button>,
22 #[template_child]
23 media: TemplateChild<MediaContentViewer>,
24 sender: RefCell<Option<oneshot::Sender<gtk::ResponseType>>>,
25 }
26
27 #[glib::object_subclass]
28 impl ObjectSubclass for AttachmentDialog {
29 const NAME: &'static str = "AttachmentDialog";
30 type Type = super::AttachmentDialog;
31 type ParentType = adw::Dialog;
32
33 fn class_init(klass: &mut Self::Class) {
34 Self::bind_template(klass);
35 Self::bind_template_callbacks(klass);
36 }
37
38 fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
39 obj.init_template();
40 }
41 }
42
43 impl ObjectImpl for AttachmentDialog {
44 fn constructed(&self) {
45 self.parent_constructed();
46
47 self.set_loading(true);
48 }
49 }
50
51 impl WidgetImpl for AttachmentDialog {
52 fn grab_focus(&self) -> bool {
53 let loading = !self.send_button.is_sensitive();
54
55 if loading {
56 self.cancel_button.grab_focus()
57 } else {
58 self.send_button.grab_focus()
59 }
60 }
61 }
62
63 impl AdwDialogImpl for AttachmentDialog {
64 fn closed(&self) {
65 self.send_response(gtk::ResponseType::Cancel);
66 }
67 }
68
69 #[gtk::template_callbacks]
70 impl AttachmentDialog {
71 fn set_loading(&self, loading: bool) {
73 self.send_button.set_sensitive(!loading);
74 self.grab_focus();
75 }
76
77 fn send_response(&self, response: gtk::ResponseType) {
79 if let Some(sender) = self.sender.take() {
80 if sender.send(response).is_err() {
81 error!("Could not send attachment dialog response {response:?}");
82 }
83 }
84 }
85
86 pub(super) fn set_image(&self, image: &gdk::Texture) {
88 self.media.view_image(image);
89 self.set_loading(false);
90 }
91
92 pub(super) async fn set_file(&self, file: gio::File) {
94 self.media.view_file(file.into(), None).await;
95 self.set_loading(false);
96 }
97
98 pub(super) fn set_location(&self, geo_uri: &geo_uri::GeoUri) {
100 self.media.view_location(geo_uri);
101 self.set_loading(false);
102 }
103
104 #[template_callback]
106 fn send(&self) {
107 self.send_response(gtk::ResponseType::Ok);
108 self.obj().close();
109 }
110
111 pub(super) async fn response_future(&self, parent: >k::Widget) -> gtk::ResponseType {
116 let (sender, receiver) = oneshot::channel();
117 self.sender.replace(Some(sender));
118
119 self.obj().present(Some(parent));
120
121 receiver.await.unwrap_or(gtk::ResponseType::Cancel)
122 }
123 }
124}
125
126glib::wrapper! {
127 pub struct AttachmentDialog(ObjectSubclass<imp::AttachmentDialog>)
129 @extends gtk::Widget, adw::Dialog;
130}
131
132impl AttachmentDialog {
133 pub fn new(title: &str) -> Self {
137 glib::Object::builder().property("title", title).build()
138 }
139
140 pub(crate) fn set_image(&self, image: &gdk::Texture) {
142 self.imp().set_image(image);
143 }
144
145 pub(crate) fn set_file(&self, file: gio::File) {
147 let imp = self.imp();
148
149 spawn!(clone!(
150 #[weak]
151 imp,
152 async move {
153 imp.set_file(file).await;
154 }
155 ));
156 }
157
158 pub(crate) fn set_location(&self, geo_uri: &geo_uri::GeoUri) {
160 self.imp().set_location(geo_uri);
161 }
162
163 pub(crate) async fn response_future(
168 &self,
169 parent: &impl IsA<gtk::Widget>,
170 ) -> gtk::ResponseType {
171 self.imp().response_future(parent.upcast_ref()).await
172 }
173}