From fce7d2b9bf30e09b615dbd77b4dd7db94d623bfb Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 8 Jan 2019 03:49:42 +0000 Subject: Add question dialog boxes --- examples/question.rs | 12 ++++++++++ src/backends/dialog.rs | 33 +++++++++++++++++++++++---- src/backends/mod.rs | 3 +++ src/lib.rs | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 examples/question.rs diff --git a/examples/question.rs b/examples/question.rs new file mode 100644 index 0000000..d9c0e91 --- /dev/null +++ b/examples/question.rs @@ -0,0 +1,12 @@ +// Copyright (C) 2019 Robin Krahl +// SPDX-License-Identifier: MIT + +use std::io::Result; + +use dialog::DialogBox; + +fn main() -> Result<()> { + let choice = dialog::Question::new("Do you want to continue?").show()?; + println!("The user chose: {:?}", choice); + Ok(()) +} diff --git a/src/backends/dialog.rs b/src/backends/dialog.rs index 8cde8cc..fb0f73f 100644 --- a/src/backends/dialog.rs +++ b/src/backends/dialog.rs @@ -5,7 +5,7 @@ use std::io; use std::io::Result; use std::process; -use crate::{Input, Message}; +use crate::{Choice, Input, Message, Question}; /// The `dialog` backend. /// @@ -87,6 +87,25 @@ fn require_success(status: process::ExitStatus) -> Result<()> { } } +fn get_choice(status: process::ExitStatus) -> Result { + if let Some(code) = status.code() { + match code { + 0 => Ok(Choice::Yes), + 1 => Ok(Choice::No), + 255 => Ok(Choice::Cancel), + code => Err(io::Error::new( + io::ErrorKind::Other, + format!("Could not execute dialog: {}", code), + )), + } + } else { + Err(io::Error::new( + io::ErrorKind::Other, + "dialog was terminated by a signal", + )) + } +} + fn get_stderr(output: process::Output) -> Result> { if output.status.success() { String::from_utf8(output.stderr) @@ -99,10 +118,10 @@ fn get_stderr(output: process::Output) -> Result> { match code { 0 => Ok(None), 1 => Ok(None), - -1 => Ok(None), - _ => Err(io::Error::new( + 255 => Ok(None), + code => Err(io::Error::new( io::ErrorKind::Other, - "Could not execute dialog", + format!("Could not execute dialog: {}", code), )), } } else { @@ -131,4 +150,10 @@ impl super::Backend for Dialog { .and_then(|output| require_success(output.status)) .map(|_| ()) } + + fn show_question(&self, question: &Question) -> Result { + let args = vec!["--yesno", &question.text]; + self.execute(args, vec![], &question.title) + .and_then(|output| get_choice(output.status)) + } } diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 1abb8d1..12a178b 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -22,4 +22,7 @@ pub trait Backend { /// Shows the given message dialog. fn show_message(&self, message: &super::Message) -> Result<()>; + + /// Shows the given question dialog and returns the choice. + fn show_question(&self, question: &super::Question) -> Result; } diff --git a/src/lib.rs b/src/lib.rs index 0c9d9f9..b51a3cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ //! are: //! - [`Input`][]: a text input dialog //! - [`Message`][]: a simple message box +//! - [`Question`][]: a question dialog box //! //! These dialog boxes can be displayed using various backends: //! - [`Dialog`][]: uses `dialog` to display ncurses-based dialog boxes (requires the external @@ -64,6 +65,7 @@ //! [`Dialog`]: backends/struct.Dialog.html //! [`Input`]: struct.Input.html //! [`Message`]: struct.Message.html +//! [`Question`]: struct.Question.html //! [`default_backend`]: fn.default_backend.html //! [`show`]: trait.DialogBox.html#method.show //! [`show_with`]: trait.DialogBox.html#method.show_with @@ -203,6 +205,64 @@ impl DialogBox for Input { } } +/// A user choise in a dialog box. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Choice { + /// The yes button. + Yes, + /// The no button. + No, + /// The cancel button or a cancelled dialog. + Cancel, +} + +/// A question dialog box. +/// +/// This dialog box displays a text and an optional title and has a yes and a no button. The +/// output is the button presed by the user, or Cancel if the dialog has been cancelled. +/// +/// # Example +/// +/// ```no_run +/// use dialog::DialogBox; +/// +/// let choice = dialog::Question::new("Do you want to continue?") +/// .title("Question") +/// .show() +/// .expect("Could not display dialog box"); +/// println!("The user chose: {:?}", choice); +/// ``` +pub struct Question { + text: String, + title: Option, +} + +impl Question { + /// Creates a new question dialog with the given text. + pub fn new(text: impl Into) -> Question { + Question { + text: text.into(), + title: None, + } + } + + /// Sets the title of this question dialog box. + /// + /// This method returns a reference to `self` to enable chaining. + pub fn title(&mut self, title: impl Into) -> &mut Question { + self.title = Some(title.into()); + self + } +} + +impl DialogBox for Question { + type Output = Choice; + + fn show_with(&self, backend: &impl backends::Backend) -> Result { + backend.show_question(self) + } +} + /// Creates a new instance of the default backend. /// /// The current implementation always returns a [`Dialog`][] instance. -- cgit v1.2.1