nvdialog_rs/
file_dialog.rs

1/*
2 *  The MIT License (MIT)
3 *
4 *  Copyright (c) 2022-2025 Aggelos Tselios
5 *
6 *  Permission is hereby granted, free of charge, to any person obtaining a copy
7 *  of this software and associated documentation files (the "Software"), to
8 * deal in the Software without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom the Software is
11 *  furnished to do so, subject to the following conditions:
12 *
13 *  The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 * IN THE SOFTWARE.
23 */
24
25use crate::{cstr, Object};
26use nvdialog_sys::ffi::*;
27use std::{
28    ffi::{c_char, CStr},
29    os::raw::c_void,
30    path::PathBuf,
31    ptr::null_mut,
32};
33
34/// Mode of the file dialog
35///
36/// A file dialog may either be used for getting a file (`OpenFile`), getting a directory (`OpenFolder`) or
37/// saving a file (`SaveFile`). When creating a new file dialog, you must set
38/// its mode by one of the enums below.
39/// # Example
40/// ```
41/// extern crate nvdialog_rs;
42/// use nvdialog_rs::{
43///     FileDialog,
44///     FileDialogType
45/// }
46///
47/// fn main() {
48///     let dialog = FileDialog::new(
49///             "Choose a file",
50///             &FileDialogType::OpenFile
51///     );
52///     println!("Filename: {:?}", dialog.retrieve_filename());
53/// }
54/// ```
55pub enum FileDialogType {
56    OpenFile,
57    SaveFile,
58    OpenFolder,
59}
60
61/// A struct representing a file dialog window.
62///
63/// This struct is used to display a file dialog window to the user,
64/// allowing them to select a file or folder. It provides an interface to the
65/// operating system's file dialog APIs, which are used to display the dialog
66/// and retrieve the selected file or folder location.
67///
68/// # Examples
69/// Creating a new `FileDialog` and showing it to the user, returning the path selected:
70/// ```
71/// use nvdialog_rs as nvdialog;
72/// use nvdialog::FileDialog;
73/// use nvdialog::FileDialogType;
74///
75/// let mut file_dialog = FileDialog::new("Title", FileDialogType::OpenFile);
76/// let path = PathBuf::new();
77/// if let Some(filename) = file_dialog.retrieve_filename() {
78///     path = filename;
79///     // do something with the returned Path
80/// } else {
81///     println!("No file selected.");
82/// }
83/// ```
84/// ## FFI
85/// Corresponds to `NvdFileDialog`.
86pub struct FileDialog {
87    raw: *mut NvdFileDialog,
88    location_chosen: Option<String>,
89}
90
91impl FileDialog {
92    /// Creates a new `FileDialog` instance with the specified title and
93    /// type of dialog.
94    ///
95    /// This function returns a new `FileDialog` instance
96    /// with a raw pointer to the underlying `NvdFileDialog` struct
97    /// initialized based on the specified type of dialog. The title
98    /// argument specifies the title to be displayed in the file dialog
99    /// window. The `type_of_dialog` argument determines whether the dialog
100    /// is used for opening a file (`FileDialogType::OpenFile`) or saving
101    /// a file (`FileDialogType::SaveFile`).
102    ///
103    /// If `FileDialogType::OpenFile` is specified, the `raw` pointer is
104    /// obtained by calling the `nvd_open_file_dialog_new` function from
105    /// the underlying C API. If `FileDialogType::SaveFile` is specified,
106    /// the `raw` pointer is obtained by calling the
107    /// `nvd_save_file_dialog_new` function from the underlying C API. In
108    /// the case of `FileDialogType::SaveFile`, the dialog defaults to
109    /// suggesting a filename of "filename".
110    ///
111    /// # Examples
112    ///
113    /// Creating a new `FileDialog` instance for opening a file:
114    ///
115    /// ```
116    /// let file_dialog = FileDialog::new("Open File", FileDialogType::OpenFile);
117    /// ```
118    ///
119    /// Creating a new `FileDialog` instance for saving a file:
120    ///
121    /// ```
122    /// let file_dialog = FileDialog::new("Save File", FileDialogType::SaveFile);
123    /// ```
124    pub fn new<S: AsRef<str>>(
125        title: S,
126        type_of_dialog: FileDialogType,
127        file_extensions: Option<impl IntoIterator<Item = S>>,
128    ) -> Self {
129        /* Just converting this into a format NvDialog will understand */
130        let mut extensions = String::new();
131        if file_extensions.is_some() {
132            for extension in file_extensions.unwrap() {
133                extensions += extension.as_ref();
134                extensions += ";";
135                extensions += "\0";
136            }
137        }
138        match type_of_dialog {
139            FileDialogType::OpenFile => {
140                let t = cstr!(title.as_ref());
141                Self {
142                    raw: unsafe {
143                        nvd_open_file_dialog_new(
144                            t.as_ptr(),
145                            if extensions.is_empty() {
146                                null_mut()
147                            } else {
148                                extensions.as_ptr() as *const c_char
149                            },
150                        )
151                    },
152                    location_chosen: None,
153                }
154            }
155            FileDialogType::SaveFile => {
156                let t = cstr!(title.as_ref());
157                let f = cstr!("filename");
158                Self {
159                    raw: unsafe { nvd_save_file_dialog_new(t.as_ptr(), f.as_ptr()) },
160                    location_chosen: None,
161                }
162            }
163            FileDialogType::OpenFolder => {
164                let t = cstr!(title.as_ref());
165                Self {
166                    raw: unsafe { nvd_open_folder_dialog_new(t.as_ptr(), null_mut()) },
167                    location_chosen: None,
168                }
169            }
170        }
171    }
172
173    /// Retrieves the file name selected in the file dialog. This
174    /// function returns a `PathBuf` instance containing the selected
175    /// file name, or `None` if no file was selected.
176    ///
177    /// This function calls the `nvd_get_file_location`
178    /// function from the underlying C API. If the returned pointer from NvDialog is `NULL`,
179    /// this function returns `None`. If the pointer is not null, this function constructs a
180    /// `CStr` instance from the raw buffer, and constructs a `PathBuf`
181    /// instance from the `CStr`.
182    ///
183    /// # Safety
184    /// This function constructs the returned [`PathBuf`](std::path::PathBuf) using C bytes, which may
185    /// cause a panic if the C data contains non-valid UTF-8 characters.
186    ///
187    /// # Returns
188    ///
189    /// - `Some(PathBuf)` if a file was selected and the selected file name
190    ///   could be converted to a `PathBuf` instance.
191    /// - `None` if no file was selected.
192    ///
193    /// # Panics
194    /// This function may panic with the message "Invalid UTF-8 data" if the
195    /// raw buffer returned from the underlying C API contains invalid
196    /// UTF-8 data.
197    ///
198    /// # Examples
199    /// ```
200    /// let mut file_dialog = FileDialog::new("Open File", FileDialogType::OpenFile);
201    ///
202    /// if let Some(path) = file_dialog.retrieve_filename() {
203    ///     // A file was selected. Do something with the file...
204    /// } else {
205    ///     eprintln!("No file was selected")
206    /// }
207    /// ```
208    pub fn retrieve_filename(&self) -> Option<PathBuf> {
209        let str = unsafe { nvd_get_file_location(self.raw) };
210        if str.is_null() {
211            None
212        } else {
213            let filename = unsafe { CStr::from_ptr(nvd_string_to_cstr(str)) };
214            Some(PathBuf::from(
215                filename.to_str().expect("Invalid UTF-8 data"),
216            ))
217        }
218    }
219}
220
221impl Object for FileDialog {
222    type NativeType = NvdFileDialog;
223    type ReturnValue = Option<PathBuf>;
224
225    fn get_raw(&self) -> *mut Self::NativeType {
226        self.raw
227    }
228
229    fn show(&self) -> Option<PathBuf> {
230        self.retrieve_filename()
231    }
232
233    fn free(&mut self) {
234        unsafe {
235            nvd_free_object(self.raw as *mut c_void);
236        }
237    }
238}
239
240impl Drop for FileDialog {
241    fn drop(&mut self) {
242        self.free();
243    }
244}