native_windows_gui/resources/
file_dialog.rs

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