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<>k::EventBox>, label: Option<>k::Widget>, ) -> MessageMenu124 pub fn new(
125 id: Option<&EventId>,
126 mtype: &RowType,
127 redactable: &bool,
128 widget: Option<>k::EventBox>,
129 label: Option<>k::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: >k::EventBox)145 fn show(&self, w: >k::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: >k::Widget) -> Option<()>172 fn connect_copy_selected_text(&self, w: >k::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<>k::Label>) -> Option<SelectedText>193 fn get_selected_text(event_widget: Option<>k::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