use std::collections::HashMap;
use std::rc::Rc;
use clap;
use cursive::event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent};
use cursive::view::{View, ViewWrapper};
use cursive::views::{BoxedView, Dialog, DialogFocus, LinearLayout};
use cursive::Cursive;
use serde_json::map::Map;
use serde_json::value::Value;
use fields::{FieldErrors, FormField};
pub type FormErrors = HashMap<String, FieldErrors>;
type OnSubmit = Option<Rc<dyn Fn(&mut Cursive, Value)>>;
type OnCancel = Option<Rc<dyn Fn(&mut Cursive)>>;
pub struct FormView {
view: Dialog,
fields: Vec<Box<dyn FormField>>,
on_submit: OnSubmit,
on_cancel: OnCancel,
}
impl FormView {
pub fn new() -> Self {
let layout = Dialog::new()
.content(LinearLayout::vertical())
.button("Cancel", |_| {})
.button("Submit (Ctrl+f)", |_| {});
FormView {
view: layout,
fields: Vec::new(),
on_submit: None,
on_cancel: None,
}
}
pub fn field<V: FormField + 'static>(self, field: V) -> Self {
self.boxed_field(Box::new(field))
}
pub fn boxed_field(mut self, field: Box<dyn FormField>) -> Self {
let widget = field.build_widget();
self.view
.get_content_mut()
.as_any_mut()
.downcast_mut::<LinearLayout>()
.unwrap()
.add_child(widget);
self.fields.push(field);
self
}
pub fn set_on_submit<F>(&mut self, callback: F)
where
F: Fn(&mut Cursive, Value) + 'static,
{
self.on_submit = Some(Rc::new(callback));
}
pub fn on_submit<F>(mut self, callback: F) -> Self
where
F: Fn(&mut Cursive, Value) + 'static,
{
self.set_on_submit(callback);
self
}
pub fn set_on_cancel<F>(&mut self, callback: F)
where
F: Fn(&mut Cursive) + 'static,
{
self.on_cancel = Some(Rc::new(callback));
}
pub fn on_cancel<F>(mut self, callback: F) -> Self
where
F: Fn(&mut Cursive) + 'static,
{
self.set_on_cancel(callback);
self
}
pub fn fields2clap_args(&self) -> Vec<clap::Arg> {
let mut args = Vec::with_capacity(self.fields.len());
for field in &self.fields {
let arg = field.clap_arg();
args.push(arg);
}
return args;
}
pub fn clap_arg_matches2value(&self, arg_matches: &clap::ArgMatches) -> Value {
let mut form_data = Map::with_capacity(self.fields.len());
for field in self.fields.iter() {
let data = field.clap_args2str(&arg_matches);
match field.validate(data.as_ref()) {
Ok(v) => {
form_data.insert(field.get_label().to_string(), v);
}
Err(e) => {
let msg: Vec<String> = e.iter().map(|s| format!("ERROR: {:?}", s)).collect();
eprintln!("{}", msg.join("\n"));
}
}
}
Value::Object(form_data)
}
pub fn validate(&mut self) -> Result<Value, FormErrors> {
let mut data = Map::with_capacity(self.fields.len());
let mut errors: FormErrors = HashMap::with_capacity(self.fields.len());
for (idx, field) in self.fields.iter().enumerate() {
let view = self
.view
.get_content()
.as_any()
.downcast_ref::<LinearLayout>()
.unwrap()
.get_child(idx)
.unwrap();
let view_box: &BoxedView = (*view).as_any().downcast_ref().unwrap();
let value = field.get_widget_manager().get_value(view_box);
let label = field.get_label();
match field.validate(value.as_ref()) {
Ok(v) => {
data.insert(label.to_owned(), v);
}
Err(e) => {
errors.insert(label.to_owned(), e);
}
}
}
if errors.is_empty() {
Ok(Value::Object(data))
} else {
self.show_errors(&errors);
Err(errors)
}
}
fn show_errors(&mut self, form_errors: &FormErrors) {
for (idx, field) in self.fields.iter().enumerate() {
let label = field.get_label();
let error = form_errors
.get(label)
.and_then(|field_errors| field_errors.first());
let view = self
.view
.get_content_mut()
.as_any_mut()
.downcast_mut::<LinearLayout>()
.unwrap()
.get_child_mut(idx)
.unwrap();
let viewbox: &mut BoxedView = view.as_any_mut().downcast_mut().unwrap();
field.set_error(viewbox, error.unwrap_or(&"".to_string()));
}
}
fn event_submit(&mut self) -> EventResult {
match self.validate() {
Ok(data_map) => {
let opt_cb = self
.on_submit
.clone()
.map(|cb| Callback::from_fn(move |c| cb(c, data_map.clone())));
EventResult::Consumed(opt_cb)
}
Err(_) => {
EventResult::Consumed(None)
}
}
}
fn event_cancel(&mut self) -> EventResult {
let cb = self
.on_cancel
.clone()
.map(|cb| Callback::from_fn(move |c| cb(c)));
EventResult::Consumed(cb)
}
pub fn title(mut self, title: &str) -> Self {
self.view.set_title(title);
self
}
pub fn get_fields(&self) -> &[Box<dyn FormField>] {
&self.fields
}
pub fn get_field_value(&self, field_label: &str) -> Option<String> {
let mut value = None;
for (idx, form_field) in self.fields.iter().enumerate() {
if form_field.get_label() == field_label {
let view = self
.view
.get_content()
.as_any()
.downcast_ref::<LinearLayout>()
.unwrap()
.get_child(idx)
.unwrap();
let view_box: &BoxedView = (*view).as_any().downcast_ref().unwrap();
value = Some(form_field.get_widget_manager().get_value(view_box));
break;
}
}
value
}
}
impl ViewWrapper for FormView {
wrap_impl!(self.view: Dialog);
fn wrap_on_event(&mut self, event: Event) -> EventResult {
match event {
Event::Mouse {
offset: _,
position: _,
event: MouseEvent::Press(btn),
} => {
if btn == MouseButton::Left {
self.with_view_mut(|v| v.on_event(event))
.unwrap_or(EventResult::Ignored);
match self.view.focus() {
DialogFocus::Button(0) => self.event_cancel(),
DialogFocus::Button(1) => self.event_submit(),
_ => EventResult::Ignored,
}
} else {
EventResult::Ignored
}
}
Event::Key(Key::Enter) => match self.view.focus() {
DialogFocus::Button(0) => self.event_cancel(),
DialogFocus::Button(1) => self.event_submit(),
_ => self
.with_view_mut(|v| v.on_event(event))
.unwrap_or(EventResult::Ignored),
},
Event::CtrlChar('f') => self.event_submit(),
_ => {
self.with_view_mut(|v| v.on_event(event))
.unwrap_or(EventResult::Ignored)
}
}
}
}