1 use fractal_api::identifiers::EventId;
2 use gdk::prelude::*;
3 use gtk::prelude::*;
4 
5 use crate::uitypes::RowType;
6 
7 #[derive(Clone)]
8 struct Widgets {
9     popover: gtk::Popover,
10     reply_button: gtk::ModelButton,
11     open_with_button: gtk::ModelButton,
12     save_image_as_button: gtk::ModelButton,
13     save_video_as_button: gtk::ModelButton,
14     copy_image_button: gtk::ModelButton,
15     delete_message_button: gtk::ModelButton,
16     view_source_button: gtk::ModelButton,
17     copy_text_button: gtk::ModelButton,
18     copy_selected_button: gtk::ModelButton,
19     menu_separator: gtk::Widget,
20 }
21 
22 impl Widgets {
new(id: Option<&EventId>, mtype: &RowType, redactable: &bool) -> Widgets23     pub fn new(id: Option<&EventId>, mtype: &RowType, redactable: &bool) -> Widgets {
24         let builder = gtk::Builder::new();
25         builder
26             .add_from_resource("/org/gnome/Fractal/ui/message_menu.ui")
27             .expect("Can't load ui file: message_menu.ui");
28 
29         let popover: gtk::Popover = builder
30             .get_object("message_menu_popover")
31             .expect("Can't find message_menu_popover in ui file.");
32 
33         let reply_button: gtk::ModelButton = builder
34             .get_object("reply_button")
35             .expect("Can't find reply_button in ui file.");
36 
37         let open_with_button: gtk::ModelButton = builder
38             .get_object("open_with_button")
39             .expect("Can't find open_with_button in ui file.");
40 
41         let save_image_as_button: gtk::ModelButton = builder
42             .get_object("save_image_as_button")
43             .expect("Can't find save_image_as_button in ui file.");
44 
45         let save_video_as_button: gtk::ModelButton = builder
46             .get_object("save_video_as_button")
47             .expect("Can't find save_video_as_button in ui file.");
48 
49         let copy_image_button: gtk::ModelButton = builder
50             .get_object("copy_image_button")
51             .expect("Can't find copy_image_button in ui file.");
52 
53         let copy_text_button: gtk::ModelButton = builder
54             .get_object("copy_text_button")
55             .expect("Can't find copy_text_button in ui file.");
56 
57         let delete_message_button: gtk::ModelButton = builder
58             .get_object("delete_message_button")
59             .expect("Can't find delete_message_button in ui file.");
60 
61         let view_source_button: gtk::ModelButton = builder
62             .get_object("view_source_button")
63             .expect("Can't find view_source_button in ui file.");
64 
65         let copy_selected_button: gtk::ModelButton = builder
66             .get_object("copy_selected_text_button")
67             .expect("Can't find copy_selected_text_button in ui file.");
68 
69         let menu_separator: gtk::Widget = builder
70             .get_object("message_menu_separator")
71             .expect("Can't find message_menu_separator");
72 
73         /* Set visibility of buttons */
74         copy_selected_button.hide();
75         delete_message_button.set_visible(*redactable);
76         menu_separator.set_visible(*redactable);
77         open_with_button.set_visible(mtype == &RowType::Image || mtype == &RowType::Video);
78         save_image_as_button.set_visible(mtype == &RowType::Image);
79         save_video_as_button.set_visible(mtype == &RowType::Video);
80         copy_image_button.set_visible(mtype == &RowType::Image);
81         copy_text_button.set_visible(mtype != &RowType::Image && mtype != &RowType::Video);
82 
83         let evid = id.map(|evid| evid.to_string()).unwrap_or_default();
84         let data = glib::Variant::from(evid);
85         reply_button.set_action_target_value(Some(&data));
86         open_with_button.set_action_target_value(Some(&data));
87         view_source_button.set_action_target_value(Some(&data));
88         delete_message_button.set_action_target_value(Some(&data));
89         open_with_button.set_action_target_value(Some(&data));
90         save_image_as_button.set_action_target_value(Some(&data));
91         save_video_as_button.set_action_target_value(Some(&data));
92         copy_image_button.set_action_target_value(Some(&data));
93         copy_text_button.set_action_target_value(Some(&data));
94 
95         Widgets {
96             popover,
97             reply_button,
98             open_with_button,
99             save_image_as_button,
100             save_video_as_button,
101             copy_image_button,
102             delete_message_button,
103             view_source_button,
104             copy_text_button,
105             copy_selected_button,
106             menu_separator,
107         }
108     }
109 }
110 
111 struct SelectedText {
112     pub widget: glib::WeakRef<gtk::Label>,
113     pub text: String,
114     pub start: i32,
115     pub end: i32,
116 }
117 
118 #[derive(Clone)]
119 pub struct MessageMenu {
120     widgets: Widgets,
121 }
122 
123 impl MessageMenu {
new( id: Option<&EventId>, mtype: &RowType, redactable: &bool, widget: Option<&gtk::EventBox>, label: Option<&gtk::Widget>, ) -> MessageMenu124     pub fn new(
125         id: Option<&EventId>,
126         mtype: &RowType,
127         redactable: &bool,
128         widget: Option<&gtk::EventBox>,
129         label: Option<&gtk::Widget>,
130     ) -> MessageMenu {
131         let menu = MessageMenu {
132             widgets: Widgets::new(id, mtype, redactable),
133         };
134         /* Copy selected text works a little different then the other actions, because it need the
135          * label */
136         if let Some(label) = label {
137             menu.connect_copy_selected_text(label);
138         }
139         if let Some(widget) = widget {
140             menu.show(widget);
141         }
142         menu
143     }
144 
show(&self, w: &gtk::EventBox)145     fn show(&self, w: &gtk::EventBox) {
146         gdk::Display::get_default()
147             .and_then(|disp| disp.get_default_seat())
148             .and_then(|seat| seat.get_pointer())
149             .map(|ptr| {
150                 let win = w.get_window()?;
151                 let (_, x, y, _) = win.get_device_position(&ptr);
152 
153                 let rect = gtk::Rectangle {
154                     x,
155                     y,
156                     width: 0,
157                     height: 0,
158                 };
159 
160                 self.widgets.popover.set_relative_to(Some(w));
161                 self.widgets.popover.set_pointing_to(&rect);
162                 self.widgets.popover.set_position(gtk::PositionType::Bottom);
163 
164                 self.widgets.popover.popup();
165 
166                 Some(true)
167             });
168     }
169 
170     /* This should also be a action, but for some reason we need to set again the selection on the
171      * label after the click event */
connect_copy_selected_text(&self, w: &gtk::Widget) -> Option<()>172     fn connect_copy_selected_text(&self, w: &gtk::Widget) -> Option<()> {
173         let label = w.downcast_ref::<gtk::Label>();
174         let s = get_selected_text(label)?;
175         self.widgets.copy_selected_button.show();
176         self.widgets.copy_selected_button.connect_clicked(move |_| {
177             if let Some(widget) = s.widget.upgrade() {
178                 let atom = gdk::Atom::intern("CLIPBOARD");
179                 let clipboard = gtk::Clipboard::get(&atom);
180                 clipboard.set_text(&s.text);
181                 /* FIXME: for some reason we have to set the selection again */
182                 widget.select_region(s.start, s.end);
183             }
184         });
185         None
186     }
187 
get_popover(&self) -> gtk::Popover188     pub fn get_popover(&self) -> gtk::Popover {
189         self.widgets.popover.clone()
190     }
191 }
192 
get_selected_text(event_widget: Option<&gtk::Label>) -> Option<SelectedText>193 fn get_selected_text(event_widget: Option<&gtk::Label>) -> Option<SelectedText> {
194     let w = event_widget?;
195     match w.get_selection_bounds() {
196         Some((s, e)) => {
197             let text = w.get_text()?;
198             let slice: String = text.chars().take(e as usize).skip(s as usize).collect();
199             Some(SelectedText {
200                 widget: w.downgrade(),
201                 text: slice,
202                 start: s,
203                 end: e,
204             })
205         }
206         _ => None,
207     }
208 }
209