Merge work selector and editor to single dialog

This commit is contained in:
Elias Projahn 2020-11-08 20:23:53 +01:00
parent 9ee7bf166d
commit d20d80d1ac
26 changed files with 1559 additions and 1742 deletions

View file

@ -24,6 +24,7 @@ res/ui/window.ui
res/ui/work_editor.ui res/ui/work_editor.ui
res/ui/work_screen.ui res/ui/work_screen.ui
res/ui/work_selector.ui res/ui/work_selector.ui
res/ui/work_selector_screen.ui
src/database/database.rs src/database/database.rs
src/database/models.rs src/database/models.rs
@ -36,7 +37,6 @@ src/dialogs/ensemble_selector.rs
src/dialogs/instrument_editor.rs src/dialogs/instrument_editor.rs
src/dialogs/instrument_selector.rs src/dialogs/instrument_selector.rs
src/dialogs/mod.rs src/dialogs/mod.rs
src/dialogs/part_editor.rs
src/dialogs/person_editor.rs src/dialogs/person_editor.rs
src/dialogs/person_selector.rs src/dialogs/person_selector.rs
src/dialogs/preferences.rs src/dialogs/preferences.rs
@ -48,11 +48,16 @@ src/dialogs/recording/recording_editor.rs
src/dialogs/recording/recording_selector_person_screen.rs src/dialogs/recording/recording_selector_person_screen.rs
src/dialogs/recording/recording_selector.rs src/dialogs/recording/recording_selector.rs
src/dialogs/recording/recording_selector_work_screen.rs src/dialogs/recording/recording_selector_work_screen.rs
src/dialogs/section_editor.rs
src/dialogs/track_editor.rs src/dialogs/track_editor.rs
src/dialogs/tracks_editor.rs src/dialogs/tracks_editor.rs
src/dialogs/work_editor.rs src/dialogs/work/mod.rs
src/dialogs/work_selector.rs src/dialogs/work/part_editor.rs
src/dialogs/work/section_editor.rs
src/dialogs/work/work_dialog.rs
src/dialogs/work/work_editor_dialog.rs
src/dialogs/work/work_editor.rs
src/dialogs/work/work_selector_person_screen.rs
src/dialogs/work/work_selector.rs
src/screens/ensemble_screen.rs src/screens/ensemble_screen.rs
src/screens/mod.rs src/screens/mod.rs
src/screens/person_screen.rs src/screens/person_screen.rs

