Skip to main content

cosmic/dialog/file_chooser/
mod.rs

1// Copyright 2023 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4//! Dialogs for opening and save files.
5//!
6//! # Features
7//!
8//! - On Linux, the `xdg-portal` feature will use XDG Portal dialogs.
9//! - Alternatively, `rfd` can be used for platform support beyond Linux.
10//!
11//! # Open a file
12//!
13//! ```no_run
14//! cosmic::task::future(async {
15//!     use cosmic::dialog::file_chooser;
16//!
17//!     let dialog = file_chooser::open::Dialog::new()
18//!         .title("Choose a file");
19//!
20//!     match dialog.open_file().await {
21//!         Ok(response) => println!("selected to open {:?}", response.url()),
22//!
23//!         Err(file_chooser::Error::Cancelled) => (),
24//!
25//!         Err(why) => eprintln!("error selecting file to open: {why:?}")
26//!     }
27//! });
28//! ```
29//!
30//! # Open multiple files
31//!
32//! ```no_run
33//! cosmic::task::future(async {
34//!     use cosmic::dialog::file_chooser;
35//!
36//!     let dialog = file_chooser::open::Dialog::new()
37//!         .title("Choose multiple files");
38//!
39//!     match dialog.open_files().await {
40//!         Ok(response) => println!("selected to open {:?}", response.urls()),
41//!
42//!         Err(file_chooser::Error::Cancelled) => (),
43//!
44//!         Err(why) => eprintln!("error selecting file(s) to open: {why:?}")
45//!     }
46//! });
47//! ```
48//!
49//! # Open a folder
50//!
51//! ```no_run
52//! cosmic::task::future(async {
53//!     use cosmic::dialog::file_chooser;
54//!
55//!     let dialog = file_chooser::open::Dialog::new()
56//!         .title("Choose a folder");
57//!
58//!     match dialog.open_folder().await {
59//!         Ok(response) => println!("selected to open {:?}", response.url()),
60//!
61//!         Err(file_chooser::Error::Cancelled) => (),
62//!
63//!         Err(why) => eprintln!("error selecting folder to open: {why:?}")
64//!     }
65//! });
66//! ```
67//!
68//! # Open multiple folders
69//!
70//! ```no_run
71//! cosmic::task::future(async {
72//!     use cosmic::dialog::file_chooser;
73//!
74//!     let dialog = file_chooser::open::Dialog::new()
75//!         .title("Choose a folder");
76//!
77//!     match dialog.open_folders().await {
78//!         Ok(response) => println!("selected to open {:?}", response.urls()),
79//!
80//!         Err(file_chooser::Error::Cancelled) => (),
81//!
82//!         Err(why) => eprintln!("error selecting folder(s) to open: {why:?}")
83//!     }
84//! });
85//! ```
86
87/// Open file dialog.
88pub mod open;
89
90/// Save file dialog.
91pub mod save;
92
93#[cfg(xdg_portal)]
94pub use ashpd::desktop::file_chooser::{Choice, FileFilter};
95
96use thiserror::Error;
97
98#[cfg(xdg_portal)]
99fn parse_uri(uri: &ashpd::Uri) -> Result<url::Url, Error> {
100    url::Url::parse(uri.as_str()).map_err(|_| Error::UrlAbsolute)
101}
102
103/// A file filter, to limit the available file choices to certain extensions.
104#[cfg(feature = "rfd")]
105#[must_use]
106pub struct FileFilter {
107    description: String,
108    extensions: Vec<String>,
109}
110
111#[cfg(feature = "rfd")]
112impl FileFilter {
113    pub fn new(description: impl Into<String>) -> Self {
114        Self {
115            description: description.into(),
116            extensions: Vec::new(),
117        }
118    }
119
120    pub fn extension(mut self, extension: impl Into<String>) -> Self {
121        self.extensions.push(extension.into());
122        self
123    }
124}
125
126/// Errors that my occur when interacting with the file chooser subscription
127#[derive(Debug, Error)]
128pub enum Error {
129    #[error("dialog request cancelled")]
130    Cancelled,
131    #[error("dialog close failed")]
132    Close(#[source] DialogError),
133    #[error("open dialog failed")]
134    Open(#[source] DialogError),
135    #[error("dialog response failed")]
136    Response(#[source] DialogError),
137    #[error("save dialog failed")]
138    Save(#[source] DialogError),
139    #[error("could not set directory")]
140    SetDirectory(#[source] DialogError),
141    #[error("could not set absolute path for file name")]
142    SetAbsolutePath(#[source] DialogError),
143    #[error("path from dialog was not absolute")]
144    UrlAbsolute,
145}
146
147#[cfg(xdg_portal)]
148pub type DialogError = ashpd::Error;
149
150#[cfg(feature = "rfd")]
151#[derive(Debug, Error)]
152#[error("no file selected")]
153pub struct DialogError {}