/* Copyright 2023-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 . */ mod action_group; pub use action_group::ActionGroup; mod day; pub use day::{DayDetail, DayEdit, DaySummary}; mod singleton; pub use singleton::{Singleton, SingletonImpl}; mod steps; pub use steps::{steps_editor, Steps}; mod text_entry; pub use text_entry::TextEntry; mod time_distance; pub use time_distance::TimeDistanceView; mod weight; pub use weight::{weight_editor, WeightLabel}; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; use std::{cell::RefCell, path::PathBuf, rc::Rc}; pub struct FileChooserRowPrivate { path: RefCell>, label: gtk::Label, } #[glib::object_subclass] impl ObjectSubclass for FileChooserRowPrivate { const NAME: &'static str = "FileChooser"; type Type = FileChooserRow; type ParentType = gtk::Box; fn new() -> Self { Self { path: RefCell::new(None), label: gtk::Label::builder().hexpand(true).build(), } } } impl ObjectImpl for FileChooserRowPrivate {} impl WidgetImpl for FileChooserRowPrivate {} impl BoxImpl for FileChooserRowPrivate {} glib::wrapper! { pub struct FileChooserRow(ObjectSubclass) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable; } impl FileChooserRow { pub fn new(on_selected: F) -> Self where F: Fn(PathBuf) + 'static, { let s: Self = Object::builder().build(); s.set_css_classes(&["dialog-row", "card"]); s.set_orientation(gtk::Orientation::Horizontal); s.set_spacing(8); // The database selection row should be a box that shows a default database path, along with a // button that triggers a file chooser dialog. Once the dialog returns, the box should be // updated to reflect the chosen path. s.imp().label.set_text("No database selected"); let on_selected = Rc::new(Box::new(on_selected)); let import_button = gtk::Button::builder().label("Import a Database").build(); let handle_file_selection = Rc::new(Box::new({ let s = s.clone(); let on_selected = on_selected.clone(); move |file_id: Result| match file_id { Ok(file_id) => match file_id.path() { Some(path) => { s.imp().label.set_text(path.to_str().unwrap()); on_selected(path.clone()); *s.imp().path.borrow_mut() = Some(path); } None => { *s.imp().path.borrow_mut() = None; s.imp().label.set_text("No database selected"); } }, Err(err) => println!("file opening failed: {}", err), } })); import_button.connect_clicked({ let handle_file_selection = handle_file_selection.clone(); move |_| { let no_window: Option<>k::Window> = None; let not_cancellable: Option<&gio::Cancellable> = None; let handle_file_selection = handle_file_selection.clone(); gtk::FileDialog::builder().build().open( no_window, not_cancellable, move |file_id| handle_file_selection(file_id), ); } }); let new_button = gtk::Button::builder().label("Create Database").build(); new_button.connect_clicked({ let handle_file_selection = handle_file_selection.clone(); move |_| { let no_window: Option<>k::Window> = None; let not_cancellable: Option<&gio::Cancellable> = None; let handle_file_selection = handle_file_selection.clone(); gtk::FileDialog::builder().build().save( no_window, not_cancellable, move |file_id| handle_file_selection(file_id), ); } }); s.imp().label.set_halign(gtk::Align::Start); s.append(&s.imp().label); s.append(&import_button); s.append(&new_button); s } pub fn path(&self) -> Option { self.imp().path.borrow().clone() } }