View file

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-11-08 12:11+0100\n" "POT-Creation-Date: 2020-11-08 20:12+0100\n"
"PO-Revision-Date: 2020-11-08 02:37+0100\n" "PO-Revision-Date: 2020-11-08 20:13+0100\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: de\n" "Language: de\n"
@ -27,7 +27,7 @@ msgstr "Ensemble"
#: res/ui/part_editor.ui:25 res/ui/performance_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:17 #: 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/section_editor.ui:23 res/ui/track_editor.ui:24
#: res/ui/tracks_editor.ui:39 res/ui/work_editor.ui:25 #: res/ui/tracks_editor.ui:39 res/ui/work_editor.ui:17
msgid "Cancel" msgid "Cancel"
msgstr "Abbrechen" msgstr "Abbrechen"
@ -35,7 +35,7 @@ msgstr "Abbrechen"
#: res/ui/part_editor.ui:33 res/ui/performance_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:25 #: 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/section_editor.ui:31 res/ui/track_editor.ui:32
#: res/ui/tracks_editor.ui:24 res/ui/work_editor.ui:33 #: res/ui/tracks_editor.ui:24 res/ui/work_editor.ui:25
msgid "Save" msgid "Save"
msgstr "Speichern" msgstr "Speichern"
@ -83,36 +83,32 @@ msgstr "Keine Instrumente gefunden."
msgid "Work part" msgid "Work part"
msgstr "Werkabschnitt" msgstr "Werkabschnitt"
#: res/ui/part_editor.ui:70 res/ui/work_editor.ui:91 #: res/ui/part_editor.ui:70 res/ui/work_editor.ui:84
msgid "Composer" msgid "Composer"
msgstr "Komponist" msgstr "Komponist"
#: res/ui/part_editor.ui:93 res/ui/player_bar.ui:118 #: res/ui/part_editor.ui:93 res/ui/player_bar.ui:118
#: res/ui/player_screen.ui:160 res/ui/section_editor.ui:64 #: res/ui/player_screen.ui:160 res/ui/section_editor.ui:64
#: res/ui/work_editor.ui:113 #: res/ui/work_editor.ui:106
msgid "Title" msgid "Title"
msgstr "Titel" msgstr "Titel"
#: res/ui/part_editor.ui:116 res/ui/performance_editor.ui:87 #: res/ui/part_editor.ui:116 res/ui/performance_editor.ui:87
#: res/ui/performance_editor.ui:170 res/ui/performance_editor.ui:214 #: 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/recording_editor.ui:81 res/ui/tracks_editor.ui:93
#: res/ui/work_editor.ui:77 src/dialogs/part_editor.rs:99 #: res/ui/work_editor.ui:69 src/dialogs/recording/performance_editor.rs:150
#: src/dialogs/recording/performance_editor.rs:150
#: src/dialogs/recording/performance_editor.rs:160 #: src/dialogs/recording/performance_editor.rs:160
#: src/dialogs/recording/performance_editor.rs:170 #: src/dialogs/recording/performance_editor.rs:170
#: src/dialogs/work/part_editor.rs:166
msgid "Select …" msgid "Select …"
msgstr "Auswählen …" msgstr "Auswählen …"
#: res/ui/part_editor.ui:160 res/ui/recording_editor.ui:119 #: res/ui/part_editor.ui:159 res/ui/recording_editor.ui:119
#: res/ui/work_editor.ui:126 #: res/ui/work_editor.ui:119
msgid "Overview" msgid "Overview"
msgstr "Überblick" msgstr "Überblick"
#: res/ui/part_editor.ui:191 res/ui/work_editor.ui:157 #: res/ui/part_editor.ui:247 res/ui/work_editor.ui:207
msgid "No instruments added."
msgstr "Keine Instrumente hinzugefügt."
#: res/ui/part_editor.ui:266 res/ui/work_editor.ui:232
msgid "Instruments" msgid "Instruments"
msgstr "Instrumente" msgstr "Instrumente"
@ -141,7 +137,7 @@ msgstr "Vorname"
msgid "Last name" msgid "Last name"
msgstr "Nachname" msgstr "Nachname"
#: res/ui/person_list.ui:28 res/ui/work_selector.ui:69 #: res/ui/person_list.ui:28
msgid "Search persons …" msgid "Search persons …"
msgstr "Personen durchsuchen …" msgstr "Personen durchsuchen …"
@ -211,7 +207,7 @@ msgid "Comment"
msgstr "Kommentar" msgstr "Kommentar"
#: res/ui/recording_editor.ui:106 res/ui/tracks_editor.ui:109 #: res/ui/recording_editor.ui:106 res/ui/tracks_editor.ui:109
#: res/ui/work_editor.ui:22 #: res/ui/work_editor.ui:14
msgid "Work" msgid "Work"
msgstr "Werk" msgstr "Werk"
@ -227,7 +223,7 @@ msgstr "Tracks"
msgid "Add to playlist" msgid "Add to playlist"
msgstr "Zur Wiedergabeliste hinzufügen" msgstr "Zur Wiedergabeliste hinzufügen"
#: res/ui/recording_selector.ui:27 res/ui/work_selector.ui:180 #: res/ui/recording_selector.ui:27 res/ui/work_selector.ui:26
msgid "Select a composer on the left." msgid "Select a composer on the left."
msgstr "Wählen Sie einen Komponisten aus." msgstr "Wählen Sie einen Komponisten aus."
@ -284,26 +280,14 @@ msgstr "Einstellungen"
msgid "About Musicus" msgid "About Musicus"
msgstr "Über Musicus" msgstr "Über Musicus"
#: res/ui/work_editor.ui:264 #: res/ui/work_editor.ui:373
msgid "No work parts added."
msgstr "Keine Werkabschnitte hinzugefügt."
#: res/ui/work_editor.ui:416
msgid "Structure" msgid "Structure"
msgstr "Struktur" msgstr "Struktur"
#: res/ui/work_selector.ui:113 src/widgets/person_list.rs:26 #: res/ui/work_selector.ui:51
msgid "No persons found." msgid "Select a work"
msgstr "Keine Personen gefunden."
#: res/ui/work_selector.ui:167
msgid "Select work"
msgstr "Werk auswählen" msgstr "Werk auswählen"
#: res/ui/work_selector.ui:268
msgid "Search works …"
msgstr "Werke durchsuchen …"
#: src/dialogs/about.rs:12 #: src/dialogs/about.rs:12
msgid "The classical music player and organizer." msgid "The classical music player and organizer."
msgstr "Das Programm zum Abspielen und Organisieren von Klassik." msgstr "Das Programm zum Abspielen und Organisieren von Klassik."
@ -321,6 +305,7 @@ msgid "No performers added."
msgstr "Keine Interpreten hinzugefügt." msgstr "Keine Interpreten hinzugefügt."
#: src/dialogs/recording/recording_selector_person_screen.rs:39 #: src/dialogs/recording/recording_selector_person_screen.rs:39
#: src/dialogs/work/work_selector_person_screen.rs:36
#: src/screens/person_screen.rs:57 #: src/screens/person_screen.rs:57
msgid "No works found." msgid "No works found."
msgstr "Keine Werke gefunden." msgstr "Keine Werke gefunden."
@ -338,6 +323,14 @@ msgstr "Audiodateien auswählen"
msgid "Unknown" msgid "Unknown"
msgstr "Unbekannt" msgstr "Unbekannt"
#: src/dialogs/work/part_editor.rs:49 src/dialogs/work/work_editor.rs:69
msgid "No instruments added."
msgstr "Keine Instrumente hinzugefügt."
#: src/dialogs/work/work_editor.rs:72
msgid "No work parts added."
msgstr "Keine Werkabschnitte hinzugefügt."
#: src/screens/ensemble_screen.rs:35 #: src/screens/ensemble_screen.rs:35
msgid "Edit ensemble" msgid "Edit ensemble"
msgstr "Ensemble bearbeiten" msgstr "Ensemble bearbeiten"
@ -382,6 +375,13 @@ msgstr "Werk bearbeiten"
msgid "Delete work" msgid "Delete work"
msgstr "Werk löschen" msgstr "Werk löschen"
#: src/widgets/person_list.rs:26
msgid "No persons found."
msgstr "Keine Personen gefunden."
#: src/widgets/poe_list.rs:41 #: src/widgets/poe_list.rs:41
msgid "No persons or ensembles found." msgid "No persons or ensembles found."
msgstr "Keine Personen oder Ensembles gefunden." msgstr "Keine Personen oder Ensembles gefunden."
#~ msgid "Search works …"
#~ msgstr "Werke durchsuchen …"

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: musicus\n" "Project-Id-Version: musicus\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-11-08 12:11+0100\n" "POT-Creation-Date: 2020-11-08 20:12+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -26,7 +26,7 @@ msgstr ""
#: res/ui/part_editor.ui:25 res/ui/performance_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:17 #: 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/section_editor.ui:23 res/ui/track_editor.ui:24
#: res/ui/tracks_editor.ui:39 res/ui/work_editor.ui:25 #: res/ui/tracks_editor.ui:39 res/ui/work_editor.ui:17
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
@ -34,7 +34,7 @@ msgstr ""
#: res/ui/part_editor.ui:33 res/ui/performance_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:25 #: 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/section_editor.ui:31 res/ui/track_editor.ui:32
#: res/ui/tracks_editor.ui:24 res/ui/work_editor.ui:33 #: res/ui/tracks_editor.ui:24 res/ui/work_editor.ui:25
msgid "Save" msgid "Save"
msgstr "" msgstr ""
@ -82,36 +82,32 @@ msgstr ""
msgid "Work part" msgid "Work part"
msgstr "" msgstr ""
#: res/ui/part_editor.ui:70 res/ui/work_editor.ui:91 #: res/ui/part_editor.ui:70 res/ui/work_editor.ui:84
msgid "Composer" msgid "Composer"
msgstr "" msgstr ""
#: res/ui/part_editor.ui:93 res/ui/player_bar.ui:118 #: res/ui/part_editor.ui:93 res/ui/player_bar.ui:118
#: res/ui/player_screen.ui:160 res/ui/section_editor.ui:64 #: res/ui/player_screen.ui:160 res/ui/section_editor.ui:64
#: res/ui/work_editor.ui:113 #: res/ui/work_editor.ui:106
msgid "Title" msgid "Title"
msgstr "" msgstr ""
#: res/ui/part_editor.ui:116 res/ui/performance_editor.ui:87 #: res/ui/part_editor.ui:116 res/ui/performance_editor.ui:87
#: res/ui/performance_editor.ui:170 res/ui/performance_editor.ui:214 #: 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/recording_editor.ui:81 res/ui/tracks_editor.ui:93
#: res/ui/work_editor.ui:77 src/dialogs/part_editor.rs:99 #: res/ui/work_editor.ui:69 src/dialogs/recording/performance_editor.rs:150
#: src/dialogs/recording/performance_editor.rs:150
#: src/dialogs/recording/performance_editor.rs:160 #: src/dialogs/recording/performance_editor.rs:160
#: src/dialogs/recording/performance_editor.rs:170 #: src/dialogs/recording/performance_editor.rs:170
#: src/dialogs/work/part_editor.rs:166
msgid "Select …" msgid "Select …"
msgstr "" msgstr ""
#: res/ui/part_editor.ui:160 res/ui/recording_editor.ui:119 #: res/ui/part_editor.ui:159 res/ui/recording_editor.ui:119
#: res/ui/work_editor.ui:126 #: res/ui/work_editor.ui:119
msgid "Overview" msgid "Overview"
msgstr "" msgstr ""
#: res/ui/part_editor.ui:191 res/ui/work_editor.ui:157 #: res/ui/part_editor.ui:247 res/ui/work_editor.ui:207
msgid "No instruments added."
msgstr ""
#: res/ui/part_editor.ui:266 res/ui/work_editor.ui:232
msgid "Instruments" msgid "Instruments"
msgstr "" msgstr ""
@ -140,7 +136,7 @@ msgstr ""
msgid "Last name" msgid "Last name"
msgstr "" msgstr ""
#: res/ui/person_list.ui:28 res/ui/work_selector.ui:69 #: res/ui/person_list.ui:28
msgid "Search persons …" msgid "Search persons …"
msgstr "" msgstr ""
@ -210,7 +206,7 @@ msgid "Comment"
msgstr "" msgstr ""
#: res/ui/recording_editor.ui:106 res/ui/tracks_editor.ui:109 #: res/ui/recording_editor.ui:106 res/ui/tracks_editor.ui:109
#: res/ui/work_editor.ui:22 #: res/ui/work_editor.ui:14
msgid "Work" msgid "Work"
msgstr "" msgstr ""
@ -226,7 +222,7 @@ msgstr ""
msgid "Add to playlist" msgid "Add to playlist"
msgstr "" msgstr ""
#: res/ui/recording_selector.ui:27 res/ui/work_selector.ui:180 #: res/ui/recording_selector.ui:27 res/ui/work_selector.ui:26
msgid "Select a composer on the left." msgid "Select a composer on the left."
msgstr "" msgstr ""
@ -278,24 +274,12 @@ msgstr ""
msgid "About Musicus" msgid "About Musicus"
msgstr "" msgstr ""
#: res/ui/work_editor.ui:264 #: res/ui/work_editor.ui:373
msgid "No work parts added."
msgstr ""
#: res/ui/work_editor.ui:416
msgid "Structure" msgid "Structure"
msgstr "" msgstr ""
#: res/ui/work_selector.ui:113 src/widgets/person_list.rs:26 #: res/ui/work_selector.ui:51
msgid "No persons found." msgid "Select a work"
msgstr ""
#: res/ui/work_selector.ui:167
msgid "Select work"
msgstr ""
#: res/ui/work_selector.ui:268
msgid "Search works …"
msgstr "" msgstr ""
#: src/dialogs/about.rs:12 #: src/dialogs/about.rs:12
@ -315,6 +299,7 @@ msgid "No performers added."
msgstr "" msgstr ""
#: src/dialogs/recording/recording_selector_person_screen.rs:39 #: src/dialogs/recording/recording_selector_person_screen.rs:39
#: src/dialogs/work/work_selector_person_screen.rs:36
#: src/screens/person_screen.rs:57 #: src/screens/person_screen.rs:57
msgid "No works found." msgid "No works found."
msgstr "" msgstr ""
@ -332,6 +317,14 @@ msgstr ""
msgid "Unknown" msgid "Unknown"
msgstr "" msgstr ""
#: src/dialogs/work/part_editor.rs:49 src/dialogs/work/work_editor.rs:69
msgid "No instruments added."
msgstr ""
#: src/dialogs/work/work_editor.rs:72
msgid "No work parts added."
msgstr ""
#: src/screens/ensemble_screen.rs:35 #: src/screens/ensemble_screen.rs:35
msgid "Edit ensemble" msgid "Edit ensemble"
msgstr "" msgstr ""
@ -376,6 +369,10 @@ msgstr ""
msgid "Delete work" msgid "Delete work"
msgstr "" msgstr ""
#: src/widgets/person_list.rs:26
msgid "No persons found."
msgstr ""
#: src/widgets/poe_list.rs:41 #: src/widgets/poe_list.rs:41
msgid "No persons or ensembles found." msgid "No persons or ensembles found."
msgstr "" msgstr ""

View file

@ -27,5 +27,6 @@
<file preprocess="xml-stripblanks">ui/work_editor.ui</file> <file preprocess="xml-stripblanks">ui/work_editor.ui</file>
<file preprocess="xml-stripblanks">ui/work_screen.ui</file> <file preprocess="xml-stripblanks">ui/work_screen.ui</file>
<file preprocess="xml-stripblanks">ui/work_selector.ui</file> <file preprocess="xml-stripblanks">ui/work_selector.ui</file>
<file preprocess="xml-stripblanks">ui/work_selector_screen.ui</file>
</gresource> </gresource>
</gresources> </gresources>

