mod repetition; use gtk4 as gtk; use anyhow::Result; use chrono::{NaiveDate, NaiveTime}; use gtk::glib; use gtk::prelude::*; use rusqlite::Connection; use thiserror::Error; use uuid::Uuid; use crate::{ db, gui::{update, update::Msg, App}, model::{event, event::Event}, }; pub async fn repetition_dialog(app: &App, date: NaiveDate, event: Event) { let dialog = gtk::Dialog::builder() .transient_for(&*app.window) .modal(true) .title("Modifier") .css_classes(vec!["g-Dialog".to_string()]) .build(); let content_area = dialog.content_area(); let lines = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) .build(); content_area.append(&lines); let button = gtk::Button::builder() .label("Cette occurence") .margin_bottom(10) .build(); lines.append(&button); let tx = app.tx.clone(); button.connect_clicked(glib::clone!(@weak dialog, @strong event => move |_| { update::send(tx.clone(), Msg::ShowUpdateRepetitionForm { date, event: event.clone() }); dialog.close() })); let button = gtk::Button::builder() .label("Toutes les occurences") .build(); lines.append(&button); let tx = app.tx.clone(); button.connect_clicked(glib::clone!(@weak dialog, @strong event => move |_| { update::send(tx.clone(), Msg::ShowUpdateForm { event: event.clone() }); dialog.close() })); dialog.run_future().await; } #[derive(Clone)] pub enum Target { New { date: NaiveDate }, Update { event: Event }, UpdateRepetition { event: Event, date: NaiveDate }, } pub async fn show(app: &App, target: Target) { let event = match target { Target::New { .. } => None, Target::Update { ref event } => Some(event.clone()), Target::UpdateRepetition { ref event, .. } => Some(event.clone()), }; let title = if event.is_some() { "Modifier" } else { "Ajouter" }; let dialog = gtk::Dialog::builder() .transient_for(&*app.window) .modal(true) .title(title) .css_classes(vec!["g-Dialog".to_string()]) .build(); let content_area = dialog.content_area(); let lines = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) .build(); content_area.append(&lines); let columns = gtk::Box::builder() .orientation(gtk::Orientation::Horizontal) .build(); lines.append(&columns); // First column let column1 = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) .build(); columns.append(&column1); let name = event.as_ref().map(|e| entry(&e.name)).unwrap_or_default(); column1.append(&label("Événement")); column1.append(&name); let date = match target { Target::New { date } => date, Target::Update { ref event } => event.date, Target::UpdateRepetition { date, .. } => date, }; let date = entry(&date.format(event::DATE_FORMAT).to_string()); column1.append(&label("Jour")); column1.append(&date); let start = event .as_ref() .map(|e| time_entry(e.start)) .unwrap_or_else(|| entry("")); column1.append(&label("Début")); column1.append(&start); let end = event .as_ref() .map(|e| time_entry(e.end)) .unwrap_or_else(|| entry("")); column1.append(&label("Fin")); column1.append(&end); // Second column let repetition = match target { Target::Update { ref event } => event.repetition.as_ref(), _ => None, }; let repetition_model = repetition::view(repetition); columns.append(&repetition_model.view); // Buttons let button_title = match target { Target::New { .. } => "Créer", Target::Update { .. } => "Modifier", Target::UpdateRepetition { .. } => "Modifier l’occurence", }; let button = gtk::Button::builder() .label(button_title) .margin_bottom(10) .build(); lines.append(&button); let conn = app.conn.clone(); let tx = app.tx.clone(); button.connect_clicked(glib::clone!(@weak dialog, @strong target, @strong event => move |_| { match repetition::validate(&repetition_model) { Ok(repetition) => { let id = match &target { Target::Update {event} => event.id, _ => Uuid::new_v4(), }; match event::validate(id, date.buffer().text(), name.buffer().text(), start.buffer().text(), end.buffer().text(), repetition) { Some(new) => { match &target { Target::New {..} => { match db::insert(&conn, &new) { Ok(_) => { update::send(tx.clone(), Msg::AddEvent { new }); dialog.close() }, Err(err) => eprintln!("Error when inserting event: {}", err) } } Target::Update {event} => { match db::update(&conn, &new) { Ok(_) => { update::send(tx.clone(), Msg::UpdateEvent { old: event.clone(), new }); dialog.close() }, Err(err) => eprintln!("Error when updating event: {}", err) } } Target::UpdateRepetition { event, date } => { // TODO: improve intermediate error state match delete_repetition_occurence(&conn, event, *date) { Ok(occurence) => { match db::insert(&conn, &new) { Ok(_) => { update::send(tx.clone(), Msg::UpdateEventOccurence { event: event.clone(), occurence, date: *date, new }) } Err(err) => eprintln!("Error when updating repetition: {}", err) }; dialog.close() }, Err(err) => eprintln!("Error when updating repetition: {}", err) } } } } None => eprintln!("Event is not valid.") } }, Err(message) => eprintln!("{}", message) } })); if let Some(event) = event { let label = match target { Target::Update { .. } => "Supprimer", _ => "Supprimer l’occurence", }; let button = gtk::Button::builder().label(label).build(); lines.append(&button); let conn = app.conn.clone(); let tx = app.tx.clone(); button.connect_clicked(glib::clone!(@weak dialog => move |_| { match target { Target::UpdateRepetition { date, .. } => { match delete_repetition_occurence(&conn, &event, date) { Ok(occurence) => { update::send(tx.clone(), Msg::DeleteOccurence { event: event.clone(), date, occurence }); dialog.close() } Err(err) => { eprintln!("{:?}", err); } } } _ => { let operation = db::delete(&conn, &event.id); if operation.is_ok() { update::send(tx.clone(), Msg::DeleteEvent { event: event.clone() }); dialog.close() } } } })); } dialog.run_future().await; } #[derive(Error, Debug)] enum DeleteError { #[error("Repetition not found")] RepetitionNotFound, #[error("Occurence not found")] OccurenceNotFound, } fn delete_repetition_occurence( conn: &Connection, event: &Event, occurence_date: NaiveDate, ) -> Result { if let Some(ref repetition) = event.repetition { if let Some(occurence) = repetition.occurence_index(event.date, occurence_date) { let mut event = event.clone(); let mut repetition = repetition.clone(); repetition.removed_occurences.insert(occurence); event.repetition = Some(repetition); db::update(conn, &event).map(|_| occurence) } else { Err(anyhow::Error::new(DeleteError::OccurenceNotFound)) } } else { Err(anyhow::Error::new(DeleteError::RepetitionNotFound)) } } fn time_entry(time: Option) -> gtk::Entry { entry( &time .map(event::pprint_time) .unwrap_or_else(|| "".to_string()), ) } fn entry(text: &str) -> gtk::Entry { gtk::Entry::builder().text(text).margin_bottom(10).build() } fn label(text: &str) -> gtk::Label { gtk::Label::builder() .label(text) .halign(gtk::Align::Start) .margin_bottom(5) .build() }