From ec84c425282c3fd26f5e862b7864ad2ae7ec1e2e Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 8 Jan 2019 03:16:20 +0000 Subject: Add input dialog boxes This patch implements input dialog boxes. This required some refactoring in the dialog backend to allow additional arguments after the width and the height. --- examples/input.rs | 22 +++++++++++++ src/backends/dialog.rs | 85 ++++++++++++++++++++++++++++++++++++-------------- src/backends/mod.rs | 3 ++ src/lib.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 167 insertions(+), 26 deletions(-) create mode 100644 examples/input.rs diff --git a/examples/input.rs b/examples/input.rs new file mode 100644 index 0000000..bb36fb6 --- /dev/null +++ b/examples/input.rs @@ -0,0 +1,22 @@ +// Copyright (C) 2019 Robin Krahl +// SPDX-License-Identifier: MIT + +use std::io::Result; + +use dialog::DialogBox; + +fn main() -> Result<()> { + let input1 = dialog::Input::new("Please enter something").show()?; + let input2 = dialog::Input::new("Please enter something") + .title("Input form") + .show()?; + let input3 = dialog::Input::new("Please enter something with a default") + .title("Input form") + .default("input") + .show()?; + + println!("Input 1: {:?}", input1); + println!("Input 2: {:?}", input2); + println!("Input 3: {:?}", input3); + Ok(()) +} diff --git a/src/backends/dialog.rs b/src/backends/dialog.rs index d50be43..8cde8cc 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::Message; +use crate::{Input, Message}; /// The `dialog` backend. /// @@ -28,20 +28,6 @@ impl Dialog { } } - fn execute(&self, args: Vec<&str>) -> Result { - let mut args = args; - if let Some(ref backtitle) = self.backtitle { - args.insert(0, "--backtitle"); - args.insert(1, backtitle); - } - println!("{:?}", args); - process::Command::new("dialog") - .args(args) - .stdin(process::Stdio::inherit()) - .stdout(process::Stdio::inherit()) - .output() - } - /// Sets the backtitle for the dialog boxes. /// /// The backtitle is displayed on the backdrop, at the top of the screen. @@ -65,15 +51,31 @@ impl Dialog { self.width = width.to_string(); } - fn show_box(&self, args: Vec<&str>, title: &Option) -> Result { - let mut args = args; + fn execute( + &self, + args: Vec<&str>, + post_args: Vec<&str>, + title: &Option, + ) -> Result { + let mut command = process::Command::new("dialog"); + command.stdin(process::Stdio::inherit()); + command.stdout(process::Stdio::inherit()); + + if let Some(ref backtitle) = self.backtitle { + command.arg("--backtitle"); + command.arg(backtitle); + } if let Some(ref title) = title { - args.insert(0, "--title"); - args.insert(1, title); + command.arg("--title"); + command.arg(title); } - args.push(&self.height); - args.push(&self.width); - self.execute(args) + + command.args(args); + command.arg(&self.height); + command.arg(&self.width); + command.args(post_args); + + command.output() } } @@ -85,10 +87,47 @@ fn require_success(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(|_| { + io::Error::new(io::ErrorKind::Other, "Input contained invalid UTF-8 bytes") + }) + } else { + if let Some(code) = output.status.code() { + match code { + 0 => Ok(None), + 1 => Ok(None), + -1 => Ok(None), + _ => Err(io::Error::new( + io::ErrorKind::Other, + "Could not execute dialog", + )), + } + } else { + Err(io::Error::new( + io::ErrorKind::Other, + "dialog was terminated by a signal", + )) + } + } +} + impl super::Backend for Dialog { + fn show_input(&self, input: &Input) -> Result> { + let args = vec!["--inputbox", &input.text]; + let mut post_args: Vec<&str> = Vec::new(); + if let Some(ref default) = input.default { + post_args.push(default); + } + self.execute(args, post_args, &input.title) + .and_then(get_stderr) + } + fn show_message(&self, message: &Message) -> Result<()> { let args = vec!["--msgbox", &message.text]; - self.show_box(args, &message.title) + self.execute(args, vec![], &message.title) .and_then(|output| require_success(output.status)) .map(|_| ()) } diff --git a/src/backends/mod.rs b/src/backends/mod.rs index c316307..1abb8d1 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -17,6 +17,9 @@ use std::io::Result; /// [`default_backend`]: ../function.default_backend.html /// [`show_with`]: ../trait.DialogBox.html#method.show_with pub trait Backend { + /// Shows the given input dialog and returns the input. + fn show_input(&self, input: &super::Input) -> Result>; + /// Shows the given message dialog. fn show_message(&self, message: &super::Message) -> Result<()>; } diff --git a/src/lib.rs b/src/lib.rs index 67bef2d..0c9d9f9 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: +//! - [`Input`][]: a text input dialog //! - [`Message`][]: a simple message box //! //! These dialog boxes can be displayed using various backends: @@ -45,8 +46,24 @@ //! .expect("Could not display dialog box"); //! ``` //! -//! [`Message`]: struct.Message.html +//! Query a string from the user: +//! +//! ```no_run +//! use dialog::DialogBox; +//! +//! let name = dialog::Input::new("Please enter your name") +//! .title("Name") +//! .show() +//! .expect("Could not display dialog box"); +//! match name { +//! Some(name) => println!("Hello {}!", name), +//! None => println!("Hello stranger!"), +//! }; +//! ``` +//! //! [`Dialog`]: backends/struct.Dialog.html +//! [`Input`]: struct.Input.html +//! [`Message`]: struct.Message.html //! [`default_backend`]: fn.default_backend.html //! [`show`]: trait.DialogBox.html#method.show //! [`show_with`]: trait.DialogBox.html#method.show_with @@ -69,14 +86,14 @@ pub trait DialogBox { /// The type of the data returned by the dialog box. type Output; - /// Shows this dialog box using the default backend. + /// Shows this dialog box using the default backend and returns the output. /// /// `box.show()` is a shorthand for `box.show_with(&default_backend())`. fn show(&self) -> Result { self.show_with(&default_backend()) } - /// Shows this dialog box using the given backend. + /// Shows this dialog box using the given backend and returns the output. fn show_with(&self, backend: &impl backends::Backend) -> Result; } @@ -126,6 +143,66 @@ impl DialogBox for Message { } } +/// A dialog box with a text input field. +/// +/// This dialog box displays a text and an input field. It returns the text entered by the user or +/// `None` if the user cancelled the dialog. +/// +/// # Example +/// +/// ```no_run +/// use dialog::DialogBox; +/// +/// let name = dialog::Input::new("Please enter your name") +/// .title("Name") +/// .show() +/// .expect("Could not display dialog box"); +/// match name { +/// Some(name) => println!("Hello {}!", name), +/// None => println!("Hello stranger!"), +/// }; +/// ``` +pub struct Input { + text: String, + title: Option, + default: Option, +} + +impl Input { + /// Creates a new input dialog box with the given text. + pub fn new(text: impl Into) -> Input { + Input { + text: text.into(), + title: None, + default: None, + } + } + + /// Sets the title of this input box. + /// + /// This method returns a reference to `self` to enable chaining. + pub fn title(&mut self, title: impl Into) -> &mut Input { + self.title = Some(title.into()); + self + } + + /// Sets the default value of this input box. + /// + /// This method returns a reference to `self` to enable chaining. + pub fn default(&mut self, default: impl Into) -> &mut Input { + self.default = Some(default.into()); + self + } +} + +impl DialogBox for Input { + type Output = Option; + + fn show_with(&self, backend: &impl backends::Backend) -> Result { + backend.show_input(self) + } +} + /// Creates a new instance of the default backend. /// /// The current implementation always returns a [`Dialog`][] instance. -- cgit v1.2.3