Merge recording editor and selector into one dialog

This commit is contained in:
Elias Projahn 2020-11-08 12:11:54 +01:00
parent 3e34658b25
commit 99bbd9e58f
22 changed files with 1167 additions and 1032 deletions

View file

@ -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"

View file

@ -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

View file

@ -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."

View file

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View file

@ -96,7 +96,6 @@
</child>
<child>
<object class="GtkButton" id="reset_role_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>

View file

@ -3,15 +3,7 @@
<interface>
<requires lib="gtk+" version="3.22"/>
<requires lib="libhandy" version="0.0"/>
<object class="HdyWindow" id="window">
<property name="can-focus">False</property>
<property name="modal">True</property>
<property name="default-width">500</property>
<property name="default-height">450</property>
<property name="destroy-with-parent">True</property>
<property name="type-hint">dialog</property>
<child>
<object class="GtkBox">
<object class="GtkBox" id="widget">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
@ -87,6 +79,7 @@
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Select …</property>
<property name="ellipsize">end</property>
</object>
</child>
</object>
@ -136,30 +129,12 @@
<property name="border-width">18</property>
<property name="spacing">6</property>
<child>
<object class="GtkScrolledWindow">
<object class="GtkScrolledWindow" id="scroll">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkListBox" id="performer_list">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child type="placeholder">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">No performers added.</property>
</object>
</child>
</object>
</child>
</object>
<placeholder/>
</child>
</object>
<packing>
@ -263,6 +238,4 @@
</packing>
</child>
</object>
</child>
</object>
</interface>

View file

@ -33,64 +33,7 @@
</packing>
</child>
</object>
<object class="GtkBox" id="person_screen">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="HdyHeaderBar" id="person_screen_header">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="show-close-button">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkStack" id="person_screen_stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkSpinner">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="active">True</property>
</object>
<packing>
<property name="name">loading</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">No works found.</property>
</object>
<packing>
<property name="name">empty</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<object class="HdyWindow" id="window">
<property name="can-focus">False</property>
<property name="modal">True</property>
<property name="default-width">600</property>
<property name="default-height">424</property>
<property name="type-hint">dialog</property>
<child>
<object class="HdyLeaflet" id="leaflet">
<object class="HdyLeaflet" id="widget">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="visible-child">sidebar_box</property>
@ -106,7 +49,7 @@
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="title" translatable="yes">Select a recording</property>
<property name="show-close-button" bind-source="leaflet" bind-property="folded" bind-flags="sync-create">False</property>
<property name="show-close-button" bind-source="widget" bind-property="folded" bind-flags="sync-create">False</property>
<child>
<object class="GtkButton" id="add_button">
<property name="visible">True</property>
@ -147,55 +90,4 @@
</packing>
</child>
</object>
</child>
</object>
<object class="GtkBox" id="work_screen">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="HdyHeaderBar" id="work_screen_header">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkStack" id="work_screen_stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkSpinner">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="active">True</property>
</object>
<packing>
<property name="name">loading</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">No works found.</property>
</object>
<packing>
<property name="name">empty</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>

View file

@ -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::*;

View file

@ -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<F>
where
F: Fn(PerformanceDescription) -> () + 'static,
{
backend: Rc<Backend>,
window: libhandy::Window,
callback: F,
save_button: gtk::Button,
person_label: gtk::Label,
ensemble_label: gtk::Label,
role_label: gtk::Label,
person: RefCell<Option<Person>>,
ensemble: RefCell<Option<Ensemble>>,
role: RefCell<Option<Instrument>>,
}
impl<F> PerformanceEditor<F>
where
F: Fn(PerformanceDescription) -> () + 'static,
{
pub fn new<P: IsA<gtk::Window>>(
backend: Rc<Backend>,
parent: &P,
performance: Option<PerformanceDescription>,
callback: F,
) -> Rc<Self> {
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();
}
}

View file

@ -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;

View file

@ -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<Backend>,
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<Option<Person>>,
ensemble: RefCell<Option<Ensemble>>,
role: RefCell<Option<Instrument>>,
selected_cb: RefCell<Option<Box<dyn Fn(PerformanceDescription) -> ()>>>,
}
impl PerformanceEditor {
/// Create a new performance editor.
pub fn new<P: IsA<gtk::Window>>(
backend: Rc<Backend>,
parent: &P,
performance: Option<PerformanceDescription>,
) -> Rc<Self> {
// 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<F: Fn(PerformanceDescription) -> () + '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();
}
}
}

View file

@ -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<RecordingSelector>,
editor: Rc<RecordingEditor>,
selected_cb: RefCell<Option<Box<dyn Fn(RecordingDescription) -> ()>>>,
}
impl RecordingDialog {
/// Create a new recording dialog.
pub fn new<W: IsA<gtk::Window>>(backend: Rc<Backend>, parent: &W) -> Rc<Self> {
// 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<F: Fn(RecordingDescription) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
/// Show the recording dialog.
pub fn show(&self) {
self.window.show();
}
}

View file

@ -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<Backend>,
parent: gtk::Window,
save_button: gtk::Button,
work_label: gtk::Label,
comment_entry: gtk::Entry,
performance_list: Rc<List<PerformanceDescription>>,
id: i64,
work: RefCell<Option<WorkDescription>>,
performances: RefCell<Vec<PerformanceDescription>>,
selected_cb: RefCell<Option<Box<dyn Fn(RecordingDescription) -> ()>>>,
back_cb: RefCell<Option<Box<dyn Fn() -> ()>>>,
}
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<W: IsA<gtk::Window>>(
backend: Rc<Backend>,
parent: &W,
recording: Option<RecordingDescription>,
) -> Rc<Self> {
// 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::<u32>().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<F: Fn() -> () + '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<F: Fn(RecordingDescription) -> () + '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);
}
}

View file

@ -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<Option<Box<dyn Fn(RecordingDescription) -> ()>>>,
}
impl RecordingEditorDialog {
/// Create a new recording editor dialog and optionally initialize it.
pub fn new<W: IsA<gtk::Window>>(
backend: Rc<Backend>,
parent: &W,
recording: Option<RecordingDescription>,
) -> Rc<Self> {
// 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<F: Fn(RecordingDescription) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
/// Show the recording editor dialog.
pub fn show(&self) {
self.window.show();
}
}

View file

@ -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<Backend>,
sidebar_box: gtk::Box,
selected_cb: RefCell<Option<Box<dyn Fn(RecordingDescription) -> ()>>>,
add_cb: RefCell<Option<Box<dyn Fn() -> ()>>>,
navigator: Rc<Navigator>,
}
impl RecordingSelector {
/// Create a new recording selector.
pub fn new(backend: Rc<Backend>) -> Rc<Self> {
// 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<F: Fn() -> () + '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<F: Fn(RecordingDescription) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
}

View file

@ -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<Backend>,
widget: gtk::Box,
stack: gtk::Stack,
work_list: Rc<List<WorkDescription>>,
selected_cb: RefCell<Option<Box<dyn Fn(RecordingDescription) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
}
impl RecordingSelectorPersonScreen {
/// Create a new recording selector person screen.
pub fn new(backend: Rc<Backend>, person: Person) -> Rc<Self> {
// 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<F: Fn(RecordingDescription) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
}
impl NavigatorScreen for RecordingSelectorPersonScreen {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
}
fn get_widget(&self) -> gtk::Widget {
self.widget.clone().upcast()
}
fn detach_navigator(&self) {
self.navigator.replace(None);
}
}

View file

@ -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<Backend>,
widget: gtk::Box,
stack: gtk::Stack,
recording_list: Rc<List<RecordingDescription>>,
selected_cb: RefCell<Option<Box<dyn Fn(RecordingDescription) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
}
impl RecordingSelectorWorkScreen {
/// Create a new recording selector work screen.
pub fn new(backend: Rc<Backend>, work: WorkDescription) -> Rc<Self> {
// 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<F: Fn(RecordingDescription) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
}
impl NavigatorScreen for RecordingSelectorWorkScreen {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
}
fn get_widget(&self) -> gtk::Widget {
self.widget.clone().upcast()
}
fn detach_navigator(&self) {
self.navigator.replace(None);
}
}

View file

@ -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<F>
where
F: Fn(RecordingDescription) -> () + 'static,
{
backend: Rc<Backend>,
window: libhandy::Window,
callback: F,
id: i64,
save_button: gtk::Button,
work_label: gtk::Label,
work: RefCell<Option<WorkDescription>>,
comment_entry: gtk::Entry,
performers: RefCell<Vec<PerformanceDescription>>,
performer_list: gtk::ListBox,
}
impl<F> RecordingEditor<F>
where
F: Fn(RecordingDescription) -> () + 'static,
{
pub fn new<P: IsA<gtk::Window>>(
backend: Rc<Backend>,
parent: &P,
recording: Option<RecordingDescription>,
callback: F,
) -> Rc<Self> {
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::<u32>().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<SelectorRow> {
match self.performer_list.get_selected_rows().first() {
Some(row) => match row.get_child() {
Some(child) => Some(child.downcast().unwrap()),
None => None,
},
None => None,
}
}
}

View file

@ -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<Backend>,
window: libhandy::Window,
callback: Box<dyn Fn(RecordingDescription) -> () + 'static>,
leaflet: libhandy::Leaflet,
navigator: Rc<Navigator>,
}
impl RecordingSelector {
pub fn new<P, F>(backend: Rc<Backend>, parent: &P, callback: F) -> Rc<Self>
where
P: IsA<gtk::Window>,
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<Backend>,
selector: Rc<RecordingSelector>,
widget: gtk::Box,
stack: gtk::Stack,
work_list: Rc<List<WorkDescription>>,
navigator: RefCell<Option<Rc<Navigator>>>,
}
impl RecordingSelectorPersonScreen {
pub fn new(backend: Rc<Backend>, selector: Rc<RecordingSelector>, person: Person) -> Rc<Self> {
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<Navigator>) {
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<Backend>,
selector: Rc<RecordingSelector>,
widget: gtk::Box,
stack: gtk::Stack,
recording_list: Rc<List<RecordingDescription>>,
navigator: RefCell<Option<Rc<Navigator>>>,
}
impl RecordingSelectorWorkScreen {
pub fn new(
backend: Rc<Backend>,
selector: Rc<RecordingSelector>,
work: WorkDescription,
) -> Rc<Self> {
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<Navigator>) {
self.navigator.replace(Some(navigator));
}
fn get_widget(&self) -> gtk::Widget {
self.widget.clone().upcast()
}
fn detach_navigator(&self) {
self.navigator.replace(None);
}
}

View file

@ -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| {
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));
}),
).show();
}));
dialog.show();
}
));

View file

@ -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',

View file

@ -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();
});
})
);