1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use crate::dialog::{DialogImpl, OpenMultipleFile, OpenSingleDir, OpenSingleFile, SaveSingleFile};
use crate::Result;
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
use std::path::{Path, PathBuf};

/// Represents a set of file extensions and their description.
#[derive(Debug, Clone)]
pub struct Filter<'a> {
    pub(crate) description: &'a str,
    pub(crate) extensions: &'a [&'a str],
}

/// Builds and shows file dialogs.
#[derive(Debug, Clone)]
pub struct FileDialog<'a> {
    pub(crate) filename: Option<&'a str>,
    pub(crate) location: Option<&'a Path>,
    pub(crate) filters: Vec<Filter<'a>>,
    pub(crate) owner: Option<RawWindowHandle>,
}

impl<'a> FileDialog<'a> {
    /// Creates a file dialog builder.
    pub fn new() -> Self {
        FileDialog {
            filename: None,
            location: None,
            filters: vec![],
            owner: None,
        }
    }

    /// Sets the default value of the filename text field in the dialog. For open dialogs of macOS
    /// and zenity, this is a no-op because there's no such text field on the dialog.
    pub fn set_filename(mut self, filename: &'a str) -> Self {
        self.filename = Some(filename);
        self
    }

    /// Resets the default value of the filename field in the dialog.
    pub fn reset_filename(mut self) -> Self {
        self.filename = None;
        self
    }

    /// Sets the default location that the dialog shows at open.
    pub fn set_location<P: AsRef<Path> + ?Sized>(mut self, path: &'a P) -> Self {
        self.location = Some(path.as_ref());
        self
    }

    /// Resets the default location that the dialog shows at open. Without a default location set,
    /// the dialog will probably use the current working directory as default location.
    pub fn reset_location(mut self) -> Self {
        self.location = None;
        self
    }

    /// Adds a file type filter. The filter must contains at least one extension, otherwise this
    /// method will panic. For dialogs that open directories, this is a no-op.
    pub fn add_filter(mut self, description: &'a str, extensions: &'a [&'a str]) -> Self {
        if extensions.is_empty() {
            panic!("The file extensions of a filter must be specified.")
        }
        self.filters.push(Filter {
            description,
            extensions,
        });
        self
    }

    /// Removes all file type filters.
    pub fn remove_all_filters(mut self) -> Self {
        self.filters = vec![];
        self
    }

    /// Sets the owner of the dialog. On Unix and GNU/Linux, this is a no-op.
    pub fn set_owner<W: HasRawWindowHandle>(mut self, window: &W) -> Self {
        self.owner = Some(window.raw_window_handle());
        self
    }

    /// Sets the owner of the dialog by raw handle. On Unix and GNU/Linux, this is a no-op.
    ///
    /// # Safety
    ///
    /// It's the caller's responsibility that ensuring the handle is valid.
    pub unsafe fn set_owner_handle(mut self, handle: RawWindowHandle) -> Self {
        self.owner = Some(handle);
        self
    }

    /// Resets the owner of the dialog to nothing.
    pub fn reset_owner(mut self) -> Self {
        self.owner = None;
        self
    }

    /// Shows a dialog that let users to open one file.
    pub fn show_open_single_file(self) -> Result<Option<PathBuf>> {
        let mut dialog = OpenSingleFile {
            filename: self.filename,
            location: self.location,
            filters: self.filters,
            owner: self.owner,
        };
        dialog.show()
    }

    /// Shows a dialog that let users to open multiple files.
    pub fn show_open_multiple_file(self) -> Result<Vec<PathBuf>> {
        let mut dialog = OpenMultipleFile {
            filename: self.filename,
            location: self.location,
            filters: self.filters,
            owner: self.owner,
        };
        dialog.show()
    }

    /// Shows a dialog that let users to open one directory.
    pub fn show_open_single_dir(self) -> Result<Option<PathBuf>> {
        let mut dialog = OpenSingleDir {
            filename: self.filename,
            location: self.location,
            owner: self.owner,
        };
        dialog.show()
    }

    /// Shows a dialog that let users to save one file.
    pub fn show_save_single_file(self) -> Result<Option<PathBuf>> {
        let mut dialog = SaveSingleFile {
            filename: self.filename,
            location: self.location,
            filters: self.filters,
            owner: self.owner,
        };
        dialog.show()
    }
}

impl Default for FileDialog<'_> {
    fn default() -> Self {
        Self::new()
    }
}