View file

@ -125,7 +125,6 @@
</child> </child>
<child> <child>
<object class="GtkButton" id="reset_composer_button"> <object class="GtkButton" id="reset_composer_button">
<property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">True</property>
<property name="receives-default">True</property> <property name="receives-default">True</property>
<child> <child>
@ -170,30 +169,12 @@
<property name="border-width">18</property> <property name="border-width">18</property>
<property name="spacing">6</property> <property name="spacing">6</property>
<child> <child>
<object class="GtkScrolledWindow"> <object class="GtkScrolledWindow" id="scroll">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">True</property>
<property name="shadow-type">in</property> <property name="shadow-type">in</property>
<child> <child>
<object class="GtkViewport"> <placeholder/>
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkListBox" id="instrument_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 instruments added.</property>
</object>
</child>
</object>
</child>
</object>
</child> </child>
</object> </object>
<packing> <packing>

View file

@ -3,15 +3,7 @@
<interface> <interface>
<requires lib="gtk+" version="3.22"/> <requires lib="gtk+" version="3.22"/>
<requires lib="libhandy" version="0.0"/> <requires lib="libhandy" version="0.0"/>
<object class="HdyWindow" id="window"> <object class="GtkBox" id="widget">
<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">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
@ -75,6 +67,7 @@
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="halign">start</property> <property name="halign">start</property>
<property name="label" translatable="yes">Select …</property> <property name="label" translatable="yes">Select …</property>
<property name="ellipsize">end</property>
</object> </object>
</child> </child>
</object> </object>
@ -136,30 +129,12 @@
<property name="border-width">18</property> <property name="border-width">18</property>
<property name="spacing">6</property> <property name="spacing">6</property>
<child> <child>
<object class="GtkScrolledWindow"> <object class="GtkScrolledWindow" id="instruments_scroll">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">True</property>
<property name="shadow-type">in</property> <property name="shadow-type">in</property>
<child> <child>
<object class="GtkViewport"> <placeholder/>
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkListBox" id="instrument_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 instruments added.</property>
</object>
</child>
</object>
</child>
</object>
</child> </child>
</object> </object>
<packing> <packing>
@ -243,30 +218,12 @@
<property name="border-width">18</property> <property name="border-width">18</property>
<property name="spacing">6</property> <property name="spacing">6</property>
<child> <child>
<object class="GtkScrolledWindow"> <object class="GtkScrolledWindow" id="structure_scroll">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">True</property>
<property name="shadow-type">in</property> <property name="shadow-type">in</property>
<child> <child>
<object class="GtkViewport"> <placeholder/>
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkListBox" id="part_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 work parts added.</property>
</object>
</child>
</object>
</child>
</object>
</child> </child>
</object> </object>
<packing> <packing>
@ -422,12 +379,10 @@
</child> </child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">True</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
</object> </object>
</child>
</object>
</interface> </interface>

View file

@ -1,170 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.1 --> <!-- Generated with glade 3.38.1 -->
<interface> <interface>
<requires lib="gtk+" version="3.22" /> <requires lib="gtk+" version="3.22"/>
<requires lib="libhandy" version="1.0" /> <requires lib="libhandy" version="1.0"/>
<object class="HdyWindow" id="window">
<property name="can-focus">False</property>
<property name="default-width">600</property>
<property name="default-height">424</property>
<property name="modal">True</property>
<property name="destroy-with-parent">True</property>
<property name="type-hint">dialog</property>
<child>
<object class="HdyLeaflet" id="leaflet">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="visible-child">sidebar_box</property>
<property name="can-swipe-back">True</property>
<child>
<object class="GtkBox" id="sidebar_box">
<property name="width-request">225</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">False</property>
<property name="orientation">vertical</property>
<child>
<object class="HdyHeaderBar" id="left_header">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="show-close-button">True</property>
<child>
<object class="GtkButton" id="add_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">list-add-symbolic</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="HdySearchBar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="search-mode-enabled">True</property>
<child>
<object class="HdyClamp">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="maximum-size">400</property>
<child>
<object class="GtkSearchEntry" id="person_search_entry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="primary-icon-name">edit-find-symbolic</property>
<property name="primary-icon-activatable">False</property>
<property name="primary-icon-sensitive">False</property>
<property name="placeholder-text" translatable="yes">Search persons …</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkStack" id="sidebar_stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="transition-type">crossfade</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="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can-focus">True</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="shadow-type">none</property>
<child>
<object class="GtkListBox" id="person_list">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child type="placeholder">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">No persons found.</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">persons_list</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="name">sidebar</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<style>
<class name="sidebar" />
</style>
</object>
<packing>
<property name="navigatable">False</property>
</packing>
</child>
<child>
<object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="transition-type">crossfade</property>
<child>
<object class="GtkBox" id="empty_screen"> <object class="GtkBox" id="empty_screen">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child> <child>
<object class="HdyHeaderBar" id="empty_header"> <object class="HdyHeaderBar">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="hexpand">True</property> <property name="hexpand">True</property>
<property name="title" translatable="yes">Select work</property>
<property name="show-close-button">True</property> <property name="show-close-button">True</property>
</object> </object>
<packing> <packing>
@ -186,29 +33,24 @@
</packing> </packing>
</child> </child>
</object> </object>
<packing> <object class="HdyLeaflet" id="widget">
<property name="name">empty_screen</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="visible-child">sidebar_box</property>
<child>
<object class="GtkBox" id="sidebar_box">
<property name="width-request">250</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">False</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child> <child>
<object class="HdyHeaderBar" id="header"> <object class="HdyHeaderBar">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="show-close-button">True</property> <property name="title" translatable="yes">Select a work</property>
<child> <child>
<object class="GtkRevealer"> <object class="GtkButton" id="add_button">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="transition-type">crossfade</property>
<property name="transition-duration" bind-source="leaflet" bind-property="mode-transition-duration" bind-flags="bidirectional|sync-create">0</property>
<property name="reveal-child" bind-source="leaflet" bind-property="folded" bind-flags="sync-create">False</property>
<child>
<object class="GtkButton" id="back_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">True</property>
<property name="receives-default">True</property> <property name="receives-default">True</property>
@ -216,136 +58,35 @@
<object class="GtkImage"> <object class="GtkImage">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="icon-name">go-previous-symbolic</property> <property name="icon-name">list-add-symbolic</property>
</object> </object>
</child> </child>
</object> </object>
</child> </child>
</object> </object>
</child>
<child>
<object class="GtkToggleButton" id="search_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">edit-find-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">0</property> <property name="position">0</property>
</packing> </packing>
</child> </child>
<child>
<object class="HdySearchBar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="search-mode-enabled" bind-source="search_button" bind-property="active" bind-flags="bidirectional|sync-create">False</property>
<child>
<object class="HdyClamp">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="maximum-size">400</property>
<child>
<object class="GtkSearchEntry" id="search_entry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="primary-icon-name">edit-find-symbolic</property>
<property name="primary-icon-activatable">False</property>
<property name="primary-icon-sensitive">False</property>
<property name="placeholder-text" translatable="yes">Search works …</property>
</object>
</child>
</object>
</child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="name">sidebar</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkStack" id="content_stack"> <object class="GtkSeparator">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="transition-type">crossfade</property> <property name="orientation">vertical</property>
<child> <style>
<object class="GtkSpinner"> <class name="sidebar"/>
<property name="visible">True</property> </style>
<property name="can-focus">False</property>
<property name="active">True</property>
</object> </object>
<packing> <packing>
<property name="name">loading</property> <property name="navigatable">False</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can-focus">True</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="shadow-type">none</property>
<child>
<object class="GtkListBox" id="work_list">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">content</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="name">person_screen</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="name">content</property>
</packing>
</child>
</object>
</child>
</object>
<object class="HdyHeaderGroup" id="inner_header_group">
<headerbars>
<headerbar name="empty_header" />
<headerbar name="header" />
</headerbars>
</object>
<object class="HdyHeaderGroup">
<headerbars>
<headerbar name="left_header" />
<headerbar name="inner_header_group" />
</headerbars>
</object> </object>
</interface> </interface>

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.1 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<requires lib="libhandy" version="0.0"/>
<object class="GtkBox" id="widget">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="HdyHeaderBar" id="header">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="show-close-button">True</property>
<child>
<object class="GtkButton" id="back_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">go-previous-symbolic</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkStack" id="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>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>

View file

@ -13,9 +13,6 @@ pub use instrument_editor::*;
pub mod instrument_selector; pub mod instrument_selector;
pub use instrument_selector::*; pub use instrument_selector::*;
pub mod part_editor;
pub use part_editor::*;
pub mod person_editor; pub mod person_editor;
pub use person_editor::*; pub use person_editor::*;
@ -28,17 +25,11 @@ pub use preferences::*;
pub mod recording; pub mod recording;
pub use recording::*; pub use recording::*;
pub mod section_editor;
pub use section_editor::*;
pub mod track_editor; pub mod track_editor;
pub use track_editor::*; pub use track_editor::*;
pub mod tracks_editor; pub mod tracks_editor;
pub use tracks_editor::*; pub use tracks_editor::*;
pub mod work_editor; pub mod work;
pub use work_editor::*; pub use work::*;
pub mod work_selector;
pub use work_selector::*;

