use gtk4 as gtk; use chrono::{NaiveDate, Weekday, Weekday::*}; use gtk::prelude::*; use std::collections::HashSet; use crate::gui::form::utils; use crate::model::{ event, repetition, repetition::{DayOfMonth, Frequency, Repetition}, }; use crate::validation; static WEEKDAYS_STR: [&str; 7] = [ "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche", ]; static WEEKDAYS: [Weekday; 7] = [Mon, Tue, Wed, Thu, Fri, Sat, Sun]; static WEEKS_STR: [&str; 4] = [ "1er", "2ème", "3ème", "4ème" ]; static WEEKS: [u8; 4] = [1, 2, 3, 4]; pub struct Model { pub view: gtk::Box, no_radio: gtk::CheckButton, day_interval_radio: gtk::CheckButton, day_interval_entry: gtk::Entry, monthly_radio: gtk::CheckButton, monthly_entry: gtk::Entry, day_of_month_radio: gtk::CheckButton, week_of_month: gtk::DropDown, day_of_week: gtk::DropDown, yearly_radio: gtk::CheckButton, until: gtk::Entry, } pub fn view(repetition: Option<&Repetition>) -> Model { let view = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) .build(); view.add_css_class("g-Form__Inputs"); view.append(&label("Répétition")); let no_radio = gtk::CheckButton::builder() .label("Non") .active(repetition.is_none()) .build(); view.append(&no_radio); let frequency = repetition.as_ref().map(|r| r.frequency.clone()); let default = match frequency { Some(Frequency::Daily { period }) => period.to_string(), _ => "".to_string(), }; let day_interval_entry = gtk::Entry::builder().text(&default).build(); let (day_interval_box, day_interval_radio) = radio_input( &no_radio, !default.is_empty(), &day_interval_entry, "Intervalle de jours", ); view.append(&day_interval_box); let default = match frequency { Some(Frequency::Monthly { day: DayOfMonth::Day { day }, }) => day.to_string(), _ => "".to_string(), }; let monthly_entry = gtk::Entry::builder().text(&default).build(); let (monthly_box, monthly_radio) = radio_input(&no_radio, !default.is_empty(), &monthly_entry, "Mensuel"); view.append(&monthly_box); let (active, default_week, default_day) = match frequency { Some(Frequency::Monthly { day: DayOfMonth::Weekday { week, day }, }) => (true, week, day), _ => (false, 1, Mon), }; let week_of_month = gtk::DropDown::from_strings(&WEEKS_STR); week_of_month.set_selected(WEEKS.iter().position(|d| d == &default_week).unwrap_or(0) as u32); let day_of_week = gtk::DropDown::from_strings(&WEEKDAYS_STR); day_of_week.set_selected(WEEKDAYS.iter().position(|d| d == &default_day).unwrap_or(0) as u32); let (day_of_month_box, day_of_month_radio) = radio_day_of_month(&no_radio, active, &week_of_month, &day_of_week); view.append(&day_of_month_box); let yearly_radio = gtk::CheckButton::builder() .group(&no_radio) .label("Annuel") .active(frequency == Some(Frequency::Yearly)) .margin_bottom(10) .build(); view.append(&yearly_radio); let until = repetition .as_ref() .and_then(|r| r.until) .map(|u| utils::entry(&u.format(event::DATE_FORMAT).to_string())) .unwrap_or_default(); view.append(&utils::label("Répéter jusqu’au")); view.append(&until); Model { view, no_radio, day_interval_radio, day_interval_entry, monthly_radio, monthly_entry, day_of_month_radio, week_of_month, day_of_week, yearly_radio, until, } } fn radio_input( radio_group: &impl IsA, active: bool, input: &impl IsA, text: &str, ) -> (gtk::Box, gtk::CheckButton) { let radio_box = gtk::Box::builder().build(); let radio = gtk::CheckButton::builder() .group(radio_group) .label(text) .active(active) .build(); radio_box.append(&radio); input.add_css_class("g-Form__RadioInput"); radio_box.append(input); (radio_box, radio) } fn radio_day_of_month( radio_group: &impl IsA, active: bool, number: &impl IsA, day: &impl IsA, ) -> (gtk::Box, gtk::CheckButton) { let radio_box = gtk::Box::builder().build(); let radio = gtk::CheckButton::builder() .group(radio_group) .active(active) .build(); radio_box.append(&radio); number.add_css_class("g-Form__RadioInput"); radio_box.append(number); day.add_css_class("g-Form__RadioInput"); radio_box.append(day); let text = gtk::Text::builder().text("du mois.").build(); radio_box.append(&text); (radio_box, radio) } fn label(text: &str) -> gtk::Label { gtk::Label::builder() .label(text) .halign(gtk::Align::Start) .margin_bottom(5) .build() } pub fn validate( model: &Model, removed_occurences: HashSet, ) -> Result, String> { let frequency = if model.no_radio.is_active() { Ok(None) } else if model.day_interval_radio.is_active() { let period = repetition::validate_period(&model.day_interval_entry.buffer().text())?; Ok(Some(Frequency::Daily { period })) } else if model.monthly_radio.is_active() { let day = repetition::validate_day(&model.monthly_entry.buffer().text())?; Ok(Some(Frequency::Monthly { day: DayOfMonth::Day { day }, })) } else if model.day_of_month_radio.is_active() { let week = WEEKS[model.week_of_month.selected() as usize]; let day = WEEKDAYS[model.day_of_week.selected() as usize]; Ok(Some(Frequency::Monthly { day: DayOfMonth::Weekday { week, day }, })) } else if model.yearly_radio.is_active() { Ok(Some(Frequency::Yearly)) } else { Err("Aucune option n’a été sélectionnée".to_string()) }?; // Check until let until = (if frequency.is_some() { match validation::non_empty(model.until.buffer().text()) { Some(until) => match NaiveDate::parse_from_str(&until, event::DATE_FORMAT) { Ok(until) => Ok(Some(until)), Err(_) => Err(format!("Can’t parse date from {}", until)), }, None => Ok(None), } } else { Ok(None) })?; Ok(frequency.map(|frequency| Repetition { frequency, removed_occurences, until, })) }