use std::collections::HashMap;
use std::sync::mpsc::{Receiver, SyncSender, sync_channel};
use gtk;
use mg_settings::{EnumFromStr, EnumMetaData, SettingCompletion, SpecialCommand};
use mg_settings::key::Key;
use mg_settings::settings;
use relm::{
ContainerComponent,
EventStream,
Relm,
Update,
Widget,
};
use app::{Mg, BLOCKING_INPUT_MODE, INPUT_MODE};
use app::color::color_blue;
use app::Msg::{
BlockingCustomDialog,
BlockingInput,
BlockingQuestion,
BlockingYesNoQuestion,
EnterNormalModeAndReset,
Input,
Question,
ResetInput,
YesNoQuestion,
};
use app::status_bar::Msg::{Identifier, ShowIdentifier};
use completion::NO_COMPLETER_IDENT;
use completion::completion_view::Msg::SetOriginalInput;
use self::DialogResult::{Answer, Shortcut};
pub trait Responder {
fn respond(&self, answer: DialogResult);
}
pub struct BlockingInputDialog {
tx: SyncSender<Option<String>>,
}
impl BlockingInputDialog {
pub fn new() -> (Self, Receiver<Option<String>>) {
let (tx, rx) = sync_channel(1);
(BlockingInputDialog {
tx,
}, rx)
}
}
impl Responder for BlockingInputDialog {
fn respond(&self, answer: DialogResult) {
match answer {
Answer(answer) => {
self.tx.send(answer).expect("Sending answer shouldn't fail")
},
_ => unimplemented!(),
}
gtk::main_quit();
}
}
pub struct InputDialog<WIDGET: Widget> {
callback: Box<dyn Fn(Option<String>) -> WIDGET::Msg>,
stream: EventStream<WIDGET::Msg>,
}
impl<WIDGET: Widget> InputDialog<WIDGET> {
pub fn new<F>(relm: &Relm<WIDGET>, callback: F) -> Self
where F: Fn(Option<String>) -> WIDGET::Msg + 'static,
{
InputDialog {
callback: Box::new(callback),
stream: relm.stream().clone(),
}
}
}
impl<WIDGET: Widget> Responder for InputDialog<WIDGET> {
fn respond(&self, answer: DialogResult) {
match answer {
Answer(answer) => self.stream.emit((self.callback)(answer)),
_ => unimplemented!(),
}
}
}
pub struct YesNoInputDialog<WIDGET: Widget> {
callback: Box<dyn Fn(bool) -> WIDGET::Msg>,
stream: EventStream<WIDGET::Msg>,
}
impl<WIDGET: Widget> YesNoInputDialog<WIDGET> {
pub fn new<F>(relm: &Relm<WIDGET>, callback: F) -> Self
where F: Fn(bool) -> WIDGET::Msg + 'static,
{
YesNoInputDialog {
callback: Box::new(callback),
stream: relm.stream().clone(),
}
}
}
impl<WIDGET: Widget> Responder for YesNoInputDialog<WIDGET> {
fn respond(&self, answer: DialogResult) {
match answer {
Answer(answer) => self.stream.emit((self.callback)(answer == Some("y".to_string()))),
_ => unimplemented!(),
}
}
}
pub struct DialogBuilder {
blocking: bool,
choices: Vec<char>,
completer: Option<String>,
default_answer: String,
message: String,
responder: Option<Box<dyn Responder>>,
shortcuts: HashMap<Key, String>,
}
impl DialogBuilder {
#[allow(unknown_lints, new_without_default_derive)]
pub fn new() -> Self {
DialogBuilder {
blocking: false,
choices: vec![],
completer: None,
default_answer: String::new(),
message: String::new(),
responder: None,
shortcuts: HashMap::new(),
}
}
pub fn blocking(mut self, blocking: bool) -> Self {
self.blocking = blocking;
self
}
pub fn choices(mut self, choices: Vec<char>) -> Self {
self.choices = choices;
self
}
pub fn completer(mut self, completer: &str) -> Self {
self.completer = Some(completer.to_string());
self
}
pub fn default_answer(mut self, answer: String) -> Self {
self.default_answer = answer;
self
}
pub fn message(mut self, message: String) -> Self {
self.message = message;
self
}
pub fn responder(mut self, responder: Box<dyn Responder>) -> Self {
self.responder = Some(responder);
self
}
pub fn shortcut(mut self, shortcut: Key, value: &str) -> Self {
self.shortcuts.insert(shortcut, value.to_string());
self
}
}
pub enum DialogResult {
Answer(Option<String>),
Shortcut(String),
}
impl<COMM, SETT> Mg<COMM, SETT>
where COMM: Clone + EnumFromStr + EnumMetaData + SpecialCommand + 'static,
SETT: Default + EnumMetaData + settings::Settings + SettingCompletion + 'static,
{
pub fn blocking_custom_dialog(&mut self, responder: Box<dyn Responder>, builder: DialogBuilder) {
let builder = builder
.blocking(true)
.responder(responder);
self.show_dialog_without_shortcuts(builder);
}
pub fn blocking_input(&mut self, responder: Box<dyn Responder>, message: String, default_answer: String) {
let builder = DialogBuilder::new()
.blocking(true)
.default_answer(default_answer)
.message(message)
.responder(responder);
self.show_dialog_without_shortcuts(builder);
}
pub fn blocking_question(&mut self, responder: Box<dyn Responder>, message: String, choices: Vec<char>) {
let builder = DialogBuilder::new()
.blocking(true)
.choices(choices)
.message(message)
.responder(responder);
self.show_dialog_without_shortcuts(builder);
}
pub fn blocking_yes_no_question(&mut self, responder: Box<dyn Responder>, message: String) {
let builder = DialogBuilder::new()
.blocking(true)
.choices(vec!['y', 'n'])
.message(message)
.responder(responder);
self.show_dialog_without_shortcuts(builder);
}
pub fn input(&mut self, responder: Box<dyn Responder>, message: String, default_answer: String) {
let builder = DialogBuilder::new()
.default_answer(default_answer)
.message(message)
.responder(responder);
self.show_dialog(builder);
}
pub fn question(&mut self, responder: Box<dyn Responder>, message: String, choices: &[char]) {
let builder = DialogBuilder::new()
.responder(responder)
.message(message)
.choices(choices.to_vec());
self.show_dialog(builder);
}
pub fn set_dialog_answer(&mut self, answer: &str) {
let mut should_reset = false;
if let Some(callback) = self.model.input_callback.take() {
callback(Some(answer.to_string()), self.model.shortcut_pressed);
self.model.choices.clear();
should_reset = true;
}
if should_reset {
self.model.relm.stream().emit(EnterNormalModeAndReset);
}
}
pub fn show_dialog(&mut self, mut dialog_builder: DialogBuilder) {
self.model.shortcut_pressed = false;
self.model.shortcuts.clear();
for (key, value) in dialog_builder.shortcuts {
self.model.shortcuts.insert(key, value);
}
let choices = dialog_builder.choices.clone();
if !choices.is_empty() {
self.model.choices.clear();
self.model.choices.append(&mut dialog_builder.choices);
let choices: Vec<_> = choices.iter().map(|c| c.to_string()).collect();
let choices = choices.join("/");
self.status_bar.emit(Identifier(format!("{} ({}) ", dialog_builder.message, choices)));
self.status_bar.emit(ShowIdentifier);
}
else {
self.status_bar.emit(Identifier(format!("{} ", dialog_builder.message)));
self.show_entry();
self.set_input(&dialog_builder.default_answer);
}
if let Some(completer) = dialog_builder.completer {
self.set_completer(&completer);
self.model.completion_view.emit(SetOriginalInput(dialog_builder.default_answer));
self.show_completion();
}
else {
self.set_completer(NO_COMPLETER_IDENT);
}
self.model.answer = None;
if dialog_builder.blocking {
self.set_mode(BLOCKING_INPUT_MODE);
}
else {
self.set_mode(INPUT_MODE);
}
if let Some(responder) = dialog_builder.responder {
self.model.input_callback = Some(Box::new(move |answer, shortcut_pressed| {
let answer =
if shortcut_pressed {
if let Some(answer) = answer.clone() {
Shortcut(answer)
}
else {
Answer(None)
}
}
else {
Answer(answer.clone())
};
responder.respond(answer);
}));
}
color_blue(self.status_bar.widget());
}
pub fn show_dialog_without_shortcuts(&mut self, dialog_builder: DialogBuilder) {
self.show_dialog(dialog_builder);
}
pub fn yes_no_question(&mut self, responder: Box<dyn Responder>, message: String) {
let builder = DialogBuilder::new()
.choices(vec!['y', 'n'])
.message(message)
.responder(responder);
self.show_dialog(builder);
}
}
pub fn blocking_dialog<COMM, SETT>(mg: &EventStream<<Mg<COMM, SETT> as Update>::Msg>, builder: DialogBuilder)
-> Option<String>
where COMM: Clone + EnumFromStr + EnumMetaData + SpecialCommand + 'static,
SETT: Default + EnumMetaData + settings::Settings + SettingCompletion + 'static,
{
let (blocking_input_dialog, rx) = BlockingInputDialog::new();
let responder = Box::new(blocking_input_dialog);
mg.emit(BlockingCustomDialog(responder, builder));
gtk::main();
mg.emit(ResetInput);
rx.try_recv().unwrap_or(None)
}
pub fn blocking_input<COMM, SETT>(mg: &EventStream<<Mg<COMM, SETT> as Update>::Msg>, msg: String,
default_answer: String) -> Option<String>
where COMM: Clone + EnumFromStr + EnumMetaData + SpecialCommand + 'static,
SETT: Default + EnumMetaData + settings::Settings + SettingCompletion + 'static,
{
let (blocking_input_dialog, rx) = BlockingInputDialog::new();
let responder = Box::new(blocking_input_dialog);
mg.emit(BlockingInput(responder, msg, default_answer));
gtk::main();
mg.emit(ResetInput);
rx.try_recv().unwrap_or(None)
}
pub fn blocking_question<COMM, SETT>(mg: &EventStream<<Mg<COMM, SETT> as Update>::Msg>, msg: String,
choices: &[char]) -> Option<String>
where COMM: Clone + EnumFromStr + EnumMetaData + SpecialCommand + 'static,
SETT: Default + EnumMetaData + settings::Settings + SettingCompletion + 'static,
{
let (blocking_input_dialog, rx) = BlockingInputDialog::new();
let responder = Box::new(blocking_input_dialog);
mg.emit(BlockingQuestion(responder, msg, choices.to_vec()));
gtk::main();
mg.emit(ResetInput);
rx.try_recv().unwrap_or(None)
}
pub fn blocking_yes_no_question<COMM, SETT>(mg: &EventStream<<Mg<COMM, SETT> as Update>::Msg>, msg: String)
-> bool
where COMM: Clone + EnumFromStr + EnumMetaData + SpecialCommand + 'static,
SETT: Default + EnumMetaData + settings::Settings + SettingCompletion + 'static,
{
let (blocking_input_dialog, rx) = BlockingInputDialog::new();
let responder = Box::new(blocking_input_dialog);
mg.emit(BlockingYesNoQuestion(responder, msg));
gtk::main();
mg.emit(ResetInput);
rx.try_recv() == Ok(Some("y".to_string()))
}
pub fn input<CALLBACK, COMM, SETT, WIDGET>(mg: &ContainerComponent<Mg<COMM, SETT>>, relm: &Relm<WIDGET>, msg: String,
default_answer: String, callback: CALLBACK)
where CALLBACK: Fn(Option<String>) -> WIDGET::Msg + 'static,
COMM: Clone + EnumFromStr + EnumMetaData + SpecialCommand + 'static,
SETT: Default + EnumMetaData + settings::Settings + SettingCompletion + 'static,
WIDGET: Widget + 'static,
{
let responder = Box::new(InputDialog::new(relm, callback));
mg.emit(Input(responder, msg, default_answer));
}
pub fn question<CALLBACK, COMM, SETT, WIDGET>(mg: &ContainerComponent<Mg<COMM, SETT>>, relm: &Relm<WIDGET>, msg: String,
choices: &'static [char], callback: CALLBACK)
where CALLBACK: Fn(Option<String>) -> WIDGET::Msg + 'static,
COMM: Clone + EnumFromStr + EnumMetaData + SpecialCommand + 'static,
SETT: Default + EnumMetaData + settings::Settings + SettingCompletion + 'static,
WIDGET: Widget + 'static,
{
let responder = Box::new(InputDialog::new(relm, callback));
mg.emit(Question(responder, msg, choices));
}
pub fn yes_no_question<CALLBACK, COMM, SETT, WIDGET>(mg: &ContainerComponent<Mg<COMM, SETT>>, relm: &Relm<WIDGET>,
msg: String, callback: CALLBACK)
where CALLBACK: Fn(bool) -> WIDGET::Msg + 'static,
COMM: Clone + EnumFromStr + EnumMetaData + SpecialCommand + 'static,
SETT: Default + EnumMetaData + settings::Settings + SettingCompletion + 'static,
WIDGET: Widget + 'static,
{
let responder = Box::new(YesNoInputDialog::new(relm, callback));
mg.emit(YesNoQuestion(responder, msg));
}