From 99bbd9e58f832c33726541a472af18e679d89ad0 Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Sun, 8 Nov 2020 12:11:54 +0100 Subject: [PATCH] Merge recording editor and selector into one dialog --- Cargo.toml | 1 + po/POTFILES.in | 11 +- po/de.po | 58 +-- po/musicus.pot | 58 +-- res/ui/performance_editor.ui | 1 - res/ui/recording_editor.ui | 377 ++++++++---------- res/ui/recording_selector.ui | 162 ++------ src/dialogs/mod.rs | 10 +- src/dialogs/performance_editor.rs | 147 ------- src/dialogs/recording/mod.rs | 11 + src/dialogs/recording/performance_editor.rs | 174 ++++++++ src/dialogs/recording/recording_dialog.rs | 86 ++++ src/dialogs/recording/recording_editor.rs | 197 +++++++++ .../recording/recording_editor_dialog.rs | 63 +++ src/dialogs/recording/recording_selector.rs | 89 +++++ .../recording_selector_person_screen.rs | 126 ++++++ .../recording_selector_work_screen.rs | 120 ++++++ src/dialogs/recording_editor.rs | 186 --------- src/dialogs/recording_selector.rs | 279 ------------- src/dialogs/tracks_editor.rs | 16 +- src/meson.build | 11 +- src/window.rs | 16 +- 22 files changed, 1167 insertions(+), 1032 deletions(-) delete mode 100644 src/dialogs/performance_editor.rs create mode 100644 src/dialogs/recording/mod.rs create mode 100644 src/dialogs/recording/performance_editor.rs create mode 100644 src/dialogs/recording/recording_dialog.rs create mode 100644 src/dialogs/recording/recording_editor.rs create mode 100644 src/dialogs/recording/recording_editor_dialog.rs create mode 100644 src/dialogs/recording/recording_selector.rs create mode 100644 src/dialogs/recording/recording_selector_person_screen.rs create mode 100644 src/dialogs/recording/recording_selector_work_screen.rs delete mode 100644 src/dialogs/recording_editor.rs delete mode 100644 src/dialogs/recording_selector.rs diff --git a/Cargo.toml b/Cargo.toml index 73e529b..5414ca8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ diesel_migrations = "1.4.0" fragile = "1.0.0" futures = "0.3.6" futures-channel = "0.3.5" +gdk = "0.13.2" gettext-rs = "0.5.0" gio = "0.9.1" glib = "0.10.2" diff --git a/po/POTFILES.in b/po/POTFILES.in index ff1c22f..ff61904 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -37,12 +37,17 @@ src/dialogs/instrument_editor.rs src/dialogs/instrument_selector.rs src/dialogs/mod.rs src/dialogs/part_editor.rs -src/dialogs/performance_editor.rs src/dialogs/person_editor.rs src/dialogs/person_selector.rs src/dialogs/preferences.rs -src/dialogs/recording_editor.rs -src/dialogs/recording_selector.rs +src/dialogs/recording/mod.rs +src/dialogs/recording/performance_editor.rs +src/dialogs/recording/recording_dialog.rs +src/dialogs/recording/recording_editor_dialog.rs +src/dialogs/recording/recording_editor.rs +src/dialogs/recording/recording_selector_person_screen.rs +src/dialogs/recording/recording_selector.rs +src/dialogs/recording/recording_selector_work_screen.rs src/dialogs/section_editor.rs src/dialogs/track_editor.rs src/dialogs/tracks_editor.rs diff --git a/po/de.po b/po/de.po index e6e7744..b33032e 100644 --- a/po/de.po +++ b/po/de.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-11-08 02:37+0100\n" +"POT-Creation-Date: 2020-11-08 12:11+0100\n" "PO-Revision-Date: 2020-11-08 02:37+0100\n" "Last-Translator: \n" "Language-Team: \n" @@ -18,14 +18,14 @@ msgstr "" "X-Generator: Poedit 2.4.1\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: res/ui/ensemble_editor.ui:20 res/ui/performance_editor.ui:197 -#: res/ui/performance_editor.ui:228 +#: res/ui/ensemble_editor.ui:20 res/ui/performance_editor.ui:196 +#: res/ui/performance_editor.ui:227 msgid "Ensemble" msgstr "Ensemble" #: res/ui/ensemble_editor.ui:23 res/ui/instrument_editor.ui:23 #: res/ui/part_editor.ui:25 res/ui/performance_editor.ui:23 -#: res/ui/person_editor.ui:23 res/ui/recording_editor.ui:25 +#: res/ui/person_editor.ui:23 res/ui/recording_editor.ui:17 #: res/ui/section_editor.ui:23 res/ui/track_editor.ui:24 #: res/ui/tracks_editor.ui:39 res/ui/work_editor.ui:25 msgid "Cancel" @@ -33,7 +33,7 @@ msgstr "Abbrechen" #: res/ui/ensemble_editor.ui:31 res/ui/instrument_editor.ui:31 #: res/ui/part_editor.ui:33 res/ui/performance_editor.ui:31 -#: res/ui/person_editor.ui:31 res/ui/recording_editor.ui:33 +#: res/ui/person_editor.ui:31 res/ui/recording_editor.ui:25 #: res/ui/section_editor.ui:31 res/ui/track_editor.ui:32 #: res/ui/tracks_editor.ui:24 res/ui/work_editor.ui:33 msgid "Save" @@ -53,8 +53,9 @@ msgid "Recordings" msgstr "Aufnahmen" #: res/ui/ensemble_screen.ui:188 res/ui/work_screen.ui:188 -#: src/dialogs/recording_selector.rs:205 src/screens/ensemble_screen.rs:53 -#: src/screens/person_screen.rs:77 src/screens/work_screen.rs:54 +#: src/dialogs/recording/recording_selector_work_screen.rs:38 +#: src/screens/ensemble_screen.rs:53 src/screens/person_screen.rs:77 +#: src/screens/work_screen.rs:54 msgid "No recordings found." msgstr "Keine Aufnahmen gefunden." @@ -93,15 +94,16 @@ msgid "Title" msgstr "Titel" #: res/ui/part_editor.ui:116 res/ui/performance_editor.ui:87 -#: res/ui/performance_editor.ui:171 res/ui/performance_editor.ui:215 -#: res/ui/recording_editor.ui:89 res/ui/tracks_editor.ui:93 +#: res/ui/performance_editor.ui:170 res/ui/performance_editor.ui:214 +#: res/ui/recording_editor.ui:81 res/ui/tracks_editor.ui:93 #: res/ui/work_editor.ui:77 src/dialogs/part_editor.rs:99 -#: src/dialogs/performance_editor.rs:112 src/dialogs/performance_editor.rs:122 -#: src/dialogs/performance_editor.rs:136 +#: src/dialogs/recording/performance_editor.rs:150 +#: src/dialogs/recording/performance_editor.rs:160 +#: src/dialogs/recording/performance_editor.rs:170 msgid "Select …" msgstr "Auswählen …" -#: res/ui/part_editor.ui:160 res/ui/recording_editor.ui:126 +#: res/ui/part_editor.ui:160 res/ui/recording_editor.ui:119 #: res/ui/work_editor.ui:126 msgid "Overview" msgstr "Überblick" @@ -122,11 +124,11 @@ msgstr "Auftritt" msgid "Role" msgstr "Rolle" -#: res/ui/performance_editor.ui:129 +#: res/ui/performance_editor.ui:128 msgid "Type" msgstr "Typ" -#: res/ui/performance_editor.ui:152 res/ui/performance_editor.ui:184 +#: res/ui/performance_editor.ui:151 res/ui/performance_editor.ui:183 #: res/ui/person_editor.ui:20 msgid "Person" msgstr "Person" @@ -200,24 +202,20 @@ msgstr "Keiner ausgewählt" msgid "Select" msgstr "Auswählen" -#: res/ui/recording_editor.ui:22 res/ui/tracks_editor.ui:68 +#: res/ui/recording_editor.ui:14 res/ui/tracks_editor.ui:68 msgid "Recording" msgstr "Aufnahme" -#: res/ui/recording_editor.ui:71 +#: res/ui/recording_editor.ui:63 msgid "Comment" msgstr "Kommentar" -#: res/ui/recording_editor.ui:113 res/ui/tracks_editor.ui:109 +#: res/ui/recording_editor.ui:106 res/ui/tracks_editor.ui:109 #: res/ui/work_editor.ui:22 msgid "Work" msgstr "Werk" -#: res/ui/recording_editor.ui:157 -msgid "No performers added." -msgstr "Keine Interpreten hinzugefügt." - -#: res/ui/recording_editor.ui:251 res/ui/tracks_editor.ui:127 +#: res/ui/recording_editor.ui:226 res/ui/tracks_editor.ui:127 msgid "Performers" msgstr "Interpreten" @@ -233,12 +231,7 @@ msgstr "Zur Wiedergabeliste hinzufügen" msgid "Select a composer on the left." msgstr "Wählen Sie einen Komponisten aus." -#: res/ui/recording_selector.ui:71 res/ui/recording_selector.ui:186 -#: src/dialogs/recording_selector.rs:109 src/screens/person_screen.rs:57 -msgid "No works found." -msgstr "Keine Werke gefunden." - -#: res/ui/recording_selector.ui:108 +#: res/ui/recording_selector.ui:51 msgid "Select a recording" msgstr "Aufnahme auswählen" @@ -323,6 +316,15 @@ msgstr "Weitere Informationen und Quellcode" msgid "Select music library folder" msgstr "Ordner der Musikbibliothek auswählen" +#: src/dialogs/recording/recording_editor.rs:54 +msgid "No performers added." +msgstr "Keine Interpreten hinzugefügt." + +#: src/dialogs/recording/recording_selector_person_screen.rs:39 +#: src/screens/person_screen.rs:57 +msgid "No works found." +msgstr "Keine Werke gefunden." + #: src/dialogs/tracks_editor.rs:60 msgid "Add some tracks." msgstr "Fügen Sie Tracks hinzu." diff --git a/po/musicus.pot b/po/musicus.pot index f7d12a4..2bc2329 100644 --- a/po/musicus.pot +++ b/po/musicus.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: musicus\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-11-08 02:37+0100\n" +"POT-Creation-Date: 2020-11-08 12:11+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,14 +17,14 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: res/ui/ensemble_editor.ui:20 res/ui/performance_editor.ui:197 -#: res/ui/performance_editor.ui:228 +#: res/ui/ensemble_editor.ui:20 res/ui/performance_editor.ui:196 +#: res/ui/performance_editor.ui:227 msgid "Ensemble" msgstr "" #: res/ui/ensemble_editor.ui:23 res/ui/instrument_editor.ui:23 #: res/ui/part_editor.ui:25 res/ui/performance_editor.ui:23 -#: res/ui/person_editor.ui:23 res/ui/recording_editor.ui:25 +#: res/ui/person_editor.ui:23 res/ui/recording_editor.ui:17 #: res/ui/section_editor.ui:23 res/ui/track_editor.ui:24 #: res/ui/tracks_editor.ui:39 res/ui/work_editor.ui:25 msgid "Cancel" @@ -32,7 +32,7 @@ msgstr "" #: res/ui/ensemble_editor.ui:31 res/ui/instrument_editor.ui:31 #: res/ui/part_editor.ui:33 res/ui/performance_editor.ui:31 -#: res/ui/person_editor.ui:31 res/ui/recording_editor.ui:33 +#: res/ui/person_editor.ui:31 res/ui/recording_editor.ui:25 #: res/ui/section_editor.ui:31 res/ui/track_editor.ui:32 #: res/ui/tracks_editor.ui:24 res/ui/work_editor.ui:33 msgid "Save" @@ -52,8 +52,9 @@ msgid "Recordings" msgstr "" #: res/ui/ensemble_screen.ui:188 res/ui/work_screen.ui:188 -#: src/dialogs/recording_selector.rs:205 src/screens/ensemble_screen.rs:53 -#: src/screens/person_screen.rs:77 src/screens/work_screen.rs:54 +#: src/dialogs/recording/recording_selector_work_screen.rs:38 +#: src/screens/ensemble_screen.rs:53 src/screens/person_screen.rs:77 +#: src/screens/work_screen.rs:54 msgid "No recordings found." msgstr "" @@ -92,15 +93,16 @@ msgid "Title" msgstr "" #: res/ui/part_editor.ui:116 res/ui/performance_editor.ui:87 -#: res/ui/performance_editor.ui:171 res/ui/performance_editor.ui:215 -#: res/ui/recording_editor.ui:89 res/ui/tracks_editor.ui:93 +#: res/ui/performance_editor.ui:170 res/ui/performance_editor.ui:214 +#: res/ui/recording_editor.ui:81 res/ui/tracks_editor.ui:93 #: res/ui/work_editor.ui:77 src/dialogs/part_editor.rs:99 -#: src/dialogs/performance_editor.rs:112 src/dialogs/performance_editor.rs:122 -#: src/dialogs/performance_editor.rs:136 +#: src/dialogs/recording/performance_editor.rs:150 +#: src/dialogs/recording/performance_editor.rs:160 +#: src/dialogs/recording/performance_editor.rs:170 msgid "Select …" msgstr "" -#: res/ui/part_editor.ui:160 res/ui/recording_editor.ui:126 +#: res/ui/part_editor.ui:160 res/ui/recording_editor.ui:119 #: res/ui/work_editor.ui:126 msgid "Overview" msgstr "" @@ -121,11 +123,11 @@ msgstr "" msgid "Role" msgstr "" -#: res/ui/performance_editor.ui:129 +#: res/ui/performance_editor.ui:128 msgid "Type" msgstr "" -#: res/ui/performance_editor.ui:152 res/ui/performance_editor.ui:184 +#: res/ui/performance_editor.ui:151 res/ui/performance_editor.ui:183 #: res/ui/person_editor.ui:20 msgid "Person" msgstr "" @@ -199,24 +201,20 @@ msgstr "" msgid "Select" msgstr "" -#: res/ui/recording_editor.ui:22 res/ui/tracks_editor.ui:68 +#: res/ui/recording_editor.ui:14 res/ui/tracks_editor.ui:68 msgid "Recording" msgstr "" -#: res/ui/recording_editor.ui:71 +#: res/ui/recording_editor.ui:63 msgid "Comment" msgstr "" -#: res/ui/recording_editor.ui:113 res/ui/tracks_editor.ui:109 +#: res/ui/recording_editor.ui:106 res/ui/tracks_editor.ui:109 #: res/ui/work_editor.ui:22 msgid "Work" msgstr "" -#: res/ui/recording_editor.ui:157 -msgid "No performers added." -msgstr "" - -#: res/ui/recording_editor.ui:251 res/ui/tracks_editor.ui:127 +#: res/ui/recording_editor.ui:226 res/ui/tracks_editor.ui:127 msgid "Performers" msgstr "" @@ -232,12 +230,7 @@ msgstr "" msgid "Select a composer on the left." msgstr "" -#: res/ui/recording_selector.ui:71 res/ui/recording_selector.ui:186 -#: src/dialogs/recording_selector.rs:109 src/screens/person_screen.rs:57 -msgid "No works found." -msgstr "" - -#: res/ui/recording_selector.ui:108 +#: res/ui/recording_selector.ui:51 msgid "Select a recording" msgstr "" @@ -317,6 +310,15 @@ msgstr "" msgid "Select music library folder" msgstr "" +#: src/dialogs/recording/recording_editor.rs:54 +msgid "No performers added." +msgstr "" + +#: src/dialogs/recording/recording_selector_person_screen.rs:39 +#: src/screens/person_screen.rs:57 +msgid "No works found." +msgstr "" + #: src/dialogs/tracks_editor.rs:60 msgid "Add some tracks." msgstr "" diff --git a/res/ui/performance_editor.ui b/res/ui/performance_editor.ui index 2e85510..04d8650 100644 --- a/res/ui/performance_editor.ui +++ b/res/ui/performance_editor.ui @@ -96,7 +96,6 @@ - True True True diff --git a/res/ui/recording_editor.ui b/res/ui/recording_editor.ui index c9871f2..50151eb 100644 --- a/res/ui/recording_editor.ui +++ b/res/ui/recording_editor.ui @@ -3,234 +3,183 @@ - + + True False - True - 500 - 450 - True - dialog + vertical - + True False - vertical + Recording - - True - False - Recording - - - Cancel - True - True - True - - - - - Save - True - False - True - True - - - - end - 1 - - - - - False - True - 0 - - - - + + Cancel True True + True + + + + + Save + True + False + True + True + + + + end + 1 + + + + + False + True + 0 + + + + + True + True + + + + True + False + 18 + 12 + 6 - - - True - False - 18 - 12 - 6 - - - True - False - end - Comment - - - 0 - 1 - - - - - True - True - True - True - - - True - False - start - Select … - - - - - 1 - 0 - - - - - True - True - - - 1 - 1 - - - - - True - False - end - Work - - - 0 - 0 - - - - - True False - Overview + end + Comment - False + 0 + 1 + + + + + True + True + True + True + + + True + False + start + Select … + end + + + + + 1 + 0 + + + + + True + True + + + 1 + 1 + + + + + True + False + end + Work + + + 0 + 0 + + + + + + + True + False + Overview + + + False + + + + + True + False + 18 + 6 + + + True + True + in + + + + + + True + True + 0 True False - 18 + 0 + vertical 6 - + True True - in + True - + True False - - - True - False - True - True - - - True - False - No performers added. - - - - + list-add-symbolic - True + False True 0 - + True - False - 0 - vertical - 6 + True + True - + True - True - True - - - True - False - list-add-symbolic - - + False + edit-symbolic - - False - True - 0 - - - - - True - True - True - - - True - False - edit-symbolic - - - - - False - True - 1 - - - - - True - True - True - - - True - False - list-remove-symbolic - - - - - False - True - 2 - @@ -239,30 +188,54 @@ 1 + + + True + True + True + + + True + False + list-remove-symbolic + + + + + False + True + 2 + + + False + True 1 - - - True - False - Performers - - - 1 - False - - - True - True 1 + + + True + False + Performers + + + 1 + False + + + + True + True + 1 + diff --git a/res/ui/recording_selector.ui b/res/ui/recording_selector.ui index a07ca59..89de248 100644 --- a/res/ui/recording_selector.ui +++ b/res/ui/recording_selector.ui @@ -1,8 +1,8 @@ - - + + True False @@ -33,168 +33,60 @@ - + True False - vertical + sidebar_box - - True - False - True - True - - - False - True - 0 - - - - + + 250 True False + False + vertical - + True False - True - - - loading - - - - - True - False - No works found. - - - empty - 1 - - - - - True - True - 1 - - - - - False - True - 600 - 424 - dialog - - - True - False - sidebar_box - - - 250 - True - False - False - vertical + Select a recording + False - + True - False - Select a recording - False + True + True - + True - True - True - - - True - False - list-add-symbolic - - + False + list-add-symbolic - - False - True - 0 - - sidebar + False + True + 0 - - - True - False - vertical - - - - False - - - - - - - True - False - vertical - - - True - False - True - False - True - 0 + sidebar - + True False - - - True - False - True - - - loading - - - - - True - False - No works found. - - - empty - 1 - - + vertical + - True - True - 1 + False diff --git a/src/dialogs/mod.rs b/src/dialogs/mod.rs index e8784f7..bac60df 100644 --- a/src/dialogs/mod.rs +++ b/src/dialogs/mod.rs @@ -16,9 +16,6 @@ pub use instrument_selector::*; pub mod part_editor; pub use part_editor::*; -pub mod performance_editor; -pub use performance_editor::*; - pub mod person_editor; pub use person_editor::*; @@ -28,11 +25,8 @@ pub use person_selector::*; pub mod preferences; pub use preferences::*; -pub mod recording_editor; -pub use recording_editor::*; - -pub mod recording_selector; -pub use recording_selector::*; +pub mod recording; +pub use recording::*; pub mod section_editor; pub use section_editor::*; diff --git a/src/dialogs/performance_editor.rs b/src/dialogs/performance_editor.rs deleted file mode 100644 index 6c325e5..0000000 --- a/src/dialogs/performance_editor.rs +++ /dev/null @@ -1,147 +0,0 @@ -use super::*; -use crate::backend::Backend; -use crate::database::*; -use gettextrs::gettext; -use glib::clone; -use gtk::prelude::*; -use gtk_macros::get_widget; -use std::cell::RefCell; -use std::rc::Rc; - -pub struct PerformanceEditor -where - F: Fn(PerformanceDescription) -> () + 'static, -{ - backend: Rc, - window: libhandy::Window, - callback: F, - save_button: gtk::Button, - person_label: gtk::Label, - ensemble_label: gtk::Label, - role_label: gtk::Label, - person: RefCell>, - ensemble: RefCell>, - role: RefCell>, -} - -impl PerformanceEditor -where - F: Fn(PerformanceDescription) -> () + 'static, -{ - pub fn new>( - backend: Rc, - parent: &P, - performance: Option, - callback: F, - ) -> Rc { - let builder = - gtk::Builder::from_resource("/de/johrpan/musicus/ui/performance_editor.ui"); - - get_widget!(builder, libhandy::Window, window); - get_widget!(builder, gtk::Button, cancel_button); - get_widget!(builder, gtk::Button, save_button); - get_widget!(builder, gtk::Button, person_button); - get_widget!(builder, gtk::Button, ensemble_button); - get_widget!(builder, gtk::Button, role_button); - get_widget!(builder, gtk::Button, reset_role_button); - get_widget!(builder, gtk::Label, person_label); - get_widget!(builder, gtk::Label, ensemble_label); - get_widget!(builder, gtk::Label, role_label); - - let (person, ensemble, role) = match performance { - Some(performance) => { - match performance.person.clone() { - Some(person) => { - person_label.set_text(&person.name_fl()); - save_button.set_sensitive(true); - } - None => (), - } - - match performance.ensemble.clone() { - Some(ensemble) => { - ensemble_label.set_text(&ensemble.name); - save_button.set_sensitive(true); - } - None => (), - } - - match performance.role.clone() { - Some(role) => role_label.set_text(&role.name), - None => (), - } - - (performance.person, performance.ensemble, performance.role) - } - None => (None, None, None), - }; - - let result = Rc::new(PerformanceEditor { - backend: backend, - window: window, - callback: callback, - save_button: save_button, - person_label: person_label, - ensemble_label: ensemble_label, - role_label: role_label, - person: RefCell::new(person), - ensemble: RefCell::new(ensemble), - role: RefCell::new(role), - }); - - cancel_button.connect_clicked(clone!(@strong result => move |_| { - result.window.close(); - })); - - result - .save_button - .connect_clicked(clone!(@strong result => move |_| { - (result.callback)(PerformanceDescription { - person: result.person.borrow().clone(), - ensemble: result.ensemble.borrow().clone(), - role: result.role.borrow().clone(), - }); - result.window.close(); - })); - - person_button.connect_clicked(clone!(@strong result => move |_| { - PersonSelector::new(result.backend.clone(), &result.window, clone!(@strong result => move |person| { - result.person.replace(Some(person.clone())); - result.person_label.set_text(&person.name_fl()); - result.ensemble.replace(None); - result.ensemble_label.set_text(&gettext("Select …")); - result.save_button.set_sensitive(true); - })).show(); - })); - - ensemble_button.connect_clicked(clone!(@strong result => move |_| { - EnsembleSelector::new(result.backend.clone(), &result.window, clone!(@strong result => move |ensemble| { - result.ensemble.replace(Some(ensemble.clone())); - result.ensemble_label.set_text(&ensemble.name); - result.person.replace(None); - result.person_label.set_text(&gettext("Select …")); - result.save_button.set_sensitive(true); - })).show(); - })); - - role_button.connect_clicked(clone!(@strong result => move |_| { - InstrumentSelector::new(result.backend.clone(), &result.window, clone!(@strong result => move |role| { - result.role.replace(Some(role.clone())); - result.role_label.set_text(&role.name); - })).show(); - })); - - reset_role_button.connect_clicked(clone!(@strong result => move |_| { - result.role.replace(None); - result.role_label.set_text(&gettext("Select …")); - })); - - result.window.set_transient_for(Some(parent)); - - result - } - - pub fn show(&self) { - self.window.show(); - } -} diff --git a/src/dialogs/recording/mod.rs b/src/dialogs/recording/mod.rs new file mode 100644 index 0000000..6578b75 --- /dev/null +++ b/src/dialogs/recording/mod.rs @@ -0,0 +1,11 @@ +pub mod recording_dialog; +pub use recording_dialog::*; + +pub mod recording_editor_dialog; +pub use recording_editor_dialog::*; + +mod performance_editor; +mod recording_editor; +mod recording_selector; +mod recording_selector_person_screen; +mod recording_selector_work_screen; diff --git a/src/dialogs/recording/performance_editor.rs b/src/dialogs/recording/performance_editor.rs new file mode 100644 index 0000000..000c208 --- /dev/null +++ b/src/dialogs/recording/performance_editor.rs @@ -0,0 +1,174 @@ +use crate::backend::*; +use crate::database::*; +use crate::dialogs::*; +use gettextrs::gettext; +use glib::clone; +use gtk::prelude::*; +use gtk_macros::get_widget; +use std::cell::RefCell; +use std::rc::Rc; + +/// A dialog for editing a performance within a recording. +pub struct PerformanceEditor { + backend: Rc, + window: libhandy::Window, + save_button: gtk::Button, + person_label: gtk::Label, + ensemble_label: gtk::Label, + role_label: gtk::Label, + reset_role_button: gtk::Button, + person: RefCell>, + ensemble: RefCell>, + role: RefCell>, + selected_cb: RefCell ()>>>, +} + +impl PerformanceEditor { + /// Create a new performance editor. + pub fn new>( + backend: Rc, + parent: &P, + performance: Option, + ) -> Rc { + // Create UI + + let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/performance_editor.ui"); + + get_widget!(builder, libhandy::Window, window); + get_widget!(builder, gtk::Button, cancel_button); + get_widget!(builder, gtk::Button, save_button); + get_widget!(builder, gtk::Button, person_button); + get_widget!(builder, gtk::Button, ensemble_button); + get_widget!(builder, gtk::Button, role_button); + get_widget!(builder, gtk::Button, reset_role_button); + get_widget!(builder, gtk::Label, person_label); + get_widget!(builder, gtk::Label, ensemble_label); + get_widget!(builder, gtk::Label, role_label); + + window.set_transient_for(Some(parent)); + + let this = Rc::new(PerformanceEditor { + backend, + window, + save_button, + person_label, + ensemble_label, + role_label, + reset_role_button, + person: RefCell::new(None), + ensemble: RefCell::new(None), + role: RefCell::new(None), + selected_cb: RefCell::new(None), + }); + + // Connect signals and callbacks + + cancel_button.connect_clicked(clone!(@strong this => move |_| { + this.window.close(); + })); + + this.save_button + .connect_clicked(clone!(@strong this => move |_| { + if let Some(cb) = &*this.selected_cb.borrow() { + cb(PerformanceDescription { + person: this.person.borrow().clone(), + ensemble: this.ensemble.borrow().clone(), + role: this.role.borrow().clone(), + }); + + this.window.close(); + } + })); + + person_button.connect_clicked(clone!(@strong this => move |_| { + PersonSelector::new(this.backend.clone(), &this.window, clone!(@strong this => move |person| { + this.show_person(Some(&person)); + this.person.replace(Some(person)); + this.show_ensemble(None); + this.ensemble.replace(None); + })).show(); + })); + + ensemble_button.connect_clicked(clone!(@strong this => move |_| { + EnsembleSelector::new(this.backend.clone(), &this.window, clone!(@strong this => move |ensemble| { + this.show_person(None); + this.person.replace(None); + this.show_ensemble(Some(&ensemble)); + this.ensemble.replace(Some(ensemble)); + })).show(); + })); + + role_button.connect_clicked(clone!(@strong this => move |_| { + InstrumentSelector::new(this.backend.clone(), &this.window, clone!(@strong this => move |role| { + this.show_role(Some(&role)); + this.role.replace(Some(role)); + })).show(); + })); + + this.reset_role_button + .connect_clicked(clone!(@strong this => move |_| { + this.show_role(None); + this.role.replace(None); + })); + + // Initialize + + if let Some(performance) = performance { + if let Some(person) = performance.person { + this.show_person(Some(&person)); + this.person.replace(Some(person)); + } else if let Some(ensemble) = performance.ensemble { + this.show_ensemble(Some(&ensemble)); + this.ensemble.replace(Some(ensemble)); + } + + if let Some(role) = performance.role { + this.show_role(Some(&role)); + this.role.replace(Some(role)); + } + } + + this + } + + /// Set a closure to be called when the user has chosen to save the performance. + pub fn set_selected_cb () + 'static>(&self, cb: F) { + self.selected_cb.replace(Some(Box::new(cb))); + } + + /// Show the performance editor. + pub fn show(&self) { + self.window.show(); + } + + /// Update the UI according to person. + fn show_person(&self, person: Option<&Person>) { + if let Some(person) = person { + self.person_label.set_text(&person.name_fl()); + self.save_button.set_sensitive(true); + } else { + self.person_label.set_text(&gettext("Select …")); + } + } + + /// Update the UI according to ensemble. + fn show_ensemble(&self, ensemble: Option<&Ensemble>) { + if let Some(ensemble) = ensemble { + self.ensemble_label.set_text(&ensemble.name); + self.save_button.set_sensitive(true); + } else { + self.ensemble_label.set_text(&gettext("Select …")); + } + } + + /// Update the UI according to role. + fn show_role(&self, role: Option<&Instrument>) { + if let Some(role) = role { + self.role_label.set_text(&role.name); + self.reset_role_button.show(); + } else { + self.role_label.set_text(&gettext("Select …")); + self.reset_role_button.hide(); + } + } +} diff --git a/src/dialogs/recording/recording_dialog.rs b/src/dialogs/recording/recording_dialog.rs new file mode 100644 index 0000000..3a0e1ba --- /dev/null +++ b/src/dialogs/recording/recording_dialog.rs @@ -0,0 +1,86 @@ +use super::recording_editor::*; +use super::recording_selector::*; +use crate::backend::*; +use crate::database::*; +use glib::clone; +use gtk::prelude::*; +use std::cell::RefCell; +use std::rc::Rc; + +/// A dialog for selecting and creating a recording. +pub struct RecordingDialog { + pub window: libhandy::Window, + stack: gtk::Stack, + selector: Rc, + editor: Rc, + selected_cb: RefCell ()>>>, +} + +impl RecordingDialog { + /// Create a new recording dialog. + pub fn new>(backend: Rc, parent: &W) -> Rc { + // Create UI + + let window = libhandy::Window::new(); + window.set_type_hint(gdk::WindowTypeHint::Dialog); + window.set_modal(true); + window.set_transient_for(Some(parent)); + window.set_default_size(600, 424); + + let selector = RecordingSelector::new(backend.clone()); + let editor = RecordingEditor::new(backend.clone(), &window, None); + + let stack = gtk::Stack::new(); + stack.set_transition_type(gtk::StackTransitionType::Crossfade); + stack.add(&selector.widget); + stack.add(&editor.widget); + window.add(&stack); + window.show_all(); + + let this = Rc::new(Self { + window, + stack, + selector, + editor, + selected_cb: RefCell::new(None), + }); + + // Connect signals and callbacks + + this.selector.set_add_cb(clone!(@strong this => move || { + this.stack.set_visible_child(&this.editor.widget); + })); + + this.selector + .set_selected_cb(clone!(@strong this => move |recording| { + if let Some(cb) = &*this.selected_cb.borrow() { + cb(recording); + this.window.close(); + } + })); + + this.editor.set_back_cb(clone!(@strong this => move || { + this.stack.set_visible_child(&this.selector.widget); + })); + + this.editor + .set_selected_cb(clone!(@strong this => move |recording| { + if let Some(cb) = &*this.selected_cb.borrow() { + cb(recording); + this.window.close(); + } + })); + + this + } + + /// Set the closure to be called when the user has selected or created a recording. + pub fn set_selected_cb () + 'static>(&self, cb: F) { + self.selected_cb.replace(Some(Box::new(cb))); + } + + /// Show the recording dialog. + pub fn show(&self) { + self.window.show(); + } +} diff --git a/src/dialogs/recording/recording_editor.rs b/src/dialogs/recording/recording_editor.rs new file mode 100644 index 0000000..d845629 --- /dev/null +++ b/src/dialogs/recording/recording_editor.rs @@ -0,0 +1,197 @@ +use super::performance_editor::*; +use crate::backend::*; +use crate::database::*; +use crate::dialogs::*; +use crate::widgets::*; +use gettextrs::gettext; +use glib::clone; +use gtk::prelude::*; +use gtk_macros::get_widget; +use std::cell::RefCell; +use std::rc::Rc; + +/// A widget for creating or editing a recording. +// TODO: Disable buttons if no performance is selected. +pub struct RecordingEditor { + pub widget: gtk::Box, + backend: Rc, + parent: gtk::Window, + save_button: gtk::Button, + work_label: gtk::Label, + comment_entry: gtk::Entry, + performance_list: Rc>, + id: i64, + work: RefCell>, + performances: RefCell>, + selected_cb: RefCell ()>>>, + back_cb: RefCell ()>>>, +} + +impl RecordingEditor { + /// Create a new recording editor widget and optionally initialize it. The parent window is + /// used as the parent for newly created dialogs. + pub fn new>( + backend: Rc, + parent: &W, + recording: Option, + ) -> Rc { + // Create UI + + let builder = + gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_editor.ui"); + + get_widget!(builder, gtk::Box, widget); + get_widget!(builder, gtk::Button, cancel_button); + get_widget!(builder, gtk::Button, save_button); + get_widget!(builder, gtk::Button, work_button); + get_widget!(builder, gtk::Label, work_label); + get_widget!(builder, gtk::Entry, comment_entry); + get_widget!(builder, gtk::ScrolledWindow, scroll); + get_widget!(builder, gtk::Button, add_performer_button); + get_widget!(builder, gtk::Button, edit_performer_button); + get_widget!(builder, gtk::Button, remove_performer_button); + + let performance_list = List::new(&gettext("No performers added.")); + scroll.add(&performance_list.widget); + + let (id, work, performances) = match recording { + Some(recording) => (recording.id, Some(recording.work), recording.performances), + None => (rand::random::().into(), None, Vec::new()), + }; + + let this = Rc::new(RecordingEditor { + widget, + backend, + parent: parent.clone().upcast(), + save_button, + work_label, + comment_entry, + performance_list, + id, + work: RefCell::new(work), + performances: RefCell::new(performances), + selected_cb: RefCell::new(None), + back_cb: RefCell::new(None), + }); + + // Connect signals and callbacks + + cancel_button.connect_clicked(clone!(@strong this => move |_| { + if let Some(cb) = &*this.back_cb.borrow() { + cb(); + } + })); + + this.save_button + .connect_clicked(clone!(@strong this => move |_| { + let recording = RecordingDescription { + id: this.id, + work: this.work.borrow().clone().expect("Tried to create recording without work!"), + comment: this.comment_entry.get_text().to_string(), + performances: this.performances.borrow().clone(), + }; + + let c = glib::MainContext::default(); + let clone = this.clone(); + c.spawn_local(async move { + clone.backend.update_recording(recording.clone().into()).await.unwrap(); + if let Some(cb) = &*clone.selected_cb.borrow() { + cb(recording.clone()); + } + }); + })); + + work_button.connect_clicked(clone!(@strong this => move |_| { + WorkSelector::new(this.backend.clone(), &this.parent, clone!(@strong this => move |work| { + this.work_selected(&work); + this.work.replace(Some(work)); + })).show(); + })); + + this.performance_list.set_make_widget(|performance| { + let label = gtk::Label::new(Some(&performance.get_title())); + label.set_ellipsize(pango::EllipsizeMode::End); + label.set_halign(gtk::Align::Start); + label.set_margin_start(6); + label.set_margin_end(6); + label.set_margin_top(6); + label.set_margin_bottom(6); + label.upcast() + }); + + add_performer_button.connect_clicked(clone!(@strong this => move |_| { + let editor = PerformanceEditor::new(this.backend.clone(), &this.parent, None); + + editor.set_selected_cb(clone!(@strong this => move |performance| { + let mut performances = this.performances.borrow_mut(); + + let index = match this.performance_list.get_selected_index() { + Some(index) => index + 1, + None => performances.len(), + }; + + performances.push(performance); + this.performance_list.show_items(performances.clone()); + this.performance_list.select_index(index); + })); + + editor.show(); + })); + + edit_performer_button.connect_clicked(clone!(@strong this => move |_| { + if let Some(index) = this.performance_list.get_selected_index() { + let performance = &this.performances.borrow()[index]; + + let editor = PerformanceEditor::new( + this.backend.clone(), + &this.parent, + Some(performance.clone()), + ); + + editor.set_selected_cb(clone!(@strong this => move |performance| { + let mut performances = this.performances.borrow_mut(); + performances[index] = performance; + this.performance_list.show_items(performances.clone()); + this.performance_list.select_index(index); + })); + + editor.show(); + } + })); + + remove_performer_button.connect_clicked(clone!(@strong this => move |_| { + if let Some(index) = this.performance_list.get_selected_index() { + let mut performances = this.performances.borrow_mut(); + performances.remove(index); + this.performance_list.show_items(performances.clone()); + this.performance_list.select_index(index); + } + })); + + // Initialize + + if let Some(work) = &*this.work.borrow() { + this.work_selected(work); + } + + this.performance_list.show_items(this.performances.borrow().clone()); + + this + } + + /// Set the closure to be called if the editor is canceled. + pub fn set_back_cb () + 'static>(&self, cb: F) { + self.back_cb.replace(Some(Box::new(cb))); + } + + /// Set the closure to be called if the recording was created. + pub fn set_selected_cb () + 'static>(&self, cb: F) { + self.selected_cb.replace(Some(Box::new(cb))); + } + + /// Update the UI according to work. + fn work_selected(&self, work: &WorkDescription) { + self.work_label.set_text(&format!("{}: {}", work.composer.name_fl(), work.title)); + self.save_button.set_sensitive(true); + } +} diff --git a/src/dialogs/recording/recording_editor_dialog.rs b/src/dialogs/recording/recording_editor_dialog.rs new file mode 100644 index 0000000..d036667 --- /dev/null +++ b/src/dialogs/recording/recording_editor_dialog.rs @@ -0,0 +1,63 @@ +use super::recording_editor::*; +use crate::backend::*; +use crate::database::*; +use glib::clone; +use gtk::prelude::*; +use std::cell::RefCell; +use std::rc::Rc; + +/// A dialog for creating or editing a recording. +pub struct RecordingEditorDialog { + pub window: libhandy::Window, + selected_cb: RefCell ()>>>, +} + +impl RecordingEditorDialog { + /// Create a new recording editor dialog and optionally initialize it. + pub fn new>( + backend: Rc, + parent: &W, + recording: Option, + ) -> Rc { + // Create UI + + let window = libhandy::Window::new(); + window.set_type_hint(gdk::WindowTypeHint::Dialog); + window.set_modal(true); + window.set_transient_for(Some(parent)); + + let editor = RecordingEditor::new(backend.clone(), &window, recording); + window.add(&editor.widget); + window.show_all(); + + let this = Rc::new(Self { + window, + selected_cb: RefCell::new(None), + }); + + // Connect signals and callbacks + + editor.set_back_cb(clone!(@strong this => move || { + this.window.close(); + })); + + editor.set_selected_cb(clone!(@strong this => move |recording| { + if let Some(cb) = &*this.selected_cb.borrow() { + cb(recording); + this.window.close(); + } + })); + + this + } + + /// Set the closure to be called when the user edited or created a recording. + pub fn set_selected_cb () + 'static>(&self, cb: F) { + self.selected_cb.replace(Some(Box::new(cb))); + } + + /// Show the recording editor dialog. + pub fn show(&self) { + self.window.show(); + } +} diff --git a/src/dialogs/recording/recording_selector.rs b/src/dialogs/recording/recording_selector.rs new file mode 100644 index 0000000..4435034 --- /dev/null +++ b/src/dialogs/recording/recording_selector.rs @@ -0,0 +1,89 @@ +use super::recording_selector_person_screen::*; +use crate::backend::Backend; +use crate::database::*; +use crate::widgets::*; +use glib::clone; +use gtk::prelude::*; +use gtk_macros::get_widget; +use libhandy::prelude::*; +use std::cell::RefCell; +use std::rc::Rc; + +/// A widget for selecting a recording from a list of existing ones. +pub struct RecordingSelector { + pub widget: libhandy::Leaflet, + backend: Rc, + sidebar_box: gtk::Box, + selected_cb: RefCell ()>>>, + add_cb: RefCell ()>>>, + navigator: Rc, +} + +impl RecordingSelector { + /// Create a new recording selector. + pub fn new(backend: Rc) -> Rc { + // Create UI + + let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_selector.ui"); + + get_widget!(builder, libhandy::Leaflet, widget); + get_widget!(builder, gtk::Button, add_button); + get_widget!(builder, gtk::Box, sidebar_box); + get_widget!(builder, gtk::Box, empty_screen); + + let person_list = PersonList::new(backend.clone()); + sidebar_box.pack_start(&person_list.widget, true, true, 0); + + let navigator = Navigator::new(&empty_screen); + widget.add(&navigator.widget); + + let this = Rc::new(Self { + widget, + backend, + sidebar_box, + selected_cb: RefCell::new(None), + add_cb: RefCell::new(None), + navigator, + }); + + // Connect signals and callbacks + + add_button.connect_clicked(clone!(@strong this => move |_| { + if let Some(cb) = &*this.add_cb.borrow() { + cb(); + } + })); + + person_list.set_selected(clone!(@strong this => move |person| { + let person_screen = RecordingSelectorPersonScreen::new( + this.backend.clone(), + person.clone(), + ); + + person_screen.set_selected_cb(clone!(@strong this => move |recording| { + if let Some(cb) = &*this.selected_cb.borrow() { + cb(recording); + } + })); + + this.navigator.clone().push(person_screen); + this.widget.set_visible_child(&this.navigator.widget); + })); + + this.navigator.set_back_cb(clone!(@strong this => move || { + this.widget.set_visible_child(&this.sidebar_box); + })); + + this + } + + /// Set the closure to be called if the editor is user wants to add a new recording. + pub fn set_add_cb () + 'static>(&self, cb: F) { + self.add_cb.replace(Some(Box::new(cb))); + } + + /// Set the closure to be called when the user has selected a recording. + pub fn set_selected_cb () + 'static>(&self, cb: F) { + self.selected_cb.replace(Some(Box::new(cb))); + } +} diff --git a/src/dialogs/recording/recording_selector_person_screen.rs b/src/dialogs/recording/recording_selector_person_screen.rs new file mode 100644 index 0000000..98037ca --- /dev/null +++ b/src/dialogs/recording/recording_selector_person_screen.rs @@ -0,0 +1,126 @@ +use super::recording_selector_work_screen::*; +use crate::backend::*; +use crate::database::*; +use crate::widgets::*; +use gettextrs::gettext; +use glib::clone; +use gtk::prelude::*; +use gtk_macros::get_widget; +use libhandy::HeaderBarExt; +use std::cell::RefCell; +use std::rc::Rc; + +/// A screen within the recording selector that presents a list of persons +/// and switches to a work screen on selection. +pub struct RecordingSelectorPersonScreen { + backend: Rc, + widget: gtk::Box, + stack: gtk::Stack, + work_list: Rc>, + selected_cb: RefCell ()>>>, + navigator: RefCell>>, +} + +impl RecordingSelectorPersonScreen { + /// Create a new recording selector person screen. + pub fn new(backend: Rc, person: Person) -> Rc { + // Create UI + + let builder = + gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_selector_screen.ui"); + + get_widget!(builder, gtk::Box, widget); + get_widget!(builder, libhandy::HeaderBar, header); + get_widget!(builder, gtk::Button, back_button); + get_widget!(builder, gtk::Stack, stack); + + header.set_title(Some(&person.name_fl())); + + let work_list = List::new(&gettext("No works found.")); + stack.add_named(&work_list.widget, "content"); + + let this = Rc::new(Self { + backend, + widget, + stack, + work_list, + selected_cb: RefCell::new(None), + navigator: RefCell::new(None), + }); + + // Connect signals and callbacks + + back_button.connect_clicked(clone!(@strong this => move |_| { + let navigator = this.navigator.borrow().clone(); + if let Some(navigator) = navigator { + navigator.pop(); + } + })); + + this.work_list.set_make_widget(|work: &WorkDescription| { + let label = gtk::Label::new(Some(&work.title)); + label.set_ellipsize(pango::EllipsizeMode::End); + label.set_halign(gtk::Align::Start); + label.set_margin_start(6); + label.set_margin_end(6); + label.set_margin_top(6); + label.set_margin_bottom(6); + label.upcast() + }); + + this.work_list + .set_selected(clone!(@strong this => move |work| { + let navigator = this.navigator.borrow().clone(); + if let Some(navigator) = navigator { + let work_screen = RecordingSelectorWorkScreen::new( + this.backend.clone(), + work.clone(), + ); + + work_screen.set_selected_cb(clone!(@strong this => move |recording| { + if let Some(cb) = &*this.selected_cb.borrow() { + cb(recording); + } + })); + + navigator.push(work_screen); + } + })); + + // Initialize + + let context = glib::MainContext::default(); + let clone = this.clone(); + context.spawn_local(async move { + let works = clone + .backend + .get_work_descriptions(person.id) + .await + .unwrap(); + + clone.work_list.show_items(works); + clone.stack.set_visible_child_name("content"); + }); + + this + } + + /// Sets a closure to be called when the user has selected a recording. + pub fn set_selected_cb () + 'static>(&self, cb: F) { + self.selected_cb.replace(Some(Box::new(cb))); + } +} + +impl NavigatorScreen for RecordingSelectorPersonScreen { + fn attach_navigator(&self, navigator: Rc) { + self.navigator.replace(Some(navigator)); + } + + fn get_widget(&self) -> gtk::Widget { + self.widget.clone().upcast() + } + + fn detach_navigator(&self) { + self.navigator.replace(None); + } +} diff --git a/src/dialogs/recording/recording_selector_work_screen.rs b/src/dialogs/recording/recording_selector_work_screen.rs new file mode 100644 index 0000000..975ee98 --- /dev/null +++ b/src/dialogs/recording/recording_selector_work_screen.rs @@ -0,0 +1,120 @@ +use crate::backend::*; +use crate::database::*; +use crate::widgets::*; +use gettextrs::gettext; +use glib::clone; +use gtk::prelude::*; +use gtk_macros::get_widget; +use libhandy::HeaderBarExt; +use std::cell::RefCell; +use std::rc::Rc; + +/// A screen within the recording selector presenting a list of recordings for a work. +pub struct RecordingSelectorWorkScreen { + backend: Rc, + widget: gtk::Box, + stack: gtk::Stack, + recording_list: Rc>, + selected_cb: RefCell ()>>>, + navigator: RefCell>>, +} + +impl RecordingSelectorWorkScreen { + /// Create a new recording selector work screen. + pub fn new(backend: Rc, work: WorkDescription) -> Rc { + // Create UI + + let builder = + gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_selector_screen.ui"); + + get_widget!(builder, gtk::Box, widget); + get_widget!(builder, libhandy::HeaderBar, header); + get_widget!(builder, gtk::Button, back_button); + get_widget!(builder, gtk::Stack, stack); + + header.set_title(Some(&work.title)); + header.set_subtitle(Some(&work.composer.name_fl())); + + let recording_list = List::new(&gettext("No recordings found.")); + stack.add_named(&recording_list.widget, "content"); + + let this = Rc::new(Self { + backend, + widget, + stack, + recording_list, + selected_cb: RefCell::new(None), + navigator: RefCell::new(None), + }); + + // Connect signals and callbacks + + back_button.connect_clicked(clone!(@strong this => move |_| { + let navigator = this.navigator.borrow().clone(); + if let Some(navigator) = navigator { + navigator.pop(); + } + })); + + this.recording_list.set_make_widget(|recording: &RecordingDescription| { + let work_label = gtk::Label::new(Some(&recording.work.get_title())); + work_label.set_ellipsize(pango::EllipsizeMode::End); + work_label.set_halign(gtk::Align::Start); + + let performers_label = gtk::Label::new(Some(&recording.get_performers())); + performers_label.set_ellipsize(pango::EllipsizeMode::End); + performers_label.set_opacity(0.5); + performers_label.set_halign(gtk::Align::Start); + + let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); + vbox.set_border_width(6); + vbox.add(&work_label); + vbox.add(&performers_label); + + vbox.upcast() + }); + + this.recording_list + .set_selected(clone!(@strong this => move |recording| { + if let Some(cb) = &*this.selected_cb.borrow() { + cb(recording.clone()); + } + })); + + // Initialize + + let context = glib::MainContext::default(); + let clone = this.clone(); + context.spawn_local(async move { + let recordings = clone + .backend + .get_recordings_for_work(work.id) + .await + .unwrap(); + + clone.recording_list.show_items(recordings); + clone.stack.set_visible_child_name("content"); + }); + + this + } + + /// Sets a closure to be called when the user has selected a recording. + pub fn set_selected_cb () + 'static>(&self, cb: F) { + self.selected_cb.replace(Some(Box::new(cb))); + } +} + +impl NavigatorScreen for RecordingSelectorWorkScreen { + fn attach_navigator(&self, navigator: Rc) { + self.navigator.replace(Some(navigator)); + } + + fn get_widget(&self) -> gtk::Widget { + self.widget.clone().upcast() + } + + fn detach_navigator(&self) { + self.navigator.replace(None); + } +} diff --git a/src/dialogs/recording_editor.rs b/src/dialogs/recording_editor.rs deleted file mode 100644 index 5a73260..0000000 --- a/src/dialogs/recording_editor.rs +++ /dev/null @@ -1,186 +0,0 @@ -use super::*; -use crate::backend::Backend; -use crate::database::*; -use crate::widgets::*; -use glib::clone; -use gtk::prelude::*; -use gtk_macros::get_widget; -use std::cell::RefCell; -use std::rc::Rc; -use std::convert::TryInto; - -pub struct RecordingEditor -where - F: Fn(RecordingDescription) -> () + 'static, -{ - backend: Rc, - window: libhandy::Window, - callback: F, - id: i64, - save_button: gtk::Button, - work_label: gtk::Label, - work: RefCell>, - comment_entry: gtk::Entry, - performers: RefCell>, - performer_list: gtk::ListBox, -} - -impl RecordingEditor -where - F: Fn(RecordingDescription) -> () + 'static, -{ - pub fn new>( - backend: Rc, - parent: &P, - recording: Option, - callback: F, - ) -> Rc { - let builder = - gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_editor.ui"); - - get_widget!(builder, libhandy::Window, window); - get_widget!(builder, gtk::Button, cancel_button); - get_widget!(builder, gtk::Button, save_button); - get_widget!(builder, gtk::Button, work_button); - get_widget!(builder, gtk::Label, work_label); - get_widget!(builder, gtk::Entry, comment_entry); - get_widget!(builder, gtk::ListBox, performer_list); - get_widget!(builder, gtk::Button, add_performer_button); - get_widget!(builder, gtk::Button, edit_performer_button); - get_widget!(builder, gtk::Button, remove_performer_button); - - let (id, work, performers) = match recording { - Some(recording) => { - save_button.set_sensitive(true); - work_label.set_text(&recording.work.get_title()); - comment_entry.set_text(&recording.comment); - (recording.id, Some(recording.work), recording.performances) - } - None => (rand::random::().into(), None, Vec::new()), - }; - - let result = Rc::new(RecordingEditor { - backend: backend, - window: window, - callback: callback, - id: id, - save_button: save_button, - work_label: work_label, - work: RefCell::new(work), - comment_entry: comment_entry, - performers: RefCell::new(performers), - performer_list: performer_list, - }); - - cancel_button.connect_clicked(clone!(@strong result => move |_| { - result.window.close(); - })); - - result - .save_button - .connect_clicked(clone!(@strong result => move |_| { - let recording = RecordingDescription { - id: result.id, - work: result.work.borrow().clone().expect("Tried to create recording without work!"), - comment: result.comment_entry.get_text().to_string(), - performances: result.performers.borrow().to_vec(), - }; - - let c = glib::MainContext::default(); - let clone = result.clone(); - c.spawn_local(async move { - clone.backend.update_recording(recording.clone().into()).await.unwrap(); - clone.window.close(); - (clone.callback)(recording.clone()); - }); - })); - - work_button.connect_clicked(clone!(@strong result => move |_| { - WorkSelector::new(result.backend.clone(), &result.window, clone!(@strong result => move |work| { - result.work.replace(Some(work.clone())); - result.work_label.set_text(&format!("{}: {}", work.composer.name_fl(), work.title)); - result.save_button.set_sensitive(true); - })).show(); - })); - - add_performer_button.connect_clicked(clone!(@strong result => move |_| { - PerformanceEditor::new(result.backend.clone(), &result.window, None, clone!(@strong result => move |performance| { - { - let mut performers = result.performers.borrow_mut(); - performers.push(performance); - } - - result.show_performers(); - })).show(); - })); - - edit_performer_button.connect_clicked(clone!(@strong result => move |_| { - let row = result.get_selected_performer_row(); - match row { - Some(row) => { - let index = row.get_index(); - let index: usize = index.try_into().unwrap(); - let performer = result.performers.borrow()[index].clone(); - - PerformanceEditor::new(result.backend.clone(), &result.window, Some(performer), clone!(@strong result => move |performer| { - result.performers.borrow_mut()[index] = performer; - result.show_performers(); - })).show(); - } - None => (), - } - })); - - remove_performer_button.connect_clicked(clone!(@strong result => move |_| { - let row = result.get_selected_performer_row(); - match row { - Some(row) => { - let index = row.get_index(); - let index: usize = index.try_into().unwrap(); - result.performers.borrow_mut().remove(index); - result.show_performers(); - } - None => (), - } - })); - - result.window.set_transient_for(Some(parent)); - - result.show_performers(); - - result - } - - pub fn show(&self) { - self.window.show(); - } - - fn show_performers(&self) { - for child in self.performer_list.get_children() { - self.performer_list.remove(&child); - } - - for (index, performer) in self.performers.borrow().iter().enumerate() { - let label = gtk::Label::new(Some(&performer.get_title())); - label.set_halign(gtk::Align::Start); - label.set_margin_start(6); - label.set_margin_end(6); - label.set_margin_top(6); - label.set_margin_bottom(6); - - let row = SelectorRow::new(index.try_into().unwrap(), &label); - row.show_all(); - self.performer_list.insert(&row, -1); - } - } - - fn get_selected_performer_row(&self) -> Option { - match self.performer_list.get_selected_rows().first() { - Some(row) => match row.get_child() { - Some(child) => Some(child.downcast().unwrap()), - None => None, - }, - None => None, - } - } -} diff --git a/src/dialogs/recording_selector.rs b/src/dialogs/recording_selector.rs deleted file mode 100644 index c9f28a6..0000000 --- a/src/dialogs/recording_selector.rs +++ /dev/null @@ -1,279 +0,0 @@ -use super::*; -use crate::backend::Backend; -use crate::database::*; -use crate::widgets::*; -use gettextrs::gettext; -use gio::prelude::*; -use glib::clone; -use gtk::prelude::*; -use gtk_macros::get_widget; -use libhandy::prelude::*; -use libhandy::HeaderBarExt; -use std::cell::RefCell; -use std::rc::Rc; - -pub struct RecordingSelector { - backend: Rc, - window: libhandy::Window, - callback: Box () + 'static>, - leaflet: libhandy::Leaflet, - navigator: Rc, -} - -impl RecordingSelector { - pub fn new(backend: Rc, parent: &P, callback: F) -> Rc - where - P: IsA, - F: Fn(RecordingDescription) -> () + 'static, - { - let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_selector.ui"); - - get_widget!(builder, libhandy::Window, window); - get_widget!(builder, libhandy::Leaflet, leaflet); - get_widget!(builder, gtk::Button, add_button); - get_widget!(builder, gtk::Box, sidebar_box); - get_widget!(builder, gtk::Box, empty_screen); - - let person_list = PersonList::new(backend.clone()); - sidebar_box.pack_start(&person_list.widget, true, true, 0); - - let navigator = Navigator::new(&empty_screen); - leaflet.add(&navigator.widget); - - navigator.set_back_cb(clone!(@strong leaflet, @strong sidebar_box => move || { - leaflet.set_visible_child(&sidebar_box); - })); - - let result = Rc::new(Self { - backend: backend, - window: window, - callback: Box::new(callback), - leaflet: leaflet, - navigator: navigator, - }); - - add_button.connect_clicked(clone!(@strong result => move |_| { - let editor = RecordingEditor::new( - result.backend.clone(), - &result.window, - None, - clone!(@strong result => move |recording| { - result.select(recording); - }), - ); - - editor.show(); - })); - - person_list.set_selected(clone!(@strong result => move |person| { - result.navigator.clone().replace(RecordingSelectorPersonScreen::new(result.backend.clone(), result.clone(), person.clone())); - result.leaflet.set_visible_child(&result.navigator.widget); - })); - - result.window.set_transient_for(Some(parent)); - - result - } - - pub fn show(&self) { - self.window.show(); - } - - pub fn select(&self, recording: RecordingDescription) { - self.window.close(); - (self.callback)(recording); - } -} - -struct RecordingSelectorPersonScreen { - backend: Rc, - selector: Rc, - widget: gtk::Box, - stack: gtk::Stack, - work_list: Rc>, - navigator: RefCell>>, -} - -impl RecordingSelectorPersonScreen { - pub fn new(backend: Rc, selector: Rc, person: Person) -> Rc { - let builder = - gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_selector_screen.ui"); - - get_widget!(builder, gtk::Box, widget); - get_widget!(builder, libhandy::HeaderBar, header); - get_widget!(builder, gtk::Button, back_button); - get_widget!(builder, gtk::Stack, stack); - - header.set_title(Some(&person.name_fl())); - - let work_list = List::new(&gettext("No works found.")); - - work_list.set_make_widget(|work: &WorkDescription| { - let label = gtk::Label::new(Some(&work.title)); - label.set_halign(gtk::Align::Start); - label.set_margin_start(6); - label.set_margin_end(6); - label.set_margin_top(6); - label.set_margin_bottom(6); - label.upcast() - }); - - stack.add_named(&work_list.widget, "content"); - - let result = Rc::new(Self { - backend, - selector, - widget, - stack, - work_list, - navigator: RefCell::new(None), - }); - - back_button.connect_clicked(clone!(@strong result => move |_| { - let navigator = result.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.clone().pop(); - } - })); - - result - .work_list - .set_selected(clone!(@strong result => move |work| { - let navigator = result.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.push(RecordingSelectorWorkScreen::new(result.backend.clone(), result.selector.clone(), work.clone())); - } - })); - - let context = glib::MainContext::default(); - let clone = result.clone(); - context.spawn_local(async move { - let works = clone - .backend - .get_work_descriptions(person.id) - .await - .unwrap(); - - clone.work_list.show_items(works); - clone.stack.set_visible_child_name("content"); - }); - - result - } -} - -impl NavigatorScreen for RecordingSelectorPersonScreen { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - - fn get_widget(&self) -> gtk::Widget { - self.widget.clone().upcast() - } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } -} - -struct RecordingSelectorWorkScreen { - backend: Rc, - selector: Rc, - widget: gtk::Box, - stack: gtk::Stack, - recording_list: Rc>, - navigator: RefCell>>, -} - -impl RecordingSelectorWorkScreen { - pub fn new( - backend: Rc, - selector: Rc, - work: WorkDescription, - ) -> Rc { - let builder = - gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_selector_screen.ui"); - - get_widget!(builder, gtk::Box, widget); - get_widget!(builder, libhandy::HeaderBar, header); - get_widget!(builder, gtk::Button, back_button); - get_widget!(builder, gtk::Stack, stack); - - header.set_title(Some(&work.title)); - header.set_subtitle(Some(&work.composer.name_fl())); - - let recording_list = List::new(&gettext("No recordings found.")); - - recording_list.set_make_widget(|recording: &RecordingDescription| { - let work_label = gtk::Label::new(Some(&recording.work.get_title())); - - work_label.set_ellipsize(pango::EllipsizeMode::End); - work_label.set_halign(gtk::Align::Start); - - let performers_label = gtk::Label::new(Some(&recording.get_performers())); - performers_label.set_ellipsize(pango::EllipsizeMode::End); - performers_label.set_opacity(0.5); - performers_label.set_halign(gtk::Align::Start); - - let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); - vbox.set_border_width(6); - vbox.add(&work_label); - vbox.add(&performers_label); - - vbox.upcast() - }); - - stack.add_named(&recording_list.widget, "content"); - - let result = Rc::new(Self { - backend, - selector, - widget, - stack, - recording_list, - navigator: RefCell::new(None), - }); - - back_button.connect_clicked(clone!(@strong result => move |_| { - let navigator = result.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.clone().pop(); - } - })); - - result - .recording_list - .set_selected(clone!(@strong result => move |recording| { - result.selector.select(recording.clone()); - })); - - let context = glib::MainContext::default(); - let clone = result.clone(); - context.spawn_local(async move { - let recordings = clone - .backend - .get_recordings_for_work(work.id) - .await - .unwrap(); - - clone.recording_list.show_items(recordings); - clone.stack.set_visible_child_name("content"); - }); - - result - } -} - -impl NavigatorScreen for RecordingSelectorWorkScreen { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - - fn get_widget(&self) -> gtk::Widget { - self.widget.clone().upcast() - } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } -} diff --git a/src/dialogs/tracks_editor.rs b/src/dialogs/tracks_editor.rs index 89abe79..e173242 100644 --- a/src/dialogs/tracks_editor.rs +++ b/src/dialogs/tracks_editor.rs @@ -95,14 +95,14 @@ impl TracksEditor { })); recording_button.connect_clicked(clone!(@strong this => move |_| { - RecordingSelector::new( - this.backend.clone(), - &this.window, - clone!(@strong this => move |recording| { - this.recording_selected(&recording); - this.recording.replace(Some(recording)); - }), - ).show(); + let dialog = RecordingDialog::new(this.backend.clone(), &this.window); + + dialog.set_selected_cb(clone!(@strong this => move |recording| { + this.recording_selected(&recording); + this.recording.replace(Some(recording)); + })); + + dialog.show(); } )); diff --git a/src/meson.build b/src/meson.build index aa53a58..e14b79b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -45,12 +45,17 @@ sources = files( 'dialogs/instrument_selector.rs', 'dialogs/mod.rs', 'dialogs/part_editor.rs', - 'dialogs/performance_editor.rs', 'dialogs/person_editor.rs', 'dialogs/person_selector.rs', 'dialogs/preferences.rs', - 'dialogs/recording_editor.rs', - 'dialogs/recording_selector.rs', + 'dialogs/recording/mod.rs', + 'dialogs/recording/performance_editor.rs', + 'dialogs/recording/recording_dialog.rs', + 'dialogs/recording/recording_editor_dialog.rs', + 'dialogs/recording/recording_editor.rs', + 'dialogs/recording/recording_selector_person_screen.rs', + 'dialogs/recording/recording_selector.rs', + 'dialogs/recording/recording_selector_work_screen.rs', 'dialogs/section_editor.rs', 'dialogs/track_editor.rs', 'dialogs/tracks_editor.rs', diff --git a/src/window.rs b/src/window.rs index 9208c93..6777f2e 100644 --- a/src/window.rs +++ b/src/window.rs @@ -166,9 +166,13 @@ impl Window { result.window, "add-recording", clone!(@strong result => move |_, _| { - RecordingEditor::new(result.backend.clone(), &result.window, None, clone!(@strong result => move |_| { + let dialog = RecordingDialog::new(result.backend.clone(), &result.window); + + dialog.set_selected_cb(clone!(@strong result => move |_| { result.reload(); - })).show(); + })); + + dialog.show(); }) ); @@ -292,9 +296,13 @@ impl Window { let c = glib::MainContext::default(); c.spawn_local(async move { let recording = result.backend.get_recording_description(id).await.unwrap(); - RecordingEditor::new(result.backend.clone(), &result.window, Some(recording), clone!(@strong result => move |_| { + let dialog = RecordingEditorDialog::new(result.backend.clone(), &result.window, Some(recording)); + + dialog.set_selected_cb(clone!(@strong result => move |_| { result.reload(); - })).show(); + })); + + dialog.show(); }); }) );