diff --git a/musicus/src/editors/ensemble.rs b/musicus/src/editors/ensemble.rs index 64379ae..44e77e9 100644 --- a/musicus/src/editors/ensemble.rs +++ b/musicus/src/editors/ensemble.rs @@ -23,7 +23,7 @@ impl Screen, Ensemble> for EnsembleEditor { /// Create a new ensemble editor and optionally initialize it. fn new(ensemble: Option, handle: NavigationHandle) -> Rc { let editor = Editor::new(); - editor.set_title("Ensemble/Role"); + editor.set_title("Ensemble"); let list = gtk::ListBoxBuilder::new() .selection_mode(gtk::SelectionMode::None) @@ -75,11 +75,22 @@ impl Screen, Ensemble> for EnsembleEditor { }); })); + this.name + .entry + .connect_changed(clone!(@weak this => move |_| this.validate())); + + this.validate(); + this } } impl EnsembleEditor { + /// Validate inputs and enable/disable saving. + fn validate(&self) { + self.editor.set_may_save(!self.name.get_text().is_empty()); + } + /// Save the ensemble and possibly upload it to the server. async fn save(&self) -> Result { let name = self.name.get_text(); @@ -93,7 +104,11 @@ impl EnsembleEditor { self.handle.backend.cl().post_ensemble(&ensemble).await?; } - self.handle.backend.db().update_ensemble(ensemble.clone()).await?; + self.handle + .backend + .db() + .update_ensemble(ensemble.clone()) + .await?; self.handle.backend.library_changed(); Ok(ensemble) @@ -105,4 +120,3 @@ impl Widget for EnsembleEditor { self.editor.widget.clone().upcast() } } - diff --git a/musicus/src/editors/instrument.rs b/musicus/src/editors/instrument.rs index 589e6ef..a8bf724 100644 --- a/musicus/src/editors/instrument.rs +++ b/musicus/src/editors/instrument.rs @@ -75,11 +75,22 @@ impl Screen, Instrument> for InstrumentEditor { }); })); + this.name + .entry + .connect_changed(clone!(@weak this => move |_| this.validate())); + + this.validate(); + this } } impl InstrumentEditor { + /// Validate inputs and enable/disable saving. + fn validate(&self) { + self.editor.set_may_save(!self.name.get_text().is_empty()); + } + /// Save the instrument and possibly upload it to the server. async fn save(&self) -> Result { let name = self.name.get_text(); @@ -90,10 +101,18 @@ impl InstrumentEditor { }; if self.upload.get_active() { - self.handle.backend.cl().post_instrument(&instrument).await?; + self.handle + .backend + .cl() + .post_instrument(&instrument) + .await?; } - self.handle.backend.db().update_instrument(instrument.clone()).await?; + self.handle + .backend + .db() + .update_instrument(instrument.clone()) + .await?; self.handle.backend.library_changed(); Ok(instrument) @@ -105,4 +124,3 @@ impl Widget for InstrumentEditor { self.editor.widget.clone().upcast() } } - diff --git a/musicus/src/editors/person.rs b/musicus/src/editors/person.rs index 3ffdc43..0839c90 100644 --- a/musicus/src/editors/person.rs +++ b/musicus/src/editors/person.rs @@ -82,11 +82,28 @@ impl Screen, Person> for PersonEditor { }); })); + this.first_name + .entry + .connect_changed(clone!(@weak this => move |_| this.validate())); + + this.last_name + .entry + .connect_changed(clone!(@weak this => move |_| this.validate())); + + this.validate(); + this } } impl PersonEditor { + /// Validate inputs and enable/disable saving. + fn validate(&self) { + self.editor.set_may_save( + !self.first_name.get_text().is_empty() && !self.last_name.get_text().is_empty(), + ); + } + /// Save the person and possibly upload it to the server. async fn save(self: &Rc) -> Result { let first_name = self.first_name.get_text(); @@ -102,7 +119,11 @@ impl PersonEditor { self.handle.backend.cl().post_person(&person).await?; } - self.handle.backend.db().update_person(person.clone()).await?; + self.handle + .backend + .db() + .update_person(person.clone()) + .await?; self.handle.backend.library_changed(); Ok(person) @@ -114,4 +135,3 @@ impl Widget for PersonEditor { self.editor.widget.clone().upcast() } } - diff --git a/musicus/src/editors/work.rs b/musicus/src/editors/work.rs index e8e5e40..02d3e83 100644 --- a/musicus/src/editors/work.rs +++ b/musicus/src/editors/work.rs @@ -1,7 +1,7 @@ use super::work_part::WorkPartEditor; use super::work_section::WorkSectionEditor; -use crate::selectors::{InstrumentSelector, PersonSelector}; use crate::navigator::{NavigationHandle, Screen}; +use crate::selectors::{InstrumentSelector, PersonSelector}; use crate::widgets::{List, Widget}; use anyhow::Result; use gettextrs::gettext; @@ -119,20 +119,21 @@ impl Screen, Work> for WorkEditor { this.handle.pop(None); })); - this.save_button.connect_clicked(clone!(@weak this => move |_| { - spawn!(@clone this, async move { - this.widget.set_visible_child_name("loading"); - match this.save().await { - Ok(work) => { - this.handle.pop(Some(work)); + this.save_button + .connect_clicked(clone!(@weak this => move |_| { + spawn!(@clone this, async move { + this.widget.set_visible_child_name("loading"); + match this.save().await { + Ok(work) => { + this.handle.pop(Some(work)); + } + Err(_) => { + this.info_bar.set_revealed(true); + this.widget.set_visible_child_name("content"); + } } - Err(_) => { - this.info_bar.set_revealed(true); - this.widget.set_visible_child_name("content"); - } - } - }); - })); + }); + })); composer_button.connect_clicked(clone!(@weak this => move |_| { spawn!(@clone this, async move { @@ -143,29 +144,33 @@ impl Screen, Work> for WorkEditor { }); })); - this.instrument_list.set_make_widget_cb(clone!(@weak this => move |index| { - let instrument = &this.instruments.borrow()[index]; + this.title_entry + .connect_changed(clone!(@weak this => move |_| this.validate())); - let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic")); - delete_button.set_valign(gtk::Align::Center); + this.instrument_list + .set_make_widget_cb(clone!(@weak this => move |index| { + let instrument = &this.instruments.borrow()[index]; - delete_button.connect_clicked(clone!(@strong this => move |_| { - let length = { - let mut instruments = this.instruments.borrow_mut(); - instruments.remove(index); - instruments.len() - }; + let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic")); + delete_button.set_valign(gtk::Align::Center); - this.instrument_list.update(length); + delete_button.connect_clicked(clone!(@strong this => move |_| { + let length = { + let mut instruments = this.instruments.borrow_mut(); + instruments.remove(index); + instruments.len() + }; + + this.instrument_list.update(length); + })); + + let row = libadwaita::ActionRow::new(); + row.set_title(Some(&instrument.name)); + row.add_suffix(&delete_button); + + row.upcast() })); - let row = libadwaita::ActionRow::new(); - row.set_title(Some(&instrument.name)); - row.add_suffix(&delete_button); - - row.upcast() - })); - add_instrument_button.connect_clicked(clone!(@weak this => move |_| { spawn!(@clone this, async move { if let Some(instrument) = push!(this.handle, InstrumentSelector).await { @@ -243,15 +248,16 @@ impl Screen, Work> for WorkEditor { row.upcast() })); - this.part_list.set_move_cb(clone!(@weak this => move |old_index, new_index| { - let length = { - let mut structure = this.structure.borrow_mut(); - structure.swap(old_index, new_index); - structure.len() - }; + this.part_list + .set_move_cb(clone!(@weak this => move |old_index, new_index| { + let length = { + let mut structure = this.structure.borrow_mut(); + structure.swap(old_index, new_index); + structure.len() + }; - this.part_list.update(length); - })); + this.part_list.update(length); + })); add_part_button.connect_clicked(clone!(@weak this => move |_| { spawn!(@clone this, async move { @@ -299,7 +305,14 @@ impl WorkEditor { fn show_composer(&self, person: &Person) { self.composer_row.set_title(Some(&gettext("Composer"))); self.composer_row.set_subtitle(Some(&person.name_fl())); - self.save_button.set_sensitive(true); + self.validate(); + } + + /// Validate inputs and enable/disable saving. + fn validate(&self) { + self.save_button.set_sensitive( + !self.title_entry.get_text().is_empty() && self.composer.borrow().is_some(), + ); } /// Save the work and possibly upload it to the server. @@ -338,7 +351,8 @@ impl WorkEditor { self.handle.backend.cl().post_work(&work).await?; } - self.handle.backend + self.handle + .backend .db() .update_work(work.clone().into()) .await diff --git a/musicus/src/editors/work_part.rs b/musicus/src/editors/work_part.rs index 22ebcaf..b0ac68b 100644 --- a/musicus/src/editors/work_part.rs +++ b/musicus/src/editors/work_part.rs @@ -10,10 +10,11 @@ use std::rc::Rc; pub struct WorkPartEditor { handle: NavigationHandle, widget: gtk::Box, + save_button: gtk::Button, title_entry: gtk::Entry, } -impl Screen, WorkPart> for WorkPartEditor { +impl Screen, WorkPart> for WorkPartEditor { /// Create a new part editor and optionally initialize it. fn new(section: Option, handle: NavigationHandle) -> Rc { // Create UI @@ -32,6 +33,7 @@ impl Screen, WorkPart> for WorkPartEditor { let this = Rc::new(Self { handle, widget, + save_button, title_entry, }); @@ -41,18 +43,32 @@ impl Screen, WorkPart> for WorkPartEditor { this.handle.pop(None); })); - save_button.connect_clicked(clone!(@weak this => move |_| { - let section = WorkPart { - title: this.title_entry.get_text().to_string(), - }; + this.save_button + .connect_clicked(clone!(@weak this => move |_| { + let section = WorkPart { + title: this.title_entry.get_text().to_string(), + }; - this.handle.pop(Some(section)); - })); + this.handle.pop(Some(section)); + })); + + this.title_entry + .connect_changed(clone!(@weak this => move |_| this.validate())); + + this.validate(); this } } +impl WorkPartEditor { + /// Validate inputs and enable/disable saving. + fn validate(&self) { + self.save_button + .set_sensitive(!self.title_entry.get_text().is_empty()); + } +} + impl Widget for WorkPartEditor { fn get_widget(&self) -> gtk::Widget { self.widget.clone().upcast() diff --git a/musicus/src/editors/work_section.rs b/musicus/src/editors/work_section.rs index 5be6d58..ff09409 100644 --- a/musicus/src/editors/work_section.rs +++ b/musicus/src/editors/work_section.rs @@ -10,10 +10,11 @@ use std::rc::Rc; pub struct WorkSectionEditor { handle: NavigationHandle, widget: gtk::Box, + save_button: gtk::Button, title_entry: gtk::Entry, } -impl Screen, WorkSection> for WorkSectionEditor { +impl Screen, WorkSection> for WorkSectionEditor { /// Create a new section editor and optionally initialize it. fn new(section: Option, handle: NavigationHandle) -> Rc { // Create UI @@ -32,6 +33,7 @@ impl Screen, WorkSection> for WorkSectionEditor { let this = Rc::new(Self { handle, widget, + save_button, title_entry, }); @@ -41,19 +43,33 @@ impl Screen, WorkSection> for WorkSectionEditor { this.handle.pop(None); })); - save_button.connect_clicked(clone!(@weak this => move |_| { - let section = WorkSection { - before_index: 0, - title: this.title_entry.get_text().to_string(), - }; + this.save_button + .connect_clicked(clone!(@weak this => move |_| { + let section = WorkSection { + before_index: 0, + title: this.title_entry.get_text().to_string(), + }; - this.handle.pop(Some(section)); - })); + this.handle.pop(Some(section)); + })); + + this.title_entry + .connect_changed(clone!(@weak this => move |_| this.validate())); + + this.validate(); this } } +impl WorkSectionEditor { + /// Validate inputs and enable/disable saving. + fn validate(&self) { + self.save_button + .set_sensitive(!self.title_entry.get_text().is_empty()); + } +} + impl Widget for WorkSectionEditor { fn get_widget(&self) -> gtk::Widget { self.widget.clone().upcast() diff --git a/musicus/src/import/medium_editor.rs b/musicus/src/import/medium_editor.rs index 46483f6..6475142 100644 --- a/musicus/src/import/medium_editor.rs +++ b/musicus/src/import/medium_editor.rs @@ -78,6 +78,8 @@ impl Screen<(Arc, Option), Medium> for MediumEditor { }); })); + this.name_entry.connect_changed(clone!(@weak this => move |_| this.validate())); + add_button.connect_clicked(clone!(@weak this => move |_| { spawn!(@clone this, async move { if let Some(track_set) = push!(this.handle, TrackSetEditor, Arc::clone(&this.session)).await { @@ -157,11 +159,18 @@ impl Screen<(Arc, Option), Medium> for MediumEditor { this.track_set_list.update(length); } + this.validate(); + this } } impl MediumEditor { + /// Validate inputs and enable/disable saving. + fn validate(&self) { + self.done_button.set_sensitive(!self.name_entry.get_text().is_empty()); + } + /// Create the medium and, if necessary, upload it to the server. async fn save(&self) -> Result { // Convert the track set data to real track sets. diff --git a/musicus/src/widgets/entry_row.rs b/musicus/src/widgets/entry_row.rs index b61e960..f3f6442 100644 --- a/musicus/src/widgets/entry_row.rs +++ b/musicus/src/widgets/entry_row.rs @@ -7,7 +7,7 @@ pub struct EntryRow { pub widget: libadwaita::ActionRow, /// The managed entry. - entry: gtk::Entry, + pub entry: gtk::Entry, } impl EntryRow {