View file

@ -1,163 +0,0 @@
use super::{InstrumentSelector, PersonSelector};
use crate::backend::*;
use crate::database::*;
use crate::widgets::*;
use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use std::cell::RefCell;
use std::convert::TryInto;
use std::rc::Rc;
pub struct PartEditor {
backend: Rc<Backend>,
window: libhandy::Window,
title_entry: gtk::Entry,
composer: RefCell<Option<Person>>,
composer_label: gtk::Label,
instruments: RefCell<Vec<Instrument>>,
instrument_list: gtk::ListBox,
}
impl PartEditor {
pub fn new<F: Fn(WorkPartDescription) -> () + 'static, P: IsA<gtk::Window>>(
backend: Rc<Backend>,
parent: &P,
part: Option<WorkPartDescription>,
callback: F,
) -> Rc<Self> {
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/part_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::Entry, title_entry);
get_widget!(builder, gtk::Button, composer_button);
get_widget!(builder, gtk::Label, composer_label);
get_widget!(builder, gtk::Button, reset_composer_button);
get_widget!(builder, gtk::ListBox, instrument_list);
get_widget!(builder, gtk::Button, add_instrument_button);
get_widget!(builder, gtk::Button, remove_instrument_button);
match part.clone() {
Some(part) => {
title_entry.set_text(&part.title);
}
None => (),
};
let composer = RefCell::new(match part.clone() {
Some(work) => {
match work.composer.clone() {
Some(composer) => composer_label.set_text(&composer.name_fl()),
None => (),
}
work.composer
},
None => None,
});
let instruments = RefCell::new(match part.clone() {
Some(work) => work.instruments,
None => Vec::new(),
});
let result = Rc::new(PartEditor {
backend: backend,
window: window,
title_entry: title_entry,
composer: composer,
composer_label: composer_label,
instruments: instruments,
instrument_list: instrument_list,
});
cancel_button.connect_clicked(clone!(@strong result => move |_| {
result.window.close();
}));
save_button.connect_clicked(clone!(@strong result => move |_| {
result.window.close();
callback(WorkPartDescription {
title: result.title_entry.get_text().to_string(),
composer: result.composer.borrow().clone(),
instruments: result.instruments.borrow().clone(),
});
}));
composer_button.connect_clicked(clone!(@strong result => move |_| {
PersonSelector::new(result.backend.clone(), &result.window, clone!(@strong result => move |person| {
result.composer.replace(Some(person.clone()));
result.composer_label.set_text(&person.name_fl());
})).show();
}));
reset_composer_button.connect_clicked(clone!(@strong result => move |_| {
result.composer.replace(None);
result.composer_label.set_text(&gettext("Select …"));
}));
add_instrument_button.connect_clicked(clone!(@strong result => move |_| {
InstrumentSelector::new(result.backend.clone(), &result.window, clone!(@strong result => move |instrument| {
{
let mut instruments = result.instruments.borrow_mut();
instruments.push(instrument);
}
result.show_instruments();
})).show();
}));
remove_instrument_button.connect_clicked(clone!(@strong result => move |_| {
let row = result.get_selected_instrument_row();
match row {
Some(row) => {
let index = row.get_index();
let index: usize = index.try_into().unwrap();
result.instruments.borrow_mut().remove(index);
result.show_instruments();
}
None => (),
}
}));
result.window.set_transient_for(Some(parent));
result
}
pub fn show(&self) {
self.window.show();
}
fn show_instruments(&self) {
for child in self.instrument_list.get_children() {
self.instrument_list.remove(&child);
}
for (index, instrument) in self.instruments.borrow().iter().enumerate() {
let label = gtk::Label::new(Some(&instrument.name));
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.instrument_list.insert(&row, -1);
}
}
fn get_selected_instrument_row(&self) -> Option<SelectorRow> {
match self.instrument_list.get_selected_rows().first() {
Some(row) => match row.get_child() {
Some(child) => Some(child.downcast().unwrap()),
None => None,
},
None => None,
}
}
}

View file

@ -105,10 +105,14 @@ impl RecordingEditor {
})); }));
work_button.connect_clicked(clone!(@strong this => move |_| { work_button.connect_clicked(clone!(@strong this => move |_| {
WorkSelector::new(this.backend.clone(), &this.parent, clone!(@strong this => move |work| { let dialog = WorkDialog::new(this.backend.clone(), &this.parent);
dialog.set_selected_cb(clone!(@strong this => move |work| {
this.work_selected(&work); this.work_selected(&work);
this.work.replace(Some(work)); this.work.replace(Some(work));
})).show(); }));
dialog.show();
})); }));
this.performance_list.set_make_widget(|performance| { this.performance_list.set_make_widget(|performance| {

View file

@ -77,7 +77,7 @@ impl RecordingSelector {
this this
} }
/// Set the closure to be called if the editor is user wants to add a new recording. /// Set the closure to be called if the user wants to add a new recording.
pub fn set_add_cb<F: Fn() -> () + 'static>(&self, cb: F) { pub fn set_add_cb<F: Fn() -> () + 'static>(&self, cb: F) {
self.add_cb.replace(Some(Box::new(cb))); self.add_cb.replace(Some(Box::new(cb)));
} }

View file

@ -10,8 +10,8 @@ use libhandy::HeaderBarExt;
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
/// A screen within the recording selector that presents a list of persons /// A screen within the recording selector that presents a list of works and switches to a work
/// and switches to a work screen on selection. /// screen on selection.
pub struct RecordingSelectorPersonScreen { pub struct RecordingSelectorPersonScreen {
backend: Rc<Backend>, backend: Rc<Backend>,
widget: gtk::Box, widget: gtk::Box,

View file

@ -1,61 +0,0 @@
use crate::database::*;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use std::rc::Rc;
pub struct SectionEditor {
window: libhandy::Window,
title_entry: gtk::Entry,
}
impl SectionEditor {
pub fn new<F: Fn(WorkSectionDescription) -> () + 'static, P: IsA<gtk::Window>>(
parent: &P,
section: Option<WorkSectionDescription>,
callback: F,
) -> Rc<Self> {
let builder =
gtk::Builder::from_resource("/de/johrpan/musicus/ui/section_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::Entry, title_entry);
match section {
Some(section) => {
title_entry.set_text(&section.title);
}
None => (),
}
let result = Rc::new(SectionEditor {
window: window,
title_entry: title_entry,
});
cancel_button.connect_clicked(clone!(@strong result => move |_| {
result.window.close();
}));
save_button.connect_clicked(clone!(@strong result => move |_| {
result.window.close();
let section = WorkSectionDescription {
before_index: 0,
title: result.title_entry.get_text().to_string(),
};
callback(section);
}));
result.window.set_transient_for(Some(parent));
result
}
pub fn show(&self) {
self.window.show();
}
}

11
src/dialogs/work/mod.rs Normal file
View file

@ -0,0 +1,11 @@
pub mod work_dialog;
pub use work_dialog::*;
pub mod work_editor_dialog;
pub use work_editor_dialog::*;
mod part_editor;
mod section_editor;
mod work_editor;
mod work_selector;
mod work_selector_person_screen;

View file

@ -0,0 +1,170 @@
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 dialog for creating or editing a work part.
pub struct PartEditor {
backend: Rc<Backend>,
window: libhandy::Window,
title_entry: gtk::Entry,
composer_label: gtk::Label,
reset_composer_button: gtk::Button,
instrument_list: Rc<List<Instrument>>,
composer: RefCell<Option<Person>>,
instruments: RefCell<Vec<Instrument>>,
ready_cb: RefCell<Option<Box<dyn Fn(WorkPartDescription) -> ()>>>,
}
impl PartEditor {
/// Create a new part editor and optionally initialize it.
pub fn new<P: IsA<gtk::Window>>(
backend: Rc<Backend>,
parent: &P,
part: Option<WorkPartDescription>,
) -> Rc<Self> {
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/part_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::Entry, title_entry);
get_widget!(builder, gtk::Button, composer_button);
get_widget!(builder, gtk::Label, composer_label);
get_widget!(builder, gtk::Button, reset_composer_button);
get_widget!(builder, gtk::ScrolledWindow, scroll);
get_widget!(builder, gtk::Button, add_instrument_button);
get_widget!(builder, gtk::Button, remove_instrument_button);
window.set_transient_for(Some(parent));
let instrument_list = List::new(&gettext("No instruments added."));
scroll.add(&instrument_list.widget);
let (composer, instruments) = match part {
Some(part) => {
title_entry.set_text(&part.title);
(part.composer, part.instruments)
}
None => (None, Vec::new()),
};
let this = Rc::new(Self {
backend,
window,
title_entry,
composer_label,
reset_composer_button,
instrument_list,
composer: RefCell::new(composer),
instruments: RefCell::new(instruments),
ready_cb: RefCell::new(None),
});
// Connect signals and callbacks
cancel_button.connect_clicked(clone!(@strong this => move |_| {
this.window.close();
}));
save_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(cb) = &*this.ready_cb.borrow() {
cb(WorkPartDescription {
title: this.title_entry.get_text().to_string(),
composer: this.composer.borrow().clone(),
instruments: this.instruments.borrow().clone(),
});
}
this.window.close();
}));
composer_button.connect_clicked(clone!(@strong this => move |_| {
PersonSelector::new(this.backend.clone(), &this.window, clone!(@strong this => move |person| {
this.show_composer(Some(&person));
this.composer.replace(Some(person));
})).show();
}));
this.reset_composer_button
.connect_clicked(clone!(@strong this => move |_| {
this.composer.replace(None);
this.show_composer(None);
}));
this.instrument_list.set_make_widget(|instrument| {
let label = gtk::Label::new(Some(&instrument.name));
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_instrument_button.connect_clicked(clone!(@strong this => move |_| {
InstrumentSelector::new(this.backend.clone(), &this.window, clone!(@strong this => move |instrument| {
let mut instruments = this.instruments.borrow_mut();
let index = match this.instrument_list.get_selected_index() {
Some(index) => index + 1,
None => instruments.len(),
};
instruments.insert(index, instrument);
this.instrument_list.show_items(instruments.clone());
this.instrument_list.select_index(index);
})).show();
}));
remove_instrument_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(index) = this.instrument_list.get_selected_index() {
let mut instruments = this.instruments.borrow_mut();
instruments.remove(index);
this.instrument_list.show_items(instruments.clone());
this.instrument_list.select_index(index);
}
}));
// Initialize
if let Some(composer) = &*this.composer.borrow() {
this.show_composer(Some(composer));
}
this.instrument_list
.show_items(this.instruments.borrow().clone());
this
}
/// Set the closure to be called when the user wants to save the part.
pub fn set_ready_cb<F: Fn(WorkPartDescription) -> () + 'static>(&self, cb: F) {
self.ready_cb.replace(Some(Box::new(cb)));
}
/// Show the part editor.
pub fn show(&self) {
self.window.show();
}
/// Update the UI according to person.
fn show_composer(&self, person: Option<&Person>) {
if let Some(person) = person {
self.composer_label.set_text(&person.name_fl());
self.reset_composer_button.show();
} else {
self.composer_label.set_text(&gettext("Select …"));
self.reset_composer_button.hide();
}
}
}

View file

@ -0,0 +1,73 @@
use crate::database::*;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use std::cell::RefCell;
use std::rc::Rc;
/// A dialog for creating or editing a work section.
pub struct SectionEditor {
window: libhandy::Window,
title_entry: gtk::Entry,
ready_cb: RefCell<Option<Box<dyn Fn(WorkSectionDescription) -> ()>>>,
}
impl SectionEditor {
/// Create a new section editor and optionally initialize it.
pub fn new<P: IsA<gtk::Window>>(
parent: &P,
section: Option<WorkSectionDescription>,
) -> Rc<Self> {
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/section_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::Entry, title_entry);
window.set_transient_for(Some(parent));
if let Some(section) = section {
title_entry.set_text(&section.title);
}
let this = Rc::new(Self {
window,
title_entry,
ready_cb: RefCell::new(None),
});
// Connect signals and callbacks
cancel_button.connect_clicked(clone!(@strong this => move |_| {
this.window.close();
}));
save_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(cb) = &*this.ready_cb.borrow() {
cb(WorkSectionDescription {
before_index: 0,
title: this.title_entry.get_text().to_string(),
});
}
this.window.close();
}));
this
}
/// Set the closure to be called when the user wants to save the section. Note that the
/// resulting object will always have `before_index` set to 0. The caller is expected to
/// change that later before adding the section to the database.
pub fn set_ready_cb<F: Fn(WorkSectionDescription) -> () + 'static>(&self, cb: F) {
self.ready_cb.replace(Some(Box::new(cb)));
}
/// Show the section editor.
pub fn show(&self) {
self.window.show();
}
}

View file

@ -0,0 +1,86 @@
use super::work_editor::*;
use super::work_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 work.
pub struct WorkDialog {
pub window: libhandy::Window,
stack: gtk::Stack,
selector: Rc<WorkSelector>,
editor: Rc<WorkEditor>,
selected_cb: RefCell<Option<Box<dyn Fn(WorkDescription) -> ()>>>,
}
impl WorkDialog {
/// Create a new work 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 = WorkSelector::new(backend.clone());
let editor = WorkEditor::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 |work| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(work);
this.window.close();
}
}));
this.editor.set_cancel_cb(clone!(@strong this => move || {
this.stack.set_visible_child(&this.selector.widget);
}));
this.editor
.set_saved_cb(clone!(@strong this => move |work| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(work);
this.window.close();
}
}));
this
}
/// Set the closure to be called when the user has selected or created a work.
pub fn set_selected_cb<F: Fn(WorkDescription) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
/// Show the work dialog.
pub fn show(&self) {
self.window.show();
}
}

View file

@ -0,0 +1,357 @@
use super::part_editor::*;
use super::section_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::convert::TryInto;
use std::rc::Rc;
/// Either a work part or a work section.
#[derive(Clone)]
enum PartOrSection {
Part(WorkPartDescription),
Section(WorkSectionDescription),
}
/// A widget for editing and creating works.
pub struct WorkEditor {
pub widget: gtk::Box,
backend: Rc<Backend>,
parent: gtk::Window,
save_button: gtk::Button,
title_entry: gtk::Entry,
composer_label: gtk::Label,
instrument_list: Rc<List<Instrument>>,
part_list: Rc<List<PartOrSection>>,
id: i64,
composer: RefCell<Option<Person>>,
instruments: RefCell<Vec<Instrument>>,
structure: RefCell<Vec<PartOrSection>>,
cancel_cb: RefCell<Option<Box<dyn Fn() -> ()>>>,
saved_cb: RefCell<Option<Box<dyn Fn(WorkDescription) -> ()>>>,
}
impl WorkEditor {
/// Create a new work editor widget and optionally initialize it. The parent window is used
/// as the parent for newly created dialogs.
pub fn new<P: IsA<gtk::Window>>(
backend: Rc<Backend>,
parent: &P,
work: Option<WorkDescription>,
) -> Rc<Self> {
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_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::Entry, title_entry);
get_widget!(builder, gtk::Button, composer_button);
get_widget!(builder, gtk::Label, composer_label);
get_widget!(builder, gtk::ScrolledWindow, instruments_scroll);
get_widget!(builder, gtk::Button, add_instrument_button);
get_widget!(builder, gtk::Button, remove_instrument_button);
get_widget!(builder, gtk::ScrolledWindow, structure_scroll);
get_widget!(builder, gtk::Button, add_part_button);
get_widget!(builder, gtk::Button, remove_part_button);
get_widget!(builder, gtk::Button, add_section_button);
get_widget!(builder, gtk::Button, edit_part_button);
get_widget!(builder, gtk::Button, move_part_up_button);
get_widget!(builder, gtk::Button, move_part_down_button);
let instrument_list = List::new(&gettext("No instruments added."));
instruments_scroll.add(&instrument_list.widget);
let part_list = List::new(&gettext("No work parts added."));
structure_scroll.add(&part_list.widget);
let (id, composer, instruments, structure) = match work {
Some(work) => {
title_entry.set_text(&work.title);
let mut structure = Vec::new();
for part in work.parts {
structure.push(PartOrSection::Part(part));
}
for section in work.sections {
structure.insert(
section.before_index.try_into().unwrap(),
PartOrSection::Section(section),
);
}
(work.id, Some(work.composer), work.instruments, structure)
}
None => (rand::random::<u32>().into(), None, Vec::new(), Vec::new()),
};
let this = Rc::new(Self {
widget,
backend,
parent: parent.clone().upcast(),
save_button,
id,
title_entry,
composer_label,
instrument_list,
part_list,
composer: RefCell::new(composer),
instruments: RefCell::new(instruments),
structure: RefCell::new(structure),
cancel_cb: RefCell::new(None),
saved_cb: RefCell::new(None),
});
// Connect signals and callbacks
cancel_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(cb) = &*this.cancel_cb.borrow() {
cb();
}
}));
this.save_button.connect_clicked(clone!(@strong this => move |_| {
let mut section_count = 0;
let mut parts = Vec::new();
let mut sections = Vec::new();
for (index, pos) in this.structure.borrow().iter().enumerate() {
match pos {
PartOrSection::Part(part) => parts.push(part.clone()),
PartOrSection::Section(section) => {
let mut section = section.clone();
let index: i64 = index.try_into().unwrap();
section.before_index = index - section_count;
sections.push(section);
section_count += 1;
}
}
}
let work = WorkDescription {
id: this.id,
title: this.title_entry.get_text().to_string(),
composer: this.composer.borrow().clone().expect("Tried to create work without composer!"),
instruments: this.instruments.borrow().clone(),
parts: parts,
sections: sections,
};
let c = glib::MainContext::default();
let clone = this.clone();
c.spawn_local(async move {
clone.backend.update_work(work.clone().into()).await.unwrap();
if let Some(cb) = &*clone.saved_cb.borrow() {
cb(work);
}
});
}));
composer_button.connect_clicked(clone!(@strong this => move |_| {
PersonSelector::new(this.backend.clone(), &this.parent, clone!(@strong this => move |person| {
this.show_composer(&person);
this.composer.replace(Some(person));
})).show();
}));
this.instrument_list.set_make_widget(|instrument| {
let label = gtk::Label::new(Some(&instrument.name));
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_instrument_button.connect_clicked(clone!(@strong this => move |_| {
InstrumentSelector::new(this.backend.clone(), &this.parent, clone!(@strong this => move |instrument| {
let mut instruments = this.instruments.borrow_mut();
let index = match this.instrument_list.get_selected_index() {
Some(index) => index + 1,
None => instruments.len(),
};
instruments.insert(index, instrument);
this.instrument_list.show_items(instruments.clone());
this.instrument_list.select_index(index);
})).show();
}));
remove_instrument_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(index) = this.instrument_list.get_selected_index() {
let mut instruments = this.instruments.borrow_mut();
instruments.remove(index);
this.instrument_list.show_items(instruments.clone());
this.instrument_list.select_index(index);
}
}));
this.part_list.set_make_widget(|pos| {
let label = gtk::Label::new(None);
label.set_ellipsize(pango::EllipsizeMode::End);
label.set_halign(gtk::Align::Start);
label.set_margin_end(6);
label.set_margin_top(6);
label.set_margin_bottom(6);
match pos {
PartOrSection::Part(part) => {
label.set_text(&part.title);
label.set_margin_start(12);
}
PartOrSection::Section(section) => {
let attrs = pango::AttrList::new();
attrs.insert(pango::Attribute::new_weight(pango::Weight::Bold).unwrap());
label.set_attributes(Some(&attrs));
label.set_text(&section.title);
label.set_margin_start(6);
}
}
label.upcast()
});
add_part_button.connect_clicked(clone!(@strong this => move |_| {
let editor = PartEditor::new(this.backend.clone(), &this.parent, None);
editor.set_ready_cb(clone!(@strong this => move |part| {
let mut structure = this.structure.borrow_mut();
let index = match this.part_list.get_selected_index() {
Some(index) => index + 1,
None => structure.len(),
};
structure.insert(index, PartOrSection::Part(part));
this.part_list.show_items(structure.clone());
this.part_list.select_index(index);
}));
editor.show();
}));
add_section_button.connect_clicked(clone!(@strong this => move |_| {
let editor = SectionEditor::new(&this.parent, None);
editor.set_ready_cb(clone!(@strong this => move |section| {
let mut structure = this.structure.borrow_mut();
let index = match this.part_list.get_selected_index() {
Some(index) => index + 1,
None => structure.len(),
};
structure.insert(index, PartOrSection::Section(section));
this.part_list.show_items(structure.clone());
this.part_list.select_index(index);
}));
editor.show();
}));
edit_part_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(index) = this.part_list.get_selected_index() {
match this.structure.borrow()[index].clone() {
PartOrSection::Part(part) => {
let editor = PartEditor::new(
this.backend.clone(),
&this.parent,
Some(part),
);
editor.set_ready_cb(clone!(@strong this => move |part| {
let mut structure = this.structure.borrow_mut();
structure[index] = PartOrSection::Part(part);
this.part_list.show_items(structure.clone());
this.part_list.select_index(index);
}));
editor.show();
}
PartOrSection::Section(section) => {
let editor = SectionEditor::new(&this.parent, Some(section));
editor.set_ready_cb(clone!(@strong this => move |section| {
let mut structure = this.structure.borrow_mut();
structure[index] = PartOrSection::Section(section);
this.part_list.show_items(structure.clone());
this.part_list.select_index(index);
}));
editor.show();
}
}
}
}));
remove_part_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(index) = this.part_list.get_selected_index() {
let mut structure = this.structure.borrow_mut();
structure.remove(index);
this.part_list.show_items(structure.clone());
this.part_list.select_index(index);
}
}));
move_part_up_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(index) = this.part_list.get_selected_index() {
if index > 0 {
let mut structure = this.structure.borrow_mut();
structure.swap(index - 1, index);
this.part_list.show_items(structure.clone());
this.part_list.select_index(index - 1);
}
}
}));
move_part_down_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(index) = this.part_list.get_selected_index() {
let mut structure = this.structure.borrow_mut();
if index < structure.len() - 1 {
structure.swap(index, index + 1);
this.part_list.show_items(structure.clone());
this.part_list.select_index(index + 1);
}
}
}));
// Initialization
if let Some(composer) = &*this.composer.borrow() {
this.show_composer(composer);
}
this.instrument_list.show_items(this.instruments.borrow().clone());
this.part_list.show_items(this.structure.borrow().clone());
this
}
/// The closure to call when the editor is canceled.
pub fn set_cancel_cb<F: Fn() -> () + 'static>(&self, cb: F) {
self.cancel_cb.replace(Some(Box::new(cb)));
}
/// The closure to call when a work was created.
pub fn set_saved_cb<F: Fn(WorkDescription) -> () + 'static>(&self, cb: F) {
self.saved_cb.replace(Some(Box::new(cb)));
}
/// Update the UI according to person.
fn show_composer(&self, person: &Person) {
self.composer_label.set_text(&person.name_fl());
self.save_button.set_sensitive(true);
}
}

View file

@ -0,0 +1,63 @@
use super::work_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 work.
pub struct WorkEditorDialog {
pub window: libhandy::Window,
saved_cb: RefCell<Option<Box<dyn Fn(WorkDescription) -> ()>>>,
}
impl WorkEditorDialog {
/// Create a new work editor dialog and optionally initialize it.
pub fn new<W: IsA<gtk::Window>>(
backend: Rc<Backend>,
parent: &W,
work: Option<WorkDescription>,
) -> 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 = WorkEditor::new(backend.clone(), &window, work);
window.add(&editor.widget);
window.show_all();
let this = Rc::new(Self {
window,
saved_cb: RefCell::new(None),
});
// Connect signals and callbacks
editor.set_cancel_cb(clone!(@strong this => move || {
this.window.close();
}));
editor.set_saved_cb(clone!(@strong this => move |work| {
if let Some(cb) = &*this.saved_cb.borrow() {
cb(work);
this.window.close();
}
}));
this
}
/// Set the closure to be called when the user edited or created a work.
pub fn set_saved_cb<F: Fn(WorkDescription) -> () + 'static>(&self, cb: F) {
self.saved_cb.replace(Some(Box::new(cb)));
}
/// Show the work editor dialog.
pub fn show(&self) {
self.window.show();
}
}

View file

@ -0,0 +1,89 @@
use super::work_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 work from a list of existing ones.
pub struct WorkSelector {
pub widget: libhandy::Leaflet,
backend: Rc<Backend>,
sidebar_box: gtk::Box,
selected_cb: RefCell<Option<Box<dyn Fn(WorkDescription) -> ()>>>,
add_cb: RefCell<Option<Box<dyn Fn() -> ()>>>,
navigator: Rc<Navigator>,
}
impl WorkSelector {
/// Create a new work selector.
pub fn new(backend: Rc<Backend>) -> Rc<Self> {
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_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 = WorkSelectorPersonScreen::new(
this.backend.clone(),
person.clone(),
);
person_screen.set_selected_cb(clone!(@strong this => move |work| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(work);
}
}));
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 user wants to add a new work.
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 work.
pub fn set_selected_cb<F: Fn(WorkDescription) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
}

View file

@ -0,0 +1,114 @@
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 work selector that presents a list of works by a person.
pub struct WorkSelectorPersonScreen {
backend: Rc<Backend>,
widget: gtk::Box,
stack: gtk::Stack,
work_list: Rc<List<WorkDescription>>,
selected_cb: RefCell<Option<Box<dyn Fn(WorkDescription) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
}
impl WorkSelectorPersonScreen {
/// Create a new work 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/work_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 {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(work.clone());
}
}
}));
// 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 work.
pub fn set_selected_cb<F: Fn(WorkDescription) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
}
impl NavigatorScreen for WorkSelectorPersonScreen {
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,401 +0,0 @@
use super::{InstrumentSelector, PersonSelector, PartEditor, SectionEditor};
use crate::backend::*;
use crate::database::*;
use crate::widgets::*;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use std::cell::RefCell;
use std::convert::TryInto;
use std::rc::Rc;
struct PartOrSection {
part: Option<WorkPartDescription>,
section: Option<WorkSectionDescription>,
}
impl PartOrSection {
pub fn part(part: WorkPartDescription) -> Self {
PartOrSection {
part: Some(part),
section: None,
}
}
pub fn section(section: WorkSectionDescription) -> Self {
PartOrSection {
part: None,
section: Some(section),
}
}
pub fn is_part(&self) -> bool {
self.part.is_some()
}
pub fn unwrap_part(&self) -> WorkPartDescription {
self.part.as_ref().unwrap().clone()
}
pub fn unwrap_section(&self) -> WorkSectionDescription {
self.section.as_ref().unwrap().clone()
}
pub fn get_title(&self) -> String {
if self.is_part() {
self.unwrap_part().title
} else {
self.unwrap_section().title
}
}
}
pub struct WorkEditor<F>
where
F: Fn(WorkDescription) -> () + 'static, {
backend: Rc<Backend>,
window: libhandy::Window,
callback: F,
save_button: gtk::Button,
id: i64,
title_entry: gtk::Entry,
composer: RefCell<Option<Person>>,
composer_label: gtk::Label,
instruments: RefCell<Vec<Instrument>>,
instrument_list: gtk::ListBox,
structure: RefCell<Vec<PartOrSection>>,
part_list: gtk::ListBox,
}
impl<F> WorkEditor<F>
where
F: Fn(WorkDescription) -> () + 'static, {
pub fn new<P: IsA<gtk::Window>>(
backend: Rc<Backend>,
parent: &P,
work: Option<WorkDescription>,
callback: F,
) -> Rc<Self> {
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_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::Entry, title_entry);
get_widget!(builder, gtk::Button, composer_button);
get_widget!(builder, gtk::Label, composer_label);
get_widget!(builder, gtk::ListBox, instrument_list);
get_widget!(builder, gtk::Button, add_instrument_button);
get_widget!(builder, gtk::Button, remove_instrument_button);
get_widget!(builder, gtk::ListBox, part_list);
get_widget!(builder, gtk::Button, add_part_button);
get_widget!(builder, gtk::Button, remove_part_button);
get_widget!(builder, gtk::Button, add_section_button);
get_widget!(builder, gtk::Button, edit_part_button);
get_widget!(builder, gtk::Button, move_part_up_button);
get_widget!(builder, gtk::Button, move_part_down_button);
let id = match work.clone() {
Some(work) => {
title_entry.set_text(&work.title);
work.id
}
None => rand::random::<u32>().into(),
};
let composer = RefCell::new(match work.clone() {
Some(work) => {
composer_label.set_text(&work.composer.name_fl());
save_button.set_sensitive(true);
Some(work.composer)
}
None => None,
});
let instruments = RefCell::new(match work.clone() {
Some(work) => work.instruments,
None => Vec::new(),
});
let structure = RefCell::new(match work.clone() {
Some(work) => {
let mut result = Vec::new();
for part in work.parts {
result.push(PartOrSection::part(part));
}
for section in work.sections {
result.insert(
section
.before_index
.try_into()
.expect("Section with unrealistic before_index!"),
PartOrSection::section(section),
);
}
result
}
None => Vec::new(),
});
let result = Rc::new(WorkEditor {
backend: backend,
window: window,
callback: callback,
save_button: save_button,
id: id,
title_entry: title_entry,
composer: composer,
composer_label: composer_label,
instruments: instruments,
instrument_list: instrument_list,
structure: structure,
part_list: part_list,
});
cancel_button.connect_clicked(clone!(@strong result => move |_| {
result.window.close();
}));
result.save_button.connect_clicked(clone!(@strong result => move |_| {
let mut section_count: i64 = 0;
let mut parts: Vec<WorkPartDescription> = Vec::new();
let mut sections: Vec<WorkSectionDescription> = Vec::new();
for (index, pos) in result.structure.borrow().iter().enumerate() {
if pos.is_part() {
parts.push(pos.unwrap_part());
} else {
let mut section = pos.unwrap_section();
let index: i64 = index.try_into().unwrap();
section.before_index = index - section_count;
sections.push(section);
section_count += 1;
}
}
let work = WorkDescription {
id: result.id,
title: result.title_entry.get_text().to_string(),
composer: result.composer.borrow().clone().expect("Tried to create work without composer!"),
instruments: result.instruments.borrow().to_vec(),
parts: parts,
sections: sections,
};
let c = glib::MainContext::default();
let clone = result.clone();
c.spawn_local(async move {
clone.backend.update_work(work.clone().into()).await.unwrap();
clone.window.close();
(clone.callback)(work.clone());
});
}));
composer_button.connect_clicked(clone!(@strong result => move |_| {
PersonSelector::new(result.backend.clone(), &result.window, clone!(@strong result => move |person| {
result.composer.replace(Some(person.clone()));
result.composer_label.set_text(&person.name_fl());
result.save_button.set_sensitive(true);
})).show();
}));
add_instrument_button.connect_clicked(clone!(@strong result => move |_| {
InstrumentSelector::new(result.backend.clone(), &result.window, clone!(@strong result => move |instrument| {
{
let mut instruments = result.instruments.borrow_mut();
instruments.push(instrument);
}
result.show_instruments();
})).show();
}));
remove_instrument_button.connect_clicked(clone!(@strong result => move |_| {
let row = result.get_selected_instrument_row();
match row {
Some(row) => {
let index = row.get_index();
let index: usize = index.try_into().unwrap();
result.instruments.borrow_mut().remove(index);
result.show_instruments();
}
None => (),
}
}));
add_part_button.connect_clicked(clone!(@strong result => move |_| {
PartEditor::new(result.backend.clone(), &result.window, None, clone!(@strong result => move |part| {
{
let mut structure = result.structure.borrow_mut();
structure.push(PartOrSection::part(part));
}
result.show_parts();
})).show();
}));
add_section_button.connect_clicked(clone!(@strong result => move |_| {
SectionEditor::new(&result.window, None, clone!(@strong result => move |section| {
{
let mut structure = result.structure.borrow_mut();
structure.push(PartOrSection::section(section));
}
result.show_parts();
})).show();
}));
edit_part_button.connect_clicked(clone!(@strong result => move |_| {
let row = result.get_selected_part_row();
match row {
Some(row) => {
let index = row.get_index();
let index: usize = index.try_into().unwrap();
let pos = &result.structure.borrow()[index];
if pos.is_part() {
let editor =
PartEditor::new(result.backend.clone(), &result.window, Some(pos.unwrap_part()), clone!(@strong result => move |part| {
result.structure.borrow_mut()[index] = PartOrSection::part(part);
result.show_parts();
}));
editor.show();
} else {
let editor =
SectionEditor::new(&result.window, Some(pos.unwrap_section()), clone!(@strong result => move |section| {
result.structure.borrow_mut()[index] = PartOrSection::section(section);
result.show_parts();
}));
editor.show();
}
}
None => (),
}
}));
remove_part_button.connect_clicked(clone!(@strong result => move |_| {
let row = result.get_selected_part_row();
match row {
Some(row) => {
let index = row.get_index();
let index: usize = index.try_into().unwrap();
result.structure.borrow_mut().remove(index);
result.show_parts();
}
None => (),
}
}));
move_part_up_button.connect_clicked(clone!(@strong result => move |_| {
let row = result.get_selected_part_row();
match row {
Some(row) => {
let index = row.get_index();
if index > 0 {
let index: usize = index.try_into().unwrap();
result.structure.borrow_mut().swap(index - 1, index);
result.show_parts();
}
}
None => (),
}
}));
move_part_down_button.connect_clicked(clone!(@strong result => move |_| {
let row = result.get_selected_part_row();
match row {
Some(row) => {
let index = row.get_index();
let index: usize = index.try_into().unwrap();
if index < result.structure.borrow().len() - 1 {
result.structure.borrow_mut().swap(index, index + 1);
result.show_parts();
}
}
None => (),
}
}));
result.window.set_transient_for(Some(parent));
result.show_instruments();
result.show_parts();
result
}
pub fn show(&self) {
self.window.show();
}
fn show_instruments(&self) {
for child in self.instrument_list.get_children() {
self.instrument_list.remove(&child);
}
for (index, instrument) in self.instruments.borrow().iter().enumerate() {
let label = gtk::Label::new(Some(&instrument.name));
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.instrument_list.insert(&row, -1);
}
}
fn get_selected_instrument_row(&self) -> Option<SelectorRow> {
match self.instrument_list.get_selected_rows().first() {
Some(row) => match row.get_child() {
Some(child) => Some(child.downcast().unwrap()),
None => None,
},
None => None,
}
}
fn show_parts(&self) {
for child in self.part_list.get_children() {
self.part_list.remove(&child);
}
for (index, part) in self.structure.borrow().iter().enumerate() {
let label = gtk::Label::new(Some(&part.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);
if part.is_part() {
label.set_margin_start(6);
} else {
let attributes = pango::AttrList::new();
attributes.insert(pango::Attribute::new_weight(pango::Weight::Bold).unwrap());
label.set_attributes(Some(&attributes));
}
let row = SelectorRow::new(index.try_into().unwrap(), &label);
row.show_all();
self.part_list.insert(&row, -1);
}
}
fn get_selected_part_row(&self) -> Option<SelectorRow> {
match self.part_list.get_selected_rows().first() {
Some(row) => match row.get_child() {
Some(child) => Some(child.downcast().unwrap()),
None => None,
},
None => None,
}
}
}

View file

@ -1,267 +0,0 @@
use super::*;
use crate::backend::Backend;
use crate::database::*;
use crate::widgets::*;
use gio::prelude::*;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use libhandy::prelude::*;
use libhandy::HeaderBarExt;
use std::cell::Cell;
use std::convert::TryInto;
use std::rc::Rc;
enum WorkSelectorState {
Loading,
Persons(Vec<Person>),
PersonLoading(Person),
Person(Vec<WorkDescription>),
}
pub struct WorkSelector<F>
where
F: Fn(WorkDescription) -> () + 'static,
{
window: libhandy::Window,
backend: Rc<Backend>,
callback: F,
leaflet: libhandy::Leaflet,
sidebar_stack: gtk::Stack,
person_search_entry: gtk::SearchEntry,
person_list: gtk::ListBox,
stack: gtk::Stack,
header: libhandy::HeaderBar,
search_entry: gtk::SearchEntry,
content_stack: gtk::Stack,
work_list: gtk::ListBox,
person_list_row_activated_handler_id: Cell<Option<glib::SignalHandlerId>>,
work_list_row_activated_handler_id: Cell<Option<glib::SignalHandlerId>>,
}
impl<F> WorkSelector<F>
where
F: Fn(WorkDescription) -> () + 'static,
{
pub fn new<P: IsA<gtk::Window>>(backend: Rc<Backend>, parent: &P, callback: F) -> Rc<Self> {
use WorkSelectorState::*;
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_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::SearchEntry, person_search_entry);
get_widget!(builder, gtk::Stack, sidebar_stack);
get_widget!(builder, gtk::ListBox, person_list);
get_widget!(builder, gtk::Stack, stack);
get_widget!(builder, libhandy::HeaderBar, header);
get_widget!(builder, gtk::SearchEntry, search_entry);
get_widget!(builder, gtk::Button, back_button);
get_widget!(builder, gtk::Stack, content_stack);
get_widget!(builder, gtk::ListBox, work_list);
let result = Rc::new(WorkSelector {
window: window,
backend: backend,
callback: callback,
leaflet: leaflet,
sidebar_stack: sidebar_stack,
person_list: person_list,
person_search_entry: person_search_entry,
stack: stack,
header: header,
search_entry: search_entry,
content_stack: content_stack,
work_list: work_list,
person_list_row_activated_handler_id: Cell::new(None),
work_list_row_activated_handler_id: Cell::new(None),
});
add_button.connect_clicked(clone!(@strong result => move |_| {
let editor = WorkEditor::new(
result.backend.clone(),
&result.window,
None,
clone!(@strong result => move |work| {
result.window.close();
(result.callback)(work);
}),
);
editor.show();
}));
back_button.connect_clicked(clone!(@strong result => move |_| {
result.back();
}));
result
.person_search_entry
.connect_search_changed(clone!(@strong result => move |_| {
result.person_list.invalidate_filter();
}));
result
.search_entry
.connect_search_changed(clone!(@strong result => move |_| {
result.work_list.invalidate_filter();
}));
result.window.set_transient_for(Some(parent));
result.clone().set_state(Loading);
result
}
pub fn show(&self) {
self.window.show();
}
fn set_state(self: Rc<Self>, state: WorkSelectorState) {
use WorkSelectorState::*;
match state {
Loading => {
let c = glib::MainContext::default();
let clone = self.clone();
c.spawn_local(async move {
let persons = clone.backend.get_persons().await.unwrap();
clone.clone().set_state(Persons(persons));
});
self.sidebar_stack.set_visible_child_name("loading");
self.stack.set_visible_child_name("empty_screen");
self.leaflet.set_visible_child_name("sidebar");
}
Persons(persons) => {
for child in self.person_list.get_children() {
self.person_list.remove(&child);
}
for (index, person) in persons.iter().enumerate() {
let label = gtk::Label::new(Some(&person.name_lf()));
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.person_list.insert(&row, -1);
}
match self.person_list_row_activated_handler_id.take() {
Some(id) => self.person_list.disconnect(id),
None => (),
}
let handler_id = self.person_list.connect_row_activated(
clone!(@strong self as self_, @strong persons => move |_, row| {
let row = row.get_child().unwrap().downcast::<SelectorRow>().unwrap();
let index: usize = row.get_index().try_into().unwrap();
let person = persons[index].clone();
self_.clone().set_state(PersonLoading(person));
}),
);
self.person_list_row_activated_handler_id
.set(Some(handler_id));
self.person_list.set_filter_func(Some(Box::new(
clone!(@strong self as self_, @strong persons => move |row| {
let row = row.get_child().unwrap().downcast::<SelectorRow>().unwrap();
let index: usize = row.get_index().try_into().unwrap();
let search = self_.person_search_entry.get_text().to_string().to_lowercase();
search.is_empty() || persons[index]
.name_lf()
.to_lowercase()
.contains(&search)
}),
)));
self.sidebar_stack.set_visible_child_name("persons_list");
self.stack.set_visible_child_name("empty_screen");
self.leaflet.set_visible_child_name("sidebar");
}
PersonLoading(person) => {
self.header.set_title(Some(&person.name_fl()));
let c = glib::MainContext::default();
let clone = self.clone();
c.spawn_local(async move {
let works = clone
.backend
.get_work_descriptions(person.id)
.await
.unwrap();
clone.clone().set_state(Person(works));
});
self.content_stack.set_visible_child_name("loading");
self.stack.set_visible_child_name("person_screen");
self.leaflet.set_visible_child_name("content");
}
Person(works) => {
for child in self.work_list.get_children() {
self.work_list.remove(&child);
}
for (index, work) in works.iter().enumerate() {
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);
let row = SelectorRow::new(index.try_into().unwrap(), &label);
row.show_all();
self.work_list.insert(&row, -1);
}
match self.work_list_row_activated_handler_id.take() {
Some(id) => self.work_list.disconnect(id),
None => (),
}
let handler_id = self.work_list.connect_row_activated(
clone!(@strong self as self_, @strong works => move |_, row| {
let row = row.get_child().unwrap().downcast::<SelectorRow>().unwrap();
let index: usize = row.get_index().try_into().unwrap();
let work = works[index].clone();
(self_.callback)(work);
self_.window.close();
}),
);
self.work_list_row_activated_handler_id
.set(Some(handler_id));
self.work_list.set_filter_func(Some(Box::new(
clone!(@strong self as self_, @strong works => move |row| {
let row = row.get_child().unwrap().downcast::<SelectorRow>().unwrap();
let index: usize = row.get_index().try_into().unwrap();
let search = self_.search_entry.get_text().to_string().to_lowercase();
search.is_empty() || works[index]
.title
.to_lowercase()
.contains(&search)
}),
)));
self.content_stack.set_visible_child_name("content");
self.stack.set_visible_child_name("person_screen");
self.leaflet.set_visible_child_name("content");
}
}
}
fn back(&self) {
self.stack.set_visible_child_name("empty_screen");
self.leaflet.set_visible_child_name("sidebar");
}
}

View file

@ -44,7 +44,6 @@ sources = files(
'dialogs/instrument_editor.rs', 'dialogs/instrument_editor.rs',
'dialogs/instrument_selector.rs', 'dialogs/instrument_selector.rs',
'dialogs/mod.rs', 'dialogs/mod.rs',
'dialogs/part_editor.rs',
'dialogs/person_editor.rs', 'dialogs/person_editor.rs',
'dialogs/person_selector.rs', 'dialogs/person_selector.rs',
'dialogs/preferences.rs', 'dialogs/preferences.rs',
@ -56,11 +55,16 @@ sources = files(
'dialogs/recording/recording_selector_person_screen.rs', 'dialogs/recording/recording_selector_person_screen.rs',
'dialogs/recording/recording_selector.rs', 'dialogs/recording/recording_selector.rs',
'dialogs/recording/recording_selector_work_screen.rs', 'dialogs/recording/recording_selector_work_screen.rs',
'dialogs/section_editor.rs',
'dialogs/track_editor.rs', 'dialogs/track_editor.rs',
'dialogs/tracks_editor.rs', 'dialogs/tracks_editor.rs',
'dialogs/work_editor.rs', 'dialogs/work/mod.rs',
'dialogs/work_selector.rs', 'dialogs/work/part_editor.rs',
'dialogs/work/section_editor.rs',
'dialogs/work/work_dialog.rs',
'dialogs/work/work_editor_dialog.rs',
'dialogs/work/work_editor.rs',
'dialogs/work/work_selector_person_screen.rs',
'dialogs/work/work_selector.rs',
'screens/ensemble_screen.rs', 'screens/ensemble_screen.rs',
'screens/mod.rs', 'screens/mod.rs',
'screens/person_screen.rs', 'screens/person_screen.rs',

View file

@ -146,9 +146,13 @@ impl Window {
result.window, result.window,
"add-work", "add-work",
clone!(@strong result => move |_, _| { clone!(@strong result => move |_, _| {
WorkEditor::new(result.backend.clone(), &result.window, None, clone!(@strong result => move |_| { let dialog = WorkDialog::new(result.backend.clone(), &result.window);
dialog.set_selected_cb(clone!(@strong result => move |_| {
result.reload(); result.reload();
})).show(); }));
dialog.show();
}) })
); );
@ -264,9 +268,13 @@ impl Window {
let c = glib::MainContext::default(); let c = glib::MainContext::default();
c.spawn_local(async move { c.spawn_local(async move {
let work = result.backend.get_work_description(id).await.unwrap(); let work = result.backend.get_work_description(id).await.unwrap();
WorkEditor::new(result.backend.clone(), &result.window, Some(work), clone!(@strong result => move |_| { let dialog = WorkEditorDialog::new(result.backend.clone(), &result.window, Some(work));
dialog.set_saved_cb(clone!(@strong result => move |_| {
result.reload(); result.reload();
})).show(); }));
dialog.show();
}); });
}) })
); );