From a371e356f761448310d812c5b74cfa6bef039cda Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Sat, 29 Mar 2025 17:29:29 +0100 Subject: [PATCH] Use nullable roles instead of default roles --- Cargo.toml | 2 +- data/ui/selector/performer_role.blp | 141 +++++++++--------- data/ui/selector/role.blp | 29 +++- .../2025-03-29-142116_nullable_roles/down.sql | 43 ++++++ .../2025-03-29-142116_nullable_roles/up.sql | 41 +++++ src/db/models.rs | 79 +++++++--- src/db/schema.rs | 12 +- src/db/tables.rs | 6 +- src/editor/recording.rs | 4 +- src/editor/recording/ensemble_row.rs | 13 +- src/editor/recording/performer_row.rs | 39 +++-- src/editor/work.rs | 3 +- src/editor/work/composer_row.rs | 21 ++- src/library.rs | 28 +--- src/selector/performer_role.rs | 90 ++++++----- src/selector/role.rs | 15 ++ 16 files changed, 380 insertions(+), 186 deletions(-) create mode 100644 migrations/2025-03-29-142116_nullable_roles/down.sql create mode 100644 migrations/2025-03-29-142116_nullable_roles/up.sql diff --git a/Cargo.toml b/Cargo.toml index 151c9f4..4f0b267 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -adw = { package = "libadwaita", version = "0.7", features = ["v1_6"] } +adw = { package = "libadwaita", version = "0.7", features = ["v1_7"] } anyhow = "1" async-channel = "2.3" chrono = "0.4" diff --git a/data/ui/selector/performer_role.blp b/data/ui/selector/performer_role.blp index ca2cc69..50234b8 100644 --- a/data/ui/selector/performer_role.blp +++ b/data/ui/selector/performer_role.blp @@ -3,91 +3,98 @@ using Adw 1; template $MusicusPerformerRoleSelectorPopover: Gtk.Popover { styles [ - "selector" + "selector", ] - Gtk.Stack stack { - transition-type: slide_left_right; + Gtk.Box { + orientation: vertical; - Adw.ToolbarView role_view { - [top] - Gtk.SearchEntry role_search_entry { - placeholder-text: _("Search roles…"); - margin-start: 8; - margin-end: 8; - margin-top: 8; - margin-bottom: 6; - search-changed => $role_search_changed() swapped; - activate => $role_activate() swapped; - stop-search => $stop_search() swapped; + Gtk.CenterBox { + margin-start: 6; + margin-end: 6; + margin-top: 6; + + [center] + Adw.InlineViewSwitcher { + stack: stack; } - Gtk.ScrolledWindow role_scrolled_window { - height-request: 200; + [end] + Gtk.Button { + icon-name: "edit-clear-symbolic"; + tooltip-text: _("Reset to default role"); + margin-start: 6; + clicked => $reset_button_clicked() swapped; - Gtk.ListBox role_list { - styles [ - "selector-list" - ] - - selection-mode: none; - activate-on-single-click: true; - } + styles [ + "flat", + ] } } - Adw.ToolbarView instrument_view { - [top] - Gtk.Box { - margin-start: 8; - margin-end: 8; - margin-top: 8; - margin-bottom: 6; - orientation: vertical; + Adw.ViewStack stack { + Adw.ViewStackPage { + name: "role"; + title: _("Role"); - Gtk.CenterBox { - [start] - Gtk.Button { - styles [ - "flat" - ] - - icon-name: "go-previous-symbolic"; - clicked => $back_button_clicked() swapped; + child: Adw.ToolbarView role_view { + [top] + Gtk.SearchEntry role_search_entry { + placeholder-text: _("Search roles…"); + margin-start: 8; + margin-end: 8; + margin-top: 8; + margin-bottom: 6; + search-changed => $role_search_changed() swapped; + activate => $role_activate() swapped; + stop-search => $stop_search() swapped; } - [center] - Gtk.Label { - styles [ - "heading" - ] + Gtk.ScrolledWindow role_scrolled_window { + height-request: 200; - label: _("Performer"); - ellipsize: end; - margin-start: 6; + Gtk.ListBox role_list { + styles [ + "selector-list", + ] + + selection-mode: none; + activate-on-single-click: true; + } } - } - - Gtk.SearchEntry instrument_search_entry { - placeholder-text: _("Search instruments…"); - margin-top: 6; - search-changed => $instrument_search_changed() swapped; - activate => $instrument_activate() swapped; - stop-search => $stop_search() swapped; - } + }; } - Gtk.ScrolledWindow instrument_scrolled_window { - height-request: 200; + Adw.ViewStackPage { + name: "instrument"; + title: _("Instrument"); - Gtk.ListBox instrument_list { - styles [ - "selector-list" - ] + child: Adw.ToolbarView instrument_view { + [top] + Gtk.SearchEntry instrument_search_entry { + placeholder-text: _("Search instruments…"); + margin-start: 8; + margin-end: 8; + margin-top: 8; + margin-bottom: 6; + search-changed => $instrument_search_changed() swapped; + activate => $instrument_activate() swapped; + stop-search => $stop_search() swapped; + } - selection-mode: none; - activate-on-single-click: true; - } + Gtk.ScrolledWindow instrument_scrolled_window { + height-request: 200; + + Gtk.ListBox instrument_list { + styles [ + "selector-list", + ] + + selection-mode: none; + activate-on-single-click: true; + } + } + }; } } } diff --git a/data/ui/selector/role.blp b/data/ui/selector/role.blp index 8e3c773..42173ce 100644 --- a/data/ui/selector/role.blp +++ b/data/ui/selector/role.blp @@ -3,20 +3,35 @@ using Adw 1; template $MusicusRoleSelectorPopover: Gtk.Popover { styles [ - "selector" + "selector", ] Adw.ToolbarView { [top] - Gtk.SearchEntry search_entry { - placeholder-text: _("Search roles…"); + Gtk.Box { + spacing: 6; margin-start: 8; margin-end: 8; margin-top: 8; margin-bottom: 6; - search-changed => $search_changed() swapped; - activate => $activate() swapped; - stop-search => $stop_search() swapped; + + Gtk.SearchEntry search_entry { + placeholder-text: _("Search roles…"); + hexpand: true; + search-changed => $search_changed() swapped; + activate => $activate() swapped; + stop-search => $stop_search() swapped; + } + + Gtk.Button { + icon-name: "edit-clear-symbolic"; + tooltip-text: _("Reset to default role"); + clicked => $reset_button_clicked() swapped; + + styles [ + "flat", + ] + } } Gtk.ScrolledWindow scrolled_window { @@ -24,7 +39,7 @@ template $MusicusRoleSelectorPopover: Gtk.Popover { Gtk.ListBox list_box { styles [ - "selector-list" + "selector-list", ] selection-mode: none; diff --git a/migrations/2025-03-29-142116_nullable_roles/down.sql b/migrations/2025-03-29-142116_nullable_roles/down.sql new file mode 100644 index 0000000..d2a2fe5 --- /dev/null +++ b/migrations/2025-03-29-142116_nullable_roles/down.sql @@ -0,0 +1,43 @@ +CREATE TABLE work_persons_old ( + work_id TEXT NOT NULL REFERENCES works(work_id) ON DELETE CASCADE, + person_id TEXT NOT NULL REFERENCES persons(person_id), + role_id TEXT NOT NULL REFERENCES roles(role_id), + sequence_number INTEGER NOT NULL, + PRIMARY KEY (work_id, person_id, role_id) +); + +CREATE TABLE recording_persons_old ( + recording_id TEXT NOT NULL REFERENCES recordings(recording_id) ON DELETE CASCADE, + person_id TEXT NOT NULL REFERENCES persons(person_id), + role_id TEXT NOT NULL REFERENCES roles(role_id), + instrument_id TEXT REFERENCES instruments(instrument_id), + sequence_number INTEGER NOT NULL, + PRIMARY KEY (recording_id, person_id, role_id, instrument_id) +); + +CREATE TABLE recording_ensembles_old ( + recording_id TEXT NOT NULL REFERENCES recordings(recording_id) ON DELETE CASCADE, + ensemble_id TEXT NOT NULL REFERENCES ensembles(ensemble_id), + role_id TEXT NOT NULL REFERENCES roles(role_id), + sequence_number INTEGER NOT NULL, + PRIMARY KEY (recording_id, ensemble_id, role_id) +); + +INSERT INTO roles (role_id, name) VALUES ('380d7e09eb2f49c1a90db2ba4acb6ffd', '{"generic":"Composer"}'); +INSERT INTO roles (role_id, name) VALUES ('28ff0aeb11c041a6916d93e9b4884eef', '{"generic":"Performer"}'); + +UPDATE work_persons SET role_id = '380d7e09eb2f49c1a90db2ba4acb6ffd' WHERE role_id IS NULL; +UPDATE recording_persons SET role_id = '28ff0aeb11c041a6916d93e9b4884eef' WHERE role_id IS NULL; +UPDATE recording_ensembles SET role_id = '28ff0aeb11c041a6916d93e9b4884eef' WHERE role_id IS NULL; + +INSERT INTO work_persons_old SELECT * FROM work_persons; +DROP TABLE work_persons; +ALTER TABLE work_persons_old RENAME TO work_persons; + +INSERT INTO recording_persons_old SELECT * FROM recording_persons; +DROP TABLE recording_persons; +ALTER TABLE recording_persons_old RENAME TO recording_persons; + +INSERT INTO recording_ensembles_old SELECT * FROM recording_ensembles; +DROP TABLE recording_ensembles; +ALTER TABLE recording_ensembles_old RENAME TO recording_ensembles; \ No newline at end of file diff --git a/migrations/2025-03-29-142116_nullable_roles/up.sql b/migrations/2025-03-29-142116_nullable_roles/up.sql new file mode 100644 index 0000000..9a24591 --- /dev/null +++ b/migrations/2025-03-29-142116_nullable_roles/up.sql @@ -0,0 +1,41 @@ +CREATE TABLE work_persons_new ( + work_id TEXT NOT NULL REFERENCES works(work_id) ON DELETE CASCADE, + person_id TEXT NOT NULL REFERENCES persons(person_id), + role_id TEXT REFERENCES roles(role_id), + sequence_number INTEGER NOT NULL, + PRIMARY KEY (work_id, person_id, sequence_number) +); + +CREATE TABLE recording_persons_new ( + recording_id TEXT NOT NULL REFERENCES recordings(recording_id) ON DELETE CASCADE, + person_id TEXT NOT NULL REFERENCES persons(person_id), + role_id TEXT REFERENCES roles(role_id), + instrument_id TEXT REFERENCES instruments(instrument_id), + sequence_number INTEGER NOT NULL, + PRIMARY KEY (recording_id, person_id, sequence_number) +); + +CREATE TABLE recording_ensembles_new ( + recording_id TEXT NOT NULL REFERENCES recordings(recording_id) ON DELETE CASCADE, + ensemble_id TEXT NOT NULL REFERENCES ensembles(ensemble_id), + role_id TEXT REFERENCES roles(role_id), + sequence_number INTEGER NOT NULL, + PRIMARY KEY (recording_id, ensemble_id, sequence_number) +); + +INSERT OR IGNORE INTO work_persons_new SELECT * FROM work_persons; +UPDATE work_persons_new SET role_id = NULL WHERE role_id = '380d7e09eb2f49c1a90db2ba4acb6ffd'; +DROP TABLE work_persons; +ALTER TABLE work_persons_new RENAME TO work_persons; + +INSERT OR IGNORE INTO recording_persons_new SELECT * FROM recording_persons; +UPDATE recording_persons_new SET role_id = NULL WHERE role_id = '28ff0aeb11c041a6916d93e9b4884eef'; +DROP TABLE recording_persons; +ALTER TABLE recording_persons_new RENAME TO recording_persons; + +INSERT OR IGNORE INTO recording_ensembles_new SELECT * FROM recording_ensembles; +UPDATE recording_ensembles_new SET role_id = NULL WHERE role_id = '28ff0aeb11c041a6916d93e9b4884eef'; +DROP TABLE recording_ensembles; +ALTER TABLE recording_ensembles_new RENAME TO recording_ensembles; + +DELETE FROM roles WHERE role_id IN ('380d7e09eb2f49c1a90db2ba4acb6ffd', '28ff0aeb11c041a6916d93e9b4884eef'); \ No newline at end of file diff --git a/src/db/models.rs b/src/db/models.rs index 2b872c8..26aec29 100644 --- a/src/db/models.rs +++ b/src/db/models.rs @@ -21,12 +21,10 @@ pub struct Work { pub instruments: Vec, } -#[derive(Queryable, Selectable, Clone, Debug)] +#[derive(Clone, Debug)] pub struct Composer { - #[diesel(embed)] pub person: Person, - #[diesel(embed)] - pub role: Role, + pub role: Option, } #[derive(Boxed, Clone, Debug)] @@ -50,14 +48,14 @@ pub struct Recording { #[derive(Clone, Debug)] pub struct Performer { pub person: Person, - pub role: Role, + pub role: Option, pub instrument: Option, } #[derive(Clone, Debug)] pub struct EnsemblePerformer { pub ensemble: Ensemble, - pub role: Role, + pub role: Option, } #[derive(Clone, Debug)] @@ -133,12 +131,13 @@ impl Work { .map(|w| Work::from_table(w, connection)) .collect::>>()?; - let persons: Vec = persons::table - .inner_join(work_persons::table.inner_join(roles::table)) + let persons = work_persons::table .order(work_persons::sequence_number) .filter(work_persons::work_id.eq(&data.work_id)) - .select(Composer::as_select()) - .load(connection)?; + .load::(connection)? + .into_iter() + .map(|r| Composer::from_table(r, connection)) + .collect::>>()?; let instruments: Vec = instruments::table .inner_join(work_instruments::table) @@ -157,11 +156,10 @@ impl Work { } pub fn composers_string(&self) -> Option { - // TODO: Include roles except default composer. let composers_string = self .persons .iter() - .map(|p| p.person.name.get().to_string()) + .map(ToString::to_string) .collect::>() .join(", "); @@ -190,6 +188,34 @@ impl Display for Work { } } +impl Composer { + pub fn from_table(data: tables::WorkPerson, connection: &mut SqliteConnection) -> Result { + let person: Person = persons::table + .filter(persons::person_id.eq(&data.person_id)) + .first(connection)?; + + let role = match &data.role_id { + Some(role_id) => Some( + roles::table + .filter(roles::role_id.eq(role_id)) + .first::(connection)?, + ), + None => None, + }; + + Ok(Self { person, role }) + } +} + +impl Display for Composer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.role { + Some(role) => format!("{} ({})", self.person.name.get(), role.name.get()).fmt(f), + None => self.person.name.get().fmt(f), + } + } +} + impl Ensemble { pub fn from_table(data: tables::Ensemble, connection: &mut SqliteConnection) -> Result { let persons: Vec<(Person, Instrument)> = persons::table @@ -297,9 +323,14 @@ impl Performer { .filter(persons::person_id.eq(&data.person_id)) .first(connection)?; - let role: Role = roles::table - .filter(roles::role_id.eq(&data.role_id)) - .first(connection)?; + let role = match &data.role_id { + Some(role_id) => Some( + roles::table + .filter(roles::role_id.eq(role_id)) + .first::(connection)?, + ), + None => None, + }; let instrument = match &data.instrument_id { Some(instrument_id) => Some( @@ -320,11 +351,12 @@ impl Performer { impl Display for Performer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match &self.instrument { - Some(instrument) => { + match (&self.role, &self.instrument) { + (_, Some(instrument)) => { format!("{} ({})", self.person.name.get(), instrument.name.get()).fmt(f) } - None => self.person.name.get().fmt(f), + (Some(role), _) => format!("{} ({})", self.person.name.get(), role.name.get()).fmt(f), + (None, None) => self.person.name.get().fmt(f), } } } @@ -340,9 +372,14 @@ impl EnsemblePerformer { let ensemble = Ensemble::from_table(ensemble_data, connection)?; - let role: Role = roles::table - .filter(roles::role_id.eq(&data.role_id)) - .first(connection)?; + let role = match &data.role_id { + Some(role_id) => Some( + roles::table + .filter(roles::role_id.eq(role_id)) + .first::(connection)?, + ), + None => None, + }; Ok(Self { ensemble, role }) } diff --git a/src/db/schema.rs b/src/db/schema.rs index e6328ca..6d90ece 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -81,19 +81,19 @@ diesel::table! { } diesel::table! { - recording_ensembles (recording_id, ensemble_id, role_id) { + recording_ensembles (recording_id, ensemble_id, sequence_number) { recording_id -> Text, ensemble_id -> Text, - role_id -> Text, + role_id -> Nullable, sequence_number -> Integer, } } diesel::table! { - recording_persons (recording_id, person_id, role_id, instrument_id) { + recording_persons (recording_id, person_id, sequence_number) { recording_id -> Text, person_id -> Text, - role_id -> Text, + role_id -> Nullable, instrument_id -> Nullable, sequence_number -> Integer, } @@ -153,10 +153,10 @@ diesel::table! { } diesel::table! { - work_persons (work_id, person_id, role_id) { + work_persons (work_id, person_id, sequence_number) { work_id -> Text, person_id -> Text, - role_id -> Text, + role_id -> Nullable, sequence_number -> Integer, } } diff --git a/src/db/tables.rs b/src/db/tables.rs index b631cdd..b697401 100644 --- a/src/db/tables.rs +++ b/src/db/tables.rs @@ -60,7 +60,7 @@ pub struct Work { pub struct WorkPerson { pub work_id: String, pub person_id: String, - pub role_id: String, + pub role_id: Option, pub sequence_number: i32, } @@ -109,7 +109,7 @@ pub struct Recording { pub struct RecordingPerson { pub recording_id: String, pub person_id: String, - pub role_id: String, + pub role_id: Option, pub instrument_id: Option, pub sequence_number: i32, } @@ -119,7 +119,7 @@ pub struct RecordingPerson { pub struct RecordingEnsemble { pub recording_id: String, pub ensemble_id: String, - pub role_id: String, + pub role_id: Option, pub sequence_number: i32, } diff --git a/src/editor/recording.rs b/src/editor/recording.rs index be7cb8f..ef5e671 100644 --- a/src/editor/recording.rs +++ b/src/editor/recording.rs @@ -256,7 +256,7 @@ impl RecordingEditor { fn new_performer(&self, person: Person) { let performer = Performer { person, - role: self.library().performer_default_role().unwrap(), + role: None, instrument: None, }; @@ -299,7 +299,7 @@ impl RecordingEditor { fn new_ensemble_performer(&self, ensemble: Ensemble) { let performer = EnsemblePerformer { ensemble, - role: self.library().performer_default_role().unwrap(), + role: None, }; self.add_ensemble_row(performer); diff --git a/src/editor/recording/ensemble_row.rs b/src/editor/recording/ensemble_row.rs index bac5744..3d96930 100644 --- a/src/editor/recording/ensemble_row.rs +++ b/src/editor/recording/ensemble_row.rs @@ -1,6 +1,7 @@ use std::cell::{OnceCell, RefCell}; use adw::{prelude::*, subclass::prelude::*}; +use gettextrs::gettext; use gtk::{ gdk, glib::{self, clone, subclass::Signal, Properties}, @@ -112,7 +113,7 @@ mod imp { role_popover.connect_role_selected(move |_, role| { if let Some(ensemble) = &mut *obj.imp().ensemble.borrow_mut() { obj.imp().role_label.set_label(&role.to_string()); - ensemble.role = role; + ensemble.role = Some(role); } }); @@ -126,7 +127,7 @@ mod imp { move |_, role| { if let Some(ensemble) = &mut *obj.imp().ensemble.borrow_mut() { obj.imp().role_label.set_label(&role.to_string()); - ensemble.role = role; + ensemble.role = Some(role); }; } )); @@ -188,7 +189,13 @@ impl RecordingEditorEnsembleRow { fn set_ensemble(&self, ensemble: EnsemblePerformer) { self.set_title(&ensemble.ensemble.to_string()); - self.imp().role_label.set_label(&ensemble.role.to_string()); + self.imp().role_label.set_label( + &ensemble + .role + .as_ref() + .map(ToString::to_string) + .unwrap_or_else(|| gettext("Performer")), + ); self.imp().ensemble.replace(Some(ensemble)); } diff --git a/src/editor/recording/performer_row.rs b/src/editor/recording/performer_row.rs index 1eac180..a12485e 100644 --- a/src/editor/recording/performer_row.rs +++ b/src/editor/recording/performer_row.rs @@ -1,6 +1,7 @@ use std::cell::{OnceCell, RefCell}; use adw::{prelude::*, subclass::prelude::*}; +use gettextrs::gettext; use gtk::{ gdk, glib::{self, clone, subclass::Signal, Properties}, @@ -110,17 +111,29 @@ mod imp { let role_popover = PerformerRoleSelectorPopover::new(self.library.get().unwrap()); let obj = self.obj().to_owned(); - role_popover.connect_selected(move |_, role, instrument| { + role_popover.connect_reset(move |_| { if let Some(performer) = &mut *obj.imp().performer.borrow_mut() { - let label = match &instrument { - Some(instrument) => instrument.to_string(), - None => role.to_string(), - }; + obj.imp().role_label.set_label(&gettext("Performer")); + performer.role = None; + performer.instrument = None; + } + }); - obj.imp().role_label.set_label(&label); + let obj = self.obj().to_owned(); + role_popover.connect_role_selected(move |_, role| { + if let Some(performer) = &mut *obj.imp().performer.borrow_mut() { + obj.imp().role_label.set_label(&role.to_string()); + performer.role = Some(role); + performer.instrument = None; + } + }); - performer.role = role; - performer.instrument = instrument; + let obj = self.obj().to_owned(); + role_popover.connect_instrument_selected(move |_, instrument| { + if let Some(performer) = &mut *obj.imp().performer.borrow_mut() { + obj.imp().role_label.set_label(&instrument.to_string()); + performer.role = None; + performer.instrument = Some(instrument); } }); @@ -134,7 +147,7 @@ mod imp { move |_, role| { if let Some(performer) = &mut *obj.imp().performer.borrow_mut() { obj.imp().role_label.set_label(&role.to_string()); - performer.role = role; + performer.role = Some(role); performer.instrument = None; }; } @@ -153,7 +166,7 @@ mod imp { move |_, instrument| { if let Some(performer) = &mut *obj.imp().performer.borrow_mut() { obj.imp().role_label.set_label(&instrument.to_string()); - performer.role = obj.library().performer_default_role().unwrap(); + performer.role = None; performer.instrument = Some(instrument); }; } @@ -215,7 +228,11 @@ impl RecordingEditorPerformerRow { let label = match &performer.instrument { Some(instrument) => instrument.to_string(), - None => performer.role.to_string(), + None => performer + .role + .as_ref() + .map(ToString::to_string) + .unwrap_or_else(|| gettext("Performer")), }; self.imp().role_label.set_label(&label.to_string()); diff --git a/src/editor/work.rs b/src/editor/work.rs index 084b4cd..b0560cf 100644 --- a/src/editor/work.rs +++ b/src/editor/work.rs @@ -233,8 +233,7 @@ impl WorkEditor { } fn add_composer(&self, person: Person) { - let role = self.library().composer_default_role().unwrap(); - let composer = Composer { person, role }; + let composer = Composer { person, role: None }; self.add_composer_row(composer); } diff --git a/src/editor/work/composer_row.rs b/src/editor/work/composer_row.rs index e3997e9..e518569 100644 --- a/src/editor/work/composer_row.rs +++ b/src/editor/work/composer_row.rs @@ -1,6 +1,7 @@ use std::cell::{OnceCell, RefCell}; use adw::{prelude::*, subclass::prelude::*}; +use gettextrs::gettext; use gtk::{ gdk, glib::{self, clone, subclass::Signal, Properties}, @@ -108,11 +109,19 @@ mod imp { let role_popover = RoleSelectorPopover::new(self.library.get().unwrap()); + let obj = self.obj().to_owned(); + role_popover.connect_reset(move |_| { + if let Some(composer) = &mut *obj.imp().composer.borrow_mut() { + obj.imp().role_label.set_label(&gettext("Composer")); + composer.role = None; + } + }); + let obj = self.obj().to_owned(); role_popover.connect_role_selected(move |_, role| { if let Some(composer) = &mut *obj.imp().composer.borrow_mut() { obj.imp().role_label.set_label(&role.to_string()); - composer.role = role; + composer.role = Some(role); } }); @@ -126,7 +135,7 @@ mod imp { move |_, role| { if let Some(composer) = &mut *obj.imp().composer.borrow_mut() { obj.imp().role_label.set_label(&role.to_string()); - composer.role = role; + composer.role = Some(role); }; } )); @@ -184,7 +193,13 @@ impl WorkEditorComposerRow { fn set_composer(&self, composer: Composer) { self.set_title(&composer.person.to_string()); - self.imp().role_label.set_label(&composer.role.to_string()); + self.imp().role_label.set_label( + &composer + .role + .as_ref() + .map(ToString::to_string) + .unwrap_or_else(|| gettext("Composer")), + ); self.imp().composer.replace(Some(composer)); } diff --git a/src/library.rs b/src/library.rs index 4a929c9..b56e895 100644 --- a/src/library.rs +++ b/src/library.rs @@ -933,22 +933,6 @@ impl Library { Ok(ensembles) } - pub fn composer_default_role(&self) -> Result { - let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap(); - - Ok(roles::table - .filter(roles::role_id.eq("380d7e09eb2f49c1a90db2ba4acb6ffd")) - .first::(connection)?) - } - - pub fn performer_default_role(&self) -> Result { - let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap(); - - Ok(roles::table - .filter(roles::role_id.eq("28ff0aeb11c041a6916d93e9b4884eef")) - .first::(connection)?) - } - pub fn create_person(&self, name: TranslatedString) -> Result { let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap(); @@ -1171,7 +1155,7 @@ impl Library { let composer_data = tables::WorkPerson { work_id: work_id.clone(), person_id: composer.person.person_id, - role_id: composer.role.role_id, + role_id: composer.role.map(|r| r.role_id), sequence_number: index as i32, }; @@ -1295,7 +1279,7 @@ impl Library { let composer_data = tables::WorkPerson { work_id: work_id.to_string(), person_id: composer.person.person_id, - role_id: composer.role.role_id, + role_id: composer.role.map(|r| r.role_id), sequence_number: index as i32, }; @@ -1425,7 +1409,7 @@ impl Library { let recording_person_data = tables::RecordingPerson { recording_id: recording_id.clone(), person_id: performer.person.person_id, - role_id: performer.role.role_id, + role_id: performer.role.map(|r| r.role_id), instrument_id: performer.instrument.map(|i| i.instrument_id), sequence_number: index as i32, }; @@ -1439,7 +1423,7 @@ impl Library { let recording_ensemble_data = tables::RecordingEnsemble { recording_id: recording_id.clone(), ensemble_id: ensemble.ensemble.ensemble_id, - role_id: ensemble.role.role_id, + role_id: ensemble.role.map(|r| r.role_id), sequence_number: index as i32, }; @@ -1485,7 +1469,7 @@ impl Library { let recording_person_data = tables::RecordingPerson { recording_id: recording_id.to_string(), person_id: performer.person.person_id, - role_id: performer.role.role_id, + role_id: performer.role.map(|r| r.role_id), instrument_id: performer.instrument.map(|i| i.instrument_id), sequence_number: index as i32, }; @@ -1503,7 +1487,7 @@ impl Library { let recording_ensemble_data = tables::RecordingEnsemble { recording_id: recording_id.to_string(), ensemble_id: ensemble.ensemble.ensemble_id, - role_id: ensemble.role.role_id, + role_id: ensemble.role.map(|r| r.role_id), sequence_number: index as i32, }; diff --git a/src/selector/performer_role.rs b/src/selector/performer_role.rs index 2f9c735..2879c91 100644 --- a/src/selector/performer_role.rs +++ b/src/selector/performer_role.rs @@ -29,9 +29,7 @@ mod imp { pub instruments: RefCell>, #[template_child] - pub stack: TemplateChild, - #[template_child] - pub role_view: TemplateChild, + pub stack: TemplateChild, #[template_child] pub role_search_entry: TemplateChild, #[template_child] @@ -39,8 +37,6 @@ mod imp { #[template_child] pub role_list: TemplateChild, #[template_child] - pub instrument_view: TemplateChild, - #[template_child] pub instrument_search_entry: TemplateChild, #[template_child] pub instrument_scrolled_window: TemplateChild, @@ -71,21 +67,35 @@ mod imp { self.obj().connect_visible_notify(|obj| { if obj.is_visible() { - obj.imp().stack.set_visible_child(&*obj.imp().role_view); obj.imp().role_search_entry.set_text(""); - obj.imp().role_search_entry.grab_focus(); obj.imp().role_scrolled_window.vadjustment().set_value(0.0); + obj.imp().instrument_search_entry.set_text(""); + obj.imp() + .instrument_scrolled_window + .vadjustment() + .set_value(0.0); + + if obj.imp().stack.visible_child_name().as_deref() == Some("role") { + obj.imp().role_search_entry.grab_focus(); + } else { + obj.imp().instrument_search_entry.grab_focus(); + } } }); self.obj().search_roles(""); + self.obj().search_instruments(""); } fn signals() -> &'static [Signal] { static SIGNALS: Lazy> = Lazy::new(|| { vec![ - Signal::builder("selected") - .param_types([Role::static_type(), Instrument::static_type()]) + Signal::builder("reset").build(), + Signal::builder("role-selected") + .param_types([Role::static_type()]) + .build(), + Signal::builder("instrument-selected") + .param_types([Instrument::static_type()]) .build(), Signal::builder("create-role").build(), Signal::builder("create-instrument").build(), @@ -100,7 +110,7 @@ mod imp { // TODO: Fix focus. fn focus(&self, direction_type: gtk::DirectionType) -> bool { if direction_type == gtk::DirectionType::Down { - if self.stack.visible_child() == Some(self.role_list.get().upcast()) { + if self.stack.visible_child_name().as_deref() == Some("role") { self.role_list.child_focus(direction_type) } else { self.instrument_list.child_focus(direction_type) @@ -125,15 +135,34 @@ impl PerformerRoleSelectorPopover { glib::Object::builder().property("library", library).build() } - pub fn connect_selected) + 'static>( + pub fn connect_reset(&self, f: F) -> glib::SignalHandlerId { + self.connect_local("reset", true, move |values| { + let obj = values[0].get::().unwrap(); + f(&obj); + None + }) + } + + pub fn connect_role_selected( &self, f: F, ) -> glib::SignalHandlerId { - self.connect_local("selected", true, move |values| { + self.connect_local("role-selected", true, move |values| { let obj = values[0].get::().unwrap(); let role = values[1].get::().unwrap(); - let instrument = values[2].get::>().unwrap(); - f(&obj, role, instrument); + f(&obj, role); + None + }) + } + + pub fn connect_instrument_selected( + &self, + f: F, + ) -> glib::SignalHandlerId { + self.connect_local("instrument-selected", true, move |values| { + let obj = values[0].get::().unwrap(); + let role = values[1].get::().unwrap(); + f(&obj, role); None }) } @@ -154,6 +183,12 @@ impl PerformerRoleSelectorPopover { }) } + #[template_callback] + fn reset_button_clicked(&self) { + self.emit_by_name::<()>("reset", &[]); + self.popdown(); + } + #[template_callback] fn role_search_changed(&self, entry: >k::SearchEntry) { self.search_roles(&entry.text()); @@ -168,12 +203,6 @@ impl PerformerRoleSelectorPopover { } } - #[template_callback] - fn back_button_clicked(&self) { - self.imp().stack.set_visible_child(&*self.imp().role_view); - self.imp().role_search_entry.grab_focus(); - } - #[template_callback] fn instrument_search_changed(&self, entry: >k::SearchEntry) { self.search_instruments(&entry.text()); @@ -293,27 +322,12 @@ impl PerformerRoleSelectorPopover { } fn select_role(&self, role: Role) { - if role == self.library().performer_default_role().unwrap() { - self.imp().instrument_search_entry.set_text(""); - self.imp().instrument_search_entry.grab_focus(); - self.imp() - .instrument_scrolled_window - .vadjustment() - .set_value(0.0); - self.imp() - .stack - .set_visible_child(&*self.imp().instrument_view); - - self.search_instruments(""); - } else { - self.emit_by_name::<()>("selected", &[&role, &None::]); - self.popdown(); - } + self.emit_by_name::<()>("role-selected", &[&role]); + self.popdown(); } fn select_instrument(&self, instrument: Instrument) { - let role = self.library().performer_default_role().unwrap(); - self.emit_by_name::<()>("selected", &[&role, &instrument]); + self.emit_by_name::<()>("instrument-selected", &[&instrument]); self.popdown(); } diff --git a/src/selector/role.rs b/src/selector/role.rs index fd919bc..512b4da 100644 --- a/src/selector/role.rs +++ b/src/selector/role.rs @@ -65,6 +65,7 @@ mod imp { fn signals() -> &'static [Signal] { static SIGNALS: Lazy> = Lazy::new(|| { vec![ + Signal::builder("reset").build(), Signal::builder("role-selected") .param_types([Role::static_type()]) .build(), @@ -101,6 +102,14 @@ impl RoleSelectorPopover { glib::Object::builder().property("library", library).build() } + pub fn connect_reset(&self, f: F) -> glib::SignalHandlerId { + self.connect_local("reset", true, move |values| { + let obj = values[0].get::().unwrap(); + f(&obj); + None + }) + } + pub fn connect_role_selected( &self, f: F, @@ -140,6 +149,12 @@ impl RoleSelectorPopover { self.popdown(); } + #[template_callback] + fn reset_button_clicked(&self) { + self.emit_by_name::<()>("reset", &[]); + self.popdown(); + } + fn search(&self, search: &str) { let imp = self.imp();