mirror of
				https://github.com/johrpan/musicus.git
				synced 2025-10-26 11:47:25 +01:00 
			
		
		
		
	Merge work selector and editor to single dialog
This commit is contained in:
		
							parent
							
								
									9ee7bf166d
								
							
						
					
					
						commit
						d20d80d1ac
					
				
					 26 changed files with 1559 additions and 1742 deletions
				
			
		|  | @ -24,6 +24,7 @@ res/ui/window.ui | |||
| res/ui/work_editor.ui | ||||
| res/ui/work_screen.ui | ||||
| res/ui/work_selector.ui | ||||
| res/ui/work_selector_screen.ui | ||||
| 
 | ||||
| src/database/database.rs | ||||
| src/database/models.rs | ||||
|  | @ -36,7 +37,6 @@ src/dialogs/ensemble_selector.rs | |||
| src/dialogs/instrument_editor.rs | ||||
| src/dialogs/instrument_selector.rs | ||||
| src/dialogs/mod.rs | ||||
| src/dialogs/part_editor.rs | ||||
| src/dialogs/person_editor.rs | ||||
| src/dialogs/person_selector.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.rs | ||||
| src/dialogs/recording/recording_selector_work_screen.rs | ||||
| src/dialogs/section_editor.rs | ||||
| src/dialogs/track_editor.rs | ||||
| src/dialogs/tracks_editor.rs | ||||
| src/dialogs/work_editor.rs | ||||
| src/dialogs/work_selector.rs | ||||
| src/dialogs/work/mod.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/mod.rs | ||||
| src/screens/person_screen.rs | ||||
|  |  | |||
							
								
								
									
										66
									
								
								po/de.po
									
										
									
									
									
								
							
							
						
						
									
										66
									
								
								po/de.po
									
										
									
									
									
								
							|  | @ -7,8 +7,8 @@ msgid "" | |||
| msgstr "" | ||||
| "Project-Id-Version: \n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2020-11-08 12:11+0100\n" | ||||
| "PO-Revision-Date: 2020-11-08 02:37+0100\n" | ||||
| "POT-Creation-Date: 2020-11-08 20:12+0100\n" | ||||
| "PO-Revision-Date: 2020-11-08 20:13+0100\n" | ||||
| "Last-Translator: \n" | ||||
| "Language-Team: \n" | ||||
| "Language: de\n" | ||||
|  | @ -27,7 +27,7 @@ msgstr "Ensemble" | |||
| #: 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/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" | ||||
| msgstr "Abbrechen" | ||||
| 
 | ||||
|  | @ -35,7 +35,7 @@ msgstr "Abbrechen" | |||
| #: 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/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" | ||||
| msgstr "Speichern" | ||||
| 
 | ||||
|  | @ -83,36 +83,32 @@ msgstr "Keine Instrumente gefunden." | |||
| msgid "Work part" | ||||
| 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" | ||||
| msgstr "Komponist" | ||||
| 
 | ||||
| #: 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/work_editor.ui:113 | ||||
| #: res/ui/work_editor.ui:106 | ||||
| msgid "Title" | ||||
| msgstr "Titel" | ||||
| 
 | ||||
| #: 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/recording_editor.ui:81 res/ui/tracks_editor.ui:93 | ||||
| #: res/ui/work_editor.ui:77 src/dialogs/part_editor.rs:99 | ||||
| #: src/dialogs/recording/performance_editor.rs:150 | ||||
| #: res/ui/work_editor.ui:69 src/dialogs/recording/performance_editor.rs:150 | ||||
| #: src/dialogs/recording/performance_editor.rs:160 | ||||
| #: src/dialogs/recording/performance_editor.rs:170 | ||||
| #: src/dialogs/work/part_editor.rs:166 | ||||
| msgid "Select …" | ||||
| msgstr "Auswählen …" | ||||
| 
 | ||||
| #: res/ui/part_editor.ui:160 res/ui/recording_editor.ui:119 | ||||
| #: res/ui/work_editor.ui:126 | ||||
| #: res/ui/part_editor.ui:159 res/ui/recording_editor.ui:119 | ||||
| #: res/ui/work_editor.ui:119 | ||||
| msgid "Overview" | ||||
| msgstr "Überblick" | ||||
| 
 | ||||
| #: res/ui/part_editor.ui:191 res/ui/work_editor.ui:157 | ||||
| msgid "No instruments added." | ||||
| msgstr "Keine Instrumente hinzugefügt." | ||||
| 
 | ||||
| #: res/ui/part_editor.ui:266 res/ui/work_editor.ui:232 | ||||
| #: res/ui/part_editor.ui:247 res/ui/work_editor.ui:207 | ||||
| msgid "Instruments" | ||||
| msgstr "Instrumente" | ||||
| 
 | ||||
|  | @ -141,7 +137,7 @@ msgstr "Vorname" | |||
| msgid "Last name" | ||||
| msgstr "Nachname" | ||||
| 
 | ||||
| #: res/ui/person_list.ui:28 res/ui/work_selector.ui:69 | ||||
| #: res/ui/person_list.ui:28 | ||||
| msgid "Search persons …" | ||||
| msgstr "Personen durchsuchen …" | ||||
| 
 | ||||
|  | @ -211,7 +207,7 @@ msgid "Comment" | |||
| msgstr "Kommentar" | ||||
| 
 | ||||
| #: 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" | ||||
| msgstr "Werk" | ||||
| 
 | ||||
|  | @ -227,7 +223,7 @@ msgstr "Tracks" | |||
| msgid "Add to playlist" | ||||
| 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." | ||||
| msgstr "Wählen Sie einen Komponisten aus." | ||||
| 
 | ||||
|  | @ -284,26 +280,14 @@ msgstr "Einstellungen" | |||
| msgid "About Musicus" | ||||
| msgstr "Über Musicus" | ||||
| 
 | ||||
| #: res/ui/work_editor.ui:264 | ||||
| msgid "No work parts added." | ||||
| msgstr "Keine Werkabschnitte hinzugefügt." | ||||
| 
 | ||||
| #: res/ui/work_editor.ui:416 | ||||
| #: res/ui/work_editor.ui:373 | ||||
| msgid "Structure" | ||||
| msgstr "Struktur" | ||||
| 
 | ||||
| #: res/ui/work_selector.ui:113 src/widgets/person_list.rs:26 | ||||
| msgid "No persons found." | ||||
| msgstr "Keine Personen gefunden." | ||||
| 
 | ||||
| #: res/ui/work_selector.ui:167 | ||||
| msgid "Select work" | ||||
| #: res/ui/work_selector.ui:51 | ||||
| msgid "Select a work" | ||||
| msgstr "Werk auswählen" | ||||
| 
 | ||||
| #: res/ui/work_selector.ui:268 | ||||
| msgid "Search works …" | ||||
| msgstr "Werke durchsuchen …" | ||||
| 
 | ||||
| #: src/dialogs/about.rs:12 | ||||
| msgid "The classical music player and organizer." | ||||
| msgstr "Das Programm zum Abspielen und Organisieren von Klassik." | ||||
|  | @ -321,6 +305,7 @@ msgid "No performers added." | |||
| msgstr "Keine Interpreten hinzugefügt." | ||||
| 
 | ||||
| #: src/dialogs/recording/recording_selector_person_screen.rs:39 | ||||
| #: src/dialogs/work/work_selector_person_screen.rs:36 | ||||
| #: src/screens/person_screen.rs:57 | ||||
| msgid "No works found." | ||||
| msgstr "Keine Werke gefunden." | ||||
|  | @ -338,6 +323,14 @@ msgstr "Audiodateien auswählen" | |||
| msgid "Unknown" | ||||
| 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 | ||||
| msgid "Edit ensemble" | ||||
| msgstr "Ensemble bearbeiten" | ||||
|  | @ -382,6 +375,13 @@ msgstr "Werk bearbeiten" | |||
| msgid "Delete work" | ||||
| msgstr "Werk löschen" | ||||
| 
 | ||||
| #: src/widgets/person_list.rs:26 | ||||
| msgid "No persons found." | ||||
| msgstr "Keine Personen gefunden." | ||||
| 
 | ||||
| #: src/widgets/poe_list.rs:41 | ||||
| msgid "No persons or ensembles found." | ||||
| msgstr "Keine Personen oder Ensembles gefunden." | ||||
| 
 | ||||
| #~ msgid "Search works …" | ||||
| #~ msgstr "Werke durchsuchen …" | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ msgid "" | |||
| msgstr "" | ||||
| "Project-Id-Version: musicus\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" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\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/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 | ||||
| #: res/ui/tracks_editor.ui:39 res/ui/work_editor.ui:17 | ||||
| msgid "Cancel" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -34,7 +34,7 @@ msgstr "" | |||
| #: 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/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" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -82,36 +82,32 @@ msgstr "" | |||
| msgid "Work part" | ||||
| 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" | ||||
| msgstr "" | ||||
| 
 | ||||
| #: 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/work_editor.ui:113 | ||||
| #: res/ui/work_editor.ui:106 | ||||
| msgid "Title" | ||||
| msgstr "" | ||||
| 
 | ||||
| #: 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/recording_editor.ui:81 res/ui/tracks_editor.ui:93 | ||||
| #: res/ui/work_editor.ui:77 src/dialogs/part_editor.rs:99 | ||||
| #: src/dialogs/recording/performance_editor.rs:150 | ||||
| #: res/ui/work_editor.ui:69 src/dialogs/recording/performance_editor.rs:150 | ||||
| #: src/dialogs/recording/performance_editor.rs:160 | ||||
| #: src/dialogs/recording/performance_editor.rs:170 | ||||
| #: src/dialogs/work/part_editor.rs:166 | ||||
| msgid "Select …" | ||||
| msgstr "" | ||||
| 
 | ||||
| #: res/ui/part_editor.ui:160 res/ui/recording_editor.ui:119 | ||||
| #: res/ui/work_editor.ui:126 | ||||
| #: res/ui/part_editor.ui:159 res/ui/recording_editor.ui:119 | ||||
| #: res/ui/work_editor.ui:119 | ||||
| msgid "Overview" | ||||
| msgstr "" | ||||
| 
 | ||||
| #: res/ui/part_editor.ui:191 res/ui/work_editor.ui:157 | ||||
| msgid "No instruments added." | ||||
| msgstr "" | ||||
| 
 | ||||
| #: res/ui/part_editor.ui:266 res/ui/work_editor.ui:232 | ||||
| #: res/ui/part_editor.ui:247 res/ui/work_editor.ui:207 | ||||
| msgid "Instruments" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -140,7 +136,7 @@ msgstr "" | |||
| msgid "Last name" | ||||
| msgstr "" | ||||
| 
 | ||||
| #: res/ui/person_list.ui:28 res/ui/work_selector.ui:69 | ||||
| #: res/ui/person_list.ui:28 | ||||
| msgid "Search persons …" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -210,7 +206,7 @@ msgid "Comment" | |||
| msgstr "" | ||||
| 
 | ||||
| #: 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" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -226,7 +222,7 @@ msgstr "" | |||
| msgid "Add to playlist" | ||||
| 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." | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -278,24 +274,12 @@ msgstr "" | |||
| msgid "About Musicus" | ||||
| msgstr "" | ||||
| 
 | ||||
| #: res/ui/work_editor.ui:264 | ||||
| msgid "No work parts added." | ||||
| msgstr "" | ||||
| 
 | ||||
| #: res/ui/work_editor.ui:416 | ||||
| #: res/ui/work_editor.ui:373 | ||||
| msgid "Structure" | ||||
| msgstr "" | ||||
| 
 | ||||
| #: res/ui/work_selector.ui:113 src/widgets/person_list.rs:26 | ||||
| msgid "No persons found." | ||||
| msgstr "" | ||||
| 
 | ||||
| #: res/ui/work_selector.ui:167 | ||||
| msgid "Select work" | ||||
| msgstr "" | ||||
| 
 | ||||
| #: res/ui/work_selector.ui:268 | ||||
| msgid "Search works …" | ||||
| #: res/ui/work_selector.ui:51 | ||||
| msgid "Select a work" | ||||
| msgstr "" | ||||
| 
 | ||||
| #: src/dialogs/about.rs:12 | ||||
|  | @ -315,6 +299,7 @@ msgid "No performers added." | |||
| msgstr "" | ||||
| 
 | ||||
| #: src/dialogs/recording/recording_selector_person_screen.rs:39 | ||||
| #: src/dialogs/work/work_selector_person_screen.rs:36 | ||||
| #: src/screens/person_screen.rs:57 | ||||
| msgid "No works found." | ||||
| msgstr "" | ||||
|  | @ -332,6 +317,14 @@ msgstr "" | |||
| msgid "Unknown" | ||||
| 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 | ||||
| msgid "Edit ensemble" | ||||
| msgstr "" | ||||
|  | @ -376,6 +369,10 @@ msgstr "" | |||
| msgid "Delete work" | ||||
| msgstr "" | ||||
| 
 | ||||
| #: src/widgets/person_list.rs:26 | ||||
| msgid "No persons found." | ||||
| msgstr "" | ||||
| 
 | ||||
| #: src/widgets/poe_list.rs:41 | ||||
| msgid "No persons or ensembles found." | ||||
| msgstr "" | ||||
|  |  | |||
|  | @ -27,5 +27,6 @@ | |||
|         <file preprocess="xml-stripblanks">ui/work_editor.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_screen.ui</file> | ||||
|     </gresource> | ||||
| </gresources> | ||||
|  |  | |||
|  | @ -125,7 +125,6 @@ | |||
|                     </child> | ||||
|                     <child> | ||||
|                       <object class="GtkButton" id="reset_composer_button"> | ||||
|                         <property name="visible">True</property> | ||||
|                         <property name="can-focus">True</property> | ||||
|                         <property name="receives-default">True</property> | ||||
|                         <child> | ||||
|  | @ -170,30 +169,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="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> | ||||
|                       <placeholder/> | ||||
|                     </child> | ||||
|                   </object> | ||||
|                   <packing> | ||||
|  |  | |||
|  | @ -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> | ||||
|  | @ -75,6 +67,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="instruments_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="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> | ||||
|                   <placeholder/> | ||||
|                 </child> | ||||
|               </object> | ||||
|               <packing> | ||||
|  | @ -243,30 +218,12 @@ | |||
|             <property name="border-width">18</property> | ||||
|             <property name="spacing">6</property> | ||||
|             <child> | ||||
|                   <object class="GtkScrolledWindow"> | ||||
|               <object class="GtkScrolledWindow" id="structure_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="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> | ||||
|                   <placeholder/> | ||||
|                 </child> | ||||
|               </object> | ||||
|               <packing> | ||||
|  | @ -422,12 +379,10 @@ | |||
|         </child> | ||||
|       </object> | ||||
|       <packing> | ||||
|             <property name="expand">False</property> | ||||
|         <property name="expand">True</property> | ||||
|         <property name="fill">True</property> | ||||
|         <property name="position">1</property> | ||||
|       </packing> | ||||
|     </child> | ||||
|   </object> | ||||
|     </child> | ||||
|   </object> | ||||
| </interface> | ||||
|  |  | |||
|  | @ -1,170 +1,17 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!-- Generated with glade 3.38.1 --> | ||||
| <interface> | ||||
|   <requires lib="gtk+" version="3.22" /> | ||||
|   <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> | ||||
|   <requires lib="gtk+" version="3.22"/> | ||||
|   <requires lib="libhandy" version="1.0"/> | ||||
|   <object class="GtkBox" id="empty_screen"> | ||||
|     <property name="visible">True</property> | ||||
|     <property name="can-focus">False</property> | ||||
|     <property name="orientation">vertical</property> | ||||
|     <child> | ||||
|                   <object class="HdyHeaderBar" id="empty_header"> | ||||
|       <object class="HdyHeaderBar"> | ||||
|         <property name="visible">True</property> | ||||
|         <property name="can-focus">False</property> | ||||
|         <property name="hexpand">True</property> | ||||
|                     <property name="title" translatable="yes">Select work</property> | ||||
|         <property name="show-close-button">True</property> | ||||
|       </object> | ||||
|       <packing> | ||||
|  | @ -186,29 +33,24 @@ | |||
|       </packing> | ||||
|     </child> | ||||
|   </object> | ||||
|               <packing> | ||||
|                 <property name="name">empty_screen</property> | ||||
|               </packing> | ||||
|             </child> | ||||
|             <child> | ||||
|               <object class="GtkBox"> | ||||
|   <object class="HdyLeaflet" id="widget"> | ||||
|     <property name="visible">True</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> | ||||
|         <child> | ||||
|                   <object class="HdyHeaderBar" id="header"> | ||||
|           <object class="HdyHeaderBar"> | ||||
|             <property name="visible">True</property> | ||||
|             <property name="can-focus">False</property> | ||||
|                     <property name="show-close-button">True</property> | ||||
|             <property name="title" translatable="yes">Select a work</property> | ||||
|             <child> | ||||
|                       <object class="GtkRevealer"> | ||||
|                         <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"> | ||||
|               <object class="GtkButton" id="add_button"> | ||||
|                 <property name="visible">True</property> | ||||
|                 <property name="can-focus">True</property> | ||||
|                 <property name="receives-default">True</property> | ||||
|  | @ -216,136 +58,35 @@ | |||
|                   <object class="GtkImage"> | ||||
|                     <property name="visible">True</property> | ||||
|                     <property name="can-focus">False</property> | ||||
|                                 <property name="icon-name">go-previous-symbolic</property> | ||||
|                     <property name="icon-name">list-add-symbolic</property> | ||||
|                   </object> | ||||
|                 </child> | ||||
|               </object> | ||||
|             </child> | ||||
|           </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> | ||||
|             <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" 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> | ||||
|       <packing> | ||||
|                     <property name="expand">False</property> | ||||
|                     <property name="fill">True</property> | ||||
|                     <property name="position">1</property> | ||||
|         <property name="name">sidebar</property> | ||||
|       </packing> | ||||
|     </child> | ||||
|     <child> | ||||
|                   <object class="GtkStack" id="content_stack"> | ||||
|       <object class="GtkSeparator"> | ||||
|         <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> | ||||
|         <property name="orientation">vertical</property> | ||||
|         <style> | ||||
|           <class name="sidebar"/> | ||||
|         </style> | ||||
|       </object> | ||||
|       <packing> | ||||
|                         <property name="name">loading</property> | ||||
|         <property name="navigatable">False</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="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> | ||||
| </interface> | ||||
|  |  | |||
							
								
								
									
										59
									
								
								res/ui/work_selector_screen.ui
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								res/ui/work_selector_screen.ui
									
										
									
									
									
										Normal 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> | ||||
|  | @ -13,9 +13,6 @@ pub use instrument_editor::*; | |||
| pub mod instrument_selector; | ||||
| pub use instrument_selector::*; | ||||
| 
 | ||||
| pub mod part_editor; | ||||
| pub use part_editor::*; | ||||
| 
 | ||||
| pub mod person_editor; | ||||
| pub use person_editor::*; | ||||
| 
 | ||||
|  | @ -28,17 +25,11 @@ pub use preferences::*; | |||
| pub mod recording; | ||||
| pub use recording::*; | ||||
| 
 | ||||
| pub mod section_editor; | ||||
| pub use section_editor::*; | ||||
| 
 | ||||
| pub mod track_editor; | ||||
| pub use track_editor::*; | ||||
| 
 | ||||
| pub mod tracks_editor; | ||||
| pub use tracks_editor::*; | ||||
| 
 | ||||
| pub mod work_editor; | ||||
| pub use work_editor::*; | ||||
| 
 | ||||
| pub mod work_selector; | ||||
| pub use work_selector::*; | ||||
| pub mod work; | ||||
| pub use work::*; | ||||
|  |  | |||
|  | @ -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, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -105,10 +105,14 @@ impl RecordingEditor { | |||
|             })); | ||||
| 
 | ||||
|         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.replace(Some(work)); | ||||
|             })).show(); | ||||
|             })); | ||||
| 
 | ||||
|             dialog.show(); | ||||
|         })); | ||||
| 
 | ||||
|         this.performance_list.set_make_widget(|performance| { | ||||
|  |  | |||
|  | @ -77,7 +77,7 @@ impl RecordingSelector { | |||
|         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) { | ||||
|         self.add_cb.replace(Some(Box::new(cb))); | ||||
|     } | ||||
|  |  | |||
|  | @ -10,8 +10,8 @@ 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.
 | ||||
| /// A screen within the recording selector that presents a list of works and switches to a work
 | ||||
| /// screen on selection.
 | ||||
| pub struct RecordingSelectorPersonScreen { | ||||
|     backend: Rc<Backend>, | ||||
|     widget: gtk::Box, | ||||
|  |  | |||
|  | @ -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(§ion.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
									
								
							
							
						
						
									
										11
									
								
								src/dialogs/work/mod.rs
									
										
									
									
									
										Normal 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; | ||||
							
								
								
									
										170
									
								
								src/dialogs/work/part_editor.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								src/dialogs/work/part_editor.rs
									
										
									
									
									
										Normal 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(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										73
									
								
								src/dialogs/work/section_editor.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/dialogs/work/section_editor.rs
									
										
									
									
									
										Normal 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(§ion.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(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										86
									
								
								src/dialogs/work/work_dialog.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/dialogs/work/work_dialog.rs
									
										
									
									
									
										Normal 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(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										357
									
								
								src/dialogs/work/work_editor.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										357
									
								
								src/dialogs/work/work_editor.rs
									
										
									
									
									
										Normal 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(§ion.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); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										63
									
								
								src/dialogs/work/work_editor_dialog.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/dialogs/work/work_editor_dialog.rs
									
										
									
									
									
										Normal 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(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										89
									
								
								src/dialogs/work/work_selector.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/dialogs/work/work_selector.rs
									
										
									
									
									
										Normal 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))); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										114
									
								
								src/dialogs/work/work_selector_person_screen.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/dialogs/work/work_selector_person_screen.rs
									
										
									
									
									
										Normal 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); | ||||
|     } | ||||
| } | ||||
|  | @ -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, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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"); | ||||
|     } | ||||
| } | ||||
|  | @ -44,7 +44,6 @@ sources = files( | |||
|   'dialogs/instrument_editor.rs', | ||||
|   'dialogs/instrument_selector.rs', | ||||
|   'dialogs/mod.rs', | ||||
|   'dialogs/part_editor.rs', | ||||
|   'dialogs/person_editor.rs', | ||||
|   'dialogs/person_selector.rs', | ||||
|   'dialogs/preferences.rs', | ||||
|  | @ -56,11 +55,16 @@ sources = files( | |||
|   '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', | ||||
|   'dialogs/work_editor.rs', | ||||
|   'dialogs/work_selector.rs', | ||||
|   'dialogs/work/mod.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/mod.rs', | ||||
|   'screens/person_screen.rs', | ||||
|  |  | |||
|  | @ -146,9 +146,13 @@ impl Window { | |||
|             result.window, | ||||
|             "add-work", | ||||
|             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(); | ||||
|                 })).show(); | ||||
|                 })); | ||||
| 
 | ||||
|                 dialog.show(); | ||||
|             }) | ||||
|         ); | ||||
| 
 | ||||
|  | @ -264,9 +268,13 @@ impl Window { | |||
|                 let c = glib::MainContext::default(); | ||||
|                 c.spawn_local(async move { | ||||
|                     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(); | ||||
|                     })).show(); | ||||
|                     })); | ||||
| 
 | ||||
|                     dialog.show(); | ||||
|                 }); | ||||
|             }) | ||||
|         ); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Elias Projahn
						Elias Projahn