/* Copyright 2024, Savanni D'Gerinel This file is part of FitnessTrax. FitnessTrax is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. FitnessTrax is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FitnessTrax. If not, see . */ use crate::{components::{i32_field_builder, TextEntry, month_field_builder}, types::ParseError}; use chrono::{Datelike, Local}; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; use std::{cell::RefCell, rc::Rc}; pub struct DateFieldPrivate { date: Rc>, year: TextEntry, month: TextEntry, day: TextEntry, } #[glib::object_subclass] impl ObjectSubclass for DateFieldPrivate { const NAME: &'static str = "DateField"; type Type = DateField; type ParentType = gtk::Box; fn new() -> Self { let date = Rc::new(RefCell::new(Local::now().date_naive())); let year = i32_field_builder() .with_value(date.borrow().year()) .with_on_update( { let date = date.clone(); move |value| { if let Some(year) = value { let mut date = date.borrow_mut(); if let Some(new_date) = date.with_year(year) { *date = new_date; } } } }) .with_length(4) .with_css_classes(vec!["date-field__year".to_owned()]).build(); let month = month_field_builder() .with_value(date.borrow().month()) .with_on_update( { let date = date.clone(); move |value| { if let Some(month) = value { let mut date = date.borrow_mut(); if let Some(new_date) = date.with_month(month) { *date = new_date; } } } }) .with_css_classes(vec!["date-field__month".to_owned()]) .build(); /* Modify this so that it enforces the number of days per month */ let day = TextEntry::builder() .with_placeholder("day".to_owned()) .with_value(date.borrow().day()) .with_renderer(|v| format!("{}", v)) .with_parser(|v| v.parse::().map_err(|_| ParseError)) .with_on_update({ let date = date.clone(); move |value| { if let Some(day) = value { let mut date = date.borrow_mut(); if let Some(new_date) = date.with_day(day) { *date = new_date; } } } }) .with_css_classes(vec!["date-field__day".to_owned()]) .build(); Self { date, year, month, day, } } } impl ObjectImpl for DateFieldPrivate {} impl WidgetImpl for DateFieldPrivate {} impl BoxImpl for DateFieldPrivate {} glib::wrapper! { pub struct DateField(ObjectSubclass) @extends gtk::Box, gtk::Widget; } /* Render a date in the format 2006 Jan 01. The entire date is editable. When the user moves to one part of the date, it will be erased and replaced with a grey placeholder. */ impl DateField { pub fn new(date: chrono::NaiveDate) -> Self { let s: Self = Object::builder().build(); s.add_css_class("date-field"); s.append(&s.imp().year.widget()); s.append(>k::Label::new(Some("-"))); s.append(&s.imp().month.widget()); s.append(>k::Label::new(Some("-"))); s.append(&s.imp().day.widget()); s.set_date(date); s } pub fn set_date(&self, date: chrono::NaiveDate) { self.imp().year.set_value(Some(date.year())); self.imp().month.set_value(Some(date.month())); self.imp().day.set_value(Some(date.day())); *self.imp().date.borrow_mut() = date; } pub fn date(&self) -> chrono::NaiveDate { *self.imp().date.borrow() } /* pub fn is_valid(&self) -> bool { false } */ } #[cfg(test)] mod test { use super::*; // use crate::gtk_init::gtk_init; // Enabling this test pushes tests on the TextField into an infinite loop. That likely indicates a bad interaction within the TextField itself, and that is going to need to be fixed. #[test] #[ignore] fn it_allows_valid_dates() { let reference = chrono::NaiveDate::from_ymd_opt(2006, 01, 02).unwrap(); let field = DateField::new(reference); field.imp().year.set_value(Some(2023)); field.imp().month.set_value(Some(10)); field.imp().day.set_value(Some(13)); // assert!(field.is_valid()); } #[test] #[ignore] fn it_disallows_out_of_range_months() { unimplemented!() } #[test] #[ignore] fn it_allows_days_within_range_for_month() { unimplemented!() } }