Skip to main content

native_windows_gui2/resources/
file_dialog.rs

1use crate::win32::resources_helper as rh;
2use winapi::um::shobjidl::{IFileDialog, IFileOpenDialog};
3
4use crate::win32::base_helper::to_utf16;
5use crate::{ControlHandle, NwgError};
6use std::{ffi::OsString, fmt, ptr};
7
8/**
9    A enum that dictates how a file dialog should behave
10    Members:
11    * `Open`: User can select a file that is not a directory
12    * `OpenDirectory`: User can select a directory
13    * `Save`: User select the name of a file. If it already exists, a confirmation message will be raised
14*/
15#[derive(Copy, Clone, PartialEq, Debug)]
16pub enum FileDialogAction {
17    Open,
18    OpenDirectory,
19    Save,
20}
21
22/**
23    A file dialog control
24
25    The file dialog builders accepts the following parameters:
26    * title: The title of the dialog
27    * action: The action to execute. Open, OpenDirectory for Save
28    * multiselect: Whether the user can select more than one file. Only supported with the Open action
29    * default_folder: Default folder to show in the dialog.
30    * filters: If defined, filter the files that the user can select (In a Open dialog) or which extension to add to the saved file (in a Save dialog)
31    The `filters` value must be a '|' separated string having this format: "Test(*.txt;*.rs)|Any(*.*)"
32
33    ```rust
34        use native_windows_gui2 as nwg;
35        fn layout(dialog: &mut nwg::FileDialog) {
36            nwg::FileDialog::builder()
37                .title("Hello")
38                .action(nwg::FileDialogAction::Open)
39                .multiselect(true)
40                .build(dialog);
41        }
42    ```
43*/
44pub struct FileDialog {
45    handle: *mut IFileDialog,
46    action: FileDialogAction,
47}
48
49impl FileDialog {
50    pub fn builder() -> FileDialogBuilder {
51        FileDialogBuilder {
52            title: None,
53            filename: None,
54            action: FileDialogAction::Save,
55            multiselect: false,
56            default_folder: None,
57            filters: None,
58        }
59    }
60
61    /// Return the action type executed by this dialog
62    pub fn action(&self) -> FileDialogAction {
63        self.action
64    }
65
66    /**
67        Display the dialog. Return true if the dialog was accepted or false if it was cancelled
68        If the dialog was accepted, `get_selected_item` or `get_selected_items` can be used to find the selected file(s)
69
70        It's important to note that `run` blocks the current thread until the user as chosen a file (similar to `dispatch_thread_events`)
71
72        The parent argument must be a window control otherwise the method will panic.
73    */
74    pub fn run<C: Into<ControlHandle>>(&self, parent: Option<C>) -> bool {
75        use winapi::shared::winerror::S_OK;
76
77        let parent_handle = match parent {
78            Some(p) => p
79                .into()
80                .hwnd()
81                .expect("File dialog parent must be a window control"),
82            None => ptr::null_mut(),
83        };
84
85        unsafe { (&mut *self.handle).Show(parent_handle) == S_OK }
86    }
87
88    /**
89        Return the item selected in the dialog by the user.
90
91        Failures:
92        • if the dialog was not called
93        • if there was a system error while reading the selected item
94        • if the dialog has the `multiselect` flag
95    */
96    pub fn get_selected_item(&self) -> Result<OsString, NwgError> {
97        if self.multiselect() {
98            return Err(NwgError::file_dialog(
99                "FileDialog have the multiselect flag",
100            ));
101        }
102
103        unsafe { rh::filedialog_get_item(&mut *self.handle) }
104    }
105
106    /**
107        Return the selected items in the dialog by the user.
108        Failures:
109        • if the dialog was not called
110        • if there was a system error while reading the selected items
111        • if the dialog has `Save` for action
112    */
113    pub fn get_selected_items(&self) -> Result<Vec<OsString>, NwgError> {
114        if self.action == FileDialogAction::Save {
115            return Err(NwgError::file_dialog(
116                "Save dialog cannot have more than one item selected",
117            ));
118        }
119
120        unsafe { rh::filedialog_get_items(&mut *(self.handle as *mut IFileOpenDialog)) }
121    }
122
123    /// Return `true` if the dialog accepts multiple values or `false` otherwise
124    pub fn multiselect(&self) -> bool {
125        use winapi::um::shobjidl::FOS_ALLOWMULTISELECT;
126
127        unsafe {
128            let flags = rh::file_dialog_options(&mut *self.handle).unwrap_or(0);
129            flags & FOS_ALLOWMULTISELECT == FOS_ALLOWMULTISELECT
130        }
131    }
132
133    /**
134        Set the multiselect flag of the dialog.
135        Failures:
136        • if there was a system error while setting the new flag value
137        • if the dialog has `Save` for action
138    */
139    pub fn set_multiselect(&self, multiselect: bool) -> Result<(), NwgError> {
140        use winapi::um::shobjidl::FOS_ALLOWMULTISELECT;
141
142        if self.action == FileDialogAction::Save {
143            return Err(NwgError::file_dialog(
144                "Cannot set multiselect flag for a save file dialog",
145            ));
146        }
147
148        let result = unsafe {
149            rh::toggle_dialog_flags(&mut *self.handle, FOS_ALLOWMULTISELECT, multiselect)
150        };
151        match result {
152            Ok(_) => Ok(()),
153            Err(e) => Err(e),
154        }
155    }
156
157    /**
158        Set the first opened folder when the dialog is shown. This value is overriden by the user after the dialog ran.
159        Call `clear_client_data` to fix that.
160        Failures:
161        • if the default folder do not identify a folder
162        • if the folder do not exists
163    */
164    pub fn set_default_folder<'a>(&self, folder: &'a str) -> Result<(), NwgError> {
165        unsafe {
166            let handle = &mut *self.handle;
167            rh::file_dialog_set_default_folder(handle, &folder)
168        }
169    }
170
171    /**
172        Filter the files that the user can select (In a `Open` dialog) in the dialog or which extension to add to the saved file (in a `Save` dialog).
173        This can only be set ONCE (the initialization counts) and won't work if the dialog is `OpenDirectory`.
174
175        The `filters` value must be a '|' separated string having this format: "Test(*.txt;*.rs)|Any(*.*)"
176        Where the fist part is the "human name" and the second part is a filter for the system.
177    */
178    pub fn set_filters<'a>(&self, filters: &'a str) -> Result<(), NwgError> {
179        unsafe {
180            let handle = &mut *self.handle;
181            rh::file_dialog_set_filters(handle, &filters)
182        }
183    }
184
185    /// Change the dialog title
186    pub fn set_title<'a>(&self, title: &'a str) {
187        unsafe {
188            let title = to_utf16(title);
189            let handle = &mut *self.handle;
190            handle.SetTitle(title.as_ptr());
191        }
192    }
193
194    /// 设置save默认名称
195    pub fn set_filename<'a>(&self, filename: &'a str) {
196        unsafe {
197            let filename = to_utf16(filename);
198            let handle = &mut *self.handle;
199            handle.SetFileName(filename.as_ptr());
200        }
201    }
202
203    /// Instructs the dialog to clear all persisted state information (such as the last folder visited).
204    pub fn clear_client_data(&self) {
205        unsafe {
206            let handle = &mut *self.handle;
207            handle.ClearClientData();
208        }
209    }
210}
211
212impl fmt::Debug for FileDialog {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        write!(f, "FileDialog {{ action: {:?} }}", self.action)
215    }
216}
217
218impl Default for FileDialog {
219    fn default() -> FileDialog {
220        FileDialog {
221            handle: ptr::null_mut(),
222            action: FileDialogAction::Open,
223        }
224    }
225}
226
227impl PartialEq for FileDialog {
228    fn eq(&self, other: &Self) -> bool {
229        self.handle == other.handle && self.action == other.action
230    }
231}
232
233impl Eq for FileDialog {}
234
235/*
236    Structure that hold the state required to build a file dialog
237*/
238pub struct FileDialogBuilder {
239    pub title: Option<String>,
240    pub filename: Option<String>,
241    pub action: FileDialogAction,
242    pub multiselect: bool,
243    pub default_folder: Option<String>,
244    pub filters: Option<String>,
245}
246
247impl FileDialogBuilder {
248    pub fn title<S: Into<String>>(mut self, t: S) -> FileDialogBuilder {
249        self.title = Some(t.into());
250        self
251    }
252
253    pub fn filename<S: Into<String>>(mut self, t: S) -> FileDialogBuilder {
254        self.filename = Some(t.into());
255        self
256    }
257
258    pub fn default_folder<S: Into<String>>(mut self, t: S) -> FileDialogBuilder {
259        self.default_folder = Some(t.into());
260        self
261    }
262
263    pub fn filters<S: Into<String>>(mut self, t: S) -> FileDialogBuilder {
264        self.filters = Some(t.into());
265        self
266    }
267
268    pub fn action(mut self, a: FileDialogAction) -> FileDialogBuilder {
269        self.action = a;
270        self
271    }
272
273    pub fn multiselect(mut self, m: bool) -> FileDialogBuilder {
274        self.multiselect = m;
275        self
276    }
277
278    pub fn build(self, out: &mut FileDialog) -> Result<(), NwgError> {
279        unsafe {
280            out.handle = rh::create_file_dialog(
281                self.action,
282                self.multiselect,
283                self.default_folder,
284                self.filters,
285            )?;
286        }
287
288        out.action = self.action;
289
290        if let Some(title) = self.title {
291            out.set_title(&title);
292        }
293
294        if let Some(filename) = self.filename {
295            out.set_filename(&filename);
296        }
297
298        Ok(())
299    }
300}