From a120f43f4bcc4766ef034cca893ca11ef0d6eb7c 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/main.rs | 1 + fitnesstrax/app/src/ui/modal.rs | 118 +++++++++++++++++++++++++++----- 2 files changed, 103 insertions(+), 16 deletions(-) diff --git a/fitnesstrax/app/src/main.rs b/fitnesstrax/app/src/main.rs index 6bcd8e7..d2abdba 100644 --- a/fitnesstrax/app/src/main.rs +++ b/fitnesstrax/app/src/main.rs @@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License along with Fit mod app; mod app_window; mod components; +mod ui; mod views; use adw::prelude::*; diff --git a/fitnesstrax/app/src/ui/modal.rs b/fitnesstrax/app/src/ui/modal.rs index ee5042c..4f708c0 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, @@ -24,7 +28,6 @@ impl ObjectSubclass for ModalPrivate { fn new() -> Self { let title = gtk::Label::builder().label("Modal").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() @@ -95,7 +98,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"); @@ -107,21 +113,101 @@ pub fn welcome_modal() -> Modal { content.append(>k::Label::new(Some("Welcome to FitnessTrax. The application has not yet been configured, so I will walk you through that. Let's start out by selecting your database."))); - // 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(); - db_row.append( - >k::Label::builder() - .label("No Path Selected") - .hexpand(true) - .build(), - ); - db_row.append(>k::Button::builder().label("Select Database").build()); + 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"), + } + } +}