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}