mirror of
				https://github.com/johrpan/musicus.git
				synced 2025-10-26 19:57:25 +01:00 
			
		
		
		
	Convert dialogs to navigator screens
This commit is contained in:
		
							parent
							
								
									6a40921ac4
								
							
						
					
					
						commit
						0e7a2f1f3d
					
				
					 56 changed files with 2650 additions and 2888 deletions
				
			
		|  | @ -7,7 +7,6 @@ | ||||||
|         <file preprocess="xml-stripblanks">ui/instrument_editor.ui</file> |         <file preprocess="xml-stripblanks">ui/instrument_editor.ui</file> | ||||||
|         <file preprocess="xml-stripblanks">ui/instrument_selector.ui</file> |         <file preprocess="xml-stripblanks">ui/instrument_selector.ui</file> | ||||||
|         <file preprocess="xml-stripblanks">ui/login_dialog.ui</file> |         <file preprocess="xml-stripblanks">ui/login_dialog.ui</file> | ||||||
|         <file preprocess="xml-stripblanks">ui/part_editor.ui</file> |  | ||||||
|         <file preprocess="xml-stripblanks">ui/performance_editor.ui</file> |         <file preprocess="xml-stripblanks">ui/performance_editor.ui</file> | ||||||
|         <file preprocess="xml-stripblanks">ui/person_editor.ui</file> |         <file preprocess="xml-stripblanks">ui/person_editor.ui</file> | ||||||
|         <file preprocess="xml-stripblanks">ui/person_list.ui</file> |         <file preprocess="xml-stripblanks">ui/person_list.ui</file> | ||||||
|  | @ -21,13 +20,15 @@ | ||||||
|         <file preprocess="xml-stripblanks">ui/recording_screen.ui</file> |         <file preprocess="xml-stripblanks">ui/recording_screen.ui</file> | ||||||
|         <file preprocess="xml-stripblanks">ui/recording_selector.ui</file> |         <file preprocess="xml-stripblanks">ui/recording_selector.ui</file> | ||||||
|         <file preprocess="xml-stripblanks">ui/recording_selector_screen.ui</file> |         <file preprocess="xml-stripblanks">ui/recording_selector_screen.ui</file> | ||||||
|         <file preprocess="xml-stripblanks">ui/section_editor.ui</file> |         <file preprocess="xml-stripblanks">ui/selector.ui</file> | ||||||
|         <file preprocess="xml-stripblanks">ui/server_dialog.ui</file> |         <file preprocess="xml-stripblanks">ui/server_dialog.ui</file> | ||||||
|         <file preprocess="xml-stripblanks">ui/tracks_editor.ui</file> |         <file preprocess="xml-stripblanks">ui/tracks_editor.ui</file> | ||||||
|         <file preprocess="xml-stripblanks">ui/track_editor.ui</file> |         <file preprocess="xml-stripblanks">ui/track_editor.ui</file> | ||||||
|         <file preprocess="xml-stripblanks">ui/window.ui</file> |         <file preprocess="xml-stripblanks">ui/window.ui</file> | ||||||
|         <file preprocess="xml-stripblanks">ui/work_editor.ui</file> |         <file preprocess="xml-stripblanks">ui/work_editor.ui</file> | ||||||
|  |         <file preprocess="xml-stripblanks">ui/work_part_editor.ui</file> | ||||||
|         <file preprocess="xml-stripblanks">ui/work_screen.ui</file> |         <file preprocess="xml-stripblanks">ui/work_screen.ui</file> | ||||||
|  |         <file preprocess="xml-stripblanks">ui/work_section_editor.ui</file> | ||||||
|         <file preprocess="xml-stripblanks">ui/work_selector.ui</file> |         <file preprocess="xml-stripblanks">ui/work_selector.ui</file> | ||||||
|         <file preprocess="xml-stripblanks">ui/work_selector_screen.ui</file> |         <file preprocess="xml-stripblanks">ui/work_selector_screen.ui</file> | ||||||
|     </gresource> |     </gresource> | ||||||
|  |  | ||||||
|  | @ -2,14 +2,8 @@ | ||||||
| <!-- Generated with glade 3.38.1 --> | <!-- Generated with glade 3.38.1 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk+" version="3.22"/> |   <requires lib="gtk+" version="3.22"/> | ||||||
|   <requires lib="libhandy" version="0.0"/> |   <requires lib="libhandy" version="1.0"/> | ||||||
|   <object class="HdyWindow" id="window"> |   <object class="GtkStack" id="widget"> | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="modal">True</property> |  | ||||||
|     <property name="destroy-with-parent">True</property> |  | ||||||
|     <property name="type-hint">dialog</property> |  | ||||||
|     <child> |  | ||||||
|       <object class="GtkStack" id="stack"> |  | ||||||
|     <property name="visible">True</property> |     <property name="visible">True</property> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
|     <property name="transition-type">crossfade</property> |     <property name="transition-type">crossfade</property> | ||||||
|  | @ -24,19 +18,31 @@ | ||||||
|             <property name="can-focus">False</property> |             <property name="can-focus">False</property> | ||||||
|             <property name="title" translatable="yes">Ensemble</property> |             <property name="title" translatable="yes">Ensemble</property> | ||||||
|             <child> |             <child> | ||||||
|                   <object class="GtkButton" id="cancel_button"> |               <object class="GtkButton" id="back_button"> | ||||||
|                     <property name="label" translatable="yes">Cancel</property> |  | ||||||
|                 <property name="visible">True</property> |                 <property name="visible">True</property> | ||||||
|                 <property name="can-focus">True</property> |                 <property name="can-focus">True</property> | ||||||
|                 <property name="receives-default">True</property> |                 <property name="receives-default">True</property> | ||||||
|  |                 <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> |               </object> | ||||||
|             </child> |             </child> | ||||||
|             <child> |             <child> | ||||||
|               <object class="GtkButton" id="save_button"> |               <object class="GtkButton" id="save_button"> | ||||||
|                     <property name="label" translatable="yes">Save</property> |  | ||||||
|                 <property name="visible">True</property> |                 <property name="visible">True</property> | ||||||
|                 <property name="can-focus">True</property> |                 <property name="can-focus">True</property> | ||||||
|                 <property name="receives-default">True</property> |                 <property name="receives-default">True</property> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkImage"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <property name="icon-name">object-select-symbolic</property> | ||||||
|  |                   </object> | ||||||
|  |                 </child> | ||||||
|                 <style> |                 <style> | ||||||
|                   <class name="suggested-action"/> |                   <class name="suggested-action"/> | ||||||
|                 </style> |                 </style> | ||||||
|  | @ -53,6 +59,27 @@ | ||||||
|             <property name="position">0</property> |             <property name="position">0</property> | ||||||
|           </packing> |           </packing> | ||||||
|         </child> |         </child> | ||||||
|  |         <child> | ||||||
|  |           <object class="GtkInfoBar" id="info_bar"> | ||||||
|  |             <property name="visible">True</property> | ||||||
|  |             <property name="can-focus">False</property> | ||||||
|  |             <property name="revealed">False</property> | ||||||
|  |             <child> | ||||||
|  |               <placeholder/> | ||||||
|  |             </child> | ||||||
|  |           </object> | ||||||
|  |           <packing> | ||||||
|  |             <property name="expand">False</property> | ||||||
|  |             <property name="fill">True</property> | ||||||
|  |             <property name="position">1</property> | ||||||
|  |           </packing> | ||||||
|  |         </child> | ||||||
|  |         <child> | ||||||
|  |           <object class="HdyClamp"> | ||||||
|  |             <property name="visible">True</property> | ||||||
|  |             <property name="can-focus">False</property> | ||||||
|  |             <property name="maximum-size">500</property> | ||||||
|  |             <property name="tightening-threshold">300</property> | ||||||
|             <child> |             <child> | ||||||
|               <!-- n-columns=2 n-rows=2 --> |               <!-- n-columns=2 n-rows=2 --> | ||||||
|               <object class="GtkGrid"> |               <object class="GtkGrid"> | ||||||
|  | @ -109,63 +136,12 @@ | ||||||
|                   </packing> |                   </packing> | ||||||
|                 </child> |                 </child> | ||||||
|               </object> |               </object> | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">False</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">1</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkInfoBar" id="info_bar"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="revealed">False</property> |  | ||||||
|                 <child internal-child="action_area"> |  | ||||||
|                   <object class="GtkButtonBox"> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="spacing">6</property> |  | ||||||
|                     <property name="layout-style">end</property> |  | ||||||
|                     <child> |  | ||||||
|                       <placeholder/> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">False</property> |  | ||||||
|                     <property name="fill">False</property> |  | ||||||
|                     <property name="position">0</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child internal-child="content_area"> |  | ||||||
|                   <object class="GtkBox"> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="spacing">16</property> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkLabel"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">False</property> |  | ||||||
|                         <property name="label" translatable="yes">Failed to save ensemble!</property> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">0</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">False</property> |  | ||||||
|                     <property name="fill">False</property> |  | ||||||
|                     <property name="position">0</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <placeholder/> |  | ||||||
|             </child> |             </child> | ||||||
|           </object> |           </object> | ||||||
|           <packing> |           <packing> | ||||||
|             <property name="expand">False</property> |             <property name="expand">False</property> | ||||||
|             <property name="fill">True</property> |             <property name="fill">True</property> | ||||||
|                 <property name="position">1</property> |             <property name="position">2</property> | ||||||
|           </packing> |           </packing> | ||||||
|         </child> |         </child> | ||||||
|       </object> |       </object> | ||||||
|  | @ -210,6 +186,4 @@ | ||||||
|       </packing> |       </packing> | ||||||
|     </child> |     </child> | ||||||
|   </object> |   </object> | ||||||
|     </child> |  | ||||||
|   </object> |  | ||||||
| </interface> | </interface> | ||||||
|  |  | ||||||
|  | @ -2,14 +2,8 @@ | ||||||
| <!-- Generated with glade 3.38.1 --> | <!-- Generated with glade 3.38.1 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk+" version="3.22"/> |   <requires lib="gtk+" version="3.22"/> | ||||||
|   <requires lib="libhandy" version="0.0"/> |   <requires lib="libhandy" version="1.0"/> | ||||||
|   <object class="HdyWindow" id="window"> |   <object class="GtkStack" id="widget"> | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="modal">True</property> |  | ||||||
|     <property name="destroy-with-parent">True</property> |  | ||||||
|     <property name="type-hint">dialog</property> |  | ||||||
|     <child> |  | ||||||
|       <object class="GtkStack" id="stack"> |  | ||||||
|     <property name="visible">True</property> |     <property name="visible">True</property> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
|     <property name="transition-type">crossfade</property> |     <property name="transition-type">crossfade</property> | ||||||
|  | @ -24,19 +18,31 @@ | ||||||
|             <property name="can-focus">False</property> |             <property name="can-focus">False</property> | ||||||
|             <property name="title" translatable="yes">Instrument</property> |             <property name="title" translatable="yes">Instrument</property> | ||||||
|             <child> |             <child> | ||||||
|                   <object class="GtkButton" id="cancel_button"> |               <object class="GtkButton" id="back_button"> | ||||||
|                     <property name="label" translatable="yes">Cancel</property> |  | ||||||
|                 <property name="visible">True</property> |                 <property name="visible">True</property> | ||||||
|                 <property name="can-focus">True</property> |                 <property name="can-focus">True</property> | ||||||
|                 <property name="receives-default">True</property> |                 <property name="receives-default">True</property> | ||||||
|  |                 <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> |               </object> | ||||||
|             </child> |             </child> | ||||||
|             <child> |             <child> | ||||||
|               <object class="GtkButton" id="save_button"> |               <object class="GtkButton" id="save_button"> | ||||||
|                     <property name="label" translatable="yes">Save</property> |  | ||||||
|                 <property name="visible">True</property> |                 <property name="visible">True</property> | ||||||
|                 <property name="can-focus">True</property> |                 <property name="can-focus">True</property> | ||||||
|                 <property name="receives-default">True</property> |                 <property name="receives-default">True</property> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkImage"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <property name="icon-name">object-select-symbolic</property> | ||||||
|  |                   </object> | ||||||
|  |                 </child> | ||||||
|                 <style> |                 <style> | ||||||
|                   <class name="suggested-action"/> |                   <class name="suggested-action"/> | ||||||
|                 </style> |                 </style> | ||||||
|  | @ -53,6 +59,27 @@ | ||||||
|             <property name="position">0</property> |             <property name="position">0</property> | ||||||
|           </packing> |           </packing> | ||||||
|         </child> |         </child> | ||||||
|  |         <child> | ||||||
|  |           <object class="GtkInfoBar" id="info_bar"> | ||||||
|  |             <property name="visible">True</property> | ||||||
|  |             <property name="can-focus">False</property> | ||||||
|  |             <property name="revealed">False</property> | ||||||
|  |             <child> | ||||||
|  |               <placeholder/> | ||||||
|  |             </child> | ||||||
|  |           </object> | ||||||
|  |           <packing> | ||||||
|  |             <property name="expand">False</property> | ||||||
|  |             <property name="fill">True</property> | ||||||
|  |             <property name="position">1</property> | ||||||
|  |           </packing> | ||||||
|  |         </child> | ||||||
|  |         <child> | ||||||
|  |           <object class="HdyClamp"> | ||||||
|  |             <property name="visible">True</property> | ||||||
|  |             <property name="can-focus">False</property> | ||||||
|  |             <property name="maximum-size">500</property> | ||||||
|  |             <property name="tightening-threshold">300</property> | ||||||
|             <child> |             <child> | ||||||
|               <!-- n-columns=2 n-rows=2 --> |               <!-- n-columns=2 n-rows=2 --> | ||||||
|               <object class="GtkGrid"> |               <object class="GtkGrid"> | ||||||
|  | @ -109,63 +136,12 @@ | ||||||
|                   </packing> |                   </packing> | ||||||
|                 </child> |                 </child> | ||||||
|               </object> |               </object> | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">False</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">1</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkInfoBar" id="info_bar"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="revealed">False</property> |  | ||||||
|                 <child internal-child="action_area"> |  | ||||||
|                   <object class="GtkButtonBox"> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="spacing">6</property> |  | ||||||
|                     <property name="layout-style">end</property> |  | ||||||
|                     <child> |  | ||||||
|                       <placeholder/> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">False</property> |  | ||||||
|                     <property name="fill">False</property> |  | ||||||
|                     <property name="position">0</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child internal-child="content_area"> |  | ||||||
|                   <object class="GtkBox"> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="spacing">16</property> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkLabel"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">False</property> |  | ||||||
|                         <property name="label" translatable="yes">Failed to save instrument!</property> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">0</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">False</property> |  | ||||||
|                     <property name="fill">False</property> |  | ||||||
|                     <property name="position">0</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <placeholder/> |  | ||||||
|             </child> |             </child> | ||||||
|           </object> |           </object> | ||||||
|           <packing> |           <packing> | ||||||
|             <property name="expand">False</property> |             <property name="expand">False</property> | ||||||
|             <property name="fill">True</property> |             <property name="fill">True</property> | ||||||
|                 <property name="position">1</property> |             <property name="position">2</property> | ||||||
|           </packing> |           </packing> | ||||||
|         </child> |         </child> | ||||||
|       </object> |       </object> | ||||||
|  | @ -210,6 +186,4 @@ | ||||||
|       </packing> |       </packing> | ||||||
|     </child> |     </child> | ||||||
|   </object> |   </object> | ||||||
|     </child> |  | ||||||
|   </object> |  | ||||||
| </interface> | </interface> | ||||||
|  |  | ||||||
|  | @ -2,14 +2,8 @@ | ||||||
| <!-- Generated with glade 3.38.1 --> | <!-- Generated with glade 3.38.1 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk+" version="3.22"/> |   <requires lib="gtk+" version="3.22"/> | ||||||
|   <requires lib="libhandy" version="0.0"/> |   <requires lib="libhandy" version="1.0"/> | ||||||
|   <object class="HdyWindow" id="window"> |   <object class="GtkBox" id="widget"> | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="modal">True</property> |  | ||||||
|     <property name="destroy-with-parent">True</property> |  | ||||||
|     <property name="type-hint">dialog</property> |  | ||||||
|     <child> |  | ||||||
|       <object class="GtkBox"> |  | ||||||
|     <property name="visible">True</property> |     <property name="visible">True</property> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
|     <property name="orientation">vertical</property> |     <property name="orientation">vertical</property> | ||||||
|  | @ -19,20 +13,32 @@ | ||||||
|         <property name="can-focus">False</property> |         <property name="can-focus">False</property> | ||||||
|         <property name="title" translatable="yes">Performance</property> |         <property name="title" translatable="yes">Performance</property> | ||||||
|         <child> |         <child> | ||||||
|               <object class="GtkButton" id="cancel_button"> |           <object class="GtkButton" id="back_button"> | ||||||
|                 <property name="label" translatable="yes">Cancel</property> |  | ||||||
|             <property name="visible">True</property> |             <property name="visible">True</property> | ||||||
|             <property name="can-focus">True</property> |             <property name="can-focus">True</property> | ||||||
|             <property name="receives-default">True</property> |             <property name="receives-default">True</property> | ||||||
|  |             <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> |           </object> | ||||||
|         </child> |         </child> | ||||||
|         <child> |         <child> | ||||||
|           <object class="GtkButton" id="save_button"> |           <object class="GtkButton" id="save_button"> | ||||||
|                 <property name="label" translatable="yes">Save</property> |  | ||||||
|             <property name="visible">True</property> |             <property name="visible">True</property> | ||||||
|             <property name="sensitive">False</property> |             <property name="sensitive">False</property> | ||||||
|             <property name="can-focus">True</property> |             <property name="can-focus">True</property> | ||||||
|             <property name="receives-default">True</property> |             <property name="receives-default">True</property> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkImage"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="icon-name">object-select-symbolic</property> | ||||||
|  |               </object> | ||||||
|  |             </child> | ||||||
|             <style> |             <style> | ||||||
|               <class name="suggested-action"/> |               <class name="suggested-action"/> | ||||||
|             </style> |             </style> | ||||||
|  | @ -49,6 +55,12 @@ | ||||||
|         <property name="position">0</property> |         <property name="position">0</property> | ||||||
|       </packing> |       </packing> | ||||||
|     </child> |     </child> | ||||||
|  |     <child> | ||||||
|  |       <object class="HdyClamp"> | ||||||
|  |         <property name="visible">True</property> | ||||||
|  |         <property name="can-focus">False</property> | ||||||
|  |         <property name="maximum-size">500</property> | ||||||
|  |         <property name="tightening-threshold">300</property> | ||||||
|         <child> |         <child> | ||||||
|           <!-- n-columns=2 n-rows=3 --> |           <!-- n-columns=2 n-rows=3 --> | ||||||
|           <object class="GtkGrid"> |           <object class="GtkGrid"> | ||||||
|  | @ -249,6 +261,8 @@ | ||||||
|               </packing> |               </packing> | ||||||
|             </child> |             </child> | ||||||
|           </object> |           </object> | ||||||
|  |         </child> | ||||||
|  |       </object> | ||||||
|       <packing> |       <packing> | ||||||
|         <property name="expand">False</property> |         <property name="expand">False</property> | ||||||
|         <property name="fill">True</property> |         <property name="fill">True</property> | ||||||
|  | @ -256,8 +270,6 @@ | ||||||
|       </packing> |       </packing> | ||||||
|     </child> |     </child> | ||||||
|   </object> |   </object> | ||||||
|     </child> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkSizeGroup"> |   <object class="GtkSizeGroup"> | ||||||
|     <widgets> |     <widgets> | ||||||
|       <widget name="label1"/> |       <widget name="label1"/> | ||||||
|  |  | ||||||
|  | @ -2,14 +2,8 @@ | ||||||
| <!-- Generated with glade 3.38.1 --> | <!-- Generated with glade 3.38.1 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk+" version="3.22"/> |   <requires lib="gtk+" version="3.22"/> | ||||||
|   <requires lib="libhandy" version="0.0"/> |   <requires lib="libhandy" version="1.0"/> | ||||||
|   <object class="HdyWindow" id="window"> |   <object class="GtkStack" id="widget"> | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="modal">True</property> |  | ||||||
|     <property name="destroy-with-parent">True</property> |  | ||||||
|     <property name="type-hint">dialog</property> |  | ||||||
|     <child> |  | ||||||
|       <object class="GtkStack" id="stack"> |  | ||||||
|     <property name="visible">True</property> |     <property name="visible">True</property> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
|     <property name="transition-type">crossfade</property> |     <property name="transition-type">crossfade</property> | ||||||
|  | @ -24,19 +18,31 @@ | ||||||
|             <property name="can-focus">False</property> |             <property name="can-focus">False</property> | ||||||
|             <property name="title" translatable="yes">Person</property> |             <property name="title" translatable="yes">Person</property> | ||||||
|             <child> |             <child> | ||||||
|                   <object class="GtkButton" id="cancel_button"> |               <object class="GtkButton" id="back_button"> | ||||||
|                     <property name="label" translatable="yes">Cancel</property> |  | ||||||
|                 <property name="visible">True</property> |                 <property name="visible">True</property> | ||||||
|                 <property name="can-focus">True</property> |                 <property name="can-focus">True</property> | ||||||
|                 <property name="receives-default">True</property> |                 <property name="receives-default">True</property> | ||||||
|  |                 <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> |               </object> | ||||||
|             </child> |             </child> | ||||||
|             <child> |             <child> | ||||||
|               <object class="GtkButton" id="save_button"> |               <object class="GtkButton" id="save_button"> | ||||||
|                     <property name="label" translatable="yes">Save</property> |  | ||||||
|                 <property name="visible">True</property> |                 <property name="visible">True</property> | ||||||
|                 <property name="can-focus">True</property> |                 <property name="can-focus">True</property> | ||||||
|                 <property name="receives-default">True</property> |                 <property name="receives-default">True</property> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkImage"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <property name="icon-name">object-select-symbolic</property> | ||||||
|  |                   </object> | ||||||
|  |                 </child> | ||||||
|                 <style> |                 <style> | ||||||
|                   <class name="suggested-action"/> |                   <class name="suggested-action"/> | ||||||
|                 </style> |                 </style> | ||||||
|  | @ -53,6 +59,27 @@ | ||||||
|             <property name="position">0</property> |             <property name="position">0</property> | ||||||
|           </packing> |           </packing> | ||||||
|         </child> |         </child> | ||||||
|  |         <child> | ||||||
|  |           <object class="GtkInfoBar" id="info_bar"> | ||||||
|  |             <property name="visible">True</property> | ||||||
|  |             <property name="can-focus">False</property> | ||||||
|  |             <property name="revealed">False</property> | ||||||
|  |             <child> | ||||||
|  |               <placeholder/> | ||||||
|  |             </child> | ||||||
|  |           </object> | ||||||
|  |           <packing> | ||||||
|  |             <property name="expand">False</property> | ||||||
|  |             <property name="fill">True</property> | ||||||
|  |             <property name="position">1</property> | ||||||
|  |           </packing> | ||||||
|  |         </child> | ||||||
|  |         <child> | ||||||
|  |           <object class="HdyClamp"> | ||||||
|  |             <property name="visible">True</property> | ||||||
|  |             <property name="can-focus">False</property> | ||||||
|  |             <property name="maximum-size">500</property> | ||||||
|  |             <property name="tightening-threshold">300</property> | ||||||
|             <child> |             <child> | ||||||
|               <!-- n-columns=2 n-rows=3 --> |               <!-- n-columns=2 n-rows=3 --> | ||||||
|               <object class="GtkGrid"> |               <object class="GtkGrid"> | ||||||
|  | @ -132,63 +159,12 @@ | ||||||
|                   </packing> |                   </packing> | ||||||
|                 </child> |                 </child> | ||||||
|               </object> |               </object> | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">False</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">1</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkInfoBar" id="info_bar"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="revealed">False</property> |  | ||||||
|                 <child internal-child="action_area"> |  | ||||||
|                   <object class="GtkButtonBox"> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="spacing">6</property> |  | ||||||
|                     <property name="layout-style">end</property> |  | ||||||
|                     <child> |  | ||||||
|                       <placeholder/> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">False</property> |  | ||||||
|                     <property name="fill">False</property> |  | ||||||
|                     <property name="position">0</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child internal-child="content_area"> |  | ||||||
|                   <object class="GtkBox"> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="spacing">16</property> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkLabel"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">False</property> |  | ||||||
|                         <property name="label" translatable="yes">Failed to save person!</property> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">0</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">False</property> |  | ||||||
|                     <property name="fill">False</property> |  | ||||||
|                     <property name="position">0</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <placeholder/> |  | ||||||
|             </child> |             </child> | ||||||
|           </object> |           </object> | ||||||
|           <packing> |           <packing> | ||||||
|             <property name="expand">False</property> |             <property name="expand">False</property> | ||||||
|             <property name="fill">True</property> |             <property name="fill">True</property> | ||||||
|                 <property name="position">1</property> |             <property name="position">2</property> | ||||||
|           </packing> |           </packing> | ||||||
|         </child> |         </child> | ||||||
|       </object> |       </object> | ||||||
|  | @ -233,6 +209,4 @@ | ||||||
|       </packing> |       </packing> | ||||||
|     </child> |     </child> | ||||||
|   </object> |   </object> | ||||||
|     </child> |  | ||||||
|   </object> |  | ||||||
| </interface> | </interface> | ||||||
|  |  | ||||||
|  | @ -99,7 +99,7 @@ | ||||||
|                 </child> |                 </child> | ||||||
|                 <child> |                 <child> | ||||||
|                   <object class="GtkCheckButton" id="server_check_button"> |                   <object class="GtkCheckButton" id="server_check_button"> | ||||||
|                     <property name="label" translatable="yes">Show works from the server</property> |                     <property name="label" translatable="yes">Show recordings from the server</property> | ||||||
|                     <property name="visible">True</property> |                     <property name="visible">True</property> | ||||||
|                     <property name="can-focus">True</property> |                     <property name="can-focus">True</property> | ||||||
|                     <property name="receives-default">False</property> |                     <property name="receives-default">False</property> | ||||||
|  |  | ||||||
							
								
								
									
										264
									
								
								musicus/res/ui/selector.ui
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								musicus/res/ui/selector.ui
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,264 @@ | ||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <!-- Generated with glade 3.38.1 --> | ||||||
|  | <interface> | ||||||
|  |   <requires lib="gtk+" version="3.24"/> | ||||||
|  |   <requires lib="libhandy" version="1.0"/> | ||||||
|  |   <object class="GtkBox" id="widget"> | ||||||
|  |     <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"> | ||||||
|  |         <property name="visible">True</property> | ||||||
|  |         <property name="can-focus">False</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> | ||||||
|  |         <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> | ||||||
|  |           <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">True</property> | ||||||
|  |         <child> | ||||||
|  |           <object class="HdyClamp"> | ||||||
|  |             <property name="visible">True</property> | ||||||
|  |             <property name="can-focus">False</property> | ||||||
|  |             <property name="maximum-size">500</property> | ||||||
|  |             <property name="tightening-threshold">300</property> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkBox"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="orientation">vertical</property> | ||||||
|  |                 <property name="spacing">6</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 …</property> | ||||||
|  |                   </object> | ||||||
|  |                   <packing> | ||||||
|  |                     <property name="expand">False</property> | ||||||
|  |                     <property name="fill">True</property> | ||||||
|  |                     <property name="position">0</property> | ||||||
|  |                   </packing> | ||||||
|  |                 </child> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkCheckButton" id="server_check_button"> | ||||||
|  |                     <property name="label" translatable="yes">Use the Musicus server</property> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">True</property> | ||||||
|  |                     <property name="receives-default">False</property> | ||||||
|  |                     <property name="halign">start</property> | ||||||
|  |                     <property name="active">True</property> | ||||||
|  |                     <property name="draw-indicator">True</property> | ||||||
|  |                   </object> | ||||||
|  |                   <packing> | ||||||
|  |                     <property name="expand">False</property> | ||||||
|  |                     <property name="fill">True</property> | ||||||
|  |                     <property name="position">1</property> | ||||||
|  |                   </packing> | ||||||
|  |                 </child> | ||||||
|  |               </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="stack"> | ||||||
|  |         <property name="visible">True</property> | ||||||
|  |         <property name="can-focus">False</property> | ||||||
|  |         <property name="hhomogeneous">False</property> | ||||||
|  |         <property name="vhomogeneous">False</property> | ||||||
|  |         <property name="transition-type">crossfade</property> | ||||||
|  |         <property name="interpolate-size">True</property> | ||||||
|  |         <child> | ||||||
|  |           <object class="GtkSpinner"> | ||||||
|  |             <property name="visible">True</property> | ||||||
|  |             <property name="can-focus">False</property> | ||||||
|  |             <property name="margin-top">12</property> | ||||||
|  |             <property name="active">True</property> | ||||||
|  |           </object> | ||||||
|  |           <packing> | ||||||
|  |             <property name="name">loading</property> | ||||||
|  |           </packing> | ||||||
|  |         </child> | ||||||
|  |         <child> | ||||||
|  |           <object class="GtkScrolledWindow"> | ||||||
|  |             <property name="height-request">200</property> | ||||||
|  |             <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="HdyClamp"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <property name="maximum-size">500</property> | ||||||
|  |                     <property name="tightening-threshold">300</property> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkFrame" id="frame"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">False</property> | ||||||
|  |                         <property name="valign">start</property> | ||||||
|  |                         <property name="margin-start">6</property> | ||||||
|  |                         <property name="margin-end">6</property> | ||||||
|  |                         <property name="margin-top">12</property> | ||||||
|  |                         <property name="margin-bottom">6</property> | ||||||
|  |                         <property name="label-xalign">0</property> | ||||||
|  |                         <property name="shadow-type">in</property> | ||||||
|  |                         <child> | ||||||
|  |                           <placeholder/> | ||||||
|  |                         </child> | ||||||
|  |                         <child type="label_item"> | ||||||
|  |                           <placeholder/> | ||||||
|  |                         </child> | ||||||
|  |                       </object> | ||||||
|  |                     </child> | ||||||
|  |                   </object> | ||||||
|  |                 </child> | ||||||
|  |               </object> | ||||||
|  |             </child> | ||||||
|  |           </object> | ||||||
|  |           <packing> | ||||||
|  |             <property name="name">content</property> | ||||||
|  |             <property name="position">1</property> | ||||||
|  |           </packing> | ||||||
|  |         </child> | ||||||
|  |         <child> | ||||||
|  |           <object class="GtkBox"> | ||||||
|  |             <property name="visible">True</property> | ||||||
|  |             <property name="can-focus">False</property> | ||||||
|  |             <property name="halign">center</property> | ||||||
|  |             <property name="valign">center</property> | ||||||
|  |             <property name="border-width">18</property> | ||||||
|  |             <property name="orientation">vertical</property> | ||||||
|  |             <property name="spacing">18</property> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkImage"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="opacity">0.5</property> | ||||||
|  |                 <property name="pixel-size">80</property> | ||||||
|  |                 <property name="icon-name">network-error-symbolic</property> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="expand">False</property> | ||||||
|  |                 <property name="fill">True</property> | ||||||
|  |                 <property name="position">0</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkLabel"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="opacity">0.5</property> | ||||||
|  |                 <property name="label" translatable="yes">An error occured!</property> | ||||||
|  |                 <attributes> | ||||||
|  |                   <attribute name="size" value="16384"/> | ||||||
|  |                 </attributes> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="expand">False</property> | ||||||
|  |                 <property name="fill">True</property> | ||||||
|  |                 <property name="position">1</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkLabel"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="opacity">0.5</property> | ||||||
|  |                 <property name="label" translatable="yes">The server was not reachable or responded with an error. Please check your internet connection.</property> | ||||||
|  |                 <property name="justify">center</property> | ||||||
|  |                 <property name="wrap">True</property> | ||||||
|  |                 <property name="max-width-chars">40</property> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="expand">False</property> | ||||||
|  |                 <property name="fill">True</property> | ||||||
|  |                 <property name="position">2</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkButton" id="try_again_button"> | ||||||
|  |                 <property name="label" translatable="yes">Try again</property> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">True</property> | ||||||
|  |                 <property name="receives-default">True</property> | ||||||
|  |                 <property name="halign">center</property> | ||||||
|  |                 <style> | ||||||
|  |                   <class name="suggested-action"/> | ||||||
|  |                 </style> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="expand">False</property> | ||||||
|  |                 <property name="fill">True</property> | ||||||
|  |                 <property name="position">3</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |           </object> | ||||||
|  |           <packing> | ||||||
|  |             <property name="name">error</property> | ||||||
|  |             <property name="position">2</property> | ||||||
|  |           </packing> | ||||||
|  |         </child> | ||||||
|  |       </object> | ||||||
|  |       <packing> | ||||||
|  |         <property name="expand">True</property> | ||||||
|  |         <property name="fill">True</property> | ||||||
|  |         <property name="position">2</property> | ||||||
|  |       </packing> | ||||||
|  |     </child> | ||||||
|  |   </object> | ||||||
|  | </interface> | ||||||
|  | @ -3,14 +3,7 @@ | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk+" version="3.24"/> |   <requires lib="gtk+" version="3.24"/> | ||||||
|   <requires lib="libhandy" version="0.0"/> |   <requires lib="libhandy" version="0.0"/> | ||||||
|   <object class="HdyWindow" id="window"> |   <object class="GtkBox" id="widget"> | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="modal">True</property> |  | ||||||
|     <property name="default-width">350</property> |  | ||||||
|     <property name="default-height">200</property> |  | ||||||
|     <property name="type-hint">dialog</property> |  | ||||||
|     <child> |  | ||||||
|       <object class="GtkBox"> |  | ||||||
|     <property name="visible">True</property> |     <property name="visible">True</property> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
|     <property name="orientation">vertical</property> |     <property name="orientation">vertical</property> | ||||||
|  | @ -20,19 +13,31 @@ | ||||||
|         <property name="can-focus">False</property> |         <property name="can-focus">False</property> | ||||||
|         <property name="title" translatable="yes">Track</property> |         <property name="title" translatable="yes">Track</property> | ||||||
|         <child> |         <child> | ||||||
|               <object class="GtkButton" id="cancel_button"> |           <object class="GtkButton" id="back_button"> | ||||||
|                 <property name="label" translatable="yes">Cancel</property> |  | ||||||
|             <property name="visible">True</property> |             <property name="visible">True</property> | ||||||
|             <property name="can-focus">True</property> |             <property name="can-focus">True</property> | ||||||
|             <property name="receives-default">True</property> |             <property name="receives-default">True</property> | ||||||
|  |             <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> |           </object> | ||||||
|         </child> |         </child> | ||||||
|         <child> |         <child> | ||||||
|           <object class="GtkButton" id="save_button"> |           <object class="GtkButton" id="save_button"> | ||||||
|                 <property name="label" translatable="yes">Save</property> |  | ||||||
|             <property name="visible">True</property> |             <property name="visible">True</property> | ||||||
|             <property name="can-focus">True</property> |             <property name="can-focus">True</property> | ||||||
|             <property name="receives-default">True</property> |             <property name="receives-default">True</property> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkImage"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="icon-name">object-select-symbolic</property> | ||||||
|  |               </object> | ||||||
|  |             </child> | ||||||
|             <style> |             <style> | ||||||
|               <class name="suggested-action"/> |               <class name="suggested-action"/> | ||||||
|             </style> |             </style> | ||||||
|  | @ -58,6 +63,23 @@ | ||||||
|             <property name="visible">True</property> |             <property name="visible">True</property> | ||||||
|             <property name="can-focus">False</property> |             <property name="can-focus">False</property> | ||||||
|             <property name="shadow-type">none</property> |             <property name="shadow-type">none</property> | ||||||
|  |             <child> | ||||||
|  |               <object class="HdyClamp"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="maximum-size">500</property> | ||||||
|  |                 <property name="tightening-threshold">300</property> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkFrame"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <property name="valign">start</property> | ||||||
|  |                     <property name="margin-start">6</property> | ||||||
|  |                     <property name="margin-end">6</property> | ||||||
|  |                     <property name="margin-top">12</property> | ||||||
|  |                     <property name="margin-bottom">6</property> | ||||||
|  |                     <property name="label-xalign">0</property> | ||||||
|  |                     <property name="shadow-type">in</property> | ||||||
|                     <child> |                     <child> | ||||||
|                       <object class="GtkListBox" id="list"> |                       <object class="GtkListBox" id="list"> | ||||||
|                         <property name="visible">True</property> |                         <property name="visible">True</property> | ||||||
|  | @ -67,7 +89,19 @@ | ||||||
|                           <object class="GtkLabel"> |                           <object class="GtkLabel"> | ||||||
|                             <property name="visible">True</property> |                             <property name="visible">True</property> | ||||||
|                             <property name="can-focus">False</property> |                             <property name="can-focus">False</property> | ||||||
|  |                             <property name="margin-start">6</property> | ||||||
|  |                             <property name="margin-end">6</property> | ||||||
|  |                             <property name="margin-top">6</property> | ||||||
|  |                             <property name="margin-bottom">6</property> | ||||||
|                             <property name="label" translatable="yes">Select a recording of a work with multiple parts.</property> |                             <property name="label" translatable="yes">Select a recording of a work with multiple parts.</property> | ||||||
|  |                             <property name="ellipsize">end</property> | ||||||
|  |                           </object> | ||||||
|  |                         </child> | ||||||
|  |                       </object> | ||||||
|  |                     </child> | ||||||
|  |                     <child type="label_item"> | ||||||
|  |                       <placeholder/> | ||||||
|  |                     </child> | ||||||
|                   </object> |                   </object> | ||||||
|                 </child> |                 </child> | ||||||
|               </object> |               </object> | ||||||
|  | @ -82,6 +116,4 @@ | ||||||
|       </packing> |       </packing> | ||||||
|     </child> |     </child> | ||||||
|   </object> |   </object> | ||||||
|     </child> |  | ||||||
|   </object> |  | ||||||
| </interface> | </interface> | ||||||
|  |  | ||||||
|  | @ -3,14 +3,7 @@ | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk+" version="3.24"/> |   <requires lib="gtk+" version="3.24"/> | ||||||
|   <requires lib="libhandy" version="0.0"/> |   <requires lib="libhandy" version="0.0"/> | ||||||
|   <object class="HdyWindow" id="window"> |   <object class="GtkBox" id="widget"> | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="modal">True</property> |  | ||||||
|     <property name="default-width">400</property> |  | ||||||
|     <property name="default-height">300</property> |  | ||||||
|     <property name="type-hint">dialog</property> |  | ||||||
|     <child> |  | ||||||
|       <object class="GtkBox"> |  | ||||||
|     <property name="visible">True</property> |     <property name="visible">True</property> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
|     <property name="orientation">vertical</property> |     <property name="orientation">vertical</property> | ||||||
|  | @ -21,11 +14,17 @@ | ||||||
|         <property name="title" translatable="yes">Tracks</property> |         <property name="title" translatable="yes">Tracks</property> | ||||||
|         <child> |         <child> | ||||||
|           <object class="GtkButton" id="save_button"> |           <object class="GtkButton" id="save_button"> | ||||||
|                 <property name="label" translatable="yes">Save</property> |  | ||||||
|             <property name="visible">True</property> |             <property name="visible">True</property> | ||||||
|             <property name="sensitive">False</property> |             <property name="sensitive">False</property> | ||||||
|             <property name="can-focus">True</property> |             <property name="can-focus">True</property> | ||||||
|             <property name="receives-default">True</property> |             <property name="receives-default">True</property> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkImage"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="icon-name">object-select-symbolic</property> | ||||||
|  |               </object> | ||||||
|  |             </child> | ||||||
|             <style> |             <style> | ||||||
|               <class name="suggested-action"/> |               <class name="suggested-action"/> | ||||||
|             </style> |             </style> | ||||||
|  | @ -35,11 +34,17 @@ | ||||||
|           </packing> |           </packing> | ||||||
|         </child> |         </child> | ||||||
|         <child> |         <child> | ||||||
|               <object class="GtkButton" id="cancel_button"> |           <object class="GtkButton" id="back_button"> | ||||||
|                 <property name="label" translatable="yes">Cancel</property> |  | ||||||
|             <property name="visible">True</property> |             <property name="visible">True</property> | ||||||
|             <property name="can-focus">True</property> |             <property name="can-focus">True</property> | ||||||
|             <property name="receives-default">True</property> |             <property name="receives-default">True</property> | ||||||
|  |             <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> |           </object> | ||||||
|           <packing> |           <packing> | ||||||
|             <property name="position">1</property> |             <property name="position">1</property> | ||||||
|  | @ -52,11 +57,19 @@ | ||||||
|         <property name="position">0</property> |         <property name="position">0</property> | ||||||
|       </packing> |       </packing> | ||||||
|     </child> |     </child> | ||||||
|  |     <child> | ||||||
|  |       <object class="HdyClamp"> | ||||||
|  |         <property name="visible">True</property> | ||||||
|  |         <property name="can-focus">False</property> | ||||||
|  |         <property name="vexpand">True</property> | ||||||
|  |         <property name="maximum-size">800</property> | ||||||
|  |         <property name="tightening-threshold">300</property> | ||||||
|         <child> |         <child> | ||||||
|           <!-- n-columns=2 n-rows=2 --> |           <!-- n-columns=2 n-rows=2 --> | ||||||
|           <object class="GtkGrid"> |           <object class="GtkGrid"> | ||||||
|             <property name="visible">True</property> |             <property name="visible">True</property> | ||||||
|             <property name="can-focus">False</property> |             <property name="can-focus">False</property> | ||||||
|  |             <property name="vexpand">True</property> | ||||||
|             <property name="border-width">18</property> |             <property name="border-width">18</property> | ||||||
|             <property name="row-spacing">12</property> |             <property name="row-spacing">12</property> | ||||||
|             <property name="column-spacing">6</property> |             <property name="column-spacing">6</property> | ||||||
|  | @ -286,13 +299,13 @@ | ||||||
|               </packing> |               </packing> | ||||||
|             </child> |             </child> | ||||||
|           </object> |           </object> | ||||||
|  |         </child> | ||||||
|  |       </object> | ||||||
|       <packing> |       <packing> | ||||||
|             <property name="expand">True</property> |         <property name="expand">False</property> | ||||||
|         <property name="fill">True</property> |         <property name="fill">True</property> | ||||||
|         <property name="position">1</property> |         <property name="position">1</property> | ||||||
|       </packing> |       </packing> | ||||||
|     </child> |     </child> | ||||||
|   </object> |   </object> | ||||||
|     </child> |  | ||||||
|   </object> |  | ||||||
| </interface> | </interface> | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| <!-- Generated with glade 3.38.1 --> | <!-- Generated with glade 3.38.1 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk+" version="3.22"/> |   <requires lib="gtk+" version="3.22"/> | ||||||
|   <requires lib="libhandy" version="0.0"/> |   <requires lib="libhandy" version="1.0"/> | ||||||
|   <object class="GtkStack" id="widget"> |   <object class="GtkStack" id="widget"> | ||||||
|     <property name="visible">True</property> |     <property name="visible">True</property> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
|  | @ -17,20 +17,32 @@ | ||||||
|             <property name="can-focus">False</property> |             <property name="can-focus">False</property> | ||||||
|             <property name="title" translatable="yes">Work</property> |             <property name="title" translatable="yes">Work</property> | ||||||
|             <child> |             <child> | ||||||
|               <object class="GtkButton" id="cancel_button"> |               <object class="GtkButton" id="back_button"> | ||||||
|                 <property name="label" translatable="yes">Cancel</property> |  | ||||||
|                 <property name="visible">True</property> |                 <property name="visible">True</property> | ||||||
|                 <property name="can-focus">True</property> |                 <property name="can-focus">True</property> | ||||||
|                 <property name="receives-default">True</property> |                 <property name="receives-default">True</property> | ||||||
|  |                 <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> |               </object> | ||||||
|             </child> |             </child> | ||||||
|             <child> |             <child> | ||||||
|               <object class="GtkButton" id="save_button"> |               <object class="GtkButton" id="save_button"> | ||||||
|                 <property name="label" translatable="yes">Save</property> |  | ||||||
|                 <property name="visible">True</property> |                 <property name="visible">True</property> | ||||||
|                 <property name="sensitive">False</property> |                 <property name="sensitive">False</property> | ||||||
|                 <property name="can-focus">True</property> |                 <property name="can-focus">True</property> | ||||||
|                 <property name="receives-default">True</property> |                 <property name="receives-default">True</property> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkImage"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <property name="icon-name">object-select-symbolic</property> | ||||||
|  |                   </object> | ||||||
|  |                 </child> | ||||||
|                 <style> |                 <style> | ||||||
|                   <class name="suggested-action"/> |                   <class name="suggested-action"/> | ||||||
|                 </style> |                 </style> | ||||||
|  | @ -87,6 +99,12 @@ | ||||||
|             <property name="visible">True</property> |             <property name="visible">True</property> | ||||||
|             <property name="can-focus">True</property> |             <property name="can-focus">True</property> | ||||||
|             <property name="show-border">False</property> |             <property name="show-border">False</property> | ||||||
|  |             <child> | ||||||
|  |               <object class="HdyClamp"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="maximum-size">500</property> | ||||||
|  |                 <property name="tightening-threshold">300</property> | ||||||
|                 <child> |                 <child> | ||||||
|                   <!-- n-columns=2 n-rows=3 --> |                   <!-- n-columns=2 n-rows=3 --> | ||||||
|                   <object class="GtkGrid"> |                   <object class="GtkGrid"> | ||||||
|  | @ -176,6 +194,8 @@ | ||||||
|                     </child> |                     </child> | ||||||
|                   </object> |                   </object> | ||||||
|                 </child> |                 </child> | ||||||
|  |               </object> | ||||||
|  |             </child> | ||||||
|             <child type="tab"> |             <child type="tab"> | ||||||
|               <object class="GtkLabel"> |               <object class="GtkLabel"> | ||||||
|                 <property name="visible">True</property> |                 <property name="visible">True</property> | ||||||
|  | @ -186,6 +206,12 @@ | ||||||
|                 <property name="tab-fill">False</property> |                 <property name="tab-fill">False</property> | ||||||
|               </packing> |               </packing> | ||||||
|             </child> |             </child> | ||||||
|  |             <child> | ||||||
|  |               <object class="HdyClamp"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="maximum-size">800</property> | ||||||
|  |                 <property name="tightening-threshold">300</property> | ||||||
|                 <child> |                 <child> | ||||||
|                   <object class="GtkBox"> |                   <object class="GtkBox"> | ||||||
|                     <property name="visible">True</property> |                     <property name="visible">True</property> | ||||||
|  | @ -260,6 +286,8 @@ | ||||||
|                       </packing> |                       </packing> | ||||||
|                     </child> |                     </child> | ||||||
|                   </object> |                   </object> | ||||||
|  |                 </child> | ||||||
|  |               </object> | ||||||
|               <packing> |               <packing> | ||||||
|                 <property name="position">1</property> |                 <property name="position">1</property> | ||||||
|               </packing> |               </packing> | ||||||
|  | @ -275,6 +303,12 @@ | ||||||
|                 <property name="tab-fill">False</property> |                 <property name="tab-fill">False</property> | ||||||
|               </packing> |               </packing> | ||||||
|             </child> |             </child> | ||||||
|  |             <child> | ||||||
|  |               <object class="HdyClamp"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="maximum-size">800</property> | ||||||
|  |                 <property name="tightening-threshold">300</property> | ||||||
|                 <child> |                 <child> | ||||||
|                   <object class="GtkBox"> |                   <object class="GtkBox"> | ||||||
|                     <property name="visible">True</property> |                     <property name="visible">True</property> | ||||||
|  | @ -426,6 +460,8 @@ | ||||||
|                       </packing> |                       </packing> | ||||||
|                     </child> |                     </child> | ||||||
|                   </object> |                   </object> | ||||||
|  |                 </child> | ||||||
|  |               </object> | ||||||
|               <packing> |               <packing> | ||||||
|                 <property name="position">2</property> |                 <property name="position">2</property> | ||||||
|               </packing> |               </packing> | ||||||
|  |  | ||||||
|  | @ -2,15 +2,8 @@ | ||||||
| <!-- Generated with glade 3.38.1 --> | <!-- Generated with glade 3.38.1 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk+" version="3.22"/> |   <requires lib="gtk+" version="3.22"/> | ||||||
|   <requires lib="libhandy" version="0.0"/> |   <requires lib="libhandy" version="1.0"/> | ||||||
|   <object class="HdyWindow" id="window"> |   <object class="GtkBox" id="widget"> | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="modal">True</property> |  | ||||||
|     <property name="default-width">350</property> |  | ||||||
|     <property name="destroy-with-parent">True</property> |  | ||||||
|     <property name="type-hint">dialog</property> |  | ||||||
|     <child> |  | ||||||
|       <object class="GtkBox"> |  | ||||||
|     <property name="visible">True</property> |     <property name="visible">True</property> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
|     <property name="orientation">vertical</property> |     <property name="orientation">vertical</property> | ||||||
|  | @ -20,19 +13,31 @@ | ||||||
|         <property name="can-focus">False</property> |         <property name="can-focus">False</property> | ||||||
|         <property name="title" translatable="yes">Work part</property> |         <property name="title" translatable="yes">Work part</property> | ||||||
|         <child> |         <child> | ||||||
|               <object class="GtkButton" id="cancel_button"> |           <object class="GtkButton" id="back_button"> | ||||||
|                 <property name="label" translatable="yes">Cancel</property> |  | ||||||
|             <property name="visible">True</property> |             <property name="visible">True</property> | ||||||
|             <property name="can-focus">True</property> |             <property name="can-focus">True</property> | ||||||
|             <property name="receives-default">True</property> |             <property name="receives-default">True</property> | ||||||
|  |             <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> |           </object> | ||||||
|         </child> |         </child> | ||||||
|         <child> |         <child> | ||||||
|           <object class="GtkButton" id="save_button"> |           <object class="GtkButton" id="save_button"> | ||||||
|                 <property name="label" translatable="yes">Save</property> |  | ||||||
|             <property name="visible">True</property> |             <property name="visible">True</property> | ||||||
|             <property name="can-focus">True</property> |             <property name="can-focus">True</property> | ||||||
|             <property name="receives-default">True</property> |             <property name="receives-default">True</property> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkImage"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="icon-name">object-select-symbolic</property> | ||||||
|  |               </object> | ||||||
|  |             </child> | ||||||
|             <style> |             <style> | ||||||
|               <class name="suggested-action"/> |               <class name="suggested-action"/> | ||||||
|             </style> |             </style> | ||||||
|  | @ -49,6 +54,12 @@ | ||||||
|         <property name="position">0</property> |         <property name="position">0</property> | ||||||
|       </packing> |       </packing> | ||||||
|     </child> |     </child> | ||||||
|  |     <child> | ||||||
|  |       <object class="HdyClamp"> | ||||||
|  |         <property name="visible">True</property> | ||||||
|  |         <property name="can-focus">False</property> | ||||||
|  |         <property name="maximum-size">500</property> | ||||||
|  |         <property name="tightening-threshold">300</property> | ||||||
|         <child> |         <child> | ||||||
|           <!-- n-columns=2 n-rows=2 --> |           <!-- n-columns=2 n-rows=2 --> | ||||||
|           <object class="GtkGrid"> |           <object class="GtkGrid"> | ||||||
|  | @ -146,6 +157,8 @@ | ||||||
|               </packing> |               </packing> | ||||||
|             </child> |             </child> | ||||||
|           </object> |           </object> | ||||||
|  |         </child> | ||||||
|  |       </object> | ||||||
|       <packing> |       <packing> | ||||||
|         <property name="expand">False</property> |         <property name="expand">False</property> | ||||||
|         <property name="fill">True</property> |         <property name="fill">True</property> | ||||||
|  | @ -153,6 +166,4 @@ | ||||||
|       </packing> |       </packing> | ||||||
|     </child> |     </child> | ||||||
|   </object> |   </object> | ||||||
|     </child> |  | ||||||
|   </object> |  | ||||||
| </interface> | </interface> | ||||||
|  | @ -2,14 +2,8 @@ | ||||||
| <!-- Generated with glade 3.38.1 --> | <!-- Generated with glade 3.38.1 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk+" version="3.22"/> |   <requires lib="gtk+" version="3.22"/> | ||||||
|   <requires lib="libhandy" version="0.0"/> |   <requires lib="libhandy" version="1.0"/> | ||||||
|   <object class="HdyWindow" id="window"> |   <object class="GtkBox" id="widget"> | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="modal">True</property> |  | ||||||
|     <property name="destroy-with-parent">True</property> |  | ||||||
|     <property name="type-hint">dialog</property> |  | ||||||
|     <child> |  | ||||||
|       <object class="GtkBox"> |  | ||||||
|     <property name="visible">True</property> |     <property name="visible">True</property> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
|     <property name="orientation">vertical</property> |     <property name="orientation">vertical</property> | ||||||
|  | @ -19,19 +13,31 @@ | ||||||
|         <property name="can-focus">False</property> |         <property name="can-focus">False</property> | ||||||
|         <property name="title" translatable="yes">Work section</property> |         <property name="title" translatable="yes">Work section</property> | ||||||
|         <child> |         <child> | ||||||
|               <object class="GtkButton" id="cancel_button"> |           <object class="GtkButton" id="back_button"> | ||||||
|                 <property name="label" translatable="yes">Cancel</property> |  | ||||||
|             <property name="visible">True</property> |             <property name="visible">True</property> | ||||||
|             <property name="can-focus">True</property> |             <property name="can-focus">True</property> | ||||||
|             <property name="receives-default">True</property> |             <property name="receives-default">True</property> | ||||||
|  |             <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> |           </object> | ||||||
|         </child> |         </child> | ||||||
|         <child> |         <child> | ||||||
|           <object class="GtkButton" id="save_button"> |           <object class="GtkButton" id="save_button"> | ||||||
|                 <property name="label" translatable="yes">Save</property> |  | ||||||
|             <property name="visible">True</property> |             <property name="visible">True</property> | ||||||
|             <property name="can-focus">True</property> |             <property name="can-focus">True</property> | ||||||
|             <property name="receives-default">True</property> |             <property name="receives-default">True</property> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkImage"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="icon-name">object-select-symbolic</property> | ||||||
|  |               </object> | ||||||
|  |             </child> | ||||||
|             <style> |             <style> | ||||||
|               <class name="suggested-action"/> |               <class name="suggested-action"/> | ||||||
|             </style> |             </style> | ||||||
|  | @ -48,6 +54,12 @@ | ||||||
|         <property name="position">0</property> |         <property name="position">0</property> | ||||||
|       </packing> |       </packing> | ||||||
|     </child> |     </child> | ||||||
|  |     <child> | ||||||
|  |       <object class="HdyClamp"> | ||||||
|  |         <property name="visible">True</property> | ||||||
|  |         <property name="can-focus">False</property> | ||||||
|  |         <property name="maximum-size">500</property> | ||||||
|  |         <property name="tightening-threshold">300</property> | ||||||
|         <child> |         <child> | ||||||
|           <!-- n-columns=2 n-rows=1 --> |           <!-- n-columns=2 n-rows=1 --> | ||||||
|           <object class="GtkGrid"> |           <object class="GtkGrid"> | ||||||
|  | @ -80,6 +92,8 @@ | ||||||
|               </packing> |               </packing> | ||||||
|             </child> |             </child> | ||||||
|           </object> |           </object> | ||||||
|  |         </child> | ||||||
|  |       </object> | ||||||
|       <packing> |       <packing> | ||||||
|         <property name="expand">False</property> |         <property name="expand">False</property> | ||||||
|         <property name="fill">True</property> |         <property name="fill">True</property> | ||||||
|  | @ -87,6 +101,4 @@ | ||||||
|       </packing> |       </packing> | ||||||
|     </child> |     </child> | ||||||
|   </object> |   </object> | ||||||
|     </child> |  | ||||||
|   </object> |  | ||||||
| </interface> | </interface> | ||||||
|  | @ -49,6 +49,20 @@ | ||||||
|             <property name="visible">True</property> |             <property name="visible">True</property> | ||||||
|             <property name="can-focus">False</property> |             <property name="can-focus">False</property> | ||||||
|             <property name="title" translatable="yes">Select a work</property> |             <property name="title" translatable="yes">Select a work</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> | ||||||
|             <child> |             <child> | ||||||
|               <object class="GtkButton" id="add_button"> |               <object class="GtkButton" id="add_button"> | ||||||
|                 <property name="visible">True</property> |                 <property name="visible">True</property> | ||||||
|  | @ -62,6 +76,10 @@ | ||||||
|                   </object> |                   </object> | ||||||
|                 </child> |                 </child> | ||||||
|               </object> |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="pack-type">end</property> | ||||||
|  |                 <property name="position">1</property> | ||||||
|  |               </packing> | ||||||
|             </child> |             </child> | ||||||
|           </object> |           </object> | ||||||
|           <packing> |           <packing> | ||||||
|  |  | ||||||
|  | @ -1,162 +0,0 @@ | ||||||
| use super::EnsembleEditor; |  | ||||||
| use crate::backend::Backend; |  | ||||||
| use crate::database::Ensemble; |  | ||||||
| use crate::widgets::List; |  | ||||||
| use gettextrs::gettext; |  | ||||||
| use gio::prelude::*; |  | ||||||
| use glib::clone; |  | ||||||
| use gtk::prelude::*; |  | ||||||
| use gtk_macros::get_widget; |  | ||||||
| use std::cell::RefCell; |  | ||||||
| use std::rc::Rc; |  | ||||||
| 
 |  | ||||||
| /// A dialog for selecting a ensemble.
 |  | ||||||
| pub struct EnsembleSelector { |  | ||||||
|     backend: Rc<Backend>, |  | ||||||
|     window: libhandy::Window, |  | ||||||
|     server_check_button: gtk::CheckButton, |  | ||||||
|     stack: gtk::Stack, |  | ||||||
|     list: Rc<List<Ensemble>>, |  | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(Ensemble) -> ()>>>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl EnsembleSelector { |  | ||||||
|     pub fn new<P>(backend: Rc<Backend>, parent: &P) -> Rc<Self> |  | ||||||
|     where |  | ||||||
|         P: IsA<gtk::Window>, |  | ||||||
|     { |  | ||||||
|         // Create UI
 |  | ||||||
| 
 |  | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/ensemble_selector.ui"); |  | ||||||
| 
 |  | ||||||
|         get_widget!(builder, libhandy::Window, window); |  | ||||||
|         get_widget!(builder, gtk::Button, add_button); |  | ||||||
|         get_widget!(builder, gtk::CheckButton, server_check_button); |  | ||||||
|         get_widget!(builder, gtk::SearchEntry, search_entry); |  | ||||||
|         get_widget!(builder, gtk::Stack, stack); |  | ||||||
|         get_widget!(builder, gtk::ScrolledWindow, scroll); |  | ||||||
|         get_widget!(builder, gtk::Button, try_again_button); |  | ||||||
| 
 |  | ||||||
|         window.set_transient_for(Some(parent)); |  | ||||||
| 
 |  | ||||||
|         let list = List::<Ensemble>::new(&gettext("No ensembles found.")); |  | ||||||
|         scroll.add(&list.widget); |  | ||||||
| 
 |  | ||||||
|         let this = Rc::new(Self { |  | ||||||
|             backend, |  | ||||||
|             window, |  | ||||||
|             server_check_button, |  | ||||||
|             stack, |  | ||||||
|             list, |  | ||||||
|             selected_cb: RefCell::new(None), |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         // Connect signals and callbacks
 |  | ||||||
| 
 |  | ||||||
|         add_button.connect_clicked(clone!(@strong this => move |_| { |  | ||||||
|             let editor = EnsembleEditor::new( |  | ||||||
|                 this.backend.clone(), |  | ||||||
|                 &this.window, |  | ||||||
|                 None, |  | ||||||
|             ); |  | ||||||
| 
 |  | ||||||
|             editor.set_saved_cb(clone!(@strong this => move |ensemble| { |  | ||||||
|                 if let Some(cb) = &*this.selected_cb.borrow() { |  | ||||||
|                     cb(ensemble); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 this.window.close(); |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|             editor.show(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         search_entry.connect_search_changed(clone!(@strong this => move |_| { |  | ||||||
|             this.list.invalidate_filter(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         let load_online = Rc::new(clone!(@strong this => move || { |  | ||||||
|             this.stack.set_visible_child_name("loading"); |  | ||||||
| 
 |  | ||||||
|             let context = glib::MainContext::default(); |  | ||||||
|             let clone = this.clone(); |  | ||||||
|             context.spawn_local(async move { |  | ||||||
|                 match clone.backend.get_ensembles().await { |  | ||||||
|                     Ok(ensembles) => { |  | ||||||
|                         clone.list.show_items(ensembles); |  | ||||||
|                         clone.stack.set_visible_child_name("content"); |  | ||||||
|                     } |  | ||||||
|                     Err(_) => { |  | ||||||
|                         clone.list.show_items(Vec::new()); |  | ||||||
|                         clone.stack.set_visible_child_name("error"); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         let load_local = Rc::new(clone!(@strong this => move || { |  | ||||||
|             this.stack.set_visible_child_name("loading"); |  | ||||||
| 
 |  | ||||||
|             let context = glib::MainContext::default(); |  | ||||||
|             let clone = this.clone(); |  | ||||||
|             context.spawn_local(async move { |  | ||||||
|                 let ensembles = clone.backend.db().get_ensembles().await.unwrap(); |  | ||||||
|                 clone.list.show_items(ensembles); |  | ||||||
|                 clone.stack.set_visible_child_name("content"); |  | ||||||
|             }); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         this.server_check_button.connect_toggled( |  | ||||||
|             clone!(@strong this, @strong load_local, @strong load_online => move |_| { |  | ||||||
|                 if this.server_check_button.get_active() { |  | ||||||
|                     load_online(); |  | ||||||
|                 } else { |  | ||||||
|                     load_local(); |  | ||||||
|                 } |  | ||||||
|             }), |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         this.list.set_make_widget(|ensemble: &Ensemble| { |  | ||||||
|             let label = gtk::Label::new(Some(&ensemble.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); |  | ||||||
|             label.upcast() |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         this.list |  | ||||||
|             .set_filter(clone!(@strong search_entry => move |ensemble: &Ensemble| { |  | ||||||
|                 let search = search_entry.get_text().to_string().to_lowercase(); |  | ||||||
|                 search.is_empty() || ensemble.name.contains(&search) |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         this.list.set_selected(clone!(@strong this => move |work| { |  | ||||||
|             if let Some(cb) = &*this.selected_cb.borrow() { |  | ||||||
|                 cb(work.clone()); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             this.window.close(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         try_again_button.connect_clicked(clone!(@strong load_online => move |_| { |  | ||||||
|             load_online(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         // Initialize
 |  | ||||||
|         load_online(); |  | ||||||
| 
 |  | ||||||
|         this |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Set the closure to be called when the user has selected a ensemble.
 |  | ||||||
|     pub fn set_selected_cb<F: Fn(Ensemble) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.selected_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Show the ensemble selector.
 |  | ||||||
|     pub fn show(&self) { |  | ||||||
|         self.window.show(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,162 +0,0 @@ | ||||||
| use super::InstrumentEditor; |  | ||||||
| use crate::backend::Backend; |  | ||||||
| use crate::database::Instrument; |  | ||||||
| use crate::widgets::List; |  | ||||||
| use gettextrs::gettext; |  | ||||||
| use gio::prelude::*; |  | ||||||
| use glib::clone; |  | ||||||
| use gtk::prelude::*; |  | ||||||
| use gtk_macros::get_widget; |  | ||||||
| use std::cell::RefCell; |  | ||||||
| use std::rc::Rc; |  | ||||||
| 
 |  | ||||||
| /// A dialog for selecting a instrument.
 |  | ||||||
| pub struct InstrumentSelector { |  | ||||||
|     backend: Rc<Backend>, |  | ||||||
|     window: libhandy::Window, |  | ||||||
|     server_check_button: gtk::CheckButton, |  | ||||||
|     stack: gtk::Stack, |  | ||||||
|     list: Rc<List<Instrument>>, |  | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(Instrument) -> ()>>>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl InstrumentSelector { |  | ||||||
|     pub fn new<P>(backend: Rc<Backend>, parent: &P) -> Rc<Self> |  | ||||||
|     where |  | ||||||
|         P: IsA<gtk::Window>, |  | ||||||
|     { |  | ||||||
|         // Create UI
 |  | ||||||
| 
 |  | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/instrument_selector.ui"); |  | ||||||
| 
 |  | ||||||
|         get_widget!(builder, libhandy::Window, window); |  | ||||||
|         get_widget!(builder, gtk::Button, add_button); |  | ||||||
|         get_widget!(builder, gtk::CheckButton, server_check_button); |  | ||||||
|         get_widget!(builder, gtk::SearchEntry, search_entry); |  | ||||||
|         get_widget!(builder, gtk::Stack, stack); |  | ||||||
|         get_widget!(builder, gtk::ScrolledWindow, scroll); |  | ||||||
|         get_widget!(builder, gtk::Button, try_again_button); |  | ||||||
| 
 |  | ||||||
|         window.set_transient_for(Some(parent)); |  | ||||||
| 
 |  | ||||||
|         let list = List::<Instrument>::new(&gettext("No instruments found.")); |  | ||||||
|         scroll.add(&list.widget); |  | ||||||
| 
 |  | ||||||
|         let this = Rc::new(Self { |  | ||||||
|             backend, |  | ||||||
|             window, |  | ||||||
|             server_check_button, |  | ||||||
|             stack, |  | ||||||
|             list, |  | ||||||
|             selected_cb: RefCell::new(None), |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         // Connect signals and callbacks
 |  | ||||||
| 
 |  | ||||||
|         add_button.connect_clicked(clone!(@strong this => move |_| { |  | ||||||
|             let editor = InstrumentEditor::new( |  | ||||||
|                 this.backend.clone(), |  | ||||||
|                 &this.window, |  | ||||||
|                 None, |  | ||||||
|             ); |  | ||||||
| 
 |  | ||||||
|             editor.set_saved_cb(clone!(@strong this => move |instrument| { |  | ||||||
|                 if let Some(cb) = &*this.selected_cb.borrow() { |  | ||||||
|                     cb(instrument); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 this.window.close(); |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|             editor.show(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         search_entry.connect_search_changed(clone!(@strong this => move |_| { |  | ||||||
|             this.list.invalidate_filter(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         let load_online = Rc::new(clone!(@strong this => move || { |  | ||||||
|             this.stack.set_visible_child_name("loading"); |  | ||||||
| 
 |  | ||||||
|             let context = glib::MainContext::default(); |  | ||||||
|             let clone = this.clone(); |  | ||||||
|             context.spawn_local(async move { |  | ||||||
|                 match clone.backend.get_instruments().await { |  | ||||||
|                     Ok(instruments) => { |  | ||||||
|                         clone.list.show_items(instruments); |  | ||||||
|                         clone.stack.set_visible_child_name("content"); |  | ||||||
|                     } |  | ||||||
|                     Err(_) => { |  | ||||||
|                         clone.list.show_items(Vec::new()); |  | ||||||
|                         clone.stack.set_visible_child_name("error"); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         let load_local = Rc::new(clone!(@strong this => move || { |  | ||||||
|             this.stack.set_visible_child_name("loading"); |  | ||||||
| 
 |  | ||||||
|             let context = glib::MainContext::default(); |  | ||||||
|             let clone = this.clone(); |  | ||||||
|             context.spawn_local(async move { |  | ||||||
|                 let instruments = clone.backend.db().get_instruments().await.unwrap(); |  | ||||||
|                 clone.list.show_items(instruments); |  | ||||||
|                 clone.stack.set_visible_child_name("content"); |  | ||||||
|             }); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         this.server_check_button.connect_toggled( |  | ||||||
|             clone!(@strong this, @strong load_local, @strong load_online => move |_| { |  | ||||||
|                 if this.server_check_button.get_active() { |  | ||||||
|                     load_online(); |  | ||||||
|                 } else { |  | ||||||
|                     load_local(); |  | ||||||
|                 } |  | ||||||
|             }), |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         this.list.set_make_widget(|instrument: &Instrument| { |  | ||||||
|             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); |  | ||||||
|             label.upcast() |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         this.list |  | ||||||
|             .set_filter(clone!(@strong search_entry => move |instrument: &Instrument| { |  | ||||||
|                 let search = search_entry.get_text().to_string().to_lowercase(); |  | ||||||
|                 search.is_empty() || instrument.name.contains(&search) |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         this.list.set_selected(clone!(@strong this => move |work| { |  | ||||||
|             if let Some(cb) = &*this.selected_cb.borrow() { |  | ||||||
|                 cb(work.clone()); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             this.window.close(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         try_again_button.connect_clicked(clone!(@strong load_online => move |_| { |  | ||||||
|             load_online(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         // Initialize
 |  | ||||||
|         load_online(); |  | ||||||
| 
 |  | ||||||
|         this |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Set the closure to be called when the user has selected a instrument.
 |  | ||||||
|     pub fn set_selected_cb<F: Fn(Instrument) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.selected_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Show the instrument selector.
 |  | ||||||
|     pub fn show(&self) { |  | ||||||
|         self.window.show(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,41 +1,11 @@ | ||||||
| pub mod about; | pub mod about; | ||||||
| pub use about::*; | pub use about::*; | ||||||
| 
 | 
 | ||||||
| pub mod ensemble_editor; |  | ||||||
| pub use ensemble_editor::*; |  | ||||||
| 
 |  | ||||||
| pub mod ensemble_selector; |  | ||||||
| pub use ensemble_selector::*; |  | ||||||
| 
 |  | ||||||
| pub mod instrument_editor; |  | ||||||
| pub use instrument_editor::*; |  | ||||||
| 
 |  | ||||||
| pub mod instrument_selector; |  | ||||||
| pub use instrument_selector::*; |  | ||||||
| 
 |  | ||||||
| pub mod login_dialog; | pub mod login_dialog; | ||||||
| pub use login_dialog::*; | pub use login_dialog::*; | ||||||
| 
 | 
 | ||||||
| pub mod person_editor; |  | ||||||
| pub use person_editor::*; |  | ||||||
| 
 |  | ||||||
| pub mod person_selector; |  | ||||||
| pub use person_selector::*; |  | ||||||
| 
 |  | ||||||
| pub mod preferences; | pub mod preferences; | ||||||
| pub use preferences::*; | pub use preferences::*; | ||||||
| 
 | 
 | ||||||
| pub mod server_dialog; | pub mod server_dialog; | ||||||
| pub use server_dialog::*; | pub use server_dialog::*; | ||||||
| 
 |  | ||||||
| pub mod recording; |  | ||||||
| pub use recording::*; |  | ||||||
| 
 |  | ||||||
| pub mod track_editor; |  | ||||||
| pub use track_editor::*; |  | ||||||
| 
 |  | ||||||
| pub mod tracks_editor; |  | ||||||
| pub use tracks_editor::*; |  | ||||||
| 
 |  | ||||||
| pub mod work; |  | ||||||
| pub use work::*; |  | ||||||
|  |  | ||||||
|  | @ -1,163 +0,0 @@ | ||||||
| use super::PersonEditor; |  | ||||||
| use crate::backend::Backend; |  | ||||||
| use crate::database::Person; |  | ||||||
| use crate::widgets::List; |  | ||||||
| use gettextrs::gettext; |  | ||||||
| use gio::prelude::*; |  | ||||||
| use glib::clone; |  | ||||||
| use gtk::prelude::*; |  | ||||||
| use gtk_macros::get_widget; |  | ||||||
| use std::cell::RefCell; |  | ||||||
| use std::rc::Rc; |  | ||||||
| 
 |  | ||||||
| /// A dialog for selecting a person.
 |  | ||||||
| pub struct PersonSelector { |  | ||||||
|     backend: Rc<Backend>, |  | ||||||
|     window: libhandy::Window, |  | ||||||
|     server_check_button: gtk::CheckButton, |  | ||||||
|     stack: gtk::Stack, |  | ||||||
|     list: Rc<List<Person>>, |  | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(Person) -> ()>>>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl PersonSelector { |  | ||||||
|     pub fn new<P>(backend: Rc<Backend>, parent: &P) -> Rc<Self> |  | ||||||
|     where |  | ||||||
|         P: IsA<gtk::Window>, |  | ||||||
|     { |  | ||||||
|         // Create UI
 |  | ||||||
| 
 |  | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/person_selector.ui"); |  | ||||||
| 
 |  | ||||||
|         get_widget!(builder, libhandy::Window, window); |  | ||||||
|         get_widget!(builder, gtk::Button, add_button); |  | ||||||
|         get_widget!(builder, gtk::CheckButton, server_check_button); |  | ||||||
|         get_widget!(builder, gtk::SearchEntry, search_entry); |  | ||||||
|         get_widget!(builder, gtk::Stack, stack); |  | ||||||
|         get_widget!(builder, gtk::ScrolledWindow, scroll); |  | ||||||
|         get_widget!(builder, gtk::Button, try_again_button); |  | ||||||
| 
 |  | ||||||
|         window.set_transient_for(Some(parent)); |  | ||||||
| 
 |  | ||||||
|         let list = List::<Person>::new(&gettext("No persons found.")); |  | ||||||
|         scroll.add(&list.widget); |  | ||||||
| 
 |  | ||||||
|         let this = Rc::new(Self { |  | ||||||
|             backend, |  | ||||||
|             window, |  | ||||||
|             server_check_button, |  | ||||||
|             stack, |  | ||||||
|             list, |  | ||||||
|             selected_cb: RefCell::new(None), |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         // Connect signals and callbacks
 |  | ||||||
| 
 |  | ||||||
|         add_button.connect_clicked(clone!(@strong this => move |_| { |  | ||||||
|             let editor = PersonEditor::new( |  | ||||||
|                 this.backend.clone(), |  | ||||||
|                 &this.window, |  | ||||||
|                 None, |  | ||||||
|             ); |  | ||||||
| 
 |  | ||||||
|             editor.set_saved_cb(clone!(@strong this => move |person| { |  | ||||||
|                 if let Some(cb) = &*this.selected_cb.borrow() { |  | ||||||
|                     cb(person); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 this.window.close(); |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|             editor.show(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         search_entry.connect_search_changed(clone!(@strong this => move |_| { |  | ||||||
|             this.list.invalidate_filter(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         let load_online = Rc::new(clone!(@strong this => move || { |  | ||||||
|             this.stack.set_visible_child_name("loading"); |  | ||||||
| 
 |  | ||||||
|             let context = glib::MainContext::default(); |  | ||||||
|             let clone = this.clone(); |  | ||||||
|             context.spawn_local(async move { |  | ||||||
|                 match clone.backend.get_persons().await { |  | ||||||
|                     Ok(persons) => { |  | ||||||
|                         clone.list.show_items(persons); |  | ||||||
|                         clone.stack.set_visible_child_name("content"); |  | ||||||
|                     } |  | ||||||
|                     Err(_) => { |  | ||||||
|                         clone.list.show_items(Vec::new()); |  | ||||||
|                         clone.stack.set_visible_child_name("error"); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         let load_local = Rc::new(clone!(@strong this => move || { |  | ||||||
|             this.stack.set_visible_child_name("loading"); |  | ||||||
| 
 |  | ||||||
|             let context = glib::MainContext::default(); |  | ||||||
|             let clone = this.clone(); |  | ||||||
|             context.spawn_local(async move { |  | ||||||
|                 let persons = clone.backend.db().get_persons().await.unwrap(); |  | ||||||
|                 clone.list.show_items(persons); |  | ||||||
|                 clone.stack.set_visible_child_name("content"); |  | ||||||
|             }); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         this.server_check_button.connect_toggled( |  | ||||||
|             clone!(@strong this, @strong load_local, @strong load_online => move |_| { |  | ||||||
|                 if this.server_check_button.get_active() { |  | ||||||
|                     load_online(); |  | ||||||
|                 } else { |  | ||||||
|                     load_local(); |  | ||||||
|                 } |  | ||||||
|             }), |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         this.list.set_make_widget(|person: &Person| { |  | ||||||
|             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); |  | ||||||
|             label.upcast() |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         this.list |  | ||||||
|             .set_filter(clone!(@strong search_entry => move |person: &Person| { |  | ||||||
|                 let search = search_entry.get_text().to_string().to_lowercase(); |  | ||||||
|                 let name = person.name_fl().to_lowercase(); |  | ||||||
|                 search.is_empty() || name.contains(&search) |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         this.list.set_selected(clone!(@strong this => move |person| { |  | ||||||
|             if let Some(cb) = &*this.selected_cb.borrow() { |  | ||||||
|                 cb(person.clone()); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             this.window.close(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         try_again_button.connect_clicked(clone!(@strong load_online => move |_| { |  | ||||||
|             load_online(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         // Initialize
 |  | ||||||
|         load_online(); |  | ||||||
| 
 |  | ||||||
|         this |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Set the closure to be called when the user has selected a person.
 |  | ||||||
|     pub fn set_selected_cb<F: Fn(Person) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.selected_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Show the person selector.
 |  | ||||||
|     pub fn show(&self) { |  | ||||||
|         self.window.show(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,11 +0,0 @@ | ||||||
| pub mod recording_dialog; |  | ||||||
| pub use recording_dialog::*; |  | ||||||
| 
 |  | ||||||
| pub mod recording_editor_dialog; |  | ||||||
| pub use recording_editor_dialog::*; |  | ||||||
| 
 |  | ||||||
| mod performance_editor; |  | ||||||
| mod recording_editor; |  | ||||||
| mod recording_selector; |  | ||||||
| mod recording_selector_person_screen; |  | ||||||
| mod recording_selector_work_screen; |  | ||||||
|  | @ -1,86 +0,0 @@ | ||||||
| use super::recording_editor::*; |  | ||||||
| use super::recording_selector::*; |  | ||||||
| use crate::backend::*; |  | ||||||
| use crate::database::*; |  | ||||||
| use glib::clone; |  | ||||||
| use gtk::prelude::*; |  | ||||||
| use std::cell::RefCell; |  | ||||||
| use std::rc::Rc; |  | ||||||
| 
 |  | ||||||
| /// A dialog for selecting and creating a recording.
 |  | ||||||
| pub struct RecordingDialog { |  | ||||||
|     pub window: libhandy::Window, |  | ||||||
|     stack: gtk::Stack, |  | ||||||
|     selector: Rc<RecordingSelector>, |  | ||||||
|     editor: Rc<RecordingEditor>, |  | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(Recording) -> ()>>>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl RecordingDialog { |  | ||||||
|     /// Create a new recording dialog.
 |  | ||||||
|     pub fn new<W: IsA<gtk::Window>>(backend: Rc<Backend>, parent: &W) -> Rc<Self> { |  | ||||||
|         // Create UI
 |  | ||||||
| 
 |  | ||||||
|         let window = libhandy::Window::new(); |  | ||||||
|         window.set_type_hint(gdk::WindowTypeHint::Dialog); |  | ||||||
|         window.set_modal(true); |  | ||||||
|         window.set_transient_for(Some(parent)); |  | ||||||
|         window.set_default_size(600, 424); |  | ||||||
| 
 |  | ||||||
|         let selector = RecordingSelector::new(backend.clone()); |  | ||||||
|         let editor = RecordingEditor::new(backend.clone(), &window, None); |  | ||||||
| 
 |  | ||||||
|         let stack = gtk::Stack::new(); |  | ||||||
|         stack.set_transition_type(gtk::StackTransitionType::Crossfade); |  | ||||||
|         stack.add(&selector.widget); |  | ||||||
|         stack.add(&editor.widget); |  | ||||||
|         window.add(&stack); |  | ||||||
|         window.show_all(); |  | ||||||
| 
 |  | ||||||
|         let this = Rc::new(Self { |  | ||||||
|             window, |  | ||||||
|             stack, |  | ||||||
|             selector, |  | ||||||
|             editor, |  | ||||||
|             selected_cb: RefCell::new(None), |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         // Connect signals and callbacks
 |  | ||||||
| 
 |  | ||||||
|         this.selector.set_add_cb(clone!(@strong this => move || { |  | ||||||
|             this.stack.set_visible_child(&this.editor.widget); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         this.selector |  | ||||||
|             .set_selected_cb(clone!(@strong this => move |recording| { |  | ||||||
|                 if let Some(cb) = &*this.selected_cb.borrow() { |  | ||||||
|                     cb(recording); |  | ||||||
|                     this.window.close(); |  | ||||||
|                 } |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         this.editor.set_back_cb(clone!(@strong this => move || { |  | ||||||
|             this.stack.set_visible_child(&this.selector.widget); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         this.editor |  | ||||||
|             .set_selected_cb(clone!(@strong this => move |recording| { |  | ||||||
|                 if let Some(cb) = &*this.selected_cb.borrow() { |  | ||||||
|                     cb(recording); |  | ||||||
|                     this.window.close(); |  | ||||||
|                 } |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         this |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Set the closure to be called when the user has selected or created a recording.
 |  | ||||||
|     pub fn set_selected_cb<F: Fn(Recording) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.selected_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Show the recording dialog.
 |  | ||||||
|     pub fn show(&self) { |  | ||||||
|         self.window.show(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,64 +0,0 @@ | ||||||
| use super::recording_editor::*; |  | ||||||
| use crate::backend::*; |  | ||||||
| use crate::database::*; |  | ||||||
| use glib::clone; |  | ||||||
| use gtk::prelude::*; |  | ||||||
| use std::cell::RefCell; |  | ||||||
| use std::rc::Rc; |  | ||||||
| 
 |  | ||||||
| /// A dialog for creating or editing a recording.
 |  | ||||||
| pub struct RecordingEditorDialog { |  | ||||||
|     pub window: libhandy::Window, |  | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(Recording) -> ()>>>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl RecordingEditorDialog { |  | ||||||
|     /// Create a new recording editor dialog and optionally initialize it.
 |  | ||||||
|     pub fn new<W: IsA<gtk::Window>>( |  | ||||||
|         backend: Rc<Backend>, |  | ||||||
|         parent: &W, |  | ||||||
|         recording: Option<Recording>, |  | ||||||
|     ) -> Rc<Self> { |  | ||||||
|         // Create UI
 |  | ||||||
| 
 |  | ||||||
|         let window = libhandy::Window::new(); |  | ||||||
|         window.set_type_hint(gdk::WindowTypeHint::Dialog); |  | ||||||
|         window.set_modal(true); |  | ||||||
|         window.set_transient_for(Some(parent)); |  | ||||||
| 
 |  | ||||||
|         let editor = RecordingEditor::new(backend.clone(), &window, recording); |  | ||||||
|         window.add(&editor.widget); |  | ||||||
|         window.show_all(); |  | ||||||
| 
 |  | ||||||
|         let this = Rc::new(Self { |  | ||||||
|             window, |  | ||||||
|             selected_cb: RefCell::new(None), |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         // Connect signals and callbacks
 |  | ||||||
| 
 |  | ||||||
|         editor.set_back_cb(clone!(@strong this => move || { |  | ||||||
|             this.window.close(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         editor.set_selected_cb(clone!(@strong this => move |recording| { |  | ||||||
|             if let Some(cb) = &*this.selected_cb.borrow() { |  | ||||||
|                 cb(recording); |  | ||||||
|             } |  | ||||||
|             
 |  | ||||||
|             this.window.close(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         this |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Set the closure to be called when the user edited or created a recording.
 |  | ||||||
|     pub fn set_selected_cb<F: Fn(Recording) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.selected_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Show the recording editor dialog.
 |  | ||||||
|     pub fn show(&self) { |  | ||||||
|         self.window.show(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,174 +0,0 @@ | ||||||
| use super::recording_selector_person_screen::*; |  | ||||||
| use crate::backend::Backend; |  | ||||||
| use crate::database::*; |  | ||||||
| use crate::widgets::*; |  | ||||||
| use gettextrs::gettext; |  | ||||||
| use glib::clone; |  | ||||||
| use gtk::prelude::*; |  | ||||||
| use gtk_macros::get_widget; |  | ||||||
| use libhandy::prelude::*; |  | ||||||
| use std::cell::RefCell; |  | ||||||
| use std::rc::Rc; |  | ||||||
| 
 |  | ||||||
| /// A widget for selecting a recording from a list of existing ones.
 |  | ||||||
| pub struct RecordingSelector { |  | ||||||
|     pub widget: libhandy::Leaflet, |  | ||||||
|     backend: Rc<Backend>, |  | ||||||
|     sidebar_box: gtk::Box, |  | ||||||
|     server_check_button: gtk::CheckButton, |  | ||||||
|     stack: gtk::Stack, |  | ||||||
|     list: Rc<List<Person>>, |  | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(Recording) -> ()>>>, |  | ||||||
|     add_cb: RefCell<Option<Box<dyn Fn() -> ()>>>, |  | ||||||
|     navigator: Rc<Navigator>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl RecordingSelector { |  | ||||||
|     /// Create a new recording selector.
 |  | ||||||
|     pub fn new(backend: Rc<Backend>) -> Rc<Self> { |  | ||||||
|         // Create UI
 |  | ||||||
| 
 |  | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_selector.ui"); |  | ||||||
| 
 |  | ||||||
|         get_widget!(builder, libhandy::Leaflet, widget); |  | ||||||
|         get_widget!(builder, gtk::Button, add_button); |  | ||||||
|         get_widget!(builder, gtk::Box, sidebar_box); |  | ||||||
|         get_widget!(builder, gtk::CheckButton, server_check_button); |  | ||||||
|         get_widget!(builder, gtk::SearchEntry, search_entry); |  | ||||||
|         get_widget!(builder, gtk::Stack, stack); |  | ||||||
|         get_widget!(builder, gtk::ScrolledWindow, scroll); |  | ||||||
|         get_widget!(builder, gtk::Button, try_again_button); |  | ||||||
|         get_widget!(builder, gtk::Box, empty_screen); |  | ||||||
| 
 |  | ||||||
|         let list = List::<Person>::new(&gettext("No persons found.")); |  | ||||||
|         scroll.add(&list.widget); |  | ||||||
| 
 |  | ||||||
|         let navigator = Navigator::new(&empty_screen); |  | ||||||
|         widget.add(&navigator.widget); |  | ||||||
| 
 |  | ||||||
|         let this = Rc::new(Self { |  | ||||||
|             widget, |  | ||||||
|             backend, |  | ||||||
|             sidebar_box, |  | ||||||
|             server_check_button, |  | ||||||
|             stack, |  | ||||||
|             list, |  | ||||||
|             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(); |  | ||||||
|             } |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         search_entry.connect_search_changed(clone!(@strong this => move |_| { |  | ||||||
|             this.list.invalidate_filter(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         let load_online = Rc::new(clone!(@strong this => move || { |  | ||||||
|             this.stack.set_visible_child_name("loading"); |  | ||||||
| 
 |  | ||||||
|             let context = glib::MainContext::default(); |  | ||||||
|             let clone = this.clone(); |  | ||||||
|             context.spawn_local(async move { |  | ||||||
|                 match clone.backend.get_persons().await { |  | ||||||
|                     Ok(persons) => { |  | ||||||
|                         clone.list.show_items(persons); |  | ||||||
|                         clone.stack.set_visible_child_name("content"); |  | ||||||
|                     } |  | ||||||
|                     Err(_) => { |  | ||||||
|                         clone.list.show_items(Vec::new()); |  | ||||||
|                         clone.stack.set_visible_child_name("error"); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         let load_local = Rc::new(clone!(@strong this => move || { |  | ||||||
|             this.stack.set_visible_child_name("loading"); |  | ||||||
| 
 |  | ||||||
|             let context = glib::MainContext::default(); |  | ||||||
|             let clone = this.clone(); |  | ||||||
|             context.spawn_local(async move { |  | ||||||
|                 let persons = clone.backend.db().get_persons().await.unwrap(); |  | ||||||
|                 clone.list.show_items(persons); |  | ||||||
|                 clone.stack.set_visible_child_name("content"); |  | ||||||
|             }); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         this.server_check_button.connect_toggled( |  | ||||||
|             clone!(@strong this, @strong load_local, @strong load_online => move |_| { |  | ||||||
|                 if this.server_check_button.get_active() { |  | ||||||
|                     load_online(); |  | ||||||
|                 } else { |  | ||||||
|                     load_local(); |  | ||||||
|                 } |  | ||||||
|             }), |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         this.list.set_make_widget(|person: &Person| { |  | ||||||
|             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); |  | ||||||
|             label.upcast() |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         this.list |  | ||||||
|             .set_filter(clone!(@strong search_entry => move |person: &Person| { |  | ||||||
|                 let search = search_entry.get_text().to_string().to_lowercase(); |  | ||||||
|                 let name = person.name_fl().to_lowercase(); |  | ||||||
|                 search.is_empty() || name.contains(&search) |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         this.list |  | ||||||
|             .set_selected(clone!(@strong this => move |person| { |  | ||||||
|                 let online = this.server_check_button.get_active(); |  | ||||||
| 
 |  | ||||||
|                 let person_screen = RecordingSelectorPersonScreen::new( |  | ||||||
|                     this.backend.clone(), |  | ||||||
|                     person.clone(), |  | ||||||
|                     online, |  | ||||||
|                 ); |  | ||||||
| 
 |  | ||||||
|                 person_screen.set_selected_cb(clone!(@strong this => move |work| { |  | ||||||
|                     if let Some(cb) = &*this.selected_cb.borrow() { |  | ||||||
|                         cb(work); |  | ||||||
|                     } |  | ||||||
|                 })); |  | ||||||
| 
 |  | ||||||
|                 this.navigator.clone().replace(person_screen); |  | ||||||
|                 this.widget.set_visible_child(&this.navigator.widget); |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         try_again_button.connect_clicked(clone!(@strong load_online => move |_| { |  | ||||||
|             load_online(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         this.navigator.set_back_cb(clone!(@strong this => move || { |  | ||||||
|             this.widget.set_visible_child(&this.sidebar_box); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         // Initialize
 |  | ||||||
|         load_online(); |  | ||||||
| 
 |  | ||||||
|         this |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// 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))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Set the closure to be called when the user has selected a recording.
 |  | ||||||
|     pub fn set_selected_cb<F: Fn(Recording) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.selected_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,161 +0,0 @@ | ||||||
| use super::recording_selector_work_screen::*; |  | ||||||
| use crate::backend::*; |  | ||||||
| use crate::database::*; |  | ||||||
| use crate::widgets::*; |  | ||||||
| use gettextrs::gettext; |  | ||||||
| use glib::clone; |  | ||||||
| use gtk::prelude::*; |  | ||||||
| use gtk_macros::get_widget; |  | ||||||
| use libhandy::HeaderBarExt; |  | ||||||
| use std::cell::RefCell; |  | ||||||
| use std::rc::Rc; |  | ||||||
| 
 |  | ||||||
| /// A screen within the recording selector that presents a list of works and switches to a work
 |  | ||||||
| /// screen on selection.
 |  | ||||||
| pub struct RecordingSelectorPersonScreen { |  | ||||||
|     backend: Rc<Backend>, |  | ||||||
|     person: Person, |  | ||||||
|     online: bool, |  | ||||||
|     widget: gtk::Box, |  | ||||||
|     stack: gtk::Stack, |  | ||||||
|     work_list: Rc<List<Work>>, |  | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(Recording) -> ()>>>, |  | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl RecordingSelectorPersonScreen { |  | ||||||
|     /// Create a new recording selector person screen.
 |  | ||||||
|     pub fn new(backend: Rc<Backend>, person: Person, online: bool) -> Rc<Self> { |  | ||||||
|         // Create UI
 |  | ||||||
| 
 |  | ||||||
|         let builder = |  | ||||||
|             gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_selector_screen.ui"); |  | ||||||
| 
 |  | ||||||
|         get_widget!(builder, gtk::Box, widget); |  | ||||||
|         get_widget!(builder, libhandy::HeaderBar, header); |  | ||||||
|         get_widget!(builder, gtk::Button, back_button); |  | ||||||
|         get_widget!(builder, gtk::Stack, stack); |  | ||||||
|         get_widget!(builder, gtk::ScrolledWindow, scroll); |  | ||||||
|         get_widget!(builder, gtk::Button, try_again_button); |  | ||||||
| 
 |  | ||||||
|         header.set_title(Some(&person.name_fl())); |  | ||||||
| 
 |  | ||||||
|         let work_list = List::new(&gettext("No works found.")); |  | ||||||
|         scroll.add(&work_list.widget); |  | ||||||
| 
 |  | ||||||
|         let this = Rc::new(Self { |  | ||||||
|             backend, |  | ||||||
|             person, |  | ||||||
|             online, |  | ||||||
|             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(); |  | ||||||
|             } |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         let load_online = Rc::new(clone!(@strong this => move || { |  | ||||||
|             this.stack.set_visible_child_name("loading"); |  | ||||||
| 
 |  | ||||||
|             let context = glib::MainContext::default(); |  | ||||||
|             let clone = this.clone(); |  | ||||||
|             context.spawn_local(async move { |  | ||||||
|                 match clone.backend.get_works(&clone.person.id).await { |  | ||||||
|                     Ok(works) => { |  | ||||||
|                         clone.work_list.show_items(works); |  | ||||||
|                         clone.stack.set_visible_child_name("content"); |  | ||||||
|                     } |  | ||||||
|                     Err(_) => { |  | ||||||
|                         clone.work_list.show_items(Vec::new()); |  | ||||||
|                         clone.stack.set_visible_child_name("error"); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         let load_local = Rc::new(clone!(@strong this => move || { |  | ||||||
|             this.stack.set_visible_child_name("loading"); |  | ||||||
| 
 |  | ||||||
|             let context = glib::MainContext::default(); |  | ||||||
|             let clone = this.clone(); |  | ||||||
|             context.spawn_local(async move { |  | ||||||
|                 let works = clone.backend.db().get_works(&clone.person.id).await.unwrap(); |  | ||||||
|                 clone.work_list.show_items(works); |  | ||||||
|                 clone.stack.set_visible_child_name("content"); |  | ||||||
|             }); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         this.work_list.set_make_widget(|work: &Work| { |  | ||||||
|             let label = gtk::Label::new(Some(&work.title)); |  | ||||||
|             label.set_ellipsize(pango::EllipsizeMode::End); |  | ||||||
|             label.set_halign(gtk::Align::Start); |  | ||||||
|             label.set_margin_start(6); |  | ||||||
|             label.set_margin_end(6); |  | ||||||
|             label.set_margin_top(6); |  | ||||||
|             label.set_margin_bottom(6); |  | ||||||
|             label.upcast() |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         this.work_list |  | ||||||
|             .set_selected(clone!(@strong this => move |work| { |  | ||||||
|                 let navigator = this.navigator.borrow().clone(); |  | ||||||
|                 if let Some(navigator) = navigator { |  | ||||||
|                     let work_screen = RecordingSelectorWorkScreen::new( |  | ||||||
|                         this.backend.clone(), |  | ||||||
|                         work.clone(), |  | ||||||
|                         this.online, |  | ||||||
|                     ); |  | ||||||
| 
 |  | ||||||
|                     work_screen.set_selected_cb(clone!(@strong this => move |recording| { |  | ||||||
|                         if let Some(cb) = &*this.selected_cb.borrow() { |  | ||||||
|                             cb(recording); |  | ||||||
|                         } |  | ||||||
|                     })); |  | ||||||
| 
 |  | ||||||
|                     navigator.push(work_screen); |  | ||||||
|                 } |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         try_again_button.connect_clicked(clone!(@strong load_online => move |_| { |  | ||||||
|             load_online(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         // Initialize
 |  | ||||||
| 
 |  | ||||||
|         if this.online { |  | ||||||
|             load_online(); |  | ||||||
|         } else { |  | ||||||
|             load_local(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         this |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Sets a closure to be called when the user has selected a recording.
 |  | ||||||
|     pub fn set_selected_cb<F: Fn(Recording) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.selected_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl NavigatorScreen for RecordingSelectorPersonScreen { |  | ||||||
|     fn attach_navigator(&self, navigator: Rc<Navigator>) { |  | ||||||
|         self.navigator.replace(Some(navigator)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_widget(&self) -> gtk::Widget { |  | ||||||
|         self.widget.clone().upcast() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn detach_navigator(&self) { |  | ||||||
|         self.navigator.replace(None); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,155 +0,0 @@ | ||||||
| use crate::backend::*; |  | ||||||
| use crate::database::*; |  | ||||||
| use crate::widgets::*; |  | ||||||
| use gettextrs::gettext; |  | ||||||
| use glib::clone; |  | ||||||
| use gtk::prelude::*; |  | ||||||
| use gtk_macros::get_widget; |  | ||||||
| use libhandy::HeaderBarExt; |  | ||||||
| use std::cell::RefCell; |  | ||||||
| use std::rc::Rc; |  | ||||||
| 
 |  | ||||||
| /// A screen within the recording selector presenting a list of recordings for a work.
 |  | ||||||
| pub struct RecordingSelectorWorkScreen { |  | ||||||
|     backend: Rc<Backend>, |  | ||||||
|     work: Work, |  | ||||||
|     online: bool, |  | ||||||
|     widget: gtk::Box, |  | ||||||
|     stack: gtk::Stack, |  | ||||||
|     recording_list: Rc<List<Recording>>, |  | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(Recording) -> ()>>>, |  | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl RecordingSelectorWorkScreen { |  | ||||||
|     /// Create a new recording selector work screen.
 |  | ||||||
|     pub fn new(backend: Rc<Backend>, work: Work, online: bool) -> Rc<Self> { |  | ||||||
|         // Create UI
 |  | ||||||
| 
 |  | ||||||
|         let builder = |  | ||||||
|             gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_selector_screen.ui"); |  | ||||||
| 
 |  | ||||||
|         get_widget!(builder, gtk::Box, widget); |  | ||||||
|         get_widget!(builder, libhandy::HeaderBar, header); |  | ||||||
|         get_widget!(builder, gtk::Button, back_button); |  | ||||||
|         get_widget!(builder, gtk::Stack, stack); |  | ||||||
|         get_widget!(builder, gtk::ScrolledWindow, scroll); |  | ||||||
|         get_widget!(builder, gtk::Button, try_again_button); |  | ||||||
| 
 |  | ||||||
|         header.set_title(Some(&work.title)); |  | ||||||
|         header.set_subtitle(Some(&work.composer.name_fl())); |  | ||||||
| 
 |  | ||||||
|         let recording_list = List::new(&gettext("No recordings found.")); |  | ||||||
|         scroll.add(&recording_list.widget); |  | ||||||
| 
 |  | ||||||
|         let this = Rc::new(Self { |  | ||||||
|             backend, |  | ||||||
|             work, |  | ||||||
|             online, |  | ||||||
|             widget, |  | ||||||
|             stack, |  | ||||||
|             recording_list, |  | ||||||
|             selected_cb: RefCell::new(None), |  | ||||||
|             navigator: RefCell::new(None), |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         // Connect signals and callbacks
 |  | ||||||
| 
 |  | ||||||
|         back_button.connect_clicked(clone!(@strong this => move |_| { |  | ||||||
|             let navigator = this.navigator.borrow().clone(); |  | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         let load_online = Rc::new(clone!(@strong this => move || { |  | ||||||
|             this.stack.set_visible_child_name("loading"); |  | ||||||
| 
 |  | ||||||
|             let context = glib::MainContext::default(); |  | ||||||
|             let clone = this.clone(); |  | ||||||
|             context.spawn_local(async move { |  | ||||||
|                 match clone.backend.get_recordings_for_work(&clone.work.id).await { |  | ||||||
|                     Ok(recordings) => { |  | ||||||
|                         clone.recording_list.show_items(recordings); |  | ||||||
|                         clone.stack.set_visible_child_name("content"); |  | ||||||
|                     } |  | ||||||
|                     Err(_) => { |  | ||||||
|                         clone.recording_list.show_items(Vec::new()); |  | ||||||
|                         clone.stack.set_visible_child_name("error"); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         let load_local = Rc::new(clone!(@strong this => move || { |  | ||||||
|             this.stack.set_visible_child_name("loading"); |  | ||||||
| 
 |  | ||||||
|             let context = glib::MainContext::default(); |  | ||||||
|             let clone = this.clone(); |  | ||||||
|             context.spawn_local(async move { |  | ||||||
|                 let recordings = clone.backend.db().get_recordings_for_work(&clone.work.id).await.unwrap(); |  | ||||||
|                 clone.recording_list.show_items(recordings); |  | ||||||
|                 clone.stack.set_visible_child_name("content"); |  | ||||||
|             }); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         this.recording_list |  | ||||||
|             .set_make_widget(|recording: &Recording| { |  | ||||||
|                 let work_label = gtk::Label::new(Some(&recording.work.get_title())); |  | ||||||
|                 work_label.set_ellipsize(pango::EllipsizeMode::End); |  | ||||||
|                 work_label.set_halign(gtk::Align::Start); |  | ||||||
| 
 |  | ||||||
|                 let performers_label = gtk::Label::new(Some(&recording.get_performers())); |  | ||||||
|                 performers_label.set_ellipsize(pango::EllipsizeMode::End); |  | ||||||
|                 performers_label.set_opacity(0.5); |  | ||||||
|                 performers_label.set_halign(gtk::Align::Start); |  | ||||||
| 
 |  | ||||||
|                 let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); |  | ||||||
|                 vbox.set_border_width(6); |  | ||||||
|                 vbox.add(&work_label); |  | ||||||
|                 vbox.add(&performers_label); |  | ||||||
| 
 |  | ||||||
|                 vbox.upcast() |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|         this.recording_list |  | ||||||
|             .set_selected(clone!(@strong this => move |recording| { |  | ||||||
|                 if let Some(cb) = &*this.selected_cb.borrow() { |  | ||||||
|                     cb(recording.clone()); |  | ||||||
|                 } |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         try_again_button.connect_clicked(clone!(@strong load_online => move |_| { |  | ||||||
|             load_online(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         // Initialize
 |  | ||||||
| 
 |  | ||||||
|         if this.online { |  | ||||||
|             load_online(); |  | ||||||
|         } else { |  | ||||||
|             load_local(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         this |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Sets a closure to be called when the user has selected a recording.
 |  | ||||||
|     pub fn set_selected_cb<F: Fn(Recording) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.selected_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl NavigatorScreen for RecordingSelectorWorkScreen { |  | ||||||
|     fn attach_navigator(&self, navigator: Rc<Navigator>) { |  | ||||||
|         self.navigator.replace(Some(navigator)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_widget(&self) -> gtk::Widget { |  | ||||||
|         self.widget.clone().upcast() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn detach_navigator(&self) { |  | ||||||
|         self.navigator.replace(None); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,11 +0,0 @@ | ||||||
| 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; |  | ||||||
|  | @ -1,86 +0,0 @@ | ||||||
| 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(Work) -> ()>>>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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(Work) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.selected_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Show the work dialog.
 |  | ||||||
|     pub fn show(&self) { |  | ||||||
|         self.window.show(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,64 +0,0 @@ | ||||||
| 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(Work) -> ()>>>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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<Work>, |  | ||||||
|     ) -> 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(Work) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.saved_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Show the work editor dialog.
 |  | ||||||
|     pub fn show(&self) { |  | ||||||
|         self.window.show(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,174 +0,0 @@ | ||||||
| use super::work_selector_person_screen::*; |  | ||||||
| use crate::backend::Backend; |  | ||||||
| use crate::database::*; |  | ||||||
| use crate::widgets::*; |  | ||||||
| use gettextrs::gettext; |  | ||||||
| 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, |  | ||||||
|     server_check_button: gtk::CheckButton, |  | ||||||
|     stack: gtk::Stack, |  | ||||||
|     list: Rc<List<Person>>, |  | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(Work) -> ()>>>, |  | ||||||
|     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::CheckButton, server_check_button); |  | ||||||
|         get_widget!(builder, gtk::SearchEntry, search_entry); |  | ||||||
|         get_widget!(builder, gtk::Stack, stack); |  | ||||||
|         get_widget!(builder, gtk::ScrolledWindow, scroll); |  | ||||||
|         get_widget!(builder, gtk::Button, try_again_button); |  | ||||||
|         get_widget!(builder, gtk::Box, empty_screen); |  | ||||||
| 
 |  | ||||||
|         let list = List::<Person>::new(&gettext("No persons found.")); |  | ||||||
|         scroll.add(&list.widget); |  | ||||||
| 
 |  | ||||||
|         let navigator = Navigator::new(&empty_screen); |  | ||||||
|         widget.add(&navigator.widget); |  | ||||||
| 
 |  | ||||||
|         let this = Rc::new(Self { |  | ||||||
|             widget, |  | ||||||
|             backend, |  | ||||||
|             sidebar_box, |  | ||||||
|             server_check_button, |  | ||||||
|             stack, |  | ||||||
|             list, |  | ||||||
|             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(); |  | ||||||
|             } |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         search_entry.connect_search_changed(clone!(@strong this => move |_| { |  | ||||||
|             this.list.invalidate_filter(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         let load_online = Rc::new(clone!(@strong this => move || { |  | ||||||
|             this.stack.set_visible_child_name("loading"); |  | ||||||
| 
 |  | ||||||
|             let context = glib::MainContext::default(); |  | ||||||
|             let clone = this.clone(); |  | ||||||
|             context.spawn_local(async move { |  | ||||||
|                 match clone.backend.get_persons().await { |  | ||||||
|                     Ok(persons) => { |  | ||||||
|                         clone.list.show_items(persons); |  | ||||||
|                         clone.stack.set_visible_child_name("content"); |  | ||||||
|                     } |  | ||||||
|                     Err(_) => { |  | ||||||
|                         clone.list.show_items(Vec::new()); |  | ||||||
|                         clone.stack.set_visible_child_name("error"); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         let load_local = Rc::new(clone!(@strong this => move || { |  | ||||||
|             this.stack.set_visible_child_name("loading"); |  | ||||||
| 
 |  | ||||||
|             let context = glib::MainContext::default(); |  | ||||||
|             let clone = this.clone(); |  | ||||||
|             context.spawn_local(async move { |  | ||||||
|                 let persons = clone.backend.db().get_persons().await.unwrap(); |  | ||||||
|                 clone.list.show_items(persons); |  | ||||||
|                 clone.stack.set_visible_child_name("content"); |  | ||||||
|             }); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         this.server_check_button.connect_toggled( |  | ||||||
|             clone!(@strong this, @strong load_local, @strong load_online => move |_| { |  | ||||||
|                 if this.server_check_button.get_active() { |  | ||||||
|                     load_online(); |  | ||||||
|                 } else { |  | ||||||
|                     load_local(); |  | ||||||
|                 } |  | ||||||
|             }), |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         this.list.set_make_widget(|person: &Person| { |  | ||||||
|             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); |  | ||||||
|             label.upcast() |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         this.list |  | ||||||
|             .set_filter(clone!(@strong search_entry => move |person: &Person| { |  | ||||||
|                 let search = search_entry.get_text().to_string().to_lowercase(); |  | ||||||
|                 let name = person.name_fl().to_lowercase(); |  | ||||||
|                 search.is_empty() || name.contains(&search) |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         this.list |  | ||||||
|             .set_selected(clone!(@strong this => move |person| { |  | ||||||
|                 let online = this.server_check_button.get_active(); |  | ||||||
| 
 |  | ||||||
|                 let person_screen = WorkSelectorPersonScreen::new( |  | ||||||
|                     this.backend.clone(), |  | ||||||
|                     person.clone(), |  | ||||||
|                     online, |  | ||||||
|                 ); |  | ||||||
| 
 |  | ||||||
|                 person_screen.set_selected_cb(clone!(@strong this => move |work| { |  | ||||||
|                     if let Some(cb) = &*this.selected_cb.borrow() { |  | ||||||
|                         cb(work); |  | ||||||
|                     } |  | ||||||
|                 })); |  | ||||||
| 
 |  | ||||||
|                 this.navigator.clone().replace(person_screen); |  | ||||||
|                 this.widget.set_visible_child(&this.navigator.widget); |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         try_again_button.connect_clicked(clone!(@strong load_online => move |_| { |  | ||||||
|             load_online(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         this.navigator.set_back_cb(clone!(@strong this => move || { |  | ||||||
|             this.widget.set_visible_child(&this.sidebar_box); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         // Initialize
 |  | ||||||
|         load_online(); |  | ||||||
| 
 |  | ||||||
|         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(Work) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.selected_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,145 +0,0 @@ | ||||||
| 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>, |  | ||||||
|     person: Person, |  | ||||||
|     online: bool, |  | ||||||
|     widget: gtk::Box, |  | ||||||
|     stack: gtk::Stack, |  | ||||||
|     work_list: Rc<List<Work>>, |  | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(Work) -> ()>>>, |  | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl WorkSelectorPersonScreen { |  | ||||||
|     /// Create a new work selector person screen.
 |  | ||||||
|     pub fn new(backend: Rc<Backend>, person: Person, online: bool) -> 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); |  | ||||||
|         get_widget!(builder, gtk::ScrolledWindow, scroll); |  | ||||||
|         get_widget!(builder, gtk::Button, try_again_button); |  | ||||||
| 
 |  | ||||||
|         header.set_title(Some(&person.name_fl())); |  | ||||||
| 
 |  | ||||||
|         let work_list = List::new(&gettext("No works found.")); |  | ||||||
|         scroll.add(&work_list.widget); |  | ||||||
| 
 |  | ||||||
|         let this = Rc::new(Self { |  | ||||||
|             backend, |  | ||||||
|             person, |  | ||||||
|             online, |  | ||||||
|             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(); |  | ||||||
|             } |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         let load_online = Rc::new(clone!(@strong this => move || { |  | ||||||
|             this.stack.set_visible_child_name("loading"); |  | ||||||
| 
 |  | ||||||
|             let context = glib::MainContext::default(); |  | ||||||
|             let clone = this.clone(); |  | ||||||
|             context.spawn_local(async move { |  | ||||||
|                 match clone.backend.get_works(&clone.person.id).await { |  | ||||||
|                     Ok(works) => { |  | ||||||
|                         clone.work_list.show_items(works); |  | ||||||
|                         clone.stack.set_visible_child_name("content"); |  | ||||||
|                     } |  | ||||||
|                     Err(_) => { |  | ||||||
|                         clone.work_list.show_items(Vec::new()); |  | ||||||
|                         clone.stack.set_visible_child_name("error"); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         let load_local = Rc::new(clone!(@strong this => move || { |  | ||||||
|             this.stack.set_visible_child_name("loading"); |  | ||||||
| 
 |  | ||||||
|             let context = glib::MainContext::default(); |  | ||||||
|             let clone = this.clone(); |  | ||||||
|             context.spawn_local(async move { |  | ||||||
|                 let works = clone.backend.db().get_works(&clone.person.id).await.unwrap(); |  | ||||||
|                 clone.work_list.show_items(works); |  | ||||||
|                 clone.stack.set_visible_child_name("content"); |  | ||||||
|             }); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         this.work_list.set_make_widget(|work: &Work| { |  | ||||||
|             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| { |  | ||||||
|                 if let Some(cb) = &*this.selected_cb.borrow() { |  | ||||||
|                     cb(work.clone()); |  | ||||||
|                 } |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         try_again_button.connect_clicked(clone!(@strong load_online => move |_| { |  | ||||||
|             load_online(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         // Initialize
 |  | ||||||
| 
 |  | ||||||
|         if this.online { |  | ||||||
|             load_online(); |  | ||||||
|         } else { |  | ||||||
|             load_local(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         this |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Sets a closure to be called when the user has selected a work.
 |  | ||||||
|     pub fn set_selected_cb<F: Fn(Work) -> () + '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,5 +1,6 @@ | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
|  | use crate::widgets::{Navigator, NavigatorScreen}; | ||||||
| use anyhow::Result; | use anyhow::Result; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
|  | @ -11,29 +12,24 @@ use std::rc::Rc; | ||||||
| pub struct EnsembleEditor { | pub struct EnsembleEditor { | ||||||
|     backend: Rc<Backend>, |     backend: Rc<Backend>, | ||||||
|     id: String, |     id: String, | ||||||
|     window: libhandy::Window, |     widget: gtk::Stack, | ||||||
|     stack: gtk::Stack, |  | ||||||
|     info_bar: gtk::InfoBar, |     info_bar: gtk::InfoBar, | ||||||
|     name_entry: gtk::Entry, |     name_entry: gtk::Entry, | ||||||
|     upload_switch: gtk::Switch, |     upload_switch: gtk::Switch, | ||||||
|     saved_cb: RefCell<Option<Box<dyn Fn(Ensemble) -> ()>>>, |     saved_cb: RefCell<Option<Box<dyn Fn(Ensemble) -> ()>>>, | ||||||
|  |     navigator: RefCell<Option<Rc<Navigator>>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl EnsembleEditor { | impl EnsembleEditor { | ||||||
|     /// Create a new ensemble editor and optionally initialize it.
 |     /// Create a new ensemble editor and optionally initialize it.
 | ||||||
|     pub fn new<P: IsA<gtk::Window>>( |     pub fn new(backend: Rc<Backend>, ensemble: Option<Ensemble>) -> Rc<Self> { | ||||||
|         backend: Rc<Backend>, |  | ||||||
|         parent: &P, |  | ||||||
|         ensemble: Option<Ensemble>, |  | ||||||
|     ) -> Rc<Self> { |  | ||||||
|         // Create UI
 |         // Create UI
 | ||||||
| 
 | 
 | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/ensemble_editor.ui"); |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/ensemble_editor.ui"); | ||||||
| 
 | 
 | ||||||
|         get_widget!(builder, libhandy::Window, window); |         get_widget!(builder, gtk::Stack, widget); | ||||||
|         get_widget!(builder, gtk::Button, cancel_button); |         get_widget!(builder, gtk::Button, back_button); | ||||||
|         get_widget!(builder, gtk::Button, save_button); |         get_widget!(builder, gtk::Button, save_button); | ||||||
|         get_widget!(builder, gtk::Stack, stack); |  | ||||||
|         get_widget!(builder, gtk::InfoBar, info_bar); |         get_widget!(builder, gtk::InfoBar, info_bar); | ||||||
|         get_widget!(builder, gtk::Entry, name_entry); |         get_widget!(builder, gtk::Entry, name_entry); | ||||||
|         get_widget!(builder, gtk::Switch, upload_switch); |         get_widget!(builder, gtk::Switch, upload_switch); | ||||||
|  | @ -50,40 +46,44 @@ impl EnsembleEditor { | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|             backend, |             backend, | ||||||
|             id, |             id, | ||||||
|             window, |             widget, | ||||||
|             stack, |  | ||||||
|             info_bar, |             info_bar, | ||||||
|             name_entry, |             name_entry, | ||||||
|             upload_switch, |             upload_switch, | ||||||
|             saved_cb: RefCell::new(None), |             saved_cb: RefCell::new(None), | ||||||
|  |             navigator: RefCell::new(None), | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         cancel_button.connect_clicked(clone!(@strong this => move |_| { |         back_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             this.window.close(); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 navigator.pop(); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         save_button.connect_clicked(clone!(@strong this => move |_| { |         save_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             let context = glib::MainContext::default(); |             let context = glib::MainContext::default(); | ||||||
|             let clone = this.clone(); |             let clone = this.clone(); | ||||||
|             context.spawn_local(async move { |             context.spawn_local(async move { | ||||||
|                 clone.stack.set_visible_child_name("loading"); |                 clone.widget.set_visible_child_name("loading"); | ||||||
|                 match clone.clone().save().await { |                 match clone.clone().save().await { | ||||||
|                     Ok(_) => { |                     Ok(_) => { | ||||||
|                         clone.window.close(); |                         let navigator = clone.navigator.borrow().clone(); | ||||||
|  |                         if let Some(navigator) = navigator { | ||||||
|  |                             navigator.pop(); | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                     Err(_) => { |                     Err(_) => { | ||||||
|                         clone.info_bar.set_revealed(true); |                         clone.info_bar.set_revealed(true); | ||||||
|                         clone.stack.set_visible_child_name("content"); |                         clone.widget.set_visible_child_name("content"); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|             }); |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.window.set_transient_for(Some(parent)); |  | ||||||
| 
 |  | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -92,11 +92,6 @@ impl EnsembleEditor { | ||||||
|         self.saved_cb.replace(Some(Box::new(cb))); |         self.saved_cb.replace(Some(Box::new(cb))); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Show the ensemble editor.
 |  | ||||||
|     pub fn show(&self) { |  | ||||||
|         self.window.show(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Save the ensemble and possibly upload it to the server.
 |     /// Save the ensemble and possibly upload it to the server.
 | ||||||
|     async fn save(self: Rc<Self>) -> Result<()> { |     async fn save(self: Rc<Self>) -> Result<()> { | ||||||
|         let name = self.name_entry.get_text().to_string(); |         let name = self.name_entry.get_text().to_string(); | ||||||
|  | @ -111,7 +106,10 @@ impl EnsembleEditor { | ||||||
|             self.backend.post_ensemble(&ensemble).await?; |             self.backend.post_ensemble(&ensemble).await?; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         self.backend.db().update_ensemble(ensemble.clone()).await?; |         self.backend | ||||||
|  |             .db() | ||||||
|  |             .update_ensemble(ensemble.clone()) | ||||||
|  |             .await?; | ||||||
|         self.backend.library_changed(); |         self.backend.library_changed(); | ||||||
| 
 | 
 | ||||||
|         if let Some(cb) = &*self.saved_cb.borrow() { |         if let Some(cb) = &*self.saved_cb.borrow() { | ||||||
|  | @ -121,3 +119,17 @@ impl EnsembleEditor { | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | impl NavigatorScreen for EnsembleEditor { | ||||||
|  |     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,5 +1,6 @@ | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
|  | use crate::widgets::{Navigator, NavigatorScreen}; | ||||||
| use anyhow::Result; | use anyhow::Result; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
|  | @ -11,29 +12,24 @@ use std::rc::Rc; | ||||||
| pub struct InstrumentEditor { | pub struct InstrumentEditor { | ||||||
|     backend: Rc<Backend>, |     backend: Rc<Backend>, | ||||||
|     id: String, |     id: String, | ||||||
|     window: libhandy::Window, |     widget: gtk::Stack, | ||||||
|     stack: gtk::Stack, |  | ||||||
|     info_bar: gtk::InfoBar, |     info_bar: gtk::InfoBar, | ||||||
|     name_entry: gtk::Entry, |     name_entry: gtk::Entry, | ||||||
|     upload_switch: gtk::Switch, |     upload_switch: gtk::Switch, | ||||||
|     saved_cb: RefCell<Option<Box<dyn Fn(Instrument) -> ()>>>, |     saved_cb: RefCell<Option<Box<dyn Fn(Instrument) -> ()>>>, | ||||||
|  |     navigator: RefCell<Option<Rc<Navigator>>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl InstrumentEditor { | impl InstrumentEditor { | ||||||
|     /// Create a new instrument editor and optionally initialize it.
 |     /// Create a new instrument editor and optionally initialize it.
 | ||||||
|     pub fn new<P: IsA<gtk::Window>>( |     pub fn new(backend: Rc<Backend>, instrument: Option<Instrument>) -> Rc<Self> { | ||||||
|         backend: Rc<Backend>, |  | ||||||
|         parent: &P, |  | ||||||
|         instrument: Option<Instrument>, |  | ||||||
|     ) -> Rc<Self> { |  | ||||||
|         // Create UI
 |         // Create UI
 | ||||||
| 
 | 
 | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/instrument_editor.ui"); |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/instrument_editor.ui"); | ||||||
| 
 | 
 | ||||||
|         get_widget!(builder, libhandy::Window, window); |         get_widget!(builder, gtk::Stack, widget); | ||||||
|         get_widget!(builder, gtk::Button, cancel_button); |         get_widget!(builder, gtk::Button, back_button); | ||||||
|         get_widget!(builder, gtk::Button, save_button); |         get_widget!(builder, gtk::Button, save_button); | ||||||
|         get_widget!(builder, gtk::Stack, stack); |  | ||||||
|         get_widget!(builder, gtk::InfoBar, info_bar); |         get_widget!(builder, gtk::InfoBar, info_bar); | ||||||
|         get_widget!(builder, gtk::Entry, name_entry); |         get_widget!(builder, gtk::Entry, name_entry); | ||||||
|         get_widget!(builder, gtk::Switch, upload_switch); |         get_widget!(builder, gtk::Switch, upload_switch); | ||||||
|  | @ -50,40 +46,44 @@ impl InstrumentEditor { | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|             backend, |             backend, | ||||||
|             id, |             id, | ||||||
|             window, |             widget, | ||||||
|             stack, |  | ||||||
|             info_bar, |             info_bar, | ||||||
|             name_entry, |             name_entry, | ||||||
|             upload_switch, |             upload_switch, | ||||||
|             saved_cb: RefCell::new(None), |             saved_cb: RefCell::new(None), | ||||||
|  |             navigator: RefCell::new(None), | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         cancel_button.connect_clicked(clone!(@strong this => move |_| { |         back_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             this.window.close(); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 navigator.pop(); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         save_button.connect_clicked(clone!(@strong this => move |_| { |         save_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             let context = glib::MainContext::default(); |             let context = glib::MainContext::default(); | ||||||
|             let clone = this.clone(); |             let clone = this.clone(); | ||||||
|             context.spawn_local(async move { |             context.spawn_local(async move { | ||||||
|                 clone.stack.set_visible_child_name("loading"); |                 clone.widget.set_visible_child_name("loading"); | ||||||
|                 match clone.clone().save().await { |                 match clone.clone().save().await { | ||||||
|                     Ok(_) => { |                     Ok(_) => { | ||||||
|                         clone.window.close(); |                         let navigator = clone.navigator.borrow().clone(); | ||||||
|  |                         if let Some(navigator) = navigator { | ||||||
|  |                             navigator.pop(); | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                     Err(_) => { |                     Err(_) => { | ||||||
|                         clone.info_bar.set_revealed(true); |                         clone.info_bar.set_revealed(true); | ||||||
|                         clone.stack.set_visible_child_name("content"); |                         clone.widget.set_visible_child_name("content"); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|             }); |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.window.set_transient_for(Some(parent)); |  | ||||||
| 
 |  | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -92,11 +92,6 @@ impl InstrumentEditor { | ||||||
|         self.saved_cb.replace(Some(Box::new(cb))); |         self.saved_cb.replace(Some(Box::new(cb))); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Show the instrument editor.
 |  | ||||||
|     pub fn show(&self) { |  | ||||||
|         self.window.show(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Save the instrument and possibly upload it to the server.
 |     /// Save the instrument and possibly upload it to the server.
 | ||||||
|     async fn save(self: Rc<Self>) -> Result<()> { |     async fn save(self: Rc<Self>) -> Result<()> { | ||||||
|         let name = self.name_entry.get_text().to_string(); |         let name = self.name_entry.get_text().to_string(); | ||||||
|  | @ -111,7 +106,10 @@ impl InstrumentEditor { | ||||||
|             self.backend.post_instrument(&instrument).await?; |             self.backend.post_instrument(&instrument).await?; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         self.backend.db().update_instrument(instrument.clone()).await?; |         self.backend | ||||||
|  |             .db() | ||||||
|  |             .update_instrument(instrument.clone()) | ||||||
|  |             .await?; | ||||||
|         self.backend.library_changed(); |         self.backend.library_changed(); | ||||||
| 
 | 
 | ||||||
|         if let Some(cb) = &*self.saved_cb.borrow() { |         if let Some(cb) = &*self.saved_cb.borrow() { | ||||||
|  | @ -121,3 +119,17 @@ impl InstrumentEditor { | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | impl NavigatorScreen for InstrumentEditor { | ||||||
|  |     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); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								musicus/src/editors/mod.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								musicus/src/editors/mod.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | pub mod ensemble; | ||||||
|  | pub use ensemble::*; | ||||||
|  | 
 | ||||||
|  | pub mod instrument; | ||||||
|  | pub use instrument::*; | ||||||
|  | 
 | ||||||
|  | pub mod person; | ||||||
|  | pub use person::*; | ||||||
|  | 
 | ||||||
|  | pub mod recording; | ||||||
|  | pub use recording::*; | ||||||
|  | 
 | ||||||
|  | pub mod tracks; | ||||||
|  | pub use tracks::*; | ||||||
|  | 
 | ||||||
|  | pub mod work; | ||||||
|  | pub use work::*; | ||||||
|  | 
 | ||||||
|  | mod performance; | ||||||
|  | mod track; | ||||||
|  | mod work_part; | ||||||
|  | mod work_section; | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| use crate::backend::*; | use crate::backend::Backend; | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
| use crate::dialogs::*; | use crate::selectors::{EnsembleSelector, InstrumentSelector, PersonSelector}; | ||||||
|  | use crate::widgets::{Navigator, NavigatorScreen}; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
|  | @ -11,7 +12,7 @@ use std::rc::Rc; | ||||||
| /// A dialog for editing a performance within a recording.
 | /// A dialog for editing a performance within a recording.
 | ||||||
| pub struct PerformanceEditor { | pub struct PerformanceEditor { | ||||||
|     backend: Rc<Backend>, |     backend: Rc<Backend>, | ||||||
|     window: libhandy::Window, |     widget: gtk::Box, | ||||||
|     save_button: gtk::Button, |     save_button: gtk::Button, | ||||||
|     person_label: gtk::Label, |     person_label: gtk::Label, | ||||||
|     ensemble_label: gtk::Label, |     ensemble_label: gtk::Label, | ||||||
|  | @ -21,21 +22,18 @@ pub struct PerformanceEditor { | ||||||
|     ensemble: RefCell<Option<Ensemble>>, |     ensemble: RefCell<Option<Ensemble>>, | ||||||
|     role: RefCell<Option<Instrument>>, |     role: RefCell<Option<Instrument>>, | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(Performance) -> ()>>>, |     selected_cb: RefCell<Option<Box<dyn Fn(Performance) -> ()>>>, | ||||||
|  |     navigator: RefCell<Option<Rc<Navigator>>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl PerformanceEditor { | impl PerformanceEditor { | ||||||
|     /// Create a new performance editor.
 |     /// Create a new performance editor.
 | ||||||
|     pub fn new<P: IsA<gtk::Window>>( |     pub fn new(backend: Rc<Backend>, performance: Option<Performance>) -> Rc<Self> { | ||||||
|         backend: Rc<Backend>, |  | ||||||
|         parent: &P, |  | ||||||
|         performance: Option<Performance>, |  | ||||||
|     ) -> Rc<Self> { |  | ||||||
|         // Create UI
 |         // Create UI
 | ||||||
| 
 | 
 | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/performance_editor.ui"); |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/performance_editor.ui"); | ||||||
| 
 | 
 | ||||||
|         get_widget!(builder, libhandy::Window, window); |         get_widget!(builder, gtk::Box, widget); | ||||||
|         get_widget!(builder, gtk::Button, cancel_button); |         get_widget!(builder, gtk::Button, back_button); | ||||||
|         get_widget!(builder, gtk::Button, save_button); |         get_widget!(builder, gtk::Button, save_button); | ||||||
|         get_widget!(builder, gtk::Button, person_button); |         get_widget!(builder, gtk::Button, person_button); | ||||||
|         get_widget!(builder, gtk::Button, ensemble_button); |         get_widget!(builder, gtk::Button, ensemble_button); | ||||||
|  | @ -45,11 +43,9 @@ impl PerformanceEditor { | ||||||
|         get_widget!(builder, gtk::Label, ensemble_label); |         get_widget!(builder, gtk::Label, ensemble_label); | ||||||
|         get_widget!(builder, gtk::Label, role_label); |         get_widget!(builder, gtk::Label, role_label); | ||||||
| 
 | 
 | ||||||
|         window.set_transient_for(Some(parent)); |  | ||||||
| 
 |  | ||||||
|         let this = Rc::new(PerformanceEditor { |         let this = Rc::new(PerformanceEditor { | ||||||
|             backend, |             backend, | ||||||
|             window, |             widget, | ||||||
|             save_button, |             save_button, | ||||||
|             person_label, |             person_label, | ||||||
|             ensemble_label, |             ensemble_label, | ||||||
|  | @ -59,12 +55,16 @@ impl PerformanceEditor { | ||||||
|             ensemble: RefCell::new(None), |             ensemble: RefCell::new(None), | ||||||
|             role: RefCell::new(None), |             role: RefCell::new(None), | ||||||
|             selected_cb: RefCell::new(None), |             selected_cb: RefCell::new(None), | ||||||
|  |             navigator: RefCell::new(None), | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         cancel_button.connect_clicked(clone!(@strong this => move |_| { |         back_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             this.window.close(); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 navigator.pop(); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.save_button |         this.save_button | ||||||
|  | @ -75,46 +75,61 @@ impl PerformanceEditor { | ||||||
|                         ensemble: this.ensemble.borrow().clone(), |                         ensemble: this.ensemble.borrow().clone(), | ||||||
|                         role: this.role.borrow().clone(), |                         role: this.role.borrow().clone(), | ||||||
|                     }); |                     }); | ||||||
|  |                 } | ||||||
| 
 | 
 | ||||||
|                     this.window.close(); |                 let navigator = this.navigator.borrow().clone(); | ||||||
|  |                 if let Some(navigator) = navigator { | ||||||
|  |                     navigator.pop(); | ||||||
|                 } |                 } | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|         person_button.connect_clicked(clone!(@strong this => move |_| { |         person_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             let dialog = PersonSelector::new(this.backend.clone(), &this.window); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 let selector = PersonSelector::new(this.backend.clone()); | ||||||
| 
 | 
 | ||||||
|             dialog.set_selected_cb(clone!(@strong this => move |person| { |                 selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| { | ||||||
|                     this.show_person(Some(&person)); |                     this.show_person(Some(&person)); | ||||||
|                 this.person.replace(Some(person)); |                     this.person.replace(Some(person.clone())); | ||||||
|                     this.show_ensemble(None); |                     this.show_ensemble(None); | ||||||
|                     this.ensemble.replace(None); |                     this.ensemble.replace(None); | ||||||
|  |                     navigator.clone().pop(); | ||||||
|                 })); |                 })); | ||||||
| 
 | 
 | ||||||
|             dialog.show(); |                 navigator.push(selector); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         ensemble_button.connect_clicked(clone!(@strong this => move |_| { |         ensemble_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             let dialog = EnsembleSelector::new(this.backend.clone(), &this.window); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 let selector = EnsembleSelector::new(this.backend.clone()); | ||||||
| 
 | 
 | ||||||
|             dialog.set_selected_cb(clone!(@strong this => move |ensemble| { |                 selector.set_selected_cb(clone!(@strong this, @strong navigator => move |ensemble| { | ||||||
|                     this.show_person(None); |                     this.show_person(None); | ||||||
|                     this.person.replace(None); |                     this.person.replace(None); | ||||||
|                     this.show_ensemble(Some(&ensemble)); |                     this.show_ensemble(Some(&ensemble)); | ||||||
|                 this.ensemble.replace(Some(ensemble)); |                     this.ensemble.replace(Some(ensemble.clone())); | ||||||
|  |                     navigator.clone().pop(); | ||||||
|                 })); |                 })); | ||||||
| 
 | 
 | ||||||
|             dialog.show(); |                 navigator.push(selector); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         role_button.connect_clicked(clone!(@strong this => move |_| { |         role_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             let dialog = InstrumentSelector::new(this.backend.clone(), &this.window); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |                 if let Some(navigator) = navigator { | ||||||
|  |                 let selector = InstrumentSelector::new(this.backend.clone()); | ||||||
| 
 | 
 | ||||||
|             dialog.set_selected_cb(clone!(@strong this => move |role| { |                 selector.set_selected_cb(clone!(@strong this, @strong navigator => move |role| { | ||||||
|                     this.show_role(Some(&role)); |                     this.show_role(Some(&role)); | ||||||
|                 this.role.replace(Some(role)); |                     this.role.replace(Some(role.clone())); | ||||||
|  |                     navigator.clone().pop(); | ||||||
|                 })); |                 })); | ||||||
| 
 | 
 | ||||||
|             dialog.show(); |                 navigator.push(selector); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.reset_role_button |         this.reset_role_button | ||||||
|  | @ -148,11 +163,6 @@ impl PerformanceEditor { | ||||||
|         self.selected_cb.replace(Some(Box::new(cb))); |         self.selected_cb.replace(Some(Box::new(cb))); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Show the performance editor.
 |  | ||||||
|     pub fn show(&self) { |  | ||||||
|         self.window.show(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Update the UI according to person.
 |     /// Update the UI according to person.
 | ||||||
|     fn show_person(&self, person: Option<&Person>) { |     fn show_person(&self, person: Option<&Person>) { | ||||||
|         if let Some(person) = person { |         if let Some(person) = person { | ||||||
|  | @ -184,3 +194,17 @@ impl PerformanceEditor { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | impl NavigatorScreen for PerformanceEditor { | ||||||
|  |     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,5 +1,6 @@ | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
|  | use crate::widgets::{Navigator, NavigatorScreen}; | ||||||
| use anyhow::Result; | use anyhow::Result; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
|  | @ -11,30 +12,25 @@ use std::rc::Rc; | ||||||
| pub struct PersonEditor { | pub struct PersonEditor { | ||||||
|     backend: Rc<Backend>, |     backend: Rc<Backend>, | ||||||
|     id: String, |     id: String, | ||||||
|     window: libhandy::Window, |     widget: gtk::Stack, | ||||||
|     stack: gtk::Stack, |  | ||||||
|     info_bar: gtk::InfoBar, |     info_bar: gtk::InfoBar, | ||||||
|     first_name_entry: gtk::Entry, |     first_name_entry: gtk::Entry, | ||||||
|     last_name_entry: gtk::Entry, |     last_name_entry: gtk::Entry, | ||||||
|     upload_switch: gtk::Switch, |     upload_switch: gtk::Switch, | ||||||
|     saved_cb: RefCell<Option<Box<dyn Fn(Person) -> ()>>>, |     saved_cb: RefCell<Option<Box<dyn Fn(Person) -> ()>>>, | ||||||
|  |     navigator: RefCell<Option<Rc<Navigator>>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl PersonEditor { | impl PersonEditor { | ||||||
|     /// Create a new person editor and optionally initialize it.
 |     /// Create a new person editor and optionally initialize it.
 | ||||||
|     pub fn new<P: IsA<gtk::Window>>( |     pub fn new(backend: Rc<Backend>, person: Option<Person>) -> Rc<Self> { | ||||||
|         backend: Rc<Backend>, |  | ||||||
|         parent: &P, |  | ||||||
|         person: Option<Person>, |  | ||||||
|     ) -> Rc<Self> { |  | ||||||
|         // Create UI
 |         // Create UI
 | ||||||
| 
 | 
 | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/person_editor.ui"); |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/person_editor.ui"); | ||||||
| 
 | 
 | ||||||
|         get_widget!(builder, libhandy::Window, window); |         get_widget!(builder, gtk::Stack, widget); | ||||||
|         get_widget!(builder, gtk::Button, cancel_button); |         get_widget!(builder, gtk::Button, back_button); | ||||||
|         get_widget!(builder, gtk::Button, save_button); |         get_widget!(builder, gtk::Button, save_button); | ||||||
|         get_widget!(builder, gtk::Stack, stack); |  | ||||||
|         get_widget!(builder, gtk::InfoBar, info_bar); |         get_widget!(builder, gtk::InfoBar, info_bar); | ||||||
|         get_widget!(builder, gtk::Entry, first_name_entry); |         get_widget!(builder, gtk::Entry, first_name_entry); | ||||||
|         get_widget!(builder, gtk::Entry, last_name_entry); |         get_widget!(builder, gtk::Entry, last_name_entry); | ||||||
|  | @ -53,41 +49,45 @@ impl PersonEditor { | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|             backend, |             backend, | ||||||
|             id, |             id, | ||||||
|             window, |             widget, | ||||||
|             stack, |  | ||||||
|             info_bar, |             info_bar, | ||||||
|             first_name_entry, |             first_name_entry, | ||||||
|             last_name_entry, |             last_name_entry, | ||||||
|             upload_switch, |             upload_switch, | ||||||
|             saved_cb: RefCell::new(None), |             saved_cb: RefCell::new(None), | ||||||
|  |             navigator: RefCell::new(None), | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         cancel_button.connect_clicked(clone!(@strong this => move |_| { |         back_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             this.window.close(); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 navigator.pop(); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         save_button.connect_clicked(clone!(@strong this => move |_| { |         save_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             let context = glib::MainContext::default(); |             let context = glib::MainContext::default(); | ||||||
|             let clone = this.clone(); |             let clone = this.clone(); | ||||||
|             context.spawn_local(async move { |             context.spawn_local(async move { | ||||||
|                 clone.stack.set_visible_child_name("loading"); |                 clone.widget.set_visible_child_name("loading"); | ||||||
|                 match clone.clone().save().await { |                 match clone.clone().save().await { | ||||||
|                     Ok(_) => { |                     Ok(_) => { | ||||||
|                         clone.window.close(); |                         let navigator = clone.navigator.borrow().clone(); | ||||||
|  |                         if let Some(navigator) = navigator { | ||||||
|  |                             navigator.pop(); | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                     Err(_) => { |                     Err(_) => { | ||||||
|                         clone.info_bar.set_revealed(true); |                         clone.info_bar.set_revealed(true); | ||||||
|                         clone.stack.set_visible_child_name("content"); |                         clone.widget.set_visible_child_name("content"); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|             }); |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.window.set_transient_for(Some(parent)); |  | ||||||
| 
 |  | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -96,11 +96,6 @@ impl PersonEditor { | ||||||
|         self.saved_cb.replace(Some(Box::new(cb))); |         self.saved_cb.replace(Some(Box::new(cb))); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Show the person editor.
 |  | ||||||
|     pub fn show(&self) { |  | ||||||
|         self.window.show(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Save the person and possibly upload it to the server.
 |     /// Save the person and possibly upload it to the server.
 | ||||||
|     async fn save(self: Rc<Self>) -> Result<()> { |     async fn save(self: Rc<Self>) -> Result<()> { | ||||||
|         let first_name = self.first_name_entry.get_text().to_string(); |         let first_name = self.first_name_entry.get_text().to_string(); | ||||||
|  | @ -127,3 +122,17 @@ impl PersonEditor { | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | impl NavigatorScreen for PersonEditor { | ||||||
|  |     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,8 +1,8 @@ | ||||||
| use super::performance_editor::*; | use super::performance::PerformanceEditor; | ||||||
| use crate::backend::*; | use crate::backend::Backend; | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
| use crate::dialogs::*; | use crate::selectors::{PersonSelector, WorkSelector}; | ||||||
| use crate::widgets::*; | use crate::widgets::{List, Navigator, NavigatorScreen}; | ||||||
| use anyhow::Result; | use anyhow::Result; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
|  | @ -16,7 +16,6 @@ use std::rc::Rc; | ||||||
| pub struct RecordingEditor { | pub struct RecordingEditor { | ||||||
|     pub widget: gtk::Stack, |     pub widget: gtk::Stack, | ||||||
|     backend: Rc<Backend>, |     backend: Rc<Backend>, | ||||||
|     parent: gtk::Window, |  | ||||||
|     save_button: gtk::Button, |     save_button: gtk::Button, | ||||||
|     info_bar: gtk::InfoBar, |     info_bar: gtk::InfoBar, | ||||||
|     work_label: gtk::Label, |     work_label: gtk::Label, | ||||||
|  | @ -28,16 +27,12 @@ pub struct RecordingEditor { | ||||||
|     performances: RefCell<Vec<Performance>>, |     performances: RefCell<Vec<Performance>>, | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(Recording) -> ()>>>, |     selected_cb: RefCell<Option<Box<dyn Fn(Recording) -> ()>>>, | ||||||
|     back_cb: RefCell<Option<Box<dyn Fn() -> ()>>>, |     back_cb: RefCell<Option<Box<dyn Fn() -> ()>>>, | ||||||
|  |     navigator: RefCell<Option<Rc<Navigator>>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl RecordingEditor { | impl RecordingEditor { | ||||||
|     /// Create a new recording editor widget and optionally initialize it. The parent window is
 |     /// Create a new recording editor widget and optionally initialize it.
 | ||||||
|     /// used as the parent for newly created dialogs.
 |     pub fn new(backend: Rc<Backend>, recording: Option<Recording>) -> Rc<Self> { | ||||||
|     pub fn new<W: IsA<gtk::Window>>( |  | ||||||
|         backend: Rc<Backend>, |  | ||||||
|         parent: &W, |  | ||||||
|         recording: Option<Recording>, |  | ||||||
|     ) -> Rc<Self> { |  | ||||||
|         // Create UI
 |         // Create UI
 | ||||||
| 
 | 
 | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_editor.ui"); |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_editor.ui"); | ||||||
|  | @ -69,7 +64,6 @@ impl RecordingEditor { | ||||||
|         let this = Rc::new(RecordingEditor { |         let this = Rc::new(RecordingEditor { | ||||||
|             widget, |             widget, | ||||||
|             backend, |             backend, | ||||||
|             parent: parent.clone().upcast(), |  | ||||||
|             save_button, |             save_button, | ||||||
|             info_bar, |             info_bar, | ||||||
|             work_label, |             work_label, | ||||||
|  | @ -81,6 +75,7 @@ impl RecordingEditor { | ||||||
|             performances: RefCell::new(performances), |             performances: RefCell::new(performances), | ||||||
|             selected_cb: RefCell::new(None), |             selected_cb: RefCell::new(None), | ||||||
|             back_cb: RefCell::new(None), |             back_cb: RefCell::new(None), | ||||||
|  |             navigator: RefCell::new(None), | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
|  | @ -89,6 +84,11 @@ impl RecordingEditor { | ||||||
|             if let Some(cb) = &*this.back_cb.borrow() { |             if let Some(cb) = &*this.back_cb.borrow() { | ||||||
|                 cb(); |                 cb(); | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|  |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 navigator.clone().pop(); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.save_button |         this.save_button | ||||||
|  | @ -99,7 +99,10 @@ impl RecordingEditor { | ||||||
|                     clone.widget.set_visible_child_name("loading"); |                     clone.widget.set_visible_child_name("loading"); | ||||||
|                     match clone.clone().save().await { |                     match clone.clone().save().await { | ||||||
|                         Ok(_) => { |                         Ok(_) => { | ||||||
|                             // We already called the callback.
 |                             let navigator = clone.navigator.borrow().clone(); | ||||||
|  |                             if let Some(navigator) = navigator { | ||||||
|  |                                 navigator.clone().pop(); | ||||||
|  |                             } | ||||||
|                         } |                         } | ||||||
|                         Err(_) => { |                         Err(_) => { | ||||||
|                             clone.info_bar.set_revealed(true); |                             clone.info_bar.set_revealed(true); | ||||||
|  | @ -111,14 +114,26 @@ impl RecordingEditor { | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|         work_button.connect_clicked(clone!(@strong this => move |_| { |         work_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             let dialog = WorkDialog::new(this.backend.clone(), &this.parent); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 let person_selector = PersonSelector::new(this.backend.clone()); | ||||||
| 
 | 
 | ||||||
|             dialog.set_selected_cb(clone!(@strong this => move |work| { |                 person_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| { | ||||||
|  |                     let work_selector = WorkSelector::new(this.backend.clone(), person.clone()); | ||||||
|  |                     
 | ||||||
|  |                     work_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |work| { | ||||||
|                         this.work_selected(&work); |                         this.work_selected(&work); | ||||||
|                 this.work.replace(Some(work)); |                         this.work.replace(Some(work.clone())); | ||||||
|  | 
 | ||||||
|  |                         navigator.clone().pop(); | ||||||
|  |                         navigator.clone().pop(); | ||||||
|                     })); |                     })); | ||||||
| 
 | 
 | ||||||
|             dialog.show(); |                     navigator.clone().push(work_selector); | ||||||
|  |                 })); | ||||||
|  | 
 | ||||||
|  |                 navigator.push(person_selector); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.performance_list.set_make_widget(|performance| { |         this.performance_list.set_make_widget(|performance| { | ||||||
|  | @ -133,9 +148,11 @@ impl RecordingEditor { | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         add_performer_button.connect_clicked(clone!(@strong this => move |_| { |         add_performer_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             let editor = PerformanceEditor::new(this.backend.clone(), &this.parent, None); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 let editor = PerformanceEditor::new(this.backend.clone(), None); | ||||||
| 
 | 
 | ||||||
|             editor.set_selected_cb(clone!(@strong this => move |performance| { |                 editor.set_selected_cb(clone!(@strong this, @strong navigator => move |performance| { | ||||||
|                     let mut performances = this.performances.borrow_mut(); |                     let mut performances = this.performances.borrow_mut(); | ||||||
| 
 | 
 | ||||||
|                     let index = match this.performance_list.get_selected_index() { |                     let index = match this.performance_list.get_selected_index() { | ||||||
|  | @ -146,29 +163,35 @@ impl RecordingEditor { | ||||||
|                     performances.insert(index, performance); |                     performances.insert(index, performance); | ||||||
|                     this.performance_list.show_items(performances.clone()); |                     this.performance_list.show_items(performances.clone()); | ||||||
|                     this.performance_list.select_index(index); |                     this.performance_list.select_index(index); | ||||||
|  | 
 | ||||||
|  |                     navigator.clone().pop(); | ||||||
|                 })); |                 })); | ||||||
| 
 | 
 | ||||||
|             editor.show(); |                 navigator.push(editor); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         edit_performer_button.connect_clicked(clone!(@strong this => move |_| { |         edit_performer_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|  |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|                 if let Some(index) = this.performance_list.get_selected_index() { |                 if let Some(index) = this.performance_list.get_selected_index() { | ||||||
|                     let performance = &this.performances.borrow()[index]; |                     let performance = &this.performances.borrow()[index]; | ||||||
| 
 | 
 | ||||||
|                     let editor = PerformanceEditor::new( |                     let editor = PerformanceEditor::new( | ||||||
|                         this.backend.clone(), |                         this.backend.clone(), | ||||||
|                     &this.parent, |  | ||||||
|                         Some(performance.clone()), |                         Some(performance.clone()), | ||||||
|                     ); |                     ); | ||||||
| 
 | 
 | ||||||
|                 editor.set_selected_cb(clone!(@strong this => move |performance| { |                     editor.set_selected_cb(clone!(@strong this, @strong navigator => move |performance| { | ||||||
|                         let mut performances = this.performances.borrow_mut(); |                         let mut performances = this.performances.borrow_mut(); | ||||||
|                         performances[index] = performance; |                         performances[index] = performance; | ||||||
|                         this.performance_list.show_items(performances.clone()); |                         this.performance_list.show_items(performances.clone()); | ||||||
|                         this.performance_list.select_index(index); |                         this.performance_list.select_index(index); | ||||||
|  |                         navigator.clone().pop(); | ||||||
|                     })); |                     })); | ||||||
| 
 | 
 | ||||||
|                 editor.show(); |                     navigator.push(editor); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|  | @ -240,6 +263,25 @@ impl RecordingEditor { | ||||||
|             cb(recording.clone()); |             cb(recording.clone()); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         let navigator = self.navigator.borrow().clone(); | ||||||
|  |         if let Some(navigator) = navigator { | ||||||
|  |             navigator.clone().pop(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | impl NavigatorScreen for RecordingEditor { | ||||||
|  |     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,4 +1,5 @@ | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
|  | use crate::widgets::{Navigator, NavigatorScreen}; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
| use gtk_macros::get_widget; | use gtk_macros::get_widget; | ||||||
|  | @ -6,43 +7,58 @@ use std::cell::RefCell; | ||||||
| use std::convert::TryInto; | use std::convert::TryInto; | ||||||
| use std::rc::Rc; | use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
|  | /// A screen for editing a single track.
 | ||||||
|  | // TODO: Refactor.
 | ||||||
| pub struct TrackEditor { | pub struct TrackEditor { | ||||||
|     window: libhandy::Window, |     widget: gtk::Box, | ||||||
|  |     ready_cb: RefCell<Option<Box<dyn Fn(Track) -> ()>>>, | ||||||
|  |     navigator: RefCell<Option<Rc<Navigator>>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl TrackEditor { | impl TrackEditor { | ||||||
|     pub fn new<W, F>(parent: &W, track: Track, work: Work, callback: F) -> Self |     /// Create a new track editor.
 | ||||||
|     where |     pub fn new(track: Track, work: Work) -> Rc<Self> { | ||||||
|         W: IsA<gtk::Window>, |         // Create UI
 | ||||||
|         F: Fn(Track) -> () + 'static, | 
 | ||||||
|     { |  | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/track_editor.ui"); |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/track_editor.ui"); | ||||||
| 
 | 
 | ||||||
|         get_widget!(builder, libhandy::Window, window); |         get_widget!(builder, gtk::Box, widget); | ||||||
|         get_widget!(builder, gtk::Button, cancel_button); |         get_widget!(builder, gtk::Button, back_button); | ||||||
|         get_widget!(builder, gtk::Button, save_button); |         get_widget!(builder, gtk::Button, save_button); | ||||||
|         get_widget!(builder, gtk::ListBox, list); |         get_widget!(builder, gtk::ListBox, list); | ||||||
| 
 | 
 | ||||||
|         window.set_transient_for(Some(parent)); |         let this = Rc::new(Self { | ||||||
|  |             widget, | ||||||
|  |             ready_cb: RefCell::new(None), | ||||||
|  |             navigator: RefCell::new(None), | ||||||
|  |         }); | ||||||
| 
 | 
 | ||||||
|         cancel_button.connect_clicked(clone!(@strong window => move |_| { |         back_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             window.close(); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 navigator.pop(); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         let work = Rc::new(work); |         let work = Rc::new(work); | ||||||
|         let work_parts = Rc::new(RefCell::new(track.work_parts)); |         let work_parts = Rc::new(RefCell::new(track.work_parts)); | ||||||
|         let file_name = track.file_name; |         let file_name = track.file_name; | ||||||
| 
 | 
 | ||||||
|         save_button.connect_clicked(clone!(@strong work_parts, @strong window => move |_| { |         save_button.connect_clicked(clone!(@strong this, @strong work_parts => move |_| { | ||||||
|             let mut work_parts = work_parts.borrow_mut(); |             let mut work_parts = work_parts.borrow_mut(); | ||||||
|             work_parts.sort(); |             work_parts.sort(); | ||||||
| 
 | 
 | ||||||
|             callback(Track { |             if let Some(cb) = &*this.ready_cb.borrow() { | ||||||
|  |                 cb(Track { | ||||||
|                     work_parts: work_parts.clone(), |                     work_parts: work_parts.clone(), | ||||||
|                     file_name: file_name.clone(), |                     file_name: file_name.clone(), | ||||||
|                 }); |                 }); | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             window.close(); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 navigator.pop(); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         for (index, part) in work.parts.iter().enumerate() { |         for (index, part) in work.parts.iter().enumerate() { | ||||||
|  | @ -108,10 +124,25 @@ impl TrackEditor { | ||||||
|             section_count += 1; |             section_count += 1; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         Self { window } |         this | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn show(&self) { |     /// Set the closure to be called when the track was edited.
 | ||||||
|         self.window.show(); |     pub fn set_ready_cb<F: Fn(Track) -> () + 'static>(&self, cb: F) { | ||||||
|  |         self.ready_cb.replace(Some(Box::new(cb))); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl NavigatorScreen for TrackEditor { | ||||||
|  |     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,7 +1,8 @@ | ||||||
| use super::*; | use super::track::TrackEditor; | ||||||
| use crate::backend::*; | use crate::backend::Backend; | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
| use crate::widgets::*; | use crate::widgets::{List, Navigator, NavigatorScreen}; | ||||||
|  | use crate::selectors::{PersonSelector, WorkSelector, RecordingSelector}; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
|  | @ -13,7 +14,7 @@ use std::rc::Rc; | ||||||
| // TODO: Disable buttons if no track is selected.
 | // TODO: Disable buttons if no track is selected.
 | ||||||
| pub struct TracksEditor { | pub struct TracksEditor { | ||||||
|     backend: Rc<Backend>, |     backend: Rc<Backend>, | ||||||
|     window: libhandy::Window, |     widget: gtk::Box, | ||||||
|     save_button: gtk::Button, |     save_button: gtk::Button, | ||||||
|     recording_stack: gtk::Stack, |     recording_stack: gtk::Stack, | ||||||
|     work_label: gtk::Label, |     work_label: gtk::Label, | ||||||
|  | @ -22,14 +23,14 @@ pub struct TracksEditor { | ||||||
|     recording: RefCell<Option<Recording>>, |     recording: RefCell<Option<Recording>>, | ||||||
|     tracks: RefCell<Vec<Track>>, |     tracks: RefCell<Vec<Track>>, | ||||||
|     callback: RefCell<Option<Box<dyn Fn() -> ()>>>, |     callback: RefCell<Option<Box<dyn Fn() -> ()>>>, | ||||||
|  |     navigator: RefCell<Option<Rc<Navigator>>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl TracksEditor { | impl TracksEditor { | ||||||
|     /// Create a new track editor an optionally initialize it with a recording and a list of
 |     /// Create a new track editor an optionally initialize it with a recording and a list of
 | ||||||
|     /// tracks.
 |     /// tracks.
 | ||||||
|     pub fn new<P: IsA<gtk::Window>>( |     pub fn new( | ||||||
|         backend: Rc<Backend>, |         backend: Rc<Backend>, | ||||||
|         parent: &P, |  | ||||||
|         recording: Option<Recording>, |         recording: Option<Recording>, | ||||||
|         tracks: Vec<Track>, |         tracks: Vec<Track>, | ||||||
|     ) -> Rc<Self> { |     ) -> Rc<Self> { | ||||||
|  | @ -37,8 +38,8 @@ impl TracksEditor { | ||||||
| 
 | 
 | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/tracks_editor.ui"); |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/tracks_editor.ui"); | ||||||
| 
 | 
 | ||||||
|         get_widget!(builder, libhandy::Window, window); |         get_widget!(builder, gtk::Box, widget); | ||||||
|         get_widget!(builder, gtk::Button, cancel_button); |         get_widget!(builder, gtk::Button, back_button); | ||||||
|         get_widget!(builder, gtk::Button, save_button); |         get_widget!(builder, gtk::Button, save_button); | ||||||
|         get_widget!(builder, gtk::Button, recording_button); |         get_widget!(builder, gtk::Button, recording_button); | ||||||
|         get_widget!(builder, gtk::Stack, recording_stack); |         get_widget!(builder, gtk::Stack, recording_stack); | ||||||
|  | @ -51,18 +52,12 @@ impl TracksEditor { | ||||||
|         get_widget!(builder, gtk::Button, move_track_up_button); |         get_widget!(builder, gtk::Button, move_track_up_button); | ||||||
|         get_widget!(builder, gtk::Button, move_track_down_button); |         get_widget!(builder, gtk::Button, move_track_down_button); | ||||||
| 
 | 
 | ||||||
|         window.set_transient_for(Some(parent)); |  | ||||||
| 
 |  | ||||||
|         cancel_button.connect_clicked(clone!(@strong window => move |_| { |  | ||||||
|             window.close(); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         let track_list = List::new(&gettext("Add some tracks.")); |         let track_list = List::new(&gettext("Add some tracks.")); | ||||||
|         scroll.add(&track_list.widget); |         scroll.add(&track_list.widget); | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|             backend, |             backend, | ||||||
|             window, |             widget, | ||||||
|             save_button, |             save_button, | ||||||
|             recording_stack, |             recording_stack, | ||||||
|             work_label, |             work_label, | ||||||
|  | @ -71,10 +66,18 @@ impl TracksEditor { | ||||||
|             recording: RefCell::new(recording), |             recording: RefCell::new(recording), | ||||||
|             tracks: RefCell::new(tracks), |             tracks: RefCell::new(tracks), | ||||||
|             callback: RefCell::new(None), |             callback: RefCell::new(None), | ||||||
|  |             navigator: RefCell::new(None), | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Signals and callbacks
 |         // 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.save_button |         this.save_button | ||||||
|             .connect_clicked(clone!(@strong this => move |_| { |             .connect_clicked(clone!(@strong this => move |_| { | ||||||
|                 let context = glib::MainContext::default(); |                 let context = glib::MainContext::default(); | ||||||
|  | @ -99,22 +102,43 @@ impl TracksEditor { | ||||||
|                         callback(); |                         callback(); | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     this.window.close(); |                     let navigator = this.navigator.borrow().clone(); | ||||||
|  |                     if let Some(navigator) = navigator { | ||||||
|  |                         navigator.pop(); | ||||||
|  |                     } | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|         recording_button.connect_clicked(clone!(@strong this => move |_| { |         recording_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             let dialog = RecordingDialog::new(this.backend.clone(), &this.window); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 let person_selector = PersonSelector::new(this.backend.clone()); | ||||||
| 
 | 
 | ||||||
|             dialog.set_selected_cb(clone!(@strong this => move |recording| { |                 person_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| { | ||||||
|                 this.recording_selected(&recording); |                     let work_selector = WorkSelector::new(this.backend.clone(), person.clone()); | ||||||
|                 this.recording.replace(Some(recording)); |                     
 | ||||||
|  |                     work_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |work| { | ||||||
|  |                         let recording_selector = RecordingSelector::new(this.backend.clone(), work.clone()); | ||||||
|  |                     
 | ||||||
|  |                         recording_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |recording| { | ||||||
|  |                             this.recording_selected(recording); | ||||||
|  |                             this.recording.replace(Some(recording.clone())); | ||||||
|  | 
 | ||||||
|  |                             navigator.clone().pop(); | ||||||
|  |                             navigator.clone().pop(); | ||||||
|  |                             navigator.clone().pop();    
 | ||||||
|                         })); |                         })); | ||||||
| 
 | 
 | ||||||
|             dialog.show(); |                         navigator.clone().push(recording_selector); | ||||||
|  |                     })); | ||||||
|  | 
 | ||||||
|  |                     navigator.clone().push(work_selector); | ||||||
|  |                 })); | ||||||
|  | 
 | ||||||
|  |                 navigator.clone().push(person_selector); | ||||||
|             } |             } | ||||||
|         )); |         })); | ||||||
| 
 | 
 | ||||||
|         this.track_list |         this.track_list | ||||||
|             .set_make_widget(clone!(@strong this => move |track| { |             .set_make_widget(clone!(@strong this => move |track| { | ||||||
|  | @ -122,11 +146,13 @@ impl TracksEditor { | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|         add_track_button.connect_clicked(clone!(@strong this => move |_| { |         add_track_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|  |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|                 let music_library_path = this.backend.get_music_library_path().unwrap(); |                 let music_library_path = this.backend.get_music_library_path().unwrap(); | ||||||
| 
 | 
 | ||||||
|                 let dialog = gtk::FileChooserNative::new( |                 let dialog = gtk::FileChooserNative::new( | ||||||
|                     Some(&gettext("Select audio files")), |                     Some(&gettext("Select audio files")), | ||||||
|                 Some(&this.window), |                     Some(&navigator.window), | ||||||
|                     gtk::FileChooserAction::Open, |                     gtk::FileChooserAction::Open, | ||||||
|                     None, |                     None, | ||||||
|                     None, |                     None, | ||||||
|  | @ -157,6 +183,7 @@ impl TracksEditor { | ||||||
|                     this.autofill_parts(); |                     this.autofill_parts(); | ||||||
|                     this.track_list.select_index(index); |                     this.track_list.select_index(index); | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         remove_track_button.connect_clicked(clone!(@strong this => move |_| { |         remove_track_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|  | @ -200,14 +227,21 @@ impl TracksEditor { | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         edit_track_button.connect_clicked(clone!(@strong this => move |_| { |         edit_track_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|  |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|                 if let Some(index) = this.track_list.get_selected_index() { |                 if let Some(index) = this.track_list.get_selected_index() { | ||||||
|                     if let Some(recording) = &*this.recording.borrow() { |                     if let Some(recording) = &*this.recording.borrow() { | ||||||
|                     TrackEditor::new(&this.window, this.tracks.borrow()[index].clone(), recording.work.clone(), clone!(@strong this => move |track| { |                         let editor = TrackEditor::new(this.tracks.borrow()[index].clone(), recording.work.clone()); | ||||||
|  |                         
 | ||||||
|  |                         editor.set_ready_cb(clone!(@strong this => move |track| { | ||||||
|                             let mut tracks = this.tracks.borrow_mut(); |                             let mut tracks = this.tracks.borrow_mut(); | ||||||
|                             tracks[index] = track; |                             tracks[index] = track; | ||||||
|                             this.track_list.show_items(tracks.clone()); |                             this.track_list.show_items(tracks.clone()); | ||||||
|                             this.track_list.select_index(index); |                             this.track_list.select_index(index); | ||||||
|                     })).show(); |                         })); | ||||||
|  | 
 | ||||||
|  |                         navigator.push(editor); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         })); |         })); | ||||||
|  | @ -228,11 +262,6 @@ impl TracksEditor { | ||||||
|         self.callback.replace(Some(Box::new(cb))); |         self.callback.replace(Some(Box::new(cb))); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Open the track editor.
 |  | ||||||
|     pub fn show(&self) { |  | ||||||
|         self.window.show(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Create a widget representing a track.
 |     /// Create a widget representing a track.
 | ||||||
|     fn build_track_row(&self, track: &Track) -> gtk::Widget { |     fn build_track_row(&self, track: &Track) -> gtk::Widget { | ||||||
|         let mut title_parts = Vec::<String>::new(); |         let mut title_parts = Vec::<String>::new(); | ||||||
|  | @ -292,3 +321,17 @@ impl TracksEditor { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | impl NavigatorScreen for TracksEditor { | ||||||
|  |     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,9 +1,9 @@ | ||||||
| use super::part_editor::*; | use super::work_part::WorkPartEditor; | ||||||
| use super::section_editor::*; | use super::work_section::WorkSectionEditor; | ||||||
| use crate::backend::*; | use crate::backend::Backend; | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
| use crate::dialogs::*; | use crate::selectors::{InstrumentSelector, PersonSelector}; | ||||||
| use crate::widgets::*; | use crate::widgets::{List, Navigator, NavigatorScreen}; | ||||||
| use anyhow::Result; | use anyhow::Result; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
|  | @ -22,9 +22,8 @@ enum PartOrSection { | ||||||
| 
 | 
 | ||||||
| /// A widget for editing and creating works.
 | /// A widget for editing and creating works.
 | ||||||
| pub struct WorkEditor { | pub struct WorkEditor { | ||||||
|     pub widget: gtk::Stack, |     widget: gtk::Stack, | ||||||
|     backend: Rc<Backend>, |     backend: Rc<Backend>, | ||||||
|     parent: gtk::Window, |  | ||||||
|     save_button: gtk::Button, |     save_button: gtk::Button, | ||||||
|     title_entry: gtk::Entry, |     title_entry: gtk::Entry, | ||||||
|     info_bar: gtk::InfoBar, |     info_bar: gtk::InfoBar, | ||||||
|  | @ -36,24 +35,19 @@ pub struct WorkEditor { | ||||||
|     composer: RefCell<Option<Person>>, |     composer: RefCell<Option<Person>>, | ||||||
|     instruments: RefCell<Vec<Instrument>>, |     instruments: RefCell<Vec<Instrument>>, | ||||||
|     structure: RefCell<Vec<PartOrSection>>, |     structure: RefCell<Vec<PartOrSection>>, | ||||||
|     cancel_cb: RefCell<Option<Box<dyn Fn() -> ()>>>, |  | ||||||
|     saved_cb: RefCell<Option<Box<dyn Fn(Work) -> ()>>>, |     saved_cb: RefCell<Option<Box<dyn Fn(Work) -> ()>>>, | ||||||
|  |     navigator: RefCell<Option<Rc<Navigator>>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl WorkEditor { | impl WorkEditor { | ||||||
|     /// Create a new work editor widget and optionally initialize it. The parent window is used
 |     /// Create a new work editor widget and optionally initialize it.
 | ||||||
|     /// as the parent for newly created dialogs.
 |     pub fn new(backend: Rc<Backend>, work: Option<Work>) -> Rc<Self> { | ||||||
|     pub fn new<P: IsA<gtk::Window>>( |  | ||||||
|         backend: Rc<Backend>, |  | ||||||
|         parent: &P, |  | ||||||
|         work: Option<Work>, |  | ||||||
|     ) -> Rc<Self> { |  | ||||||
|         // Create UI
 |         // Create UI
 | ||||||
| 
 | 
 | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_editor.ui"); |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_editor.ui"); | ||||||
| 
 | 
 | ||||||
|         get_widget!(builder, gtk::Stack, widget); |         get_widget!(builder, gtk::Stack, widget); | ||||||
|         get_widget!(builder, gtk::Button, cancel_button); |         get_widget!(builder, gtk::Button, back_button); | ||||||
|         get_widget!(builder, gtk::Button, save_button); |         get_widget!(builder, gtk::Button, save_button); | ||||||
|         get_widget!(builder, gtk::InfoBar, info_bar); |         get_widget!(builder, gtk::InfoBar, info_bar); | ||||||
|         get_widget!(builder, gtk::Entry, title_entry); |         get_widget!(builder, gtk::Entry, title_entry); | ||||||
|  | @ -102,7 +96,6 @@ impl WorkEditor { | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|             widget, |             widget, | ||||||
|             backend, |             backend, | ||||||
|             parent: parent.clone().upcast(), |  | ||||||
|             save_button, |             save_button, | ||||||
|             id, |             id, | ||||||
|             info_bar, |             info_bar, | ||||||
|  | @ -114,15 +107,16 @@ impl WorkEditor { | ||||||
|             composer: RefCell::new(composer), |             composer: RefCell::new(composer), | ||||||
|             instruments: RefCell::new(instruments), |             instruments: RefCell::new(instruments), | ||||||
|             structure: RefCell::new(structure), |             structure: RefCell::new(structure), | ||||||
|             cancel_cb: RefCell::new(None), |  | ||||||
|             saved_cb: RefCell::new(None), |             saved_cb: RefCell::new(None), | ||||||
|  |             navigator: RefCell::new(None), | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         cancel_button.connect_clicked(clone!(@strong this => move |_| { |         back_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             if let Some(cb) = &*this.cancel_cb.borrow() { |             let navigator = this.navigator.borrow().clone(); | ||||||
|                 cb(); |             if let Some(navigator) = navigator { | ||||||
|  |                 navigator.pop(); | ||||||
|             } |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|  | @ -134,7 +128,10 @@ impl WorkEditor { | ||||||
|                     clone.widget.set_visible_child_name("loading"); |                     clone.widget.set_visible_child_name("loading"); | ||||||
|                     match clone.clone().save().await { |                     match clone.clone().save().await { | ||||||
|                         Ok(_) => { |                         Ok(_) => { | ||||||
|                             // We already called the callback.
 |                             let navigator = clone.navigator.borrow().clone(); | ||||||
|  |                             if let Some(navigator) = navigator { | ||||||
|  |                                 navigator.pop(); | ||||||
|  |                             } | ||||||
|                         } |                         } | ||||||
|                         Err(_) => { |                         Err(_) => { | ||||||
|                             clone.info_bar.set_revealed(true); |                             clone.info_bar.set_revealed(true); | ||||||
|  | @ -146,14 +143,18 @@ impl WorkEditor { | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|         composer_button.connect_clicked(clone!(@strong this => move |_| { |         composer_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             let dialog = PersonSelector::new(this.backend.clone(), &this.parent); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 let selector = PersonSelector::new(this.backend.clone()); | ||||||
| 
 | 
 | ||||||
|             dialog.set_selected_cb(clone!(@strong this => move |person| { |                 selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| { | ||||||
|                 this.show_composer(&person); |                     this.show_composer(person); | ||||||
|                 this.composer.replace(Some(person)); |                     this.composer.replace(Some(person.clone())); | ||||||
|  |                     navigator.clone().pop(); | ||||||
|                 })); |                 })); | ||||||
| 
 | 
 | ||||||
|             dialog.show(); |                 navigator.push(selector); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.instrument_list.set_make_widget(|instrument| { |         this.instrument_list.set_make_widget(|instrument| { | ||||||
|  | @ -168,9 +169,11 @@ impl WorkEditor { | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         add_instrument_button.connect_clicked(clone!(@strong this => move |_| { |         add_instrument_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             let dialog = InstrumentSelector::new(this.backend.clone(), &this.parent); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 let selector = InstrumentSelector::new(this.backend.clone()); | ||||||
| 
 | 
 | ||||||
|             dialog.set_selected_cb(clone!(@strong this => move |instrument| { |                 selector.set_selected_cb(clone!(@strong this, @strong navigator => move |instrument| { | ||||||
|                     let mut instruments = this.instruments.borrow_mut(); |                     let mut instruments = this.instruments.borrow_mut(); | ||||||
| 
 | 
 | ||||||
|                     let index = match this.instrument_list.get_selected_index() { |                     let index = match this.instrument_list.get_selected_index() { | ||||||
|  | @ -178,12 +181,15 @@ impl WorkEditor { | ||||||
|                         None => instruments.len(), |                         None => instruments.len(), | ||||||
|                     }; |                     }; | ||||||
| 
 | 
 | ||||||
|                 instruments.insert(index, instrument); |                     instruments.insert(index, instrument.clone()); | ||||||
|                     this.instrument_list.show_items(instruments.clone()); |                     this.instrument_list.show_items(instruments.clone()); | ||||||
|                     this.instrument_list.select_index(index); |                     this.instrument_list.select_index(index); | ||||||
|  | 
 | ||||||
|  |                     navigator.clone().pop(); | ||||||
|                 })); |                 })); | ||||||
| 
 | 
 | ||||||
|             dialog.show(); |                 navigator.push(selector); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         remove_instrument_button.connect_clicked(clone!(@strong this => move |_| { |         remove_instrument_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|  | @ -221,9 +227,11 @@ impl WorkEditor { | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         add_part_button.connect_clicked(clone!(@strong this => move |_| { |         add_part_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             let editor = PartEditor::new(this.backend.clone(), &this.parent, None); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 let editor = WorkPartEditor::new(this.backend.clone(), None); | ||||||
| 
 | 
 | ||||||
|             editor.set_ready_cb(clone!(@strong this => move |part| { |                 editor.set_ready_cb(clone!(@strong this, @strong navigator => move |part| { | ||||||
|                     let mut structure = this.structure.borrow_mut(); |                     let mut structure = this.structure.borrow_mut(); | ||||||
| 
 | 
 | ||||||
|                     let index = match this.part_list.get_selected_index() { |                     let index = match this.part_list.get_selected_index() { | ||||||
|  | @ -234,15 +242,20 @@ impl WorkEditor { | ||||||
|                     structure.insert(index, PartOrSection::Part(part)); |                     structure.insert(index, PartOrSection::Part(part)); | ||||||
|                     this.part_list.show_items(structure.clone()); |                     this.part_list.show_items(structure.clone()); | ||||||
|                     this.part_list.select_index(index); |                     this.part_list.select_index(index); | ||||||
|  | 
 | ||||||
|  |                     navigator.clone().pop(); | ||||||
|                 })); |                 })); | ||||||
| 
 | 
 | ||||||
|             editor.show(); |                 navigator.push(editor); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         add_section_button.connect_clicked(clone!(@strong this => move |_| { |         add_section_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             let editor = SectionEditor::new(&this.parent, None); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 let editor = WorkSectionEditor::new(None); | ||||||
| 
 | 
 | ||||||
|             editor.set_ready_cb(clone!(@strong this => move |section| { |                 editor.set_ready_cb(clone!(@strong this, @strong navigator => move |section| { | ||||||
|                     let mut structure = this.structure.borrow_mut(); |                     let mut structure = this.structure.borrow_mut(); | ||||||
| 
 | 
 | ||||||
|                     let index = match this.part_list.get_selected_index() { |                     let index = match this.part_list.get_selected_index() { | ||||||
|  | @ -253,41 +266,45 @@ impl WorkEditor { | ||||||
|                     structure.insert(index, PartOrSection::Section(section)); |                     structure.insert(index, PartOrSection::Section(section)); | ||||||
|                     this.part_list.show_items(structure.clone()); |                     this.part_list.show_items(structure.clone()); | ||||||
|                     this.part_list.select_index(index); |                     this.part_list.select_index(index); | ||||||
|  | 
 | ||||||
|  |                     navigator.clone().pop(); | ||||||
|                 })); |                 })); | ||||||
| 
 | 
 | ||||||
|             editor.show(); |                 navigator.push(editor); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         edit_part_button.connect_clicked(clone!(@strong this => move |_| { |         edit_part_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|  |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|                 if let Some(index) = this.part_list.get_selected_index() { |                 if let Some(index) = this.part_list.get_selected_index() { | ||||||
|                     match this.structure.borrow()[index].clone() { |                     match this.structure.borrow()[index].clone() { | ||||||
|                         PartOrSection::Part(part) => { |                         PartOrSection::Part(part) => { | ||||||
|                         let editor = PartEditor::new( |                             let editor = WorkPartEditor::new(this.backend.clone(), Some(part)); | ||||||
|                             this.backend.clone(), |  | ||||||
|                             &this.parent, |  | ||||||
|                             Some(part), |  | ||||||
|                         ); |  | ||||||
| 
 | 
 | ||||||
|                         editor.set_ready_cb(clone!(@strong this => move |part| { |                             editor.set_ready_cb(clone!(@strong this, @strong navigator => move |part| { | ||||||
|                                 let mut structure = this.structure.borrow_mut(); |                                 let mut structure = this.structure.borrow_mut(); | ||||||
|                                 structure[index] = PartOrSection::Part(part); |                                 structure[index] = PartOrSection::Part(part); | ||||||
|                                 this.part_list.show_items(structure.clone()); |                                 this.part_list.show_items(structure.clone()); | ||||||
|                                 this.part_list.select_index(index); |                                 this.part_list.select_index(index); | ||||||
|  |                                 navigator.clone().pop(); | ||||||
|                             })); |                             })); | ||||||
| 
 | 
 | ||||||
|                         editor.show(); |                             navigator.push(editor); | ||||||
|                         } |                         } | ||||||
|                         PartOrSection::Section(section) => { |                         PartOrSection::Section(section) => { | ||||||
|                         let editor = SectionEditor::new(&this.parent, Some(section)); |                             let editor = WorkSectionEditor::new(Some(section)); | ||||||
| 
 | 
 | ||||||
|                         editor.set_ready_cb(clone!(@strong this => move |section| { |                             editor.set_ready_cb(clone!(@strong this, @strong navigator => move |section| { | ||||||
|                                 let mut structure = this.structure.borrow_mut(); |                                 let mut structure = this.structure.borrow_mut(); | ||||||
|                                 structure[index] = PartOrSection::Section(section); |                                 structure[index] = PartOrSection::Section(section); | ||||||
|                                 this.part_list.show_items(structure.clone()); |                                 this.part_list.show_items(structure.clone()); | ||||||
|                                 this.part_list.select_index(index); |                                 this.part_list.select_index(index); | ||||||
|  |                                 navigator.clone().pop(); | ||||||
|                             })); |                             })); | ||||||
| 
 | 
 | ||||||
|                         editor.show(); |                             navigator.push(editor); | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | @ -337,11 +354,6 @@ impl WorkEditor { | ||||||
|         this |         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.
 |     /// The closure to call when a work was created.
 | ||||||
|     pub fn set_saved_cb<F: Fn(Work) -> () + 'static>(&self, cb: F) { |     pub fn set_saved_cb<F: Fn(Work) -> () + 'static>(&self, cb: F) { | ||||||
|         self.saved_cb.replace(Some(Box::new(cb))); |         self.saved_cb.replace(Some(Box::new(cb))); | ||||||
|  | @ -404,3 +416,17 @@ impl WorkEditor { | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | impl NavigatorScreen for WorkEditor { | ||||||
|  |     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,6 +1,7 @@ | ||||||
| use crate::backend::*; | use crate::backend::Backend; | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
| use crate::dialogs::*; | use crate::selectors::PersonSelector; | ||||||
|  | use crate::widgets::{Navigator, NavigatorScreen}; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
|  | @ -9,37 +10,32 @@ use std::cell::RefCell; | ||||||
| use std::rc::Rc; | use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
| /// A dialog for creating or editing a work part.
 | /// A dialog for creating or editing a work part.
 | ||||||
| pub struct PartEditor { | pub struct WorkPartEditor { | ||||||
|     backend: Rc<Backend>, |     backend: Rc<Backend>, | ||||||
|     window: libhandy::Window, |     widget: gtk::Box, | ||||||
|     title_entry: gtk::Entry, |     title_entry: gtk::Entry, | ||||||
|     composer_label: gtk::Label, |     composer_label: gtk::Label, | ||||||
|     reset_composer_button: gtk::Button, |     reset_composer_button: gtk::Button, | ||||||
|     composer: RefCell<Option<Person>>, |     composer: RefCell<Option<Person>>, | ||||||
|     ready_cb: RefCell<Option<Box<dyn Fn(WorkPart) -> ()>>>, |     ready_cb: RefCell<Option<Box<dyn Fn(WorkPart) -> ()>>>, | ||||||
|  |     navigator: RefCell<Option<Rc<Navigator>>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl PartEditor { | impl WorkPartEditor { | ||||||
|     /// Create a new part editor and optionally initialize it.
 |     /// Create a new part editor and optionally initialize it.
 | ||||||
|     pub fn new<P: IsA<gtk::Window>>( |     pub fn new(backend: Rc<Backend>, part: Option<WorkPart>) -> Rc<Self> { | ||||||
|         backend: Rc<Backend>, |  | ||||||
|         parent: &P, |  | ||||||
|         part: Option<WorkPart>, |  | ||||||
|     ) -> Rc<Self> { |  | ||||||
|         // Create UI
 |         // Create UI
 | ||||||
| 
 | 
 | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/part_editor.ui"); |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_part_editor.ui"); | ||||||
| 
 | 
 | ||||||
|         get_widget!(builder, libhandy::Window, window); |         get_widget!(builder, gtk::Box, widget); | ||||||
|         get_widget!(builder, gtk::Button, cancel_button); |         get_widget!(builder, gtk::Button, back_button); | ||||||
|         get_widget!(builder, gtk::Button, save_button); |         get_widget!(builder, gtk::Button, save_button); | ||||||
|         get_widget!(builder, gtk::Entry, title_entry); |         get_widget!(builder, gtk::Entry, title_entry); | ||||||
|         get_widget!(builder, gtk::Button, composer_button); |         get_widget!(builder, gtk::Button, composer_button); | ||||||
|         get_widget!(builder, gtk::Label, composer_label); |         get_widget!(builder, gtk::Label, composer_label); | ||||||
|         get_widget!(builder, gtk::Button, reset_composer_button); |         get_widget!(builder, gtk::Button, reset_composer_button); | ||||||
| 
 | 
 | ||||||
|         window.set_transient_for(Some(parent)); |  | ||||||
| 
 |  | ||||||
|         let composer = match part { |         let composer = match part { | ||||||
|             Some(part) => { |             Some(part) => { | ||||||
|                 title_entry.set_text(&part.title); |                 title_entry.set_text(&part.title); | ||||||
|  | @ -50,18 +46,22 @@ impl PartEditor { | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|             backend, |             backend, | ||||||
|             window, |             widget, | ||||||
|             title_entry, |             title_entry, | ||||||
|             composer_label, |             composer_label, | ||||||
|             reset_composer_button, |             reset_composer_button, | ||||||
|             composer: RefCell::new(composer), |             composer: RefCell::new(composer), | ||||||
|             ready_cb: RefCell::new(None), |             ready_cb: RefCell::new(None), | ||||||
|  |             navigator: RefCell::new(None), | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         cancel_button.connect_clicked(clone!(@strong this => move |_| { |         back_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             this.window.close(); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 navigator.pop(); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         save_button.connect_clicked(clone!(@strong this => move |_| { |         save_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|  | @ -72,18 +72,26 @@ impl PartEditor { | ||||||
|                 }); |                 }); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             this.window.close(); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 navigator.pop(); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         composer_button.connect_clicked(clone!(@strong this => move |_| { |         composer_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             let dialog = PersonSelector::new(this.backend.clone(), &this.window); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 let selector = PersonSelector::new(this.backend.clone()); | ||||||
| 
 | 
 | ||||||
|             dialog.set_selected_cb(clone!(@strong this => move |person| { |                 selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| { | ||||||
|                 this.show_composer(Some(&person)); |                     this.show_composer(Some(person)); | ||||||
|                 this.composer.replace(Some(person)); |                     this.composer.replace(Some(person.clone())); | ||||||
|  |                     navigator.clone().pop(); | ||||||
|                 })); |                 })); | ||||||
| 
 | 
 | ||||||
|             dialog.show(); |                 navigator.push(selector); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.reset_composer_button |         this.reset_composer_button | ||||||
|  | @ -106,11 +114,6 @@ impl PartEditor { | ||||||
|         self.ready_cb.replace(Some(Box::new(cb))); |         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.
 |     /// Update the UI according to person.
 | ||||||
|     fn show_composer(&self, person: Option<&Person>) { |     fn show_composer(&self, person: Option<&Person>) { | ||||||
|         if let Some(person) = person { |         if let Some(person) = person { | ||||||
|  | @ -122,3 +125,17 @@ impl PartEditor { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | impl NavigatorScreen for WorkPartEditor { | ||||||
|  |     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,4 +1,5 @@ | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
|  | use crate::widgets::{Navigator, NavigatorScreen}; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
| use gtk_macros::get_widget; | use gtk_macros::get_widget; | ||||||
|  | @ -6,40 +7,43 @@ use std::cell::RefCell; | ||||||
| use std::rc::Rc; | use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
| /// A dialog for creating or editing a work section.
 | /// A dialog for creating or editing a work section.
 | ||||||
| pub struct SectionEditor { | pub struct WorkSectionEditor { | ||||||
|     window: libhandy::Window, |     widget: gtk::Box, | ||||||
|     title_entry: gtk::Entry, |     title_entry: gtk::Entry, | ||||||
|     ready_cb: RefCell<Option<Box<dyn Fn(WorkSection) -> ()>>>, |     ready_cb: RefCell<Option<Box<dyn Fn(WorkSection) -> ()>>>, | ||||||
|  |     navigator: RefCell<Option<Rc<Navigator>>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl SectionEditor { | impl WorkSectionEditor { | ||||||
|     /// Create a new section editor and optionally initialize it.
 |     /// Create a new section editor and optionally initialize it.
 | ||||||
|     pub fn new<P: IsA<gtk::Window>>(parent: &P, section: Option<WorkSection>) -> Rc<Self> { |     pub fn new(section: Option<WorkSection>) -> Rc<Self> { | ||||||
|         // Create UI
 |         // Create UI
 | ||||||
| 
 | 
 | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/section_editor.ui"); |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_section_editor.ui"); | ||||||
| 
 | 
 | ||||||
|         get_widget!(builder, libhandy::Window, window); |         get_widget!(builder, gtk::Box, widget); | ||||||
|         get_widget!(builder, gtk::Button, cancel_button); |         get_widget!(builder, gtk::Button, back_button); | ||||||
|         get_widget!(builder, gtk::Button, save_button); |         get_widget!(builder, gtk::Button, save_button); | ||||||
|         get_widget!(builder, gtk::Entry, title_entry); |         get_widget!(builder, gtk::Entry, title_entry); | ||||||
| 
 | 
 | ||||||
|         window.set_transient_for(Some(parent)); |  | ||||||
| 
 |  | ||||||
|         if let Some(section) = section { |         if let Some(section) = section { | ||||||
|             title_entry.set_text(§ion.title); |             title_entry.set_text(§ion.title); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|             window, |             widget, | ||||||
|             title_entry, |             title_entry, | ||||||
|             ready_cb: RefCell::new(None), |             ready_cb: RefCell::new(None), | ||||||
|  |             navigator: RefCell::new(None), | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         cancel_button.connect_clicked(clone!(@strong this => move |_| { |         back_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             this.window.close(); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 navigator.pop(); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         save_button.connect_clicked(clone!(@strong this => move |_| { |         save_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|  | @ -50,7 +54,10 @@ impl SectionEditor { | ||||||
|                 }); |                 }); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             this.window.close(); |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 navigator.pop(); | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this |         this | ||||||
|  | @ -62,9 +69,18 @@ impl SectionEditor { | ||||||
|     pub fn set_ready_cb<F: Fn(WorkSection) -> () + 'static>(&self, cb: F) { |     pub fn set_ready_cb<F: Fn(WorkSection) -> () + 'static>(&self, cb: F) { | ||||||
|         self.ready_cb.replace(Some(Box::new(cb))); |         self.ready_cb.replace(Some(Box::new(cb))); | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     /// Show the section editor.
 | impl NavigatorScreen for WorkSectionEditor { | ||||||
|     pub fn show(&self) { |     fn attach_navigator(&self, navigator: Rc<Navigator>) { | ||||||
|         self.window.show(); |         self.navigator.replace(Some(navigator)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_widget(&self) -> gtk::Widget { | ||||||
|  |         self.widget.clone().upcast() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn detach_navigator(&self) { | ||||||
|  |         self.navigator.replace(None); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -11,12 +11,14 @@ use glib::clone; | ||||||
| use std::cell::RefCell; | use std::cell::RefCell; | ||||||
| use std::rc::Rc; | use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
| mod config; |  | ||||||
| mod backend; | mod backend; | ||||||
|  | mod config; | ||||||
| mod database; | mod database; | ||||||
| mod dialogs; | mod dialogs; | ||||||
|  | mod editors; | ||||||
| mod player; | mod player; | ||||||
| mod screens; | mod screens; | ||||||
|  | mod selectors; | ||||||
| mod widgets; | mod widgets; | ||||||
| 
 | 
 | ||||||
| mod window; | mod window; | ||||||
|  | @ -33,10 +35,7 @@ fn main() { | ||||||
|     libhandy::init(); |     libhandy::init(); | ||||||
|     resources::init().expect("Failed to initialize resources!"); |     resources::init().expect("Failed to initialize resources!"); | ||||||
| 
 | 
 | ||||||
|     let app = gtk::Application::new( |     let app = gtk::Application::new(Some("de.johrpan.musicus"), gio::ApplicationFlags::empty()) | ||||||
|         Some("de.johrpan.musicus"), |  | ||||||
|         gio::ApplicationFlags::empty(), |  | ||||||
|     ) |  | ||||||
|         .expect("Failed to initialize GTK application!"); |         .expect("Failed to initialize GTK application!"); | ||||||
| 
 | 
 | ||||||
|     let window: RefCell<Option<Rc<Window>>> = RefCell::new(None); |     let window: RefCell<Option<Rc<Window>>> = RefCell::new(None); | ||||||
|  |  | ||||||
|  | @ -52,43 +52,38 @@ sources = files( | ||||||
|   'database/tracks.rs', |   'database/tracks.rs', | ||||||
|   'database/works.rs', |   'database/works.rs', | ||||||
|   'dialogs/about.rs', |   'dialogs/about.rs', | ||||||
|   'dialogs/ensemble_editor.rs', |  | ||||||
|   'dialogs/ensemble_selector.rs', |  | ||||||
|   'dialogs/instrument_editor.rs', |  | ||||||
|   'dialogs/instrument_selector.rs', |  | ||||||
|   'dialogs/login_dialog.rs', |   'dialogs/login_dialog.rs', | ||||||
|   'dialogs/mod.rs', |   'dialogs/mod.rs', | ||||||
|   'dialogs/person_editor.rs', |  | ||||||
|   'dialogs/person_selector.rs', |  | ||||||
|   'dialogs/preferences.rs', |   'dialogs/preferences.rs', | ||||||
|   'dialogs/server_dialog.rs', |   'dialogs/server_dialog.rs', | ||||||
|   'dialogs/recording/mod.rs', |   'editors/ensemble.rs', | ||||||
|   'dialogs/recording/performance_editor.rs', |   'editors/instrument.rs', | ||||||
|   'dialogs/recording/recording_dialog.rs', |   'editors/mod.rs', | ||||||
|   'dialogs/recording/recording_editor_dialog.rs', |   'editors/performance.rs', | ||||||
|   'dialogs/recording/recording_editor.rs', |   'editors/person.rs', | ||||||
|   'dialogs/recording/recording_selector_person_screen.rs', |   'editors/recording.rs', | ||||||
|   'dialogs/recording/recording_selector.rs', |   'editors/track.rs', | ||||||
|   'dialogs/recording/recording_selector_work_screen.rs', |   'editors/tracks.rs', | ||||||
|   'dialogs/track_editor.rs', |   'editors/work.rs', | ||||||
|   'dialogs/tracks_editor.rs', |   'editors/work_part.rs', | ||||||
|   'dialogs/work/mod.rs', |   'editors/work_section.rs', | ||||||
|   'dialogs/work/part_editor.rs', |  | ||||||
|   'dialogs/work/section_editor.rs', |  | ||||||
|   'dialogs/work/work_dialog.rs', |  | ||||||
|   'dialogs/work/work_editor_dialog.rs', |  | ||||||
|   'dialogs/work/work_editor.rs', |  | ||||||
|   'dialogs/work/work_selector_person_screen.rs', |  | ||||||
|   'dialogs/work/work_selector.rs', |  | ||||||
|   'screens/ensemble_screen.rs', |   'screens/ensemble_screen.rs', | ||||||
|   'screens/mod.rs', |   'screens/mod.rs', | ||||||
|   'screens/person_screen.rs', |   'screens/person_screen.rs', | ||||||
|   'screens/player_screen.rs', |   'screens/player_screen.rs', | ||||||
|   'screens/recording_screen.rs', |   'screens/recording_screen.rs', | ||||||
|   'screens/work_screen.rs', |   'screens/work_screen.rs', | ||||||
|  |   'selectors/ensemble.rs', | ||||||
|  |   'selectors/instrument.rs', | ||||||
|  |   'selectors/mod.rs', | ||||||
|  |   'selectors/person.rs', | ||||||
|  |   'selectors/recording.rs', | ||||||
|  |   'selectors/selector.rs', | ||||||
|  |   'selectors/work.rs', | ||||||
|   'widgets/list.rs', |   'widgets/list.rs', | ||||||
|   'widgets/mod.rs', |   'widgets/mod.rs', | ||||||
|   'widgets/navigator.rs', |   'widgets/navigator.rs', | ||||||
|  |   'widgets/navigator_window.rs', | ||||||
|   'widgets/player_bar.rs', |   'widgets/player_bar.rs', | ||||||
|   'widgets/poe_list.rs', |   'widgets/poe_list.rs', | ||||||
|   'widgets/selector_row.rs', |   'widgets/selector_row.rs', | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| use super::*; | use super::*; | ||||||
| use crate::backend::*; | use crate::backend::*; | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
| use crate::dialogs::EnsembleEditor; | use crate::editors::EnsembleEditor; | ||||||
| use crate::widgets::*; | use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow}; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use gio::prelude::*; | use gio::prelude::*; | ||||||
| use glib::clone; | use glib::clone; | ||||||
|  | @ -109,7 +109,9 @@ impl EnsembleScreen { | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|         edit_action.connect_activate(clone!(@strong result => move |_, _| { |         edit_action.connect_activate(clone!(@strong result => move |_, _| { | ||||||
|             EnsembleEditor::new(result.backend.clone(), &result.window, Some(result.ensemble.clone())).show(); |             let editor = EnsembleEditor::new(result.backend.clone(), Some(result.ensemble.clone())); | ||||||
|  |             let window = NavigatorWindow::new(editor); | ||||||
|  |             window.show(); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         delete_action.connect_activate(clone!(@strong result => move |_, _| { |         delete_action.connect_activate(clone!(@strong result => move |_, _| { | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| use super::*; | use super::*; | ||||||
| use crate::backend::*; | use crate::backend::*; | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
| use crate::dialogs::PersonEditor; | use crate::editors::PersonEditor; | ||||||
| use crate::widgets::*; | use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow}; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use gio::prelude::*; | use gio::prelude::*; | ||||||
| use glib::clone; | use glib::clone; | ||||||
|  | @ -145,7 +145,9 @@ impl PersonScreen { | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|         edit_action.connect_activate(clone!(@strong result => move |_, _| { |         edit_action.connect_activate(clone!(@strong result => move |_, _| { | ||||||
|             PersonEditor::new(result.backend.clone(), &result.window, Some(result.person.clone())).show(); |             let editor = PersonEditor::new(result.backend.clone(), Some(result.person.clone())); | ||||||
|  |             let window = NavigatorWindow::new(editor); | ||||||
|  |             window.show(); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         delete_action.connect_activate(clone!(@strong result => move |_, _| { |         delete_action.connect_activate(clone!(@strong result => move |_, _| { | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| use crate::backend::*; | use crate::backend::*; | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
| use crate::dialogs::{RecordingEditorDialog, TracksEditor}; | use crate::editors::{RecordingEditor, TracksEditor}; | ||||||
| use crate::player::*; | use crate::player::*; | ||||||
| use crate::widgets::*; | use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow}; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use gio::prelude::*; | use gio::prelude::*; | ||||||
| use glib::clone; | use glib::clone; | ||||||
|  | @ -111,7 +111,9 @@ impl RecordingScreen { | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         edit_action.connect_activate(clone!(@strong result => move |_, _| { |         edit_action.connect_activate(clone!(@strong result => move |_, _| { | ||||||
|             RecordingEditorDialog::new(result.backend.clone(), &result.window, Some(result.recording.clone())).show(); |             let editor = RecordingEditor::new(result.backend.clone(), Some(result.recording.clone())); | ||||||
|  |             let window = NavigatorWindow::new(editor); | ||||||
|  |             window.show(); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         delete_action.connect_activate(clone!(@strong result => move |_, _| { |         delete_action.connect_activate(clone!(@strong result => move |_, _| { | ||||||
|  | @ -124,7 +126,9 @@ impl RecordingScreen { | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         edit_tracks_action.connect_activate(clone!(@strong result => move |_, _| { |         edit_tracks_action.connect_activate(clone!(@strong result => move |_, _| { | ||||||
|             TracksEditor::new(result.backend.clone(), &result.window, Some(result.recording.clone()), result.tracks.borrow().clone()).show(); |             let editor = TracksEditor::new(result.backend.clone(), Some(result.recording.clone()), result.tracks.borrow().clone()); | ||||||
|  |             let window = NavigatorWindow::new(editor); | ||||||
|  |             window.show(); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         delete_tracks_action.connect_activate(clone!(@strong result => move |_, _| { |         delete_tracks_action.connect_activate(clone!(@strong result => move |_, _| { | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| use super::*; | use super::*; | ||||||
| use crate::backend::*; | use crate::backend::*; | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
| use crate::dialogs::WorkEditorDialog; | use crate::editors::WorkEditor; | ||||||
| use crate::widgets::*; | use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow}; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use gio::prelude::*; | use gio::prelude::*; | ||||||
| use glib::clone; | use glib::clone; | ||||||
|  | @ -108,7 +108,9 @@ impl WorkScreen { | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|         edit_action.connect_activate(clone!(@strong result => move |_, _| { |         edit_action.connect_activate(clone!(@strong result => move |_, _| { | ||||||
|             WorkEditorDialog::new(result.backend.clone(), &result.window, Some(result.work.clone())).show(); |             let editor = WorkEditor::new(result.backend.clone(), Some(result.work.clone())); | ||||||
|  |             let window = NavigatorWindow::new(editor); | ||||||
|  |             window.show(); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         delete_action.connect_activate(clone!(@strong result => move |_, _| { |         delete_action.connect_activate(clone!(@strong result => move |_, _| { | ||||||
|  |  | ||||||
							
								
								
									
										111
									
								
								musicus/src/selectors/ensemble.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								musicus/src/selectors/ensemble.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,111 @@ | ||||||
|  | use super::selector::Selector; | ||||||
|  | use crate::backend::Backend; | ||||||
|  | use crate::database::Ensemble; | ||||||
|  | use crate::editors::EnsembleEditor; | ||||||
|  | use crate::widgets::{Navigator, NavigatorScreen}; | ||||||
|  | use gettextrs::gettext; | ||||||
|  | use glib::clone; | ||||||
|  | use gtk::prelude::*; | ||||||
|  | use std::cell::RefCell; | ||||||
|  | use std::rc::Rc; | ||||||
|  | 
 | ||||||
|  | /// A screen for selecting a ensemble.
 | ||||||
|  | pub struct EnsembleSelector { | ||||||
|  |     backend: Rc<Backend>, | ||||||
|  |     selector: Rc<Selector<Ensemble>>, | ||||||
|  |     selected_cb: RefCell<Option<Box<dyn Fn(&Ensemble) -> ()>>>, | ||||||
|  |     navigator: RefCell<Option<Rc<Navigator>>>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl EnsembleSelector { | ||||||
|  |     /// Create a new ensemble selector.
 | ||||||
|  |     pub fn new(backend: Rc<Backend>) -> Rc<Self> { | ||||||
|  |         // Create UI
 | ||||||
|  | 
 | ||||||
|  |         let selector = Selector::<Ensemble>::new(); | ||||||
|  |         selector.set_title(&gettext("Select ensemble")); | ||||||
|  | 
 | ||||||
|  |         let this = Rc::new(Self { | ||||||
|  |             backend, | ||||||
|  |             selector, | ||||||
|  |             selected_cb: RefCell::new(None), | ||||||
|  |             navigator: RefCell::new(None), | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         // Connect signals and callbacks
 | ||||||
|  | 
 | ||||||
|  |         this.selector.set_back_cb(clone!(@strong this => move || { | ||||||
|  |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 navigator.pop(); | ||||||
|  |             } | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         this.selector.set_add_cb(clone!(@strong this => move || { | ||||||
|  |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 let editor = EnsembleEditor::new(this.backend.clone(), None); | ||||||
|  |                 editor | ||||||
|  |                     .set_saved_cb(clone!(@strong this => move |ensemble| this.select(&ensemble))); | ||||||
|  |                 navigator.push(editor); | ||||||
|  |             } | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_load_online(clone!(@strong this => move || { | ||||||
|  |                 let clone = this.clone(); | ||||||
|  |                 async move { clone.backend.get_ensembles().await } | ||||||
|  |             })); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_load_local(clone!(@strong this => move || { | ||||||
|  |                 let clone = this.clone(); | ||||||
|  |                 async move { clone.backend.db().get_ensembles().await.unwrap() } | ||||||
|  |             })); | ||||||
|  | 
 | ||||||
|  |         this.selector.set_make_widget(|ensemble| { | ||||||
|  |             let label = gtk::Label::new(Some(&ensemble.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); | ||||||
|  |             label.upcast() | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_filter(|search, ensemble| ensemble.name.to_lowercase().contains(search)); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_selected_cb(clone!(@strong this => move |ensemble| this.select(ensemble))); | ||||||
|  | 
 | ||||||
|  |         this | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Set the closure to be called when an item is selected.
 | ||||||
|  |     pub fn set_selected_cb<F: Fn(&Ensemble) -> () + 'static>(&self, cb: F) { | ||||||
|  |         self.selected_cb.replace(Some(Box::new(cb))); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Select a ensemble.
 | ||||||
|  |     fn select(&self, ensemble: &Ensemble) {        
 | ||||||
|  |         if let Some(cb) = &*self.selected_cb.borrow() { | ||||||
|  |             cb(&ensemble); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl NavigatorScreen for EnsembleSelector { | ||||||
|  |     fn attach_navigator(&self, navigator: Rc<Navigator>) { | ||||||
|  |         self.navigator.replace(Some(navigator)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_widget(&self) -> gtk::Widget { | ||||||
|  |         self.selector.widget.clone().upcast() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn detach_navigator(&self) { | ||||||
|  |         self.navigator.replace(None); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										110
									
								
								musicus/src/selectors/instrument.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								musicus/src/selectors/instrument.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,110 @@ | ||||||
|  | use super::selector::Selector; | ||||||
|  | use crate::backend::Backend; | ||||||
|  | use crate::database::Instrument; | ||||||
|  | use crate::editors::InstrumentEditor; | ||||||
|  | use crate::widgets::{Navigator, NavigatorScreen}; | ||||||
|  | use gettextrs::gettext; | ||||||
|  | use glib::clone; | ||||||
|  | use gtk::prelude::*; | ||||||
|  | use std::cell::RefCell; | ||||||
|  | use std::rc::Rc; | ||||||
|  | 
 | ||||||
|  | /// A screen for selecting a instrument.
 | ||||||
|  | pub struct InstrumentSelector { | ||||||
|  |     backend: Rc<Backend>, | ||||||
|  |     selector: Rc<Selector<Instrument>>, | ||||||
|  |     selected_cb: RefCell<Option<Box<dyn Fn(&Instrument) -> ()>>>, | ||||||
|  |     navigator: RefCell<Option<Rc<Navigator>>>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl InstrumentSelector { | ||||||
|  |     /// Create a new instrument selector.
 | ||||||
|  |     pub fn new(backend: Rc<Backend>) -> Rc<Self> { | ||||||
|  |         // Create UI
 | ||||||
|  | 
 | ||||||
|  |         let selector = Selector::<Instrument>::new(); | ||||||
|  |         selector.set_title(&gettext("Select instrument")); | ||||||
|  | 
 | ||||||
|  |         let this = Rc::new(Self { | ||||||
|  |             backend, | ||||||
|  |             selector, | ||||||
|  |             selected_cb: RefCell::new(None), | ||||||
|  |             navigator: RefCell::new(None), | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         // Connect signals and callbacks
 | ||||||
|  | 
 | ||||||
|  |         this.selector.set_back_cb(clone!(@strong this => move || { | ||||||
|  |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 navigator.pop(); | ||||||
|  |             } | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         this.selector.set_add_cb(clone!(@strong this => move || { | ||||||
|  |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 let editor = InstrumentEditor::new(this.backend.clone(), None); | ||||||
|  |                 editor | ||||||
|  |                     .set_saved_cb(clone!(@strong this => move |instrument| this.select(&instrument))); | ||||||
|  |                 navigator.push(editor); | ||||||
|  |             } | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_load_online(clone!(@strong this => move || { | ||||||
|  |                 let clone = this.clone(); | ||||||
|  |                 async move { clone.backend.get_instruments().await } | ||||||
|  |             })); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_load_local(clone!(@strong this => move || { | ||||||
|  |                 let clone = this.clone(); | ||||||
|  |                 async move { clone.backend.db().get_instruments().await.unwrap() } | ||||||
|  |             })); | ||||||
|  | 
 | ||||||
|  |         this.selector.set_make_widget(|instrument| { | ||||||
|  |             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); | ||||||
|  |             label.upcast() | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_filter(|search, instrument| instrument.name.to_lowercase().contains(search)); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_selected_cb(clone!(@strong this => move |instrument| this.select(instrument))); | ||||||
|  | 
 | ||||||
|  |         this | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Set the closure to be called when an item is selected.
 | ||||||
|  |     pub fn set_selected_cb<F: Fn(&Instrument) -> () + 'static>(&self, cb: F) { | ||||||
|  |         self.selected_cb.replace(Some(Box::new(cb))); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Select a instrument.
 | ||||||
|  |     fn select(&self, instrument: &Instrument) { | ||||||
|  |         if let Some(cb) = &*self.selected_cb.borrow() { | ||||||
|  |             cb(&instrument); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl NavigatorScreen for InstrumentSelector { | ||||||
|  |     fn attach_navigator(&self, navigator: Rc<Navigator>) { | ||||||
|  |         self.navigator.replace(Some(navigator)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_widget(&self) -> gtk::Widget { | ||||||
|  |         self.selector.widget.clone().upcast() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn detach_navigator(&self) { | ||||||
|  |         self.navigator.replace(None); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								musicus/src/selectors/mod.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								musicus/src/selectors/mod.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | ||||||
|  | pub mod ensemble; | ||||||
|  | pub use ensemble::*; | ||||||
|  | 
 | ||||||
|  | pub mod instrument; | ||||||
|  | pub use instrument::*; | ||||||
|  | 
 | ||||||
|  | pub mod person; | ||||||
|  | pub use person::*; | ||||||
|  | 
 | ||||||
|  | pub mod recording; | ||||||
|  | pub use recording::*; | ||||||
|  | 
 | ||||||
|  | pub mod work; | ||||||
|  | pub use work::*; | ||||||
|  | 
 | ||||||
|  | mod selector; | ||||||
							
								
								
									
										110
									
								
								musicus/src/selectors/person.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								musicus/src/selectors/person.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,110 @@ | ||||||
|  | use super::selector::Selector; | ||||||
|  | use crate::backend::Backend; | ||||||
|  | use crate::database::Person; | ||||||
|  | use crate::editors::PersonEditor; | ||||||
|  | use crate::widgets::{Navigator, NavigatorScreen}; | ||||||
|  | use gettextrs::gettext; | ||||||
|  | use glib::clone; | ||||||
|  | use gtk::prelude::*; | ||||||
|  | use std::cell::RefCell; | ||||||
|  | use std::rc::Rc; | ||||||
|  | 
 | ||||||
|  | /// A screen for selecting a person.
 | ||||||
|  | pub struct PersonSelector { | ||||||
|  |     backend: Rc<Backend>, | ||||||
|  |     selector: Rc<Selector<Person>>, | ||||||
|  |     selected_cb: RefCell<Option<Box<dyn Fn(&Person) -> ()>>>, | ||||||
|  |     navigator: RefCell<Option<Rc<Navigator>>>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl PersonSelector { | ||||||
|  |     /// Create a new person selector.
 | ||||||
|  |     pub fn new(backend: Rc<Backend>) -> Rc<Self> { | ||||||
|  |         // Create UI
 | ||||||
|  | 
 | ||||||
|  |         let selector = Selector::<Person>::new(); | ||||||
|  |         selector.set_title(&gettext("Select person")); | ||||||
|  | 
 | ||||||
|  |         let this = Rc::new(Self { | ||||||
|  |             backend, | ||||||
|  |             selector, | ||||||
|  |             selected_cb: RefCell::new(None), | ||||||
|  |             navigator: RefCell::new(None), | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         // Connect signals and callbacks
 | ||||||
|  | 
 | ||||||
|  |         this.selector.set_back_cb(clone!(@strong this => move || { | ||||||
|  |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 navigator.pop(); | ||||||
|  |             } | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         this.selector.set_add_cb(clone!(@strong this => move || { | ||||||
|  |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 let editor = PersonEditor::new(this.backend.clone(), None); | ||||||
|  |                 editor | ||||||
|  |                     .set_saved_cb(clone!(@strong this => move |person| this.select(&person))); | ||||||
|  |                 navigator.push(editor); | ||||||
|  |             } | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_load_online(clone!(@strong this => move || { | ||||||
|  |                 let clone = this.clone(); | ||||||
|  |                 async move { clone.backend.get_persons().await } | ||||||
|  |             })); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_load_local(clone!(@strong this => move || { | ||||||
|  |                 let clone = this.clone(); | ||||||
|  |                 async move { clone.backend.db().get_persons().await.unwrap() } | ||||||
|  |             })); | ||||||
|  | 
 | ||||||
|  |         this.selector.set_make_widget(|person| { | ||||||
|  |             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); | ||||||
|  |             label.upcast() | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_filter(|search, person| person.name_fl().to_lowercase().contains(search)); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_selected_cb(clone!(@strong this => move |person| this.select(person))); | ||||||
|  | 
 | ||||||
|  |         this | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Set the closure to be called when an item is selected.
 | ||||||
|  |     pub fn set_selected_cb<F: Fn(&Person) -> () + 'static>(&self, cb: F) { | ||||||
|  |         self.selected_cb.replace(Some(Box::new(cb))); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Select a person.
 | ||||||
|  |     fn select(&self, person: &Person) { | ||||||
|  |         if let Some(cb) = &*self.selected_cb.borrow() { | ||||||
|  |             cb(&person); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl NavigatorScreen for PersonSelector { | ||||||
|  |     fn attach_navigator(&self, navigator: Rc<Navigator>) { | ||||||
|  |         self.navigator.replace(Some(navigator)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_widget(&self) -> gtk::Widget { | ||||||
|  |         self.selector.widget.clone().upcast() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn detach_navigator(&self) { | ||||||
|  |         self.navigator.replace(None); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										114
									
								
								musicus/src/selectors/recording.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								musicus/src/selectors/recording.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,114 @@ | ||||||
|  | use super::selector::Selector; | ||||||
|  | use crate::backend::Backend; | ||||||
|  | use crate::database::{Recording, Work}; | ||||||
|  | use crate::editors::RecordingEditor; | ||||||
|  | use crate::widgets::{Navigator, NavigatorScreen}; | ||||||
|  | use gettextrs::gettext; | ||||||
|  | use glib::clone; | ||||||
|  | use gtk::prelude::*; | ||||||
|  | use std::cell::RefCell; | ||||||
|  | use std::rc::Rc; | ||||||
|  | 
 | ||||||
|  | /// A screen for selecting a recording.
 | ||||||
|  | pub struct RecordingSelector { | ||||||
|  |     backend: Rc<Backend>, | ||||||
|  |     work: Work, | ||||||
|  |     selector: Rc<Selector<Recording>>, | ||||||
|  |     selected_cb: RefCell<Option<Box<dyn Fn(&Recording) -> ()>>>, | ||||||
|  |     navigator: RefCell<Option<Rc<Navigator>>>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl RecordingSelector { | ||||||
|  |     /// Create a new recording selector for recordings of a specific work.
 | ||||||
|  |     pub fn new(backend: Rc<Backend>, work: Work) -> Rc<Self> { | ||||||
|  |         // Create UI
 | ||||||
|  | 
 | ||||||
|  |         let selector = Selector::<Recording>::new(); | ||||||
|  |         selector.set_title(&gettext("Select recording")); | ||||||
|  |         selector.set_subtitle(&work.get_title()); | ||||||
|  | 
 | ||||||
|  |         let this = Rc::new(Self { | ||||||
|  |             backend, | ||||||
|  |             work, | ||||||
|  |             selector, | ||||||
|  |             selected_cb: RefCell::new(None), | ||||||
|  |             navigator: RefCell::new(None), | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         // Connect signals and callbacks
 | ||||||
|  | 
 | ||||||
|  |         this.selector.set_back_cb(clone!(@strong this => move || { | ||||||
|  |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 navigator.pop(); | ||||||
|  |             } | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         this.selector.set_add_cb(clone!(@strong this => move || { | ||||||
|  |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 let editor = RecordingEditor::new(this.backend.clone(), None); | ||||||
|  |                 editor | ||||||
|  |                     .set_selected_cb(clone!(@strong this => move |recording| this.select(&recording))); | ||||||
|  |                 navigator.push(editor); | ||||||
|  |             } | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_load_online(clone!(@strong this => move || { | ||||||
|  |                 let clone = this.clone(); | ||||||
|  |                 async move { clone.backend.get_recordings_for_work(&clone.work.id).await } | ||||||
|  |             })); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_load_local(clone!(@strong this => move || { | ||||||
|  |                 let clone = this.clone(); | ||||||
|  |                 async move { clone.backend.db().get_recordings_for_work(&clone.work.id).await.unwrap() } | ||||||
|  |             })); | ||||||
|  | 
 | ||||||
|  |         this.selector.set_make_widget(|recording| { | ||||||
|  |             let label = gtk::Label::new(Some(&recording.get_performers())); | ||||||
|  |             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.selector.set_filter(|search, recording| { | ||||||
|  |             recording.get_performers().to_lowercase().contains(search) | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_selected_cb(clone!(@strong this => move |recording| this.select(recording))); | ||||||
|  | 
 | ||||||
|  |         this | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Set the closure to be called when an item is selected.
 | ||||||
|  |     pub fn set_selected_cb<F: Fn(&Recording) -> () + 'static>(&self, cb: F) { | ||||||
|  |         self.selected_cb.replace(Some(Box::new(cb))); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Select a recording.
 | ||||||
|  |     fn select(&self, recording: &Recording) { | ||||||
|  |         if let Some(cb) = &*self.selected_cb.borrow() { | ||||||
|  |             cb(&recording); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl NavigatorScreen for RecordingSelector { | ||||||
|  |     fn attach_navigator(&self, navigator: Rc<Navigator>) { | ||||||
|  |         self.navigator.replace(Some(navigator)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_widget(&self) -> gtk::Widget { | ||||||
|  |         self.selector.widget.clone().upcast() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn detach_navigator(&self) { | ||||||
|  |         self.navigator.replace(None); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										201
									
								
								musicus/src/selectors/selector.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								musicus/src/selectors/selector.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,201 @@ | ||||||
|  | use crate::widgets::List; | ||||||
|  | use anyhow::Result; | ||||||
|  | use gettextrs::gettext; | ||||||
|  | use glib::clone; | ||||||
|  | use gtk::prelude::*; | ||||||
|  | use gtk_macros::get_widget; | ||||||
|  | use libhandy::HeaderBarExt; | ||||||
|  | use std::cell::RefCell; | ||||||
|  | use std::future::Future; | ||||||
|  | use std::pin::Pin; | ||||||
|  | use std::rc::Rc; | ||||||
|  | 
 | ||||||
|  | /// A screen that presents a list of items. It allows to switch between the server and the local
 | ||||||
|  | /// database and to search within the list.
 | ||||||
|  | pub struct Selector<T: 'static> { | ||||||
|  |     pub widget: gtk::Box, | ||||||
|  |     header: libhandy::HeaderBar, | ||||||
|  |     server_check_button: gtk::CheckButton, | ||||||
|  |     stack: gtk::Stack, | ||||||
|  |     list: Rc<List<T>>, | ||||||
|  |     back_cb: RefCell<Option<Box<dyn Fn() -> ()>>>, | ||||||
|  |     add_cb: RefCell<Option<Box<dyn Fn() -> ()>>>, | ||||||
|  |     load_online: RefCell<Option<Box<dyn Fn() -> Box<dyn Future<Output = Result<Vec<T>>>>>>>, | ||||||
|  |     load_local: RefCell<Option<Box<dyn Fn() -> Box<dyn Future<Output = Vec<T>>>>>>, | ||||||
|  |     filter: RefCell<Option<Box<dyn Fn(&str, &T) -> bool>>>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T> Selector<T> { | ||||||
|  |     /// Create a new selector.
 | ||||||
|  |     pub fn new() -> Rc<Self> { | ||||||
|  |         // Create UI
 | ||||||
|  | 
 | ||||||
|  |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/selector.ui"); | ||||||
|  | 
 | ||||||
|  |         get_widget!(builder, gtk::Box, widget); | ||||||
|  |         get_widget!(builder, libhandy::HeaderBar, header); | ||||||
|  |         get_widget!(builder, gtk::Button, back_button); | ||||||
|  |         get_widget!(builder, gtk::Button, add_button); | ||||||
|  |         get_widget!(builder, gtk::SearchEntry, search_entry); | ||||||
|  |         get_widget!(builder, gtk::CheckButton, server_check_button); | ||||||
|  |         get_widget!(builder, gtk::Stack, stack); | ||||||
|  |         get_widget!(builder, gtk::Frame, frame); | ||||||
|  |         get_widget!(builder, gtk::Button, try_again_button); | ||||||
|  | 
 | ||||||
|  |         let list = List::<T>::new(&gettext("Nothing found.")); | ||||||
|  |         frame.add(&list.widget); | ||||||
|  | 
 | ||||||
|  |         let this = Rc::new(Self { | ||||||
|  |             widget, | ||||||
|  |             header, | ||||||
|  |             server_check_button, | ||||||
|  |             stack, | ||||||
|  |             list, | ||||||
|  |             back_cb: RefCell::new(None), | ||||||
|  |             add_cb: RefCell::new(None), | ||||||
|  |             load_online: RefCell::new(None), | ||||||
|  |             load_local: RefCell::new(None), | ||||||
|  |             filter: RefCell::new(None), | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         // Connect signals and callbacks
 | ||||||
|  | 
 | ||||||
|  |         back_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|  |             if let Some(cb) = &*this.back_cb.borrow() { | ||||||
|  |                 cb(); | ||||||
|  |             } | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         add_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|  |             if let Some(cb) = &*this.add_cb.borrow() { | ||||||
|  |                 cb(); | ||||||
|  |             } | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         search_entry.connect_search_changed(clone!(@strong this => move |_| { | ||||||
|  |             this.list.invalidate_filter(); | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         this.server_check_button | ||||||
|  |             .connect_toggled(clone!(@strong this => move |_| { | ||||||
|  |                 if this.server_check_button.get_active() { | ||||||
|  |                     this.clone().load_online(); | ||||||
|  |                 } else { | ||||||
|  |                     this.clone().load_local(); | ||||||
|  |                 } | ||||||
|  |             })); | ||||||
|  | 
 | ||||||
|  |         this.list.set_filter( | ||||||
|  |             clone!(@strong this, @strong search_entry => move |item: &T| { | ||||||
|  |                 match &*this.filter.borrow() { | ||||||
|  |                     Some(filter) => { | ||||||
|  |                         let search = search_entry.get_text().to_string().to_lowercase(); | ||||||
|  |                         search.is_empty() || filter(&search, item) | ||||||
|  |                     } | ||||||
|  |                     None => true, | ||||||
|  |                 } | ||||||
|  |             }), | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         try_again_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|  |             this.clone().load_online(); | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         // Initialize
 | ||||||
|  |         this.clone().load_online(); | ||||||
|  | 
 | ||||||
|  |         this | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Set the title to be shown in the header.
 | ||||||
|  |     pub fn set_title(&self, title: &str) { | ||||||
|  |         self.header.set_title(Some(title)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Set the subtitle to be shown in the header.
 | ||||||
|  |     pub fn set_subtitle(&self, subtitle: &str) { | ||||||
|  |         self.header.set_subtitle(Some(subtitle)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Set the closure to be called when the user wants to go back.
 | ||||||
|  |     pub fn set_back_cb<F: Fn() -> () + 'static>(&self, cb: F) { | ||||||
|  |         self.back_cb.replace(Some(Box::new(cb))); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Set the closure to be called when the user wants to add an item.
 | ||||||
|  |     pub fn set_add_cb<F: Fn() -> () + 'static>(&self, cb: F) { | ||||||
|  |         self.add_cb.replace(Some(Box::new(cb))); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Set the async closure to be called to fetch items from the server. If that results in an
 | ||||||
|  |     /// error, an error screen is shown allowing to try again.
 | ||||||
|  |     pub fn set_load_online<F, R>(&self, cb: F) | ||||||
|  |     where | ||||||
|  |         F: (Fn() -> R) + 'static, | ||||||
|  |         R: Future<Output = Result<Vec<T>>> + 'static, | ||||||
|  |     { | ||||||
|  |         self.load_online | ||||||
|  |             .replace(Some(Box::new(move || Box::new(cb())))); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Set the async closure to be called to get local items.
 | ||||||
|  |     pub fn set_load_local<F, R>(&self, cb: F) | ||||||
|  |     where | ||||||
|  |         F: (Fn() -> R) + 'static, | ||||||
|  |         R: Future<Output = Vec<T>> + 'static, | ||||||
|  |     { | ||||||
|  |         self.load_local | ||||||
|  |             .replace(Some(Box::new(move || Box::new(cb())))); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Set the closure to be called for creating a new list row.
 | ||||||
|  |     pub fn set_make_widget<F: Fn(&T) -> gtk::Widget + 'static>(&self, make_widget: F) { | ||||||
|  |         self.list.set_make_widget(make_widget); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Set a closure to call when deciding whether to show an item based on a search string. The
 | ||||||
|  |     /// search string will be converted to lowercase.
 | ||||||
|  |     pub fn set_filter<F: Fn(&str, &T) -> bool + 'static>(&self, filter: F) { | ||||||
|  |         self.filter.replace(Some(Box::new(filter))); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Set the closure to be called when an item is selected.
 | ||||||
|  |     pub fn set_selected_cb<F: Fn(&T) -> () + 'static>(&self, cb: F) { | ||||||
|  |         self.list.set_selected(cb); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn load_online(self: Rc<Self>) { | ||||||
|  |         let context = glib::MainContext::default(); | ||||||
|  |         let clone = self.clone(); | ||||||
|  |         context.spawn_local(async move { | ||||||
|  |             if let Some(cb) = &*self.load_online.borrow() { | ||||||
|  |                 self.stack.set_visible_child_name("loading"); | ||||||
|  | 
 | ||||||
|  |                 match Pin::from(cb()).await { | ||||||
|  |                     Ok(items) => { | ||||||
|  |                         clone.list.show_items(items); | ||||||
|  |                         clone.stack.set_visible_child_name("content"); | ||||||
|  |                     } | ||||||
|  |                     Err(_) => { | ||||||
|  |                         clone.list.show_items(Vec::new()); | ||||||
|  |                         clone.stack.set_visible_child_name("error"); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn load_local(self: Rc<Self>) { | ||||||
|  |         let context = glib::MainContext::default(); | ||||||
|  |         let clone = self.clone(); | ||||||
|  |         context.spawn_local(async move { | ||||||
|  |             if let Some(cb) = &*self.load_local.borrow() { | ||||||
|  |                 self.stack.set_visible_child_name("loading"); | ||||||
|  | 
 | ||||||
|  |                 let items = Pin::from(cb()).await; | ||||||
|  |                 clone.list.show_items(items); | ||||||
|  |                 clone.stack.set_visible_child_name("content"); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										113
									
								
								musicus/src/selectors/work.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								musicus/src/selectors/work.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,113 @@ | ||||||
|  | use super::selector::Selector; | ||||||
|  | use crate::backend::Backend; | ||||||
|  | use crate::database::{Person, Work}; | ||||||
|  | use crate::editors::WorkEditor; | ||||||
|  | use crate::widgets::{Navigator, NavigatorScreen}; | ||||||
|  | use gettextrs::gettext; | ||||||
|  | use glib::clone; | ||||||
|  | use gtk::prelude::*; | ||||||
|  | use std::cell::RefCell; | ||||||
|  | use std::rc::Rc; | ||||||
|  | 
 | ||||||
|  | /// A screen for selecting a work.
 | ||||||
|  | pub struct WorkSelector { | ||||||
|  |     backend: Rc<Backend>, | ||||||
|  |     person: Person, | ||||||
|  |     selector: Rc<Selector<Work>>, | ||||||
|  |     selected_cb: RefCell<Option<Box<dyn Fn(&Work) -> ()>>>, | ||||||
|  |     navigator: RefCell<Option<Rc<Navigator>>>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl WorkSelector { | ||||||
|  |     /// Create a new work selector for works by a specific composer.
 | ||||||
|  |     pub fn new(backend: Rc<Backend>, person: Person) -> Rc<Self> { | ||||||
|  |         // Create UI
 | ||||||
|  | 
 | ||||||
|  |         let selector = Selector::<Work>::new(); | ||||||
|  |         selector.set_title(&gettext("Select work")); | ||||||
|  |         selector.set_subtitle(&person.name_fl()); | ||||||
|  | 
 | ||||||
|  |         let this = Rc::new(Self { | ||||||
|  |             backend, | ||||||
|  |             person, | ||||||
|  |             selector, | ||||||
|  |             selected_cb: RefCell::new(None), | ||||||
|  |             navigator: RefCell::new(None), | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         // Connect signals and callbacks
 | ||||||
|  | 
 | ||||||
|  |         this.selector.set_back_cb(clone!(@strong this => move || { | ||||||
|  |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 navigator.pop(); | ||||||
|  |             } | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         this.selector.set_add_cb(clone!(@strong this => move || { | ||||||
|  |             let navigator = this.navigator.borrow().clone(); | ||||||
|  |             if let Some(navigator) = navigator { | ||||||
|  |                 let editor = WorkEditor::new(this.backend.clone(), None); | ||||||
|  |                 editor | ||||||
|  |                     .set_saved_cb(clone!(@strong this => move |work| this.select(&work))); | ||||||
|  |                 navigator.push(editor); | ||||||
|  |             } | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_load_online(clone!(@strong this => move || { | ||||||
|  |                 let clone = this.clone(); | ||||||
|  |                 async move { clone.backend.get_works(&clone.person.id).await } | ||||||
|  |             })); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_load_local(clone!(@strong this => move || { | ||||||
|  |                 let clone = this.clone(); | ||||||
|  |                 async move { clone.backend.db().get_works(&clone.person.id).await.unwrap() } | ||||||
|  |             })); | ||||||
|  | 
 | ||||||
|  |         this.selector.set_make_widget(|work| { | ||||||
|  |             let label = gtk::Label::new(Some(&work.title)); | ||||||
|  |             label.set_halign(gtk::Align::Start); | ||||||
|  |             label.set_margin_start(6); | ||||||
|  |             label.set_margin_end(6); | ||||||
|  |             label.set_margin_top(6); | ||||||
|  |             label.set_margin_bottom(6); | ||||||
|  |             label.upcast() | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_filter(|search, work| work.title.to_lowercase().contains(search)); | ||||||
|  | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_selected_cb(clone!(@strong this => move |work| this.select(work))); | ||||||
|  | 
 | ||||||
|  |         this | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Set the closure to be called when an item is selected.
 | ||||||
|  |     pub fn set_selected_cb<F: Fn(&Work) -> () + 'static>(&self, cb: F) { | ||||||
|  |         self.selected_cb.replace(Some(Box::new(cb))); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Select a work.
 | ||||||
|  |     fn select(&self, work: &Work) { | ||||||
|  |         if let Some(cb) = &*self.selected_cb.borrow() { | ||||||
|  |             cb(&work); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl NavigatorScreen for WorkSelector { | ||||||
|  |     fn attach_navigator(&self, navigator: Rc<Navigator>) { | ||||||
|  |         self.navigator.replace(Some(navigator)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_widget(&self) -> gtk::Widget { | ||||||
|  |         self.selector.widget.clone().upcast() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn detach_navigator(&self) { | ||||||
|  |         self.navigator.replace(None); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -4,6 +4,9 @@ pub use list::*; | ||||||
| pub mod navigator; | pub mod navigator; | ||||||
| pub use navigator::*; | pub use navigator::*; | ||||||
| 
 | 
 | ||||||
|  | pub mod navigator_window; | ||||||
|  | pub use navigator_window::*; | ||||||
|  | 
 | ||||||
| pub mod player_bar; | pub mod player_bar; | ||||||
| pub use player_bar::*; | pub use player_bar::*; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ pub trait NavigatorScreen { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub struct Navigator { | pub struct Navigator { | ||||||
|  |     pub window: gtk::Window, | ||||||
|     pub widget: gtk::Stack, |     pub widget: gtk::Stack, | ||||||
|     screens: RefCell<Vec<Rc<dyn NavigatorScreen>>>, |     screens: RefCell<Vec<Rc<dyn NavigatorScreen>>>, | ||||||
|     old_screens: RefCell<Vec<Rc<dyn NavigatorScreen>>>, |     old_screens: RefCell<Vec<Rc<dyn NavigatorScreen>>>, | ||||||
|  | @ -17,17 +18,22 @@ pub struct Navigator { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Navigator { | impl Navigator { | ||||||
|     pub fn new<W>(empty_screen: &W) -> Rc<Self> |     pub fn new<W, S>(window: &W, empty_screen: &S) -> Rc<Self> | ||||||
|     where |     where | ||||||
|         W: IsA<gtk::Widget>, |         W: IsA<gtk::Window>, | ||||||
|  |         S: IsA<gtk::Widget>, | ||||||
|     { |     { | ||||||
|         let widget = gtk::Stack::new(); |         let widget = gtk::Stack::new(); | ||||||
|  |         widget.set_hhomogeneous(false); | ||||||
|  |         widget.set_vhomogeneous(false); | ||||||
|  |         widget.set_interpolate_size(true); | ||||||
|         widget.set_transition_type(gtk::StackTransitionType::Crossfade); |         widget.set_transition_type(gtk::StackTransitionType::Crossfade); | ||||||
|         widget.set_hexpand(true); |         widget.set_hexpand(true); | ||||||
|         widget.add_named(empty_screen, "empty_screen"); |         widget.add_named(empty_screen, "empty_screen"); | ||||||
|         widget.show(); |         widget.show(); | ||||||
| 
 | 
 | ||||||
|         let result = Rc::new(Self { |         let result = Rc::new(Self { | ||||||
|  |             window: window.clone().upcast(), | ||||||
|             widget, |             widget, | ||||||
|             screens: RefCell::new(Vec::new()), |             screens: RefCell::new(Vec::new()), | ||||||
|             old_screens: RefCell::new(Vec::new()), |             old_screens: RefCell::new(Vec::new()), | ||||||
|  | @ -48,7 +54,10 @@ impl Navigator { | ||||||
|         result |         result | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn set_back_cb<F>(&self, cb: F) where F: Fn() -> () + 'static { |     pub fn set_back_cb<F>(&self, cb: F) | ||||||
|  |     where | ||||||
|  |         F: Fn() -> () + 'static, | ||||||
|  |     { | ||||||
|         self.back_cb.replace(Some(Box::new(cb))); |         self.back_cb.replace(Some(Box::new(cb))); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										42
									
								
								musicus/src/widgets/navigator_window.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								musicus/src/widgets/navigator_window.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | ||||||
|  | use crate::widgets::{Navigator, NavigatorScreen}; | ||||||
|  | use glib::clone; | ||||||
|  | use gtk::prelude::*; | ||||||
|  | use std::rc::Rc; | ||||||
|  | 
 | ||||||
|  | /// A window hosting a navigator.
 | ||||||
|  | pub struct NavigatorWindow { | ||||||
|  |     window: libhandy::Window, | ||||||
|  |     navigator: Rc<Navigator>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl NavigatorWindow { | ||||||
|  |     /// Create a new navigator window showing an initial screen.
 | ||||||
|  |     pub fn new<S: NavigatorScreen + 'static>(initial_screen: Rc<S>) -> Rc<Self> { | ||||||
|  |         // Create UI
 | ||||||
|  | 
 | ||||||
|  |         let window = libhandy::Window::new(); | ||||||
|  |         window.set_default_size(600, 424); | ||||||
|  |         let placeholder = gtk::Label::new(None); | ||||||
|  |         let navigator = Navigator::new(&window, &placeholder); | ||||||
|  |         window.add(&navigator.widget); | ||||||
|  | 
 | ||||||
|  |         let this = Rc::new(Self { window, navigator }); | ||||||
|  | 
 | ||||||
|  |         // Connect signals and callbacks
 | ||||||
|  | 
 | ||||||
|  |         this.navigator.set_back_cb(clone!(@strong this => move || { | ||||||
|  |             this.window.close(); | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         // Initialize
 | ||||||
|  | 
 | ||||||
|  |         this.navigator.clone().replace(initial_screen); | ||||||
|  | 
 | ||||||
|  |         this | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Show the navigator window.
 | ||||||
|  |     pub fn show(&self) { | ||||||
|  |         self.window.show(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| use crate::backend::*; | use crate::backend::*; | ||||||
| use crate::dialogs::*; | use crate::dialogs::*; | ||||||
|  | use crate::editors::TracksEditor; | ||||||
| use crate::screens::*; | use crate::screens::*; | ||||||
| use crate::widgets::*; | use crate::widgets::*; | ||||||
| use futures::prelude::*; | use futures::prelude::*; | ||||||
|  | @ -42,7 +43,7 @@ impl Window { | ||||||
|         stack.add_named(&player_screen.widget, "player_screen"); |         stack.add_named(&player_screen.widget, "player_screen"); | ||||||
| 
 | 
 | ||||||
|         let poe_list = PoeList::new(backend.clone()); |         let poe_list = PoeList::new(backend.clone()); | ||||||
|         let navigator = Navigator::new(&empty_screen); |         let navigator = Navigator::new(&window, &empty_screen); | ||||||
|         navigator.set_back_cb(clone!(@strong leaflet, @strong sidebar_box => move || { |         navigator.set_back_cb(clone!(@strong leaflet, @strong sidebar_box => move || { | ||||||
|             leaflet.set_visible_child(&sidebar_box); |             leaflet.set_visible_child(&sidebar_box); | ||||||
|         })); |         })); | ||||||
|  | @ -84,13 +85,14 @@ impl Window { | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         add_button.connect_clicked(clone!(@strong result => move |_| { |         add_button.connect_clicked(clone!(@strong result => move |_| { | ||||||
|             let editor = TracksEditor::new(result.backend.clone(), &result.window, None, Vec::new()); |             let editor = TracksEditor::new(result.backend.clone(), None, Vec::new()); | ||||||
| 
 | 
 | ||||||
|             editor.set_callback(clone!(@strong result => move || { |             editor.set_callback(clone!(@strong result => move || { | ||||||
|                 result.reload(); |                 result.reload(); | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|             editor.show(); |             let window = NavigatorWindow::new(editor); | ||||||
|  |             window.show(); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         result |         result | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Elias Projahn
						Elias Projahn