Skip to main content

ui/
drop.rs

1// drop -- (K1) dropped files and other content for apps, the clipboard format probe, the
2// headless image clipboard; the command lines of later starts (AF_SINGLE_INSTANCE)
3//                                                                       [src/ui/ui_drop.s]
4//
5//   open_take() -> Vec<Request>     (UIC_OPEN, arg = their count) the working folder and
6//                                   arguments of every later start of the app since the last
7//                                   call; Request::paths() gives the files, which the app may
8//                                   read and write (fs.user); the main window comes forward
9//
10//   drop_count() -> n               files of the last drop (UIC_DROP, arg = window id; or
11//                                   DS_DROP of a drop target, dnd.rs)
12//   drop_path(i) -> Option<&str>    path i; valid until the next drop arrives
13//   drop_list() -> &[String]        every path of the last drop
14//   drop_data() -> &DragData        everything the last drop carried: paths, text, URLs, HTML,
15//                                   an image file (PNG, or BMP from a DIB), the app's own kind
16//                                   and bytes; drop_types() -> its DT_* kinds, drop_text()
17//   drop_pos() -> (x, y)            drop position in DIP of the window it happened in
18//   drop_pos_px() -> (x, y)         the same in px of that window, exact (as last_rect and a
19//                                   canvas's UiInput: x - rect[0] is the point in the canvas)
20//   drop_window() -> id             that window's app id (0 = main window)
21//   clip_has(fmt) -> bool           the clipboard offers CLIP_TEXT / CLIP_PNG / CLIP_IMAGE
22//                                   (headless runs: the in-process clipboard)
23// Which kinds a window takes outside its drop targets: drop_accept (dnd.rs, default DT_FILES).
24// Internal:
25//   drop_receive(ev) -> taken       E_DROP: the platform's pending drop (runtime.rs) onto the
26//                                   drop target at the point (true; also when no effect the
27//                                   source allows is taken there: refused) or for UIC_DROP (false)
28//   drop_inject(data, allowed)      script "drop": the content of the next E_DROP and the effects
29//                                   its source allows (0: not said)
30//   hclip_img_set(fmt, data)        headless CLIP_PNG (bytes) / CLIP_IMAGE (a CLIPIMG
31//                                   record: png then bmp, see clipimg.rs): PNG + BMP copies
32//   hclip_img_get(fmt)              CLIP_PNG: the PNG; CLIP_IMAGE: the PNG, else the BMP
33//   hclip_img_clear()   hclip_img_has(fmt)
34
35use alloc::string::String;
36use alloc::vec::Vec;
37
38use super::consts::*;
39use super::Ui;
40use plat::{DragData, Event};
41
42/// drop and headless image clipboard state
43#[derive(Default)]
44pub(crate) struct S {
45    pub(crate) list: Vec<String>,
46    pub(crate) inj: Option<DragData>,
47    pub(crate) inj_allowed: u32,
48    pub(crate) hi_png: Option<Vec<u8>>,
49    pub(crate) hi_bmp: Option<Vec<u8>>,
50    pub(crate) x: i32,
51    pub(crate) y: i32,
52    /// drop_pos_px: the drop's place in px of its window (logical)
53    pub(crate) px: (i32, i32),
54    pub(crate) win: u32,
55}
56
57impl Ui {
58    /// ui_drop_count: files of the last drop
59    pub fn drop_count(&self) -> u32 {
60        self.dr.list.len() as u32
61    }
62
63    /// ui_drop_path(i): path i of the last drop (None when out of range)
64    pub fn drop_path(&self, i: u32) -> Option<&str> {
65        self.dr.list.get(i as usize).map(|s| s.as_str())
66    }
67
68    /// AF_SINGLE_INSTANCE: the command lines of later starts that arrived (UIC_OPEN), oldest
69    /// first; each call takes them
70    pub fn open_take(&mut self) -> Vec<plat::instance::Request> {
71        core::mem::take(&mut self.open_reqs)
72    }
73
74    /// E_WAKE: requests of later starts -> their files granted (fs.user), the main window
75    /// brought forward, App::command(UIC_OPEN, pending requests)
76    pub(crate) fn open_poll(&mut self) {
77        let reqs = plat::instance::take();
78        if reqs.is_empty() {
79            return;
80        }
81        for p in reqs.iter().flat_map(|r| r.paths()).filter(|p| !p.contains("://") && grantable(p)) {
82            super::perm::grant_user(&p);
83        }
84        self.bring_to_front();
85        self.open_reqs.extend(reqs);
86        let n = self.open_reqs.len() as u32;
87        self.cmd_run(UIC_OPEN, n);
88    }
89
90    /// ui_drop_list: every path of the last drop
91    pub fn drop_list(&self) -> &[String] {
92        &self.dr.list
93    }
94
95    /// ui_drop_pos: drop position in DIP of the window it happened in (rounded to whole DIP:
96    /// at 150 % three pixels share two DIP; drop_pos_px is exact)
97    pub fn drop_pos(&self) -> (i32, i32) {
98        (self.dr.x, self.dr.y)
99    }
100
101    /// drop position in px of the window it happened in, exact: logical like `last_rect` and a
102    /// canvas's `UiInput::rect` (mirrored in a right-to-left window), so `(x - rect[0], y -
103    /// rect[1])` is the point in a canvas, in the canvas's own pixels -- a drop onto a page an
104    /// engine draws at the display's resolution
105    pub fn drop_pos_px(&self) -> (i32, i32) {
106        self.dr.px
107    }
108
109    /// ui_drop_window: the app id of the window of the last drop (0 = main window)
110    pub fn drop_window(&self) -> u32 {
111        self.dr.win
112    }
113
114    /// everything the last drop carried (paths, text, URLs, HTML, image, the app's own kind
115    /// and bytes; effects = the effect it was dropped with)
116    pub fn drop_data(&self) -> &DragData {
117        &self.dnd.dropped
118    }
119
120    /// the DT_* kinds of the last drop
121    pub fn drop_types(&self) -> u32 {
122        self.dnd.dropped.types()
123    }
124
125    /// the text of the last drop (None: it had none)
126    pub fn drop_text(&self) -> Option<&str> {
127        self.dnd.dropped.text.as_deref()
128    }
129
130    // drop_install(list, win, x_px, y_px): the new current list
131    fn drop_install(&mut self, list: Vec<String>, win: u32, x: i32, y: i32) {
132        self.dr.list = list;
133        self.dr.win = self.uiw_rec_id(win);
134        self.uiw_use(win); // DIP in that window
135        let x = if self.rtl { self.wp.rt.fb.w - 1 - x } else { x }; // logical (right-to-left)
136        self.dr.px = (x, y);
137        self.dr.x = self.px2dip(x);
138        self.dr.y = self.px2dip(y);
139        self.uiw_use(0);
140    }
141
142    /// ui_drop_accept(ev) -> taken: E_DROP of window ev.win (UI index): the platform's drop
143    /// becomes current; a drop target at the point takes it (DS_DROP), else UIC_DROP follows --
144    /// unless it takes no effect the source allows there (refused: true, nothing follows)
145    pub(crate) fn drop_receive(&mut self, ev: &Event) -> bool {
146        let (data, serial, allowed) = if self.headless {
147            // headless: the content of the script's "drop"
148            (self.dr.inj.take().unwrap_or_default(), 0, core::mem::take(&mut self.dr.inj_allowed))
149        } else {
150            // the serial of its answer while it waits, the effects its source allows
151            let data = plat::drop_data_take();
152            (data, plat::drop_waiting(), plat::drop_allowed())
153        };
154        super::perm::grant_all(&data.files); // the user dropped them (fs.user)
155        let win = ev.win as u32;
156        self.drop_install(data.files.clone(), win, ev.x, ev.y);
157        let cur = self.uiw_cur;
158        self.uiw_use(win);
159        let x = if self.rtl { self.wp.rt.fb.w - 1 - ev.x } else { ev.x }; // logical (right-to-left)
160        self.uiw_use(cur);
161        self.dnd_dropped(win, x, ev.y, data, serial, allowed)
162    }
163
164    /// ui_drop_inject(data, allowed): (script) the next E_DROP gets this content, its source
165    /// allowing these DE_* effects (0: the platform does not say)
166    pub(crate) fn drop_inject(&mut self, data: DragData, allowed: u32) {
167        self.dr.inj = Some(data);
168        self.dr.inj_allowed = allowed;
169    }
170
171    /// ui_clip_has(fmt): the clipboard offers fmt (cheap: no data is transferred)
172    pub fn clip_has(&self, fmt: u32) -> bool {
173        if !self.headless {
174            return plat::clip_has(fmt);
175        }
176        if fmt == CLIP_TEXT {
177            return self.hclip_text_has();
178        }
179        if matches!(fmt, CLIP_HTML | CLIP_RTF | CLIP_DOCX) {
180            return self.m.edit.hclip_rich.iter().any(|e| e.0 == fmt);
181        }
182        self.hclip_img_has(fmt)
183    }
184
185    /// hclip_img_clear
186    pub(crate) fn hclip_img_clear(&mut self) {
187        self.dr.hi_png = None;
188        self.dr.hi_bmp = None;
189    }
190
191    /// hclip_img_has(fmt): a PNG is stored (CLIP_IMAGE: or a BMP)
192    pub(crate) fn hclip_img_has(&self, fmt: u32) -> bool {
193        self.dr.hi_png.is_some() || (fmt == CLIP_IMAGE && self.dr.hi_bmp.is_some())
194    }
195
196    /// hclip_img_set(fmt, data) -> 0 / -1: CLIP_PNG bytes, or CLIP_IMAGE: a CLIPIMG record
197    /// (clipimg::clipimg_record: u64 PNG length, PNG, u64 BMP length, BMP)
198    pub(crate) fn hclip_img_set(&mut self, fmt: u32, data: &[u8]) -> i32 {
199        match fmt {
200            CLIP_PNG => {
201                self.hclip_img_set_parts(Some(data), None);
202                0
203            }
204            CLIP_IMAGE => match super::clipimg::clipimg_parse(data) {
205                Some((png, bmp)) => {
206                    self.hclip_img_set_parts(png, bmp);
207                    0
208                }
209                None => {
210                    self.hclip_text_clear();
211                    self.hclip_img_clear();
212                    -1
213                }
214            },
215            _ => {
216                self.hclip_text_clear();
217                self.hclip_img_clear();
218                -1
219            }
220        }
221    }
222
223    /// the stored image as PNG and / or BMP copies (one content at a time)
224    pub(crate) fn hclip_img_set_parts(&mut self, png: Option<&[u8]>, bmp: Option<&[u8]>) {
225        self.hclip_text_clear();
226        self.hclip_img_clear();
227        self.dr.hi_bmp = bmp.map(|b| b.to_vec());
228        self.dr.hi_png = png.map(|p| p.to_vec());
229    }
230
231    /// hclip_img_get(fmt): CLIP_PNG: the PNG; CLIP_IMAGE: the PNG, else the BMP (copies)
232    pub(crate) fn hclip_img_get(&self, fmt: u32) -> Option<Vec<u8>> {
233        if fmt != CLIP_PNG && fmt != CLIP_IMAGE {
234            return None;
235        }
236        if let Some(p) = &self.dr.hi_png {
237            return Some(p.clone());
238        }
239        if fmt == CLIP_IMAGE {
240            return self.dr.hi_bmp.clone();
241        }
242        None
243    }
244}
245
246/// whether a command-line path of a later start may be granted: no `..` parts and not a
247/// drive / file system root (those would open the whole disk)
248fn grantable(p: &str) -> bool {
249    let rest = p.strip_prefix("\\\\?\\").unwrap_or(p);
250    let rest = match rest.as_bytes() {
251        [d, b':', ..] if d.is_ascii_alphabetic() => &rest[2..],
252        _ => rest,
253    };
254    let rest = rest.trim_matches(|c| c == '/' || c == '\\');
255    !rest.is_empty() && !p.split(|c| c == '/' || c == '\\').any(|s| s == "..")
256}