nativeshell/shell/platform/linux/
drag_context.rs

1use std::{
2    cell::{Cell, RefCell},
3    collections::HashMap,
4    mem::take,
5    rc::{Rc, Weak},
6};
7
8use cairo::{Format, ImageSurface};
9use gdk::{Atom, DragAction, EventType};
10use glib::IsA;
11use gtk::{
12    prelude::{DragContextExtManual, WidgetExt, WidgetExtManual},
13    DestDefaults, SelectionData, TargetEntry, TargetFlags, TargetList, Widget,
14};
15
16use crate::{
17    codec::Value,
18    shell::{
19        api_model::{DragData, DragEffect, DragRequest, DraggingInfo, ImageData},
20        platform::drag_data::{FallThroughDragDataAdapter, UriListDataAdapter},
21        Context, ContextRef, PlatformWindowDelegate, Point,
22    },
23};
24
25use super::{
26    drag_data::{DragDataAdapter, DragDataSetter},
27    window::PlatformWindow,
28};
29
30pub struct DropContext {
31    context: Context,
32    window: Weak<PlatformWindow>,
33    data_adapters: Vec<Box<dyn DragDataAdapter>>,
34
35    current_data: RefCell<HashMap<String, Value>>,
36    pending_data: RefCell<Vec<Atom>>,
37    drag_location: RefCell<Point>,
38    pending_effect: Cell<DragEffect>,
39    drag_context: RefCell<Option<gdk::DragContext>>,
40    dropping: Cell<bool>,
41}
42
43impl DropContext {
44    pub fn new(context: &ContextRef, window: Weak<PlatformWindow>) -> Self {
45        Self {
46            context: context.weak(),
47            window,
48            data_adapters: vec![
49                Box::new(UriListDataAdapter::new()),
50                Box::new(FallThroughDragDataAdapter::new(&context.options)),
51            ],
52            current_data: RefCell::new(HashMap::new()),
53            pending_data: RefCell::new(Vec::new()),
54            drag_location: RefCell::new(Default::default()),
55            pending_effect: Cell::new(DragEffect::None),
56            drag_context: RefCell::new(None),
57            dropping: Cell::new(false),
58        }
59    }
60
61    fn data_adapters<'a>(&'a self, context: &'a ContextRef) -> Vec<&'a dyn DragDataAdapter> {
62        context
63            .options
64            .custom_drag_data_adapters
65            .iter()
66            .chain(self.data_adapters.iter())
67            .map(|a| a.as_ref())
68            .collect()
69    }
70
71    pub fn drag_motion<T: IsA<Widget>>(
72        &self,
73        widget: &T,
74        context: &gdk::DragContext,
75        x: i32,
76        y: i32,
77        time: u32,
78    ) {
79        *self.drag_location.borrow_mut() = Point::xy(x as f64, y as f64);
80        self.drag_context.borrow_mut().replace(context.clone());
81        self.dropping.replace(false);
82
83        if !self.pending_data.borrow().is_empty() {
84            return;
85        }
86
87        self.get_data(widget, context, time);
88    }
89
90    fn get_data<T: IsA<Widget>>(&self, widget: &T, context: &gdk::DragContext, time: u32) {
91        let pending_data = {
92            let mut pending_data = self.pending_data.borrow_mut();
93
94            if !pending_data.is_empty() {
95                return;
96            }
97
98            if let Some(ctx) = self.context.get() {
99                let mut adapters = self.data_adapters(&ctx);
100
101                for target in context.list_targets() {
102                    let adapter_index = adapters
103                        .iter()
104                        .position(|p| p.data_formats().contains(&target));
105                    if let Some(adapter_index) = adapter_index {
106                        pending_data.push(target);
107                        adapters.remove(adapter_index);
108                    }
109                }
110            }
111
112            pending_data.clone()
113        };
114
115        for data in pending_data.iter() {
116            widget.drag_get_data(context, data, time);
117        }
118    }
119
120    fn cleanup(&self) {
121        self.current_data.borrow_mut().clear();
122        self.pending_data.borrow_mut().clear();
123        self.pending_effect.replace(DragEffect::None);
124        self.drag_context.borrow_mut().take();
125        self.dropping.replace(false);
126    }
127
128    pub fn drag_leave<T: IsA<Widget>>(&self, _widget: &T, _context: &gdk::DragContext, _time: u32) {
129        self.cleanup();
130
131        self.with_delegate(|d| d.dragging_exited());
132    }
133
134    pub fn drag_drop<T: IsA<Widget>>(
135        &self,
136        widget: &T,
137        context: &gdk::DragContext,
138        x: i32,
139        y: i32,
140        time: u32,
141    ) {
142        *self.drag_location.borrow_mut() = Point::xy(x as f64, y as f64);
143        self.dropping.replace(true);
144
145        if self.pending_data.borrow().is_empty() {
146            self.get_data(widget, context, time);
147        }
148    }
149
150    #[allow(clippy::too_many_arguments)]
151    pub fn drag_data_received<T: IsA<Widget>>(
152        &self,
153        _widget: &T,
154        context: &gdk::DragContext,
155        _x: i32, // always zero
156        _y: i32, // always zero
157        data: &SelectionData,
158        _info: u32,
159        time: u32,
160    ) {
161        {
162            let mut pending_data = self.pending_data.borrow_mut();
163            if pending_data.is_empty() {
164                return;
165            }
166
167            if let Some(ctx) = self.context.get() {
168                let adapters = self.data_adapters(&ctx);
169                let data_type = data.data_type();
170                if let Some(pos) = pending_data.iter().position(|d| d == &data_type) {
171                    pending_data.remove(pos);
172                    for adapter in adapters {
173                        if adapter.data_formats().contains(&data_type) {
174                            adapter.retrieve_drag_data(data, &mut self.current_data.borrow_mut());
175                        }
176                    }
177                }
178            }
179        }
180
181        if self.pending_data.borrow().is_empty() {
182            // We're done here
183            let info = DraggingInfo {
184                location: self.drag_location.borrow().clone(),
185                data: DragData {
186                    properties: take(&mut self.current_data.borrow_mut()),
187                },
188                allowed_effects: Self::convert_drag_actions_from_gtk(context.actions()),
189            };
190            if !self.dropping.get() {
191                self.with_delegate(|d| d.dragging_updated(&info));
192            } else {
193                self.with_delegate(|d| d.perform_drop(&info));
194                context.drag_finish(true, self.pending_effect.get() == DragEffect::Move, time);
195                self.cleanup();
196            }
197        }
198    }
199
200    fn convert_drag_actions_from_gtk(actions: DragAction) -> Vec<DragEffect> {
201        let mut res = Vec::new();
202        if actions.contains(DragAction::MOVE) {
203            res.push(DragEffect::Move);
204        }
205        if actions.contains(DragAction::COPY) {
206            res.push(DragEffect::Copy);
207        }
208        if actions.contains(DragAction::LINK) {
209            res.push(DragEffect::Link);
210        }
211        res
212    }
213
214    fn convert_effect_to_gtk(effect: DragEffect) -> DragAction {
215        match effect {
216            DragEffect::None => DragAction::empty(),
217            DragEffect::Copy => DragAction::COPY,
218            DragEffect::Link => DragAction::LINK,
219            DragEffect::Move => DragAction::MOVE,
220        }
221    }
222
223    fn with_delegate<F>(&self, callback: F)
224    where
225        F: FnOnce(Rc<dyn PlatformWindowDelegate>),
226    {
227        let win = self.window.upgrade();
228        if let Some(delegate) = win.and_then(|w| w.delegate.upgrade()) {
229            callback(delegate);
230        }
231    }
232
233    pub fn set_pending_effect(&self, effect: DragEffect) {
234        self.pending_effect.replace(effect);
235        if let Some(drag_context) = self.drag_context.borrow().clone() {
236            drag_context.drag_status(Self::convert_effect_to_gtk(effect), 0);
237        }
238    }
239
240    pub fn register<T: IsA<Widget>>(&self, widget: &T) {
241        let mut atoms = Vec::<Atom>::new();
242        if let Some(ctx) = self.context.get() {
243            let adapters = self.data_adapters(&ctx);
244
245            for adapter in adapters {
246                adapter.data_formats().iter().for_each(|a| atoms.push(*a));
247            }
248        }
249        let entries: Vec<TargetEntry> = atoms
250            .iter()
251            .map(|a| TargetEntry::new(&a.name(), TargetFlags::empty(), 0))
252            .collect();
253        widget.drag_dest_set(
254            // Gtk documentation says that when calling get_drag_data from drag_motion the
255            // DestDefaults::DROP flag should be set, but that causes nautilus to lock up.
256            // Not having the flag and calling drag_finish manually seems to work fine
257            DestDefaults::empty(),
258            &entries,
259            DragAction::MOVE | DragAction::COPY | DragAction::LINK,
260        );
261    }
262}
263
264//
265//
266//
267
268pub struct DragContext {
269    context: Context,
270    window: Weak<PlatformWindow>,
271    data_adapters: Vec<Box<dyn DragDataAdapter>>,
272    data: RefCell<Vec<Box<dyn DragDataSetter>>>,
273    dragging: Cell<bool>,
274}
275
276impl DragContext {
277    pub fn new(context: &ContextRef, window: Weak<PlatformWindow>) -> Self {
278        Self {
279            context: context.weak(),
280            window,
281            data_adapters: vec![
282                Box::new(UriListDataAdapter::new()),
283                Box::new(FallThroughDragDataAdapter::new(&context.options)),
284            ],
285            data: RefCell::new(Vec::new()),
286            dragging: Cell::new(false),
287        }
288    }
289
290    fn prepare_data(&self, request: &mut DragRequest) -> TargetList {
291        let properties = &mut request.data.properties;
292        let mut data = self.data.borrow_mut();
293        data.clear();
294
295        if let Some(context) = self.context.get() {
296            for a in &context.options.custom_drag_data_adapters {
297                data.append(&mut a.prepare_drag_data(properties));
298            }
299        }
300        for a in &self.data_adapters {
301            data.append(&mut a.prepare_drag_data(properties));
302        }
303
304        let targets = TargetList::new(&[]);
305        data.iter().enumerate().all(|(index, source)| {
306            for k in source.data_formats() {
307                targets.add(&k, 0, index as u32);
308            }
309            true
310        });
311
312        targets
313    }
314
315    fn convert_effects_to_gtk(effects: &[DragEffect]) -> DragAction {
316        let mut res = DragAction::empty();
317        for e in effects {
318            res |= DropContext::convert_effect_to_gtk(*e);
319        }
320        res
321    }
322
323    pub fn begin_drag<T: IsA<Widget>>(&self, mut request: DragRequest, widget: &T) {
324        let window = self.window.upgrade().unwrap();
325
326        let events = window.last_event.borrow();
327        let drag_event = events
328            .values()
329            .filter(|e| {
330                e.event_type() == EventType::ButtonPress
331                    || e.event_type() == EventType::MotionNotify
332            })
333            .max_by(|e1, e2| e1.time().cmp(&e2.time()));
334
335        let button = events
336            .get(&EventType::ButtonPress)
337            .and_then(|e| e.button())
338            .unwrap_or(0);
339
340        let targets = self.prepare_data(&mut request);
341
342        let context = widget.drag_begin_with_coordinates(
343            &targets,
344            Self::convert_effects_to_gtk(&request.allowed_effects),
345            button as i32,
346            drag_event,
347            -1,
348            -1,
349        );
350
351        let event_coords = drag_event.and_then(|e| e.coords()).unwrap_or((0.0, 0.0));
352
353        if let Some(context) = context {
354            let surface = &Self::surface_from_image_data(request.image);
355            let scale_factor = widget.scale_factor() as f64;
356            surface.set_device_scale(widget.scale_factor() as f64, widget.scale_factor() as f64);
357            surface.set_device_offset(
358                (request.rect.x - event_coords.0) * scale_factor,
359                (request.rect.y - event_coords.1) * scale_factor,
360            );
361            context.drag_set_icon_surface(surface)
362        }
363
364        self.dragging.replace(true);
365    }
366
367    fn surface_from_image_data(image: ImageData) -> ImageSurface {
368        let mut data = image.data;
369        for offset in (0..data.len()).step_by(4) {
370            let (r, g, b, a) = (
371                data[offset],
372                data[offset + 1],
373                data[offset + 2],
374                data[offset + 3],
375            );
376            data[offset] = b;
377            data[offset + 1] = g;
378            data[offset + 2] = r;
379            data[offset + 3] = a;
380        }
381        let surface = ImageSurface::create_for_data(
382            data,
383            Format::ARgb32,
384            image.width,
385            image.height,
386            image.bytes_per_row,
387        );
388        surface.unwrap()
389    }
390
391    pub fn get_data(&self, selection_data: &SelectionData, target_info: u32) {
392        let data = self.data.borrow();
393        let data = data.get(target_info as usize);
394        if let Some(data) = data {
395            data.set(selection_data);
396        }
397    }
398
399    fn with_delegate<F>(&self, callback: F)
400    where
401        F: FnOnce(Rc<dyn PlatformWindowDelegate>),
402    {
403        let win = self.window.upgrade();
404        if let Some(delegate) = win.and_then(|w| w.delegate.upgrade()) {
405            callback(delegate);
406        }
407    }
408
409    fn cleanup(&self) {
410        self.data.borrow_mut().clear();
411        self.dragging.replace(false);
412    }
413
414    pub fn drag_failed(&self) {
415        self.cleanup();
416        self.with_delegate(|d| d.drag_ended(DragEffect::None));
417    }
418
419    pub fn drag_end(&self, context: &gdk::DragContext) {
420        if self.dragging.get() {
421            // if failes it means drag faile
422            self.cleanup();
423            let action = context.selected_action();
424            let action = DropContext::convert_drag_actions_from_gtk(action)
425                .first()
426                .cloned()
427                .unwrap_or(DragEffect::None);
428            self.with_delegate(|d| d.drag_ended(action));
429        }
430    }
431}