From a23de9787bccde4b6244312f04cb7cfc3b528db6 Mon Sep 17 00:00:00 2001 From: Stephan Sokolow Date: Thu, 24 Oct 2019 20:36:42 -0400 Subject: Add a backend based on KDE's `kdialog` --- CHANGELOG.md | 3 + README.md | 5 +- examples/backend-kdialog.rs | 18 ++++++ src/backends/kdialog.rs | 143 ++++++++++++++++++++++++++++++++++++++++++++ src/backends/mod.rs | 3 + src/lib.rs | 24 +++++++- 6 files changed, 191 insertions(+), 5 deletions(-) create mode 100644 examples/backend-kdialog.rs create mode 100644 src/backends/kdialog.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index edf65a1..c327be9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- Add the `KDialog` backend (contributed by Stephan Sokolow). + # v0.2.1 (2019-06-30) - Fix the input and password dialogs for the `zenity` backend (thanks Silvano Cortesi for the bug report): diff --git a/README.md b/README.md index d2439b0..bd73da3 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,9 @@ A Rust library for displaying dialog boxes using various backends. [Documentation][] Currently `dialog-rs` supports input, message, password and question dialogs. -It can use the `dialog` or `zenity` tools to display the dialog boxes. If none -of these tools is available, the dialogs are printed to the standard output. +It can use the `dialog`, `kdialog`, or `zenity` tools to display the dialog +boxes. If none of these tools is available, the dialogs are printed to the +standard output. ## Example diff --git a/examples/backend-kdialog.rs b/examples/backend-kdialog.rs new file mode 100644 index 0000000..5c3fb46 --- /dev/null +++ b/examples/backend-kdialog.rs @@ -0,0 +1,18 @@ +// Copyright (C) 2019 Robin Krahl +// SPDX-License-Identifier: MIT + +use dialog::backends; +use dialog::DialogBox; + +fn main() -> dialog::Result<()> { + let mut backend = backends::KDialog::new(); + + dialog::Message::new("This is a message.") + .title("And this is a title:") + .show_with(&backend)?; + + backend.set_icon("error"); + dialog::Message::new("This is an error message.") + .title("Error") + .show_with(&backend) +} diff --git a/src/backends/kdialog.rs b/src/backends/kdialog.rs new file mode 100644 index 0000000..153e4c5 --- /dev/null +++ b/src/backends/kdialog.rs @@ -0,0 +1,143 @@ +// Copyright (C) 2019 Robin Krahl +// Copyright (C) 2019 Stephan Sokolow +// SPDX-License-Identifier: MIT + +use std::process; + +use crate::{Choice, Error, Input, Message, Password, Question, Result}; + +/// Subprocess exit codes +/// +/// Note: `kdialog` doesn't have a fixed correspondence between button labels and status codes. +/// The following mappings occur: +/// +/// - Yes/No = `0`/`1` +/// - Yes/No/Cancel = `0`/`1`/`2` +/// - OK/Cancel = `0`/`1` +const OK: i32 = 0; +const CANCEL: i32 = 1; + +/// The `kdialog` backend. +/// +/// This backend uses the external `kdialog` program to display KDE dialog boxes. +#[derive(Debug)] +pub struct KDialog { + icon: Option, + // TODO: --dontagain +} + +impl KDialog { + /// Creates a new `KDialog` instance without configuration. + pub fn new() -> KDialog { + KDialog { icon: None } + } + + /// Sets the icon in the dialog box's titlebar and taskbar button. + /// + /// The icon can be either a name from the user's configured icon theme, such as `error` or + /// `info` or the path to an image to use. + /// + /// The default image depends on the dialog type. + pub fn set_icon(&mut self, icon: impl Into) { + self.icon = Some(icon.into()); + } + + pub(crate) fn is_available() -> bool { + super::is_available("kdialog") + } + + fn execute(&self, args: Vec<&str>, title: &Option) -> Result { + let mut command = process::Command::new("kdialog"); + + if let Some(ref icon) = self.icon { + command.arg("--icon"); + command.arg(icon); + } + if let Some(ref title) = title { + command.arg("--title"); + command.arg(title); + } + + command.args(args); + command.output().map_err(Error::IoError) + } +} + +impl AsRef for KDialog { + fn as_ref(&self) -> &Self { + self + } +} + +fn require_success(status: process::ExitStatus) -> Result<()> { + if status.success() { + Ok(()) + } else { + if let Some(code) = status.code() { + match code { + CANCEL => Ok(()), + _ => Err(Error::from(("kdialog", status))), + } + } else { + Err(Error::from(("kdialog", status))) + } + } +} + +fn get_choice(status: process::ExitStatus) -> Result { + if let Some(code) = status.code() { + match code { + OK => Ok(Choice::Yes), + CANCEL => Ok(Choice::No), + _ => Err(Error::from(("kdialog", status))), + } + } else { + Err(Error::from(("kdialog", status))) + } +} + +fn get_stdout(output: process::Output) -> Result> { + if output.status.success() { + String::from_utf8(output.stdout) + .map(|s| Some(s.trim_end_matches('\n').to_string())) + .map_err(|err| Error::from(err)) + } else { + if let Some(code) = output.status.code() { + match code { + OK => Ok(None), + CANCEL => Ok(None), + _ => Err(Error::from(("kdialog", output.status))), + } + } else { + Err(Error::from(("kdialog", output.status))) + } + } +} + +impl super::Backend for KDialog { + fn show_input(&self, input: &Input) -> Result> { + let mut args = vec!["--inputbox", &input.text]; + if let Some(ref default) = input.default { + args.push(default); + } + self.execute(args, &input.title).and_then(get_stdout) + } + + fn show_message(&self, message: &Message) -> Result<()> { + let args = vec!["--msgbox", &message.text]; + self.execute(args, &message.title) + .and_then(|output| require_success(output.status)) + .map(|_| ()) + } + + fn show_password(&self, password: &Password) -> Result> { + let args = vec!["--password", &password.text]; + self.execute(args, &password.title).and_then(get_stdout) + } + + fn show_question(&self, question: &Question) -> Result { + let args = vec!["--yesno", &question.text]; + self.execute(args, &question.title) + .and_then(|output| get_choice(output.status)) + } +} diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 5ae3cef..d5dac1c 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -2,10 +2,12 @@ // SPDX-License-Identifier: MIT mod dialog; +mod kdialog; mod stdio; mod zenity; pub use crate::backends::dialog::Dialog; +pub use crate::backends::kdialog::KDialog; pub use crate::backends::stdio::Stdio; pub use crate::backends::zenity::Zenity; @@ -51,6 +53,7 @@ pub(crate) fn is_available(name: &str) -> bool { pub(crate) fn from_str(s: &str) -> Option> { match s.to_lowercase().as_ref() { "dialog" => Some(Box::new(Dialog::new())), + "kdialog" => Some(Box::new(KDialog::new())), "stdio" => Some(Box::new(Stdio::new())), "zenity" => Some(Box::new(Zenity::new())), _ => None, diff --git a/src/lib.rs b/src/lib.rs index f4ac763..61be22b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,8 @@ //! These dialog boxes can be displayed using various backends: //! - [`Dialog`][]: uses `dialog` to display ncurses-based dialog boxes (requires the external //! `dialog` tool) +//! - [`KDialog`][]: uses `kdialog` to display Qt-based dialog boxes (requires the external +//! `kdialog` tool) //! - [`Stdio`][]: prints messages to the standard output and reads user input form standard input //! (intended as a fallback backend) //! - [`Zenity`][]: uses `zenity` to display GTK-based dialog boxes (requires the external `zenity` @@ -72,6 +74,7 @@ //! [`Message`]: struct.Message.html //! [`Password`]: struct.Password.html //! [`Question`]: struct.Question.html +//! [`KDialog`]: backends/struct.KDialog.html //! [`Stdio`]: backends/struct.Stdio.html //! [`Zenity`]: backends/struct.Zenity.html //! [`default_backend`]: fn.default_backend.html @@ -345,13 +348,16 @@ impl DialogBox for Question { /// - If the `DIALOG` environment variable is set to a valid backend name, this backend is used. /// A valid backend name is the name of a struct in the `backends` module implementing the /// `Backend` trait in any case. -/// - If the `DISPLAY` environment variable is set, the first available backend from this list is -/// used: -/// - [`Zenity`][] +/// - If the `DISPLAY` environment variable is set, the following resolution algorithm is used: +/// - If the `XDG_CURRENT_DESKTOP` environment variable is set to `KDE`, [`KDialog`][] is used. +/// - Otherwise, the first available backend from this list is used: +/// - [`Zenity`][] +/// - [`KDialog`][] /// - If the [`Dialog`][] backend is available, it is used. /// - Otherwise, a [`Stdio`][] instance is returned. /// /// [`Dialog`]: backends/struct.Dialog.html +/// [`KDialog`]: backends/struct.KDialog.html /// [`Stdio`]: backends/struct.Stdio.html /// [`Zenity`]: backends/struct.Zenity.html pub fn default_backend() -> Box { @@ -363,9 +369,21 @@ pub fn default_backend() -> Box { if let Ok(display) = env::var("DISPLAY") { if !display.is_empty() { + // Prefer KDialog if the user is logged into a KDE session + let kdialog_available = backends::KDialog::is_available(); + if let Ok(desktop) = env::var("XDG_CURRENT_DESKTOP") { + if kdialog_available && desktop == "KDE" { + return Box::new(backends::KDialog::new()); + } + } + if backends::Zenity::is_available() { return Box::new(backends::Zenity::new()); } + + if kdialog_available { + return Box::new(backends::KDialog::new()); + } } } -- cgit v1.2.1 From 9a2e1124bc97a003ad8916a17653ff2d1fa4250d Mon Sep 17 00:00:00 2001 From: Stephan Sokolow Date: Thu, 24 Oct 2019 20:37:03 -0400 Subject: Fix broken link in `backends` documentation (Revealed by cargo-deadlinks) --- src/backends/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backends/mod.rs b/src/backends/mod.rs index d5dac1c..f69322f 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -23,7 +23,7 @@ use crate::Result; /// backend and create an instance manually. To use a backend, pass it to the [`show_with`][] /// method of a dialog box. /// -/// [`default_backend`]: ../function.default_backend.html +/// [`default_backend`]: ../fn.default_backend.html /// [`show_with`]: ../trait.DialogBox.html#method.show_with pub trait Backend { /// Shows the given input dialog and returns the input. -- cgit v1.2.1 From 9273f3f45f7418e87b6e53baef78612ef0d6c5e7 Mon Sep 17 00:00:00 2001 From: Stephan Sokolow Date: Thu, 24 Oct 2019 20:44:39 -0400 Subject: Fix clippy complaints that don't change the semantics (Clippy also complains about `new()` without `impl Default`) --- src/backends/dialog.rs | 22 ++++++++++------------ src/backends/kdialog.rs | 32 ++++++++++++++------------------ src/backends/mod.rs | 2 +- src/backends/stdio.rs | 2 +- src/backends/zenity.rs | 34 +++++++++++++++------------------- 5 files changed, 41 insertions(+), 51 deletions(-) diff --git a/src/backends/dialog.rs b/src/backends/dialog.rs index e681caf..85b0294 100644 --- a/src/backends/dialog.rs +++ b/src/backends/dialog.rs @@ -111,19 +111,17 @@ fn get_choice(status: process::ExitStatus) -> Result { fn get_stderr(output: process::Output) -> Result> { if output.status.success() { String::from_utf8(output.stderr) - .map(|s| Some(s)) - .map_err(|err| Error::from(err)) - } else { - if let Some(code) = output.status.code() { - match code { - 0 => Ok(None), - 1 => Ok(None), - 255 => Ok(None), - _ => Err(Error::from(("dialog", output.status))), - } - } else { - Err(Error::from(("dialog", output.status))) + .map(Some) + .map_err(Error::from) + } else if let Some(code) = output.status.code() { + match code { + 0 => Ok(None), + 1 => Ok(None), + 255 => Ok(None), + _ => Err(Error::from(("dialog", output.status))), } + } else { + Err(Error::from(("dialog", output.status))) } } diff --git a/src/backends/kdialog.rs b/src/backends/kdialog.rs index 153e4c5..34070c4 100644 --- a/src/backends/kdialog.rs +++ b/src/backends/kdialog.rs @@ -72,15 +72,13 @@ impl AsRef for KDialog { fn require_success(status: process::ExitStatus) -> Result<()> { if status.success() { Ok(()) - } else { - if let Some(code) = status.code() { - match code { - CANCEL => Ok(()), - _ => Err(Error::from(("kdialog", status))), - } - } else { - Err(Error::from(("kdialog", status))) + } else if let Some(code) = status.code() { + match code { + CANCEL => Ok(()), + _ => Err(Error::from(("kdialog", status))), } + } else { + Err(Error::from(("kdialog", status))) } } @@ -100,17 +98,15 @@ fn get_stdout(output: process::Output) -> Result> { if output.status.success() { String::from_utf8(output.stdout) .map(|s| Some(s.trim_end_matches('\n').to_string())) - .map_err(|err| Error::from(err)) - } else { - if let Some(code) = output.status.code() { - match code { - OK => Ok(None), - CANCEL => Ok(None), - _ => Err(Error::from(("kdialog", output.status))), - } - } else { - Err(Error::from(("kdialog", output.status))) + .map_err(Error::from) + } else if let Some(code) = output.status.code() { + match code { + OK => Ok(None), + CANCEL => Ok(None), + _ => Err(Error::from(("kdialog", output.status))), } + } else { + Err(Error::from(("kdialog", output.status))) } } diff --git a/src/backends/mod.rs b/src/backends/mod.rs index f69322f..1331323 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -41,7 +41,7 @@ pub trait Backend { pub(crate) fn is_available(name: &str) -> bool { if let Ok(path) = env::var("PATH") { - for part in path.split(":") { + for part in path.split(':') { if path::Path::new(part).join(name).exists() { return true; } diff --git a/src/backends/stdio.rs b/src/backends/stdio.rs index 6838ef1..4e4c8ec 100644 --- a/src/backends/stdio.rs +++ b/src/backends/stdio.rs @@ -35,7 +35,7 @@ fn print_title(title: &Option) { fn read_input() -> Result { let mut input = String::new(); io::stdin().read_line(&mut input)?; - Ok(input.trim_end_matches("\n").to_string()) + Ok(input.trim_end_matches('\n').to_string()) } fn parse_choice(input: &str) -> Choice { diff --git a/src/backends/zenity.rs b/src/backends/zenity.rs index 85206b3..12e052b 100644 --- a/src/backends/zenity.rs +++ b/src/backends/zenity.rs @@ -101,15 +101,13 @@ impl AsRef for Zenity { fn require_success(status: process::ExitStatus) -> Result<()> { if status.success() { Ok(()) - } else { - if let Some(code) = status.code() { - match code { - 5 => Ok(()), - _ => Err(Error::from(("zenity", status))), - } - } else { - Err(Error::from(("zenity", status))) + } else if let Some(code) = status.code() { + match code { + 5 => Ok(()), + _ => Err(Error::from(("zenity", status))), } + } else { + Err(Error::from(("zenity", status))) } } @@ -130,18 +128,16 @@ fn get_stdout(output: process::Output) -> Result> { if output.status.success() { String::from_utf8(output.stdout) .map(|s| Some(s.trim_end_matches('\n').to_string())) - .map_err(|err| Error::from(err)) - } else { - if let Some(code) = output.status.code() { - match code { - 0 => Ok(None), - 1 => Ok(None), - 5 => Ok(None), - _ => Err(Error::from(("zenity", output.status))), - } - } else { - Err(Error::from(("zenity", output.status))) + .map_err(Error::from) + } else if let Some(code) = output.status.code() { + match code { + 0 => Ok(None), + 1 => Ok(None), + 5 => Ok(None), + _ => Err(Error::from(("zenity", output.status))), } + } else { + Err(Error::from(("zenity", output.status))) } } -- cgit v1.2.1 From dfe809ae6c191cbc8478363bc5b0ed2626f91bb0 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sat, 26 Oct 2019 13:07:41 +0200 Subject: Add missing backtick in doc comment --- src/backends/zenity.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backends/zenity.rs b/src/backends/zenity.rs index 12e052b..d5745eb 100644 --- a/src/backends/zenity.rs +++ b/src/backends/zenity.rs @@ -29,7 +29,7 @@ impl Zenity { /// Sets the icon of the dialog box. /// - /// The icon can either be one of `error`, `info`, `question` or `warning, or the path to an + /// The icon can either be one of `error`, `info`, `question` or `warning`, or the path to an /// image to use. The default image depends on the dialog type. pub fn set_icon(&mut self, icon: impl Into) { self.icon = Some(icon.into()); -- cgit v1.2.1 From ca06c4c4a2846b1c0410af1c7a68c65f5489a0b9 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 27 Oct 2019 11:42:45 +0100 Subject: Comply with the REUSE specification 3.0 The REUSE specification defines best practices for handling of copyright information in open source software projects. This patch adds copyright and license statements to some files and renames the LICENSE file to comply with version 3.0 of the specification. --- .gitignore | 3 ++ CHANGELOG.md | 6 +++ Cargo.toml | 3 ++ LICENSE | 21 --------- LICENSES/CC0-1.0.txt | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++ LICENSES/MIT.txt | 21 +++++++++ README.md | 8 ++++ 7 files changed, 160 insertions(+), 21 deletions(-) delete mode 100644 LICENSE create mode 100644 LICENSES/CC0-1.0.txt create mode 100644 LICENSES/MIT.txt diff --git a/.gitignore b/.gitignore index 2fcd2ae..6f7571a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2019 Robin Krahl +# SPDX-License-Identifier: CC0-1.0 + /target **/*.rs.bk Cargo.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index c327be9..0d6256c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ + + # Unreleased - Add the `KDialog` backend (contributed by Stephan Sokolow). +- Comply with version 3.0 of the REUSE specification. # v0.2.1 (2019-06-30) - Fix the input and password dialogs for the `zenity` backend (thanks Silvano diff --git a/Cargo.toml b/Cargo.toml index 2f11081..57bc68f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2019 Robin Krahl +# SPDX-License-Identifier: MIT + [package] name = "dialog" version = "0.2.1" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 1a3601d..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2018 Robin Krahl - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/LICENSES/CC0-1.0.txt b/LICENSES/CC0-1.0.txt new file mode 100644 index 0000000..a343ccd --- /dev/null +++ b/LICENSES/CC0-1.0.txt @@ -0,0 +1,119 @@ +Creative Commons Legal Code + +CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES +NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE +AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION +ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE +OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS +LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION +OR WORKS PROVIDED HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer exclusive +Copyright and Related Rights (defined below) upon the creator and subsequent +owner(s) (each and all, an "owner") of an original work of authorship and/or +a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later claims +of infringement build upon, modify, incorporate in other works, reuse and +redistribute as freely as possible in any form whatsoever and for any purposes, +including without limitation commercial purposes. These owners may contribute +to the Commons to promote the ideal of a free culture and the further production +of creative, cultural and scientific works, or to gain reputation or greater +distribution for their Work in part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with +a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or +her Copyright and Related Rights in the Work and the meaning and intended +legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be protected +by copyright and related or neighboring rights ("Copyright and Related Rights"). +Copyright and Related Rights include, but are not limited to, the following: + +i. the right to reproduce, adapt, distribute, perform, display, communicate, +and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + +iii. publicity and privacy rights pertaining to a person's image or likeness +depicted in a Work; + +iv. rights protecting against unfair competition in regards to a Work, subject +to the limitations in paragraph 4(a), below; + +v. rights protecting the extraction, dissemination, use and reuse of data +in a Work; + +vi. database rights (such as those arising under Directive 96/9/EC of the +European Parliament and of the Council of 11 March 1996 on the legal protection +of databases, and under any national implementation thereof, including any +amended or successor version of such directive); and + +vii. other similar, equivalent or corresponding rights throughout the world +based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time extensions), +(iii) in any current or future medium and for any number of copies, and (iv) +for any purpose whatsoever, including without limitation commercial, advertising +or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the +benefit of each member of the public at large and to the detriment of Affirmer's +heirs and successors, fully intending that such Waiver shall not be subject +to revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account Affirmer's +express Statement of Purpose. In addition, to the extent the Waiver is so +judged Affirmer hereby grants to each affected person a royalty-free, non +transferable, non sublicensable, non exclusive, irrevocable and unconditional +license to exercise Affirmer's Copyright and Related Rights in the Work (i) +in all territories worldwide, (ii) for the maximum duration provided by applicable +law or treaty (including future time extensions), (iii) in any current or +future medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional purposes +(the "License"). The License shall be deemed effective as of the date CC0 +was applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder of +the License, and in such case Affirmer hereby affirms that he or she will +not (i) exercise any of his or her remaining Copyright and Related Rights +in the Work or (ii) assert any associated claims and causes of action with +respect to the Work, in either case contrary to Affirmer's express Statement +of Purpose. + + 4. Limitations and Disclaimers. + +a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, +licensed or otherwise affected by this document. + +b. Affirmer offers the Work as-is and makes no representations or warranties +of any kind concerning the Work, express, implied, statutory or otherwise, +including without limitation warranties of title, merchantability, fitness +for a particular purpose, non infringement, or the absence of latent or other +defects, accuracy, or the present or absence of errors, whether or not discoverable, +all to the greatest extent permissible under applicable law. + +c. Affirmer disclaims responsibility for clearing rights of other persons +that may apply to the Work or any use thereof, including without limitation +any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims +responsibility for obtaining any necessary consents, permissions or other +rights required for any use of the Work. + +d. Affirmer understands and acknowledges that Creative Commons is not a party +to this document and has no duty or obligation with respect to this CC0 or +use of the Work. diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 0000000..741cbc3 --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 Robin Krahl + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index bd73da3..1c6f6d0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ + + # dialog-rs A Rust library for displaying dialog boxes using various backends. @@ -35,6 +40,9 @@ mail to [dialog-rs-dev@ireas.org][]. This project is licensed under the [MIT License][]. +`dialog-rs` complies with [version 3.0 of the REUSE specification][reuse]. + [Documentation]: https://docs.rs/dialog [dialog-rs-dev@ireas.org]: mailto:dialog-rs-dev@ireas.org [MIT license]: https://opensource.org/licenses/MIT +[reuse]: https://reuse.software/practices/3.0/ -- cgit v1.2.1 From 0970381271bd07f020a888b027653ca627e8655c Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 27 Oct 2019 10:55:30 +0000 Subject: Implement Default for all backend structs This fixes the clippy warning new_without_default [0]. [0] https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default --- CHANGELOG.md | 1 + src/backends/dialog.rs | 16 +++++++++++----- src/backends/kdialog.rs | 4 ++-- src/backends/stdio.rs | 4 ++-- src/backends/zenity.rs | 9 ++------- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d6256c..939ae76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ SPDX-License-Identifier: CC0-1.0 # Unreleased - Add the `KDialog` backend (contributed by Stephan Sokolow). - Comply with version 3.0 of the REUSE specification. +- Implement `Default` for all backend structs. # v0.2.1 (2019-06-30) - Fix the input and password dialogs for the `zenity` backend (thanks Silvano diff --git a/src/backends/dialog.rs b/src/backends/dialog.rs index 85b0294..8061d98 100644 --- a/src/backends/dialog.rs +++ b/src/backends/dialog.rs @@ -19,11 +19,7 @@ pub struct Dialog { impl Dialog { /// Creates a new `Dialog` instance without configuration. pub fn new() -> Dialog { - Dialog { - backtitle: None, - height: "0".to_string(), - width: "0".to_string(), - } + Default::default() } /// Sets the backtitle for the dialog boxes. @@ -87,6 +83,16 @@ impl AsRef for Dialog { } } +impl Default for Dialog { + fn default() -> Self { + Dialog { + backtitle: None, + height: "0".to_string(), + width: "0".to_string(), + } + } +} + fn require_success(status: process::ExitStatus) -> Result<()> { if status.success() { Ok(()) diff --git a/src/backends/kdialog.rs b/src/backends/kdialog.rs index 34070c4..c2ddcd0 100644 --- a/src/backends/kdialog.rs +++ b/src/backends/kdialog.rs @@ -20,7 +20,7 @@ const CANCEL: i32 = 1; /// The `kdialog` backend. /// /// This backend uses the external `kdialog` program to display KDE dialog boxes. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct KDialog { icon: Option, // TODO: --dontagain @@ -29,7 +29,7 @@ pub struct KDialog { impl KDialog { /// Creates a new `KDialog` instance without configuration. pub fn new() -> KDialog { - KDialog { icon: None } + Default::default() } /// Sets the icon in the dialog box's titlebar and taskbar button. diff --git a/src/backends/stdio.rs b/src/backends/stdio.rs index 4e4c8ec..9a153df 100644 --- a/src/backends/stdio.rs +++ b/src/backends/stdio.rs @@ -9,13 +9,13 @@ use crate::{Choice, Input, Message, Password, Question, Result}; /// /// This backend is intended as a fallback backend to use if no other backend is available. The /// dialogs are printed to the standard output and user input is read from the standard input. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Stdio {} impl Stdio { /// Creates a new `Stdio` instance. pub fn new() -> Stdio { - Stdio {} + Default::default() } } diff --git a/src/backends/zenity.rs b/src/backends/zenity.rs index d5745eb..0deca0a 100644 --- a/src/backends/zenity.rs +++ b/src/backends/zenity.rs @@ -8,7 +8,7 @@ use crate::{Choice, Error, Input, Message, Password, Question, Result}; /// The `zenity` backend. /// /// This backend uses the external `zenity` program to display GTK+ dialog boxes. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Zenity { icon: Option, width: Option, @@ -19,12 +19,7 @@ pub struct Zenity { impl Zenity { /// Creates a new `Zenity` instance without configuration. pub fn new() -> Zenity { - Zenity { - icon: None, - width: None, - height: None, - timeout: None, - } + Default::default() } /// Sets the icon of the dialog box. -- cgit v1.2.1 From 3b76a0a46ce38eef7fe3e5a969cec6ffee47f227 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 27 Oct 2019 11:01:08 +0000 Subject: Add CI scripts for builds.sr.ht This patch adds three CI scripts: - archlinux builds dialog-rs on Arch with the latest rustc version. - debian builds dialog-rs on Debian Stable with an older rustc version. - lint checks the formatting, REUSE compliance and clippy warnings. These scripts are executed on each push. If a script fails, the committer will receive a mail. If a script fails on the master branch, a mail is sent to the mailing list instead. The builds are also listed in the web interface at: https://builds.sr.ht/~ireas/dialog-rs --- .builds/archlinux.yml | 14 ++++++++++++++ .builds/debian.yml | 14 ++++++++++++++ .builds/lint.yml | 27 +++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 .builds/archlinux.yml create mode 100644 .builds/debian.yml create mode 100644 .builds/lint.yml diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml new file mode 100644 index 0000000..52e8156 --- /dev/null +++ b/.builds/archlinux.yml @@ -0,0 +1,14 @@ +# Copyright (C) 2019 Robin Krahl +# SPDX-License-Identifier: MIT +image: archlinux +packages: + - rust +sources: + - https://git.ireas.org/dialog-rs +tasks: + - build: | + cd dialog-rs + cargo build --release + - test: | + cd dialog-rs + cargo test diff --git a/.builds/debian.yml b/.builds/debian.yml new file mode 100644 index 0000000..9140c92 --- /dev/null +++ b/.builds/debian.yml @@ -0,0 +1,14 @@ +# Copyright (C) 2019 Robin Krahl +# SPDX-License-Identifier: MIT +image: debian/stable +packages: + - cargo +sources: + - https://git.ireas.org/dialog-rs +tasks: + - build: | + cd dialog-rs + cargo build --release + - test: | + cd dialog-rs + cargo test diff --git a/.builds/lint.yml b/.builds/lint.yml new file mode 100644 index 0000000..6b06f91 --- /dev/null +++ b/.builds/lint.yml @@ -0,0 +1,27 @@ +# Copyright (C) 2019 Robin Krahl +# SPDX-License-Identifier: MIT +image: archlinux +packages: + - rustup + - python + - python-pip + - python-pygit2 +sources: + - https://git.ireas.org/dialog-rs +tasks: + - setup: | + pip install --user fsfe-reuse + rustup update stable + rustup self upgrade-data + rustup default stable + rustup component add rustfmt + rustup component add clippy + - format: | + cd dialog-rs + cargo fmt -- --check + - reuse: | + cd dialog-rs + ~/.local/bin/reuse lint + - clippy: | + cd dialog-rs + cargo clippy -- -D warnings -- cgit v1.2.1 From 2f3e2b5474834e3d733edf09b831c5607d451f49 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 10 Dec 2019 15:24:36 +0000 Subject: Add FileSelection dialog type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds the FileSelection struct representing a file selection dialog. It can be displayed using the backend’s show_file_selection function. Currently, we only support file open dialogs (i. e. choosing an existing file). Support for save dialogs should be added in the future. --- Cargo.toml | 1 + examples/file_selection.rs | 13 ++++++++ src/backends/dialog.rs | 9 ++++- src/backends/mod.rs | 3 ++ src/backends/stdio.rs | 15 ++++++++- src/backends/zenity.rs | 9 ++++- src/lib.rs | 82 +++++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 examples/file_selection.rs 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..5436742 --- /dev/null +++ b/examples/file_selection.rs @@ -0,0 +1,13 @@ +// Copyright (C) 2019 Robin Krahl +// SPDX-License-Identifier: MIT + +use dialog::DialogBox; + +fn main() -> dialog::Result<()> { + let choice = dialog::FileSelection::new("Please select a file") + .title("File Chooser Example") + .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> { + 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/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; + + /// Shows the given file selection dialog and returns the file name. + fn show_file_selection(&self, file_selection: &super::FileSelection) -> Result>; } 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> { + 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..4ee7785 100644 --- a/src/backends/zenity.rs +++ b/src/backends/zenity.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 `zenity` backend. /// @@ -163,4 +163,11 @@ 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> { + let dir = file_selection.path_to_string().ok_or("path not valid")?; + let args = vec!["--file-selection", "--filename", &dir]; + self.execute(args, &file_selection.title) + .and_then(get_stdout) + } } diff --git a/src/lib.rs b/src/lib.rs index 61be22b..da2a259 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,80 @@ impl DialogBox for Question { } } +/// A file chooser dialog box. +/// +/// This dialog box opens a file chooser with an optional title in the specified path. If the path +/// is not specified, it defaults to the user’s home directory. +/// +/// # 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); +/// ``` +pub struct FileSelection { + text: String, + title: Option, + path: Option, +} + +impl FileSelection { + /// Creates a new file chooser with the given path. + pub fn new(text: impl Into) -> FileSelection { + FileSelection { + text: text.into(), + title: None, + path: dirs::home_dir(), + } + } + + /// 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) -> &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) -> &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 { + 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, + } + } +} + +impl DialogBox for FileSelection { + type Output = Option; + + fn show_with(&self, backend: impl AsRef) -> Result + 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: -- cgit v1.2.1 From 1b50ae033b646db0efc8ea0917685a3a0c8bfc94 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 10 Dec 2019 18:10:30 +0000 Subject: Implement show_file_selection for kdialog backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds an implementation of Backend’s show_file_selection function to the KDialog backend, using KDialog’s --getopenfilename option. --- src/backends/kdialog.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/backends/kdialog.rs b/src/backends/kdialog.rs index c2ddcd0..e556ddf 100644 --- a/src/backends/kdialog.rs +++ b/src/backends/kdialog.rs @@ -4,7 +4,7 @@ use std::process; -use crate::{Choice, Error, Input, Message, Password, Question, Result}; +use crate::{Choice, Error, FileSelection, Input, Message, Password, Question, Result}; /// Subprocess exit codes /// @@ -136,4 +136,11 @@ 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> { + let dir = file_selection.path_to_string().ok_or("path not valid")?; + let args = vec!["--getopenfilename", &dir]; + self.execute(args, &file_selection.title) + .and_then(get_stdout) + } } -- cgit v1.2.1 From 7d1a146003117694e9d970b0cf08c514ffad1c6e Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 10 Dec 2019 19:24:45 +0100 Subject: Add the FileSelection dialog to the change log --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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. -- cgit v1.2.1 From 89afcb4844dd484f0c9cdfef7e5ff8d751647c43 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 10 Dec 2019 18:46:44 +0000 Subject: Add Open/Save mode to the file selection dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds the option to set a FileSelectionMode, either Open or Save. Not all backends might support this – currently, only zenity and kdialog do. Per default, the Open mode is used (as before). --- examples/file_selection.rs | 10 +++++++++- src/backends/kdialog.rs | 10 ++++++++-- src/backends/zenity.rs | 9 +++++++-- src/lib.rs | 27 ++++++++++++++++++++++++++- 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/examples/file_selection.rs b/examples/file_selection.rs index 5436742..5d2d860 100644 --- a/examples/file_selection.rs +++ b/examples/file_selection.rs @@ -5,9 +5,17 @@ use dialog::DialogBox; fn main() -> dialog::Result<()> { let choice = dialog::FileSelection::new("Please select a file") - .title("File Chooser Example") + .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/kdialog.rs b/src/backends/kdialog.rs index e556ddf..21928f8 100644 --- a/src/backends/kdialog.rs +++ b/src/backends/kdialog.rs @@ -4,7 +4,9 @@ use std::process; -use crate::{Choice, Error, FileSelection, Input, Message, Password, Question, Result}; +use crate::{ + Choice, Error, FileSelection, FileSelectionMode, Input, Message, Password, Question, Result, +}; /// Subprocess exit codes /// @@ -139,7 +141,11 @@ impl super::Backend for KDialog { fn show_file_selection(&self, file_selection: &FileSelection) -> Result> { let dir = file_selection.path_to_string().ok_or("path not valid")?; - let args = vec!["--getopenfilename", &dir]; + 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/zenity.rs b/src/backends/zenity.rs index 4ee7785..ac0f98f 100644 --- a/src/backends/zenity.rs +++ b/src/backends/zenity.rs @@ -3,7 +3,9 @@ use std::process; -use crate::{Choice, Error, FileSelection, Input, Message, Password, Question, Result}; +use crate::{ + Choice, Error, FileSelection, FileSelectionMode, Input, Message, Password, Question, Result, +}; /// The `zenity` backend. /// @@ -166,7 +168,10 @@ impl super::Backend for Zenity { fn show_file_selection(&self, file_selection: &FileSelection) -> Result> { let dir = file_selection.path_to_string().ok_or("path not valid")?; - let args = vec!["--file-selection", "--filename", &dir]; + 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 da2a259..aa19105 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -348,11 +348,24 @@ 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 chooser with an optional title in the specified path. If the path +/// 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 @@ -365,10 +378,13 @@ impl DialogBox for Question { /// .expect("Could not display dialog box"); /// println!("The user chose: {:?}", choice); /// ``` +/// +/// [`FileSelectionMode`]: enum.FileSelectionMode.html pub struct FileSelection { text: String, title: Option, path: Option, + mode: FileSelectionMode, } impl FileSelection { @@ -378,6 +394,7 @@ impl FileSelection { text: text.into(), title: None, path: dirs::home_dir(), + mode: FileSelectionMode::Open, } } @@ -409,6 +426,14 @@ impl FileSelection { _ => 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 { -- cgit v1.2.1 From 202c49a327b7a3dc3bfa199da2ac7484ee105f65 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 10 Dec 2019 20:06:00 +0100 Subject: Release v0.3.0 --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99a219f..afaabb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ SPDX-FileCopyrightText: 2019 Robin Krahl SPDX-License-Identifier: CC0-1.0 --> -# Unreleased +# v0.3.0 (2019-12-10) - 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. diff --git a/Cargo.toml b/Cargo.toml index 355b789..7fa888f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "dialog" -version = "0.2.1" +version = "0.3.0" authors = ["Robin Krahl "] edition = "2018" repository = "https://git.ireas.org/dialog-rs/" -- cgit v1.2.1