From 1d8653a0f50e8ebde452f9f7516252418a69c6a9 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Mon, 18 Dec 2023 17:17:39 -0500 Subject: [PATCH] Set up a database selector row that can dispatch operations when a database file gets selected --- fitnesstrax/app/src/ui/modal.rs | 131 ++++++++++++++++++++++++-------- 1 file changed, 101 insertions(+), 30 deletions(-) diff --git a/fitnesstrax/app/src/ui/modal.rs b/fitnesstrax/app/src/ui/modal.rs index e113815..0bf7437 100644 --- a/fitnesstrax/app/src/ui/modal.rs +++ b/fitnesstrax/app/src/ui/modal.rs @@ -3,7 +3,11 @@ //! component. use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; -use std::cell::RefCell; +use std::{ + cell::RefCell, + path::{Path, PathBuf}, + rc::Rc, +}; pub struct ModalPrivate { title: gtk::Label, @@ -27,7 +31,6 @@ impl ObjectSubclass for ModalPrivate { .css_classes(["modal-title"]) .build(); let content = gtk::Box::new(gtk::Orientation::Vertical, 0); - let actions = gtk::Box::new(gtk::Orientation::Horizontal, 0); let primary_action = gtk::Button::builder().label("Primary").build(); let footer = gtk::Box::builder() @@ -99,7 +102,10 @@ impl Modal { /// database has not been configured yet. /// /// This is a [Modal] component with all of the welcome content. -pub fn welcome_modal() -> Modal { +pub fn welcome_modal(database_selected: F) -> Modal +where + F: Fn(&Path) + 'static, +{ let modal = Modal::new(); modal.set_title("Welcome to FitnessTrax"); @@ -114,36 +120,101 @@ pub fn welcome_modal() -> Modal { .label("The application has not yet been configured, so I will walk you through that. Fortunately, it is very easy, and we only need to select the location for your database.").build(); content.append(&instructions); - // 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. - let db_row = gtk::Box::builder() - .orientation(gtk::Orientation::Horizontal) - .build(); - - let db_file_label = gtk::Label::builder() - .label("No Path Selected") - .hexpand(true) - .build(); - - let db_file_chooser = gtk::Button::builder().label("Select Database").build(); - db_file_chooser.connect_clicked(|_| { - let no_window: Option<>k::Window> = None; - let not_cancellable: Option<&gio::Cancellable> = None; - gtk::FileDialog::builder() - .build() - .open(no_window, not_cancellable, |m_file_id| match m_file_id { - Ok(m_file_id) => println!("The user selected {:?}", m_file_id.path()), - Err(err) => println!("file opening failed: {}", err), - }); - }); - - db_row.append(&db_file_label); - db_row.append(&db_file_chooser); - + let db_row = DatabaseFileChooserRow::new(database_selected); content.append(&db_row); modal.set_content(content.upcast()); modal.set_primary_action(gtk::Button::builder().label("Save Settings").build()); modal } + +pub struct DatabaseFileChooserRowPrivate { + path: RefCell>, + label: gtk::Label, + on_selected_: RefCell>, +} + +#[glib::object_subclass] +impl ObjectSubclass for DatabaseFileChooserRowPrivate { + const NAME: &'static str = "DatabaseFileChooser"; + type Type = DatabaseFileChooserRow; + type ParentType = gtk::Box; + + fn new() -> Self { + Self { + path: RefCell::new(None), + label: gtk::Label::builder().hexpand(true).build(), + on_selected_: RefCell::new(Box::new(|_| {})), + } + } +} + +impl ObjectImpl for DatabaseFileChooserRowPrivate {} +impl WidgetImpl for DatabaseFileChooserRowPrivate {} +impl BoxImpl for DatabaseFileChooserRowPrivate {} + +glib::wrapper! { + pub struct DatabaseFileChooserRow(ObjectSubclass) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable; +} + +impl DatabaseFileChooserRow { + pub fn new(database_selected: F) -> Self + where + F: Fn(&Path) + 'static, + { + let s: Self = Object::builder().build(); + + s.set_orientation(gtk::Orientation::Horizontal); + + // 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"); + + *s.imp().on_selected_.borrow_mut() = Box::new(database_selected); + + let db_file_chooser_button = gtk::Button::builder().label("Select Database").build(); + db_file_chooser_button.connect_clicked({ + let s = s.clone(); + move |_| { + let no_window: Option<>k::Window> = None; + let not_cancellable: Option<&gio::Cancellable> = None; + let s = s.clone(); + gtk::FileDialog::builder().build().open( + no_window, + not_cancellable, + move |file_id| s.on_selected(file_id), + ); + } + }); + + s.append(&s.imp().label); + s.append(&db_file_chooser_button); + + s + } + + fn on_selected(&self, m_file_id: Result) { + match m_file_id { + Ok(file_id) => { + println!("The user selected {:?}", file_id.path()); + *self.imp().path.borrow_mut() = file_id.path(); + match *self.imp().path.borrow() { + Some(ref path) => { + (*self.imp().on_selected_.borrow())(path); + self.redraw(); + } + None => {} + } + } + Err(err) => println!("file opening failed: {}", err), + } + } + + fn redraw(&self) { + match *self.imp().path.borrow() { + Some(ref path) => self.imp().label.set_text(path.to_str().unwrap()), + None => self.imp().label.set_text("No database selected"), + } + } +}