summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Krahl <robin.krahl@ireas.org>2019-12-10 20:02:59 +0100
committerRobin Krahl <robin.krahl@ireas.org>2019-12-10 20:02:59 +0100
commite152b4f4b6aa274774d1c750c3106528ebd4f1d4 (patch)
treef23e994a66ec562685ca44caa6ba939de34acdf1
parent3b76a0a46ce38eef7fe3e5a969cec6ffee47f227 (diff)
parent89afcb4844dd484f0c9cdfef7e5ff8d751647c43 (diff)
downloaddialog-rs-e152b4f4b6aa274774d1c750c3106528ebd4f1d4.tar.gz
dialog-rs-e152b4f4b6aa274774d1c750c3106528ebd4f1d4.tar.bz2
Merge branch 'file-selection' into next
This patch series introduces a new FileSelection dialog type, see [0]. [0] https://lists.sr.ht/~ireas/dialog-rs-dev/%3C20191203122011.GA64086%40kunshan.atexit.net%3E
-rw-r--r--CHANGELOG.md1
-rw-r--r--Cargo.toml1
-rw-r--r--examples/file_selection.rs21
-rw-r--r--src/backends/dialog.rs9
-rw-r--r--src/backends/kdialog.rs15
-rw-r--r--src/backends/mod.rs3
-rw-r--r--src/backends/stdio.rs15
-rw-r--r--src/backends/zenity.rs14
-rw-r--r--src/lib.rs107
9 files changed, 181 insertions, 5 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 939ae76..99a219f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ SPDX-License-Identifier: CC0-1.0
# Unreleased
- Add the `KDialog` backend (contributed by Stephan Sokolow).
+- Add the `FileSelection` dialog (contributed by Reyk Floeter).
- Comply with version 3.0 of the REUSE specification.
- Implement `Default` for all backend structs.
diff --git a/Cargo.toml b/Cargo.toml
index 57bc68f..355b789 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,3 +16,4 @@ license = "MIT"
[dependencies]
rpassword = "2"
+dirs = "2.0"
diff --git a/examples/file_selection.rs b/examples/file_selection.rs
new file mode 100644
index 0000000..5d2d860
--- /dev/null
+++ b/examples/file_selection.rs
@@ -0,0 +1,21 @@
+// Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org>
+// SPDX-License-Identifier: MIT
+
+use dialog::DialogBox;
+
+fn main() -> dialog::Result<()> {
+ let choice = dialog::FileSelection::new("Please select a file")
+ .title("File Chooser Example (Open)")
+ .path("/etc")
+ .show()?;
+ println!("The user chose: {:?}", choice);
+
+ let choice = dialog::FileSelection::new("Please select a file")
+ .title("File Chooser Example (Save)")
+ .mode(dialog::FileSelectionMode::Save)
+ .path("/etc")
+ .show()?;
+ println!("The user chose: {:?}", choice);
+
+ Ok(())
+}
diff --git a/src/backends/dialog.rs b/src/backends/dialog.rs
index 8061d98..668cf57 100644
--- a/src/backends/dialog.rs
+++ b/src/backends/dialog.rs
@@ -3,7 +3,7 @@
use std::process;
-use crate::{Choice, Error, Input, Message, Password, Question, Result};
+use crate::{Choice, Error, FileSelection, Input, Message, Password, Question, Result};
/// The `dialog` backend.
///
@@ -160,4 +160,11 @@ impl super::Backend for Dialog {
self.execute(args, vec![], &question.title)
.and_then(|output| get_choice(output.status))
}
+
+ fn show_file_selection(&self, file_selection: &FileSelection) -> Result<Option<String>> {
+ let dir = file_selection.path_to_string().ok_or("path not valid")?;
+ let args = vec!["--fselect", &dir];
+ self.execute(args, vec![], &file_selection.title)
+ .and_then(get_stderr)
+ }
}
diff --git a/src/backends/kdialog.rs b/src/backends/kdialog.rs
index c2ddcd0..21928f8 100644
--- a/src/backends/kdialog.rs
+++ b/src/backends/kdialog.rs
@@ -4,7 +4,9 @@
use std::process;
-use crate::{Choice, Error, Input, Message, Password, Question, Result};
+use crate::{
+ Choice, Error, FileSelection, FileSelectionMode, Input, Message, Password, Question, Result,
+};
/// Subprocess exit codes
///
@@ -136,4 +138,15 @@ impl super::Backend for KDialog {
self.execute(args, &question.title)
.and_then(|output| get_choice(output.status))
}
+
+ fn show_file_selection(&self, file_selection: &FileSelection) -> Result<Option<String>> {
+ let dir = file_selection.path_to_string().ok_or("path not valid")?;
+ let option = match file_selection.mode {
+ FileSelectionMode::Open => "--getopenfilename",
+ FileSelectionMode::Save => "--getsavefilename",
+ };
+ let args = vec![option, &dir];
+ self.execute(args, &file_selection.title)
+ .and_then(get_stdout)
+ }
}
diff --git a/src/backends/mod.rs b/src/backends/mod.rs
index 1331323..09013b1 100644
--- a/src/backends/mod.rs
+++ b/src/backends/mod.rs
@@ -37,6 +37,9 @@ pub trait Backend {
/// Shows the given question dialog and returns the choice.
fn show_question(&self, question: &super::Question) -> Result<super::Choice>;
+
+ /// Shows the given file selection dialog and returns the file name.
+ fn show_file_selection(&self, file_selection: &super::FileSelection) -> Result<Option<String>>;
}
pub(crate) fn is_available(name: &str) -> bool {
diff --git a/src/backends/stdio.rs b/src/backends/stdio.rs
index 9a153df..627714a 100644
--- a/src/backends/stdio.rs
+++ b/src/backends/stdio.rs
@@ -3,7 +3,7 @@
use std::io::{self, Write};
-use crate::{Choice, Input, Message, Password, Question, Result};
+use crate::{Choice, FileSelection, Input, Message, Password, Question, Result};
/// The fallback backend using standard input and output.
///
@@ -86,4 +86,17 @@ impl super::Backend for Stdio {
io::stdout().flush()?;
Ok(parse_choice(&read_input()?))
}
+
+ fn show_file_selection(&self, file_selection: &FileSelection) -> Result<Option<String>> {
+ let dir = file_selection.path_to_string().ok_or("path not valid")?;
+ print_title(&file_selection.title);
+ print!("{} [{}]: ", file_selection.text, dir);
+ io::stdout().flush()?;
+ let result = read_input()?;
+ if result.starts_with('/') {
+ Ok(Some(result))
+ } else {
+ Ok(Some(dir + &result))
+ }
+ }
}
diff --git a/src/backends/zenity.rs b/src/backends/zenity.rs
index 0deca0a..ac0f98f 100644
--- a/src/backends/zenity.rs
+++ b/src/backends/zenity.rs
@@ -3,7 +3,9 @@
use std::process;
-use crate::{Choice, Error, Input, Message, Password, Question, Result};
+use crate::{
+ Choice, Error, FileSelection, FileSelectionMode, Input, Message, Password, Question, Result,
+};
/// The `zenity` backend.
///
@@ -163,4 +165,14 @@ impl super::Backend for Zenity {
self.execute(args, &question.title)
.and_then(|output| get_choice(output.status))
}
+
+ fn show_file_selection(&self, file_selection: &FileSelection) -> Result<Option<String>> {
+ let dir = file_selection.path_to_string().ok_or("path not valid")?;
+ let mut args = vec!["--file-selection", "--filename", &dir];
+ if file_selection.mode == FileSelectionMode::Save {
+ args.push("--save");
+ }
+ self.execute(args, &file_selection.title)
+ .and_then(get_stdout)
+ }
}
diff --git a/src/lib.rs b/src/lib.rs
index 61be22b..aa19105 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -7,6 +7,7 @@
//!
//! The `dialog` crate can be used to display different types of dialog boxes. The supported types
//! are:
+//! - [`FileSelection`][]: a file chooser dialog box
//! - [`Input`][]: a text input dialog
//! - [`Message`][]: a simple message box
//! - [`Password`][]: a password input dialog
@@ -70,6 +71,7 @@
//! ```
//!
//! [`Dialog`]: backends/struct.Dialog.html
+//! [`FileSelection`]: struct.FileSelection.html
//! [`Input`]: struct.Input.html
//! [`Message`]: struct.Message.html
//! [`Password`]: struct.Password.html
@@ -92,7 +94,11 @@ mod error;
/// [`Backend`]: trait.Backend.html
pub mod backends;
-use std::env;
+use dirs;
+use std::{
+ env,
+ path::{Path, PathBuf},
+};
pub use crate::error::{Error, Result};
@@ -342,6 +348,105 @@ impl DialogBox for Question {
}
}
+/// The type of a file selection dialog.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum FileSelectionMode {
+ /// An Open File dialog, meaning that the user can only select an existing file.
+ Open,
+ /// A Save File dialog, meaning that the user is allowed to select a non-existing file.
+ Save,
+}
+
+/// A file chooser dialog box.
+///
+/// This dialog box opens a file choser with an optional title in the specified path. If the path
+/// is not specified, it defaults to the user’s home directory.
+///
+/// The backends might support multiple operation modes, for example open or save dialogs. You can
+/// select a mode using the [`FileSelectionMode`][] enum, though the backend might ignore the mode
+/// and just display a simple file dialog. Per default, the mode is set to `Open`.
+///
+/// # Example
+///
+/// ```no_run
+/// use dialog::DialogBox;
+///
+/// let choice = dialog::FileSelection::new("Please select a file")
+/// .title("File Selection")
+/// .path("/home/user/Downloads")
+/// .show()
+/// .expect("Could not display dialog box");
+/// println!("The user chose: {:?}", choice);
+/// ```
+///
+/// [`FileSelectionMode`]: enum.FileSelectionMode.html
+pub struct FileSelection {
+ text: String,
+ title: Option<String>,
+ path: Option<PathBuf>,
+ mode: FileSelectionMode,
+}
+
+impl FileSelection {
+ /// Creates a new file chooser with the given path.
+ pub fn new(text: impl Into<String>) -> FileSelection {
+ FileSelection {
+ text: text.into(),
+ title: None,
+ path: dirs::home_dir(),
+ mode: FileSelectionMode::Open,
+ }
+ }
+
+ /// Sets the title of this file chooser dialog box.
+ ///
+ /// This method returns a reference to `self` to enable chaining.
+ pub fn title(&mut self, title: impl Into<String>) -> &mut FileSelection {
+ self.title = Some(title.into());
+ self
+ }
+
+ /// Sets the path of this file chooser dialog box.
+ ///
+ /// This method returns a reference to `self` to enable chaining.
+ pub fn path(&mut self, path: impl AsRef<Path>) -> &mut FileSelection {
+ self.path = Some(path.as_ref().to_path_buf());
+ self
+ }
+
+ /// Gets the path of this file chooser dialog box.
+ ///
+ /// This method returns the validated directory as a `String`.
+ pub fn path_to_string(&self) -> Option<String> {
+ match self.path {
+ Some(ref path) if path.is_dir() => {
+ // The backends expect a trailing / after the directory
+ path.to_str().map(|s| s.to_string() + "/")
+ }
+ _ => None,
+ }
+ }
+
+ /// Sets the operation mode of the file chooser.
+ ///
+ /// This method returns a reference to `self` to enable chaining.
+ pub fn mode(&mut self, mode: FileSelectionMode) -> &mut FileSelection {
+ self.mode = mode;
+ self
+ }
+}
+
+impl DialogBox for FileSelection {
+ type Output = Option<String>;
+
+ fn show_with<B>(&self, backend: impl AsRef<B>) -> Result<Self::Output>
+ where
+ B: backends::Backend + ?Sized,
+ {
+ backend.as_ref().show_file_selection(self)
+ }
+}
+
/// Creates a new instance of the default backend.
///
/// The following steps are performed to determine the default backend: