/* 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, TextEntry, month_field}, types::ParseError}; use chrono::{Datelike, Local}; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; use std::{cell::RefCell, rc::Rc}; #[derive(Clone, Debug)] enum Part { Year, Month, Day, } 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(date.borrow().year(), { 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; } } } }, ); year.widget.set_max_length(4); year.add_css_class("date-field__year"); let month = month_field( date.borrow().month(), { 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; } } } }, ); month.add_css_class("date-field__month"); /* Modify this so that it enforces the number of days per month */ let day = TextEntry::new( "day", Some(date.borrow().day()), |v| format!("{}", v), |v| v.parse::().map_err(|_| ParseError), { 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; } } } }, ); day.add_css_class("date-field__day"); 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(); println!("{}", date); s.append(&s.imp().year.widget()); s.append(&s.imp().month.widget()); s.append(&s.imp().day.widget()); s.imp().year.set_value(Some(date.year())); s.imp().month.set_value(Some(date.month())); s.imp().day.set_value(Some(date.day())); *s.imp().date.borrow_mut() = date; s } pub fn date(&self) -> chrono::NaiveDate { self.imp().date.borrow().clone() } /* 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!() } }