mirror of
				https://github.com/johrpan/musicus.git
				synced 2025-10-26 11:47:25 +01:00 
			
		
		
		
	Port most screens to the new navigator
This commit is contained in:
		
							parent
							
								
									7eff62b5a4
								
							
						
					
					
						commit
						18e33c3d0d
					
				
					 23 changed files with 499 additions and 1063 deletions
				
			
		|  | @ -1,7 +1,8 @@ | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::database::generate_id; | use crate::database::generate_id; | ||||||
| use crate::database::Ensemble; | use crate::database::Ensemble; | ||||||
| use crate::widgets::{Editor, EntryRow, Navigator, NavigatorScreen, Section, UploadSection}; | use crate::navigator::{NavigationHandle, Screen}; | ||||||
|  | use crate::widgets::{Editor, EntryRow, Section, UploadSection, Widget}; | ||||||
| use anyhow::Result; | use anyhow::Result; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
|  | @ -11,7 +12,7 @@ use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
| /// A dialog for creating or editing a ensemble.
 | /// A dialog for creating or editing a ensemble.
 | ||||||
| pub struct EnsembleEditor { | pub struct EnsembleEditor { | ||||||
|     backend: Rc<Backend>, |     handle: NavigationHandle<Ensemble>, | ||||||
| 
 | 
 | ||||||
|     /// The ID of the ensemble that is edited or a newly generated one.
 |     /// The ID of the ensemble that is edited or a newly generated one.
 | ||||||
|     id: String, |     id: String, | ||||||
|  | @ -19,15 +20,13 @@ pub struct EnsembleEditor { | ||||||
|     editor: Editor, |     editor: Editor, | ||||||
|     name: EntryRow, |     name: EntryRow, | ||||||
|     upload: UploadSection, |     upload: UploadSection, | ||||||
|     saved_cb: RefCell<Option<Box<dyn Fn(Ensemble) -> ()>>>, |  | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl EnsembleEditor { | impl Screen<Option<Ensemble>, Ensemble> for EnsembleEditor { | ||||||
|     /// Create a new ensemble editor and optionally initialize it.
 |     /// Create a new ensemble editor and optionally initialize it.
 | ||||||
|     pub fn new(backend: Rc<Backend>, ensemble: Option<Ensemble>) -> Rc<Self> { |     fn new(ensemble: Option<Ensemble>, handle: NavigationHandle<Ensemble>) -> Rc<Self> { | ||||||
|         let editor = Editor::new(); |         let editor = Editor::new(); | ||||||
|         editor.set_title("Ensemble"); |         editor.set_title("Ensemble/Role"); | ||||||
| 
 | 
 | ||||||
|         let list = gtk::ListBoxBuilder::new() |         let list = gtk::ListBoxBuilder::new() | ||||||
|             .selection_mode(gtk::SelectionMode::None) |             .selection_mode(gtk::SelectionMode::None) | ||||||
|  | @ -51,55 +50,41 @@ impl EnsembleEditor { | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|             backend, |             handle, | ||||||
|             id, |             id, | ||||||
|             editor, |             editor, | ||||||
|             name, |             name, | ||||||
|             upload, |             upload, | ||||||
|             saved_cb: RefCell::new(None), |  | ||||||
|             navigator: RefCell::new(None), |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         this.editor.set_back_cb(clone!(@strong this => move || { |         this.editor.set_back_cb(clone!(@weak this => move || { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(None); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.editor.set_save_cb(clone!(@strong this => move || { |         this.editor.set_save_cb(clone!(@weak this => move || { | ||||||
|             let context = glib::MainContext::default(); |             spawn!(@clone this, async move { | ||||||
|             let clone = this.clone(); |                 this.editor.loading(); | ||||||
|             context.spawn_local(async move { |                 match this.save().await { | ||||||
|                 clone.editor.loading(); |                     Ok(ensemble) => { | ||||||
|                 match clone.clone().save().await { |                         this.handle.pop(Some(ensemble)); | ||||||
|                     Ok(_) => { |  | ||||||
|                         let navigator = clone.navigator.borrow().clone(); |  | ||||||
|                         if let Some(navigator) = navigator { |  | ||||||
|                             navigator.pop(); |  | ||||||
|                         } |  | ||||||
|                     } |                     } | ||||||
|                     Err(err) => { |                     Err(err) => { | ||||||
|                         let description = gettext!("Cause: {}", err); |                         let description = gettext!("Cause: {}", err); | ||||||
|                         clone.editor.error(&gettext("Failed to save ensemble!"), &description); |                         this.editor.error(&gettext("Failed to save ensemble!"), &description); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 |  | ||||||
|             }); |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     /// Set the closure to be called if the ensemble was saved.
 | impl EnsembleEditor { | ||||||
|     pub fn set_saved_cb<F: Fn(Ensemble) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.saved_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// 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) -> Result<Ensemble> { | ||||||
|         let name = self.name.get_text(); |         let name = self.name.get_text(); | ||||||
| 
 | 
 | ||||||
|         let ensemble = Ensemble { |         let ensemble = Ensemble { | ||||||
|  | @ -108,31 +93,19 @@ impl EnsembleEditor { | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         if self.upload.get_active() { |         if self.upload.get_active() { | ||||||
|             self.backend.post_ensemble(&ensemble).await?; |             self.handle.backend.post_ensemble(&ensemble).await?; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         self.backend.db().update_ensemble(ensemble.clone()).await?; |         self.handle.backend.db().update_ensemble(ensemble.clone()).await?; | ||||||
|         self.backend.library_changed(); |         self.handle.backend.library_changed(); | ||||||
| 
 | 
 | ||||||
|         if let Some(cb) = &*self.saved_cb.borrow() { |         Ok(ensemble) | ||||||
|             cb(ensemble.clone()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         Ok(()) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl NavigatorScreen for EnsembleEditor { | impl Widget for EnsembleEditor { | ||||||
|     fn attach_navigator(&self, navigator: Rc<Navigator>) { |  | ||||||
|         self.navigator.replace(Some(navigator)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_widget(&self) -> gtk::Widget { |     fn get_widget(&self) -> gtk::Widget { | ||||||
|         self.editor.widget.clone().upcast() |         self.editor.widget.clone().upcast() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     fn detach_navigator(&self) { |  | ||||||
|         self.navigator.replace(None); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,7 +1,8 @@ | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::database::generate_id; | use crate::database::generate_id; | ||||||
| use crate::database::Instrument; | use crate::database::Instrument; | ||||||
| use crate::widgets::{Editor, EntryRow, Navigator, NavigatorScreen, Section, UploadSection}; | use crate::navigator::{NavigationHandle, Screen}; | ||||||
|  | use crate::widgets::{Editor, EntryRow, Section, UploadSection, Widget}; | ||||||
| use anyhow::Result; | use anyhow::Result; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
|  | @ -11,7 +12,7 @@ use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
| /// A dialog for creating or editing a instrument.
 | /// A dialog for creating or editing a instrument.
 | ||||||
| pub struct InstrumentEditor { | pub struct InstrumentEditor { | ||||||
|     backend: Rc<Backend>, |     handle: NavigationHandle<Instrument>, | ||||||
| 
 | 
 | ||||||
|     /// The ID of the instrument that is edited or a newly generated one.
 |     /// The ID of the instrument that is edited or a newly generated one.
 | ||||||
|     id: String, |     id: String, | ||||||
|  | @ -19,13 +20,11 @@ pub struct InstrumentEditor { | ||||||
|     editor: Editor, |     editor: Editor, | ||||||
|     name: EntryRow, |     name: EntryRow, | ||||||
|     upload: UploadSection, |     upload: UploadSection, | ||||||
|     saved_cb: RefCell<Option<Box<dyn Fn(Instrument) -> ()>>>, |  | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl InstrumentEditor { | impl Screen<Option<Instrument>, Instrument> for InstrumentEditor { | ||||||
|     /// Create a new instrument editor and optionally initialize it.
 |     /// Create a new instrument editor and optionally initialize it.
 | ||||||
|     pub fn new(backend: Rc<Backend>, instrument: Option<Instrument>) -> Rc<Self> { |     fn new(instrument: Option<Instrument>, handle: NavigationHandle<Instrument>) -> Rc<Self> { | ||||||
|         let editor = Editor::new(); |         let editor = Editor::new(); | ||||||
|         editor.set_title("Instrument/Role"); |         editor.set_title("Instrument/Role"); | ||||||
| 
 | 
 | ||||||
|  | @ -51,55 +50,41 @@ impl InstrumentEditor { | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|             backend, |             handle, | ||||||
|             id, |             id, | ||||||
|             editor, |             editor, | ||||||
|             name, |             name, | ||||||
|             upload, |             upload, | ||||||
|             saved_cb: RefCell::new(None), |  | ||||||
|             navigator: RefCell::new(None), |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         this.editor.set_back_cb(clone!(@strong this => move || { |         this.editor.set_back_cb(clone!(@weak this => move || { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(None); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.editor.set_save_cb(clone!(@strong this => move || { |         this.editor.set_save_cb(clone!(@weak this => move || { | ||||||
|             let context = glib::MainContext::default(); |             spawn!(@clone this, async move { | ||||||
|             let clone = this.clone(); |                 this.editor.loading(); | ||||||
|             context.spawn_local(async move { |                 match this.save().await { | ||||||
|                 clone.editor.loading(); |                     Ok(instrument) => { | ||||||
|                 match clone.clone().save().await { |                         this.handle.pop(Some(instrument)); | ||||||
|                     Ok(_) => { |  | ||||||
|                         let navigator = clone.navigator.borrow().clone(); |  | ||||||
|                         if let Some(navigator) = navigator { |  | ||||||
|                             navigator.pop(); |  | ||||||
|                         } |  | ||||||
|                     } |                     } | ||||||
|                     Err(err) => { |                     Err(err) => { | ||||||
|                         let description = gettext!("Cause: {}", err); |                         let description = gettext!("Cause: {}", err); | ||||||
|                         clone.editor.error(&gettext("Failed to save instrument!"), &description); |                         this.editor.error(&gettext("Failed to save instrument!"), &description); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 |  | ||||||
|             }); |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     /// Set the closure to be called if the instrument was saved.
 | impl InstrumentEditor { | ||||||
|     pub fn set_saved_cb<F: Fn(Instrument) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.saved_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// 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) -> Result<Instrument> { | ||||||
|         let name = self.name.get_text(); |         let name = self.name.get_text(); | ||||||
| 
 | 
 | ||||||
|         let instrument = Instrument { |         let instrument = Instrument { | ||||||
|  | @ -108,31 +93,19 @@ impl InstrumentEditor { | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         if self.upload.get_active() { |         if self.upload.get_active() { | ||||||
|             self.backend.post_instrument(&instrument).await?; |             self.handle.backend.post_instrument(&instrument).await?; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         self.backend.db().update_instrument(instrument.clone()).await?; |         self.handle.backend.db().update_instrument(instrument.clone()).await?; | ||||||
|         self.backend.library_changed(); |         self.handle.backend.library_changed(); | ||||||
| 
 | 
 | ||||||
|         if let Some(cb) = &*self.saved_cb.borrow() { |         Ok(instrument) | ||||||
|             cb(instrument.clone()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         Ok(()) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl NavigatorScreen for InstrumentEditor { | impl Widget for InstrumentEditor { | ||||||
|     fn attach_navigator(&self, navigator: Rc<Navigator>) { |  | ||||||
|         self.navigator.replace(Some(navigator)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_widget(&self) -> gtk::Widget { |     fn get_widget(&self) -> gtk::Widget { | ||||||
|         self.editor.widget.clone().upcast() |         self.editor.widget.clone().upcast() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     fn detach_navigator(&self) { |  | ||||||
|         self.navigator.replace(None); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,7 +1,8 @@ | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
|  | use crate::navigator::{NavigationHandle, Screen}; | ||||||
| use crate::selectors::{EnsembleSelector, InstrumentSelector, PersonSelector}; | use crate::selectors::{EnsembleSelector, InstrumentSelector, PersonSelector}; | ||||||
| use crate::widgets::{Editor, Navigator, NavigatorScreen, Section, ButtonRow, Widget}; | use crate::widgets::{Editor, Section, ButtonRow, Widget}; | ||||||
| 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>, |     handle: NavigationHandle<Performance>, | ||||||
|     editor: Editor, |     editor: Editor, | ||||||
|     person_row: ButtonRow, |     person_row: ButtonRow, | ||||||
|     ensemble_row: ButtonRow, |     ensemble_row: ButtonRow, | ||||||
|  | @ -20,13 +21,11 @@ pub struct PerformanceEditor { | ||||||
|     person: RefCell<Option<Person>>, |     person: RefCell<Option<Person>>, | ||||||
|     ensemble: RefCell<Option<Ensemble>>, |     ensemble: RefCell<Option<Ensemble>>, | ||||||
|     role: RefCell<Option<Instrument>>, |     role: RefCell<Option<Instrument>>, | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(Performance) -> ()>>>, |  | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl PerformanceEditor { | impl Screen<Option<Performance>, Performance> for PerformanceEditor { | ||||||
|     /// Create a new performance editor.
 |     /// Create a new performance editor.
 | ||||||
|     pub fn new(backend: Rc<Backend>, performance: Option<Performance>) -> Rc<Self> { |     fn new(performance: Option<Performance>, handle: NavigationHandle<Performance>) -> Rc<Self> { | ||||||
|         let editor = Editor::new(); |         let editor = Editor::new(); | ||||||
|         editor.set_title("Performance"); |         editor.set_title("Performance"); | ||||||
|         editor.set_may_save(false); |         editor.set_may_save(false); | ||||||
|  | @ -68,7 +67,7 @@ impl PerformanceEditor { | ||||||
|         editor.add_content(&role_section); |         editor.add_content(&role_section); | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(PerformanceEditor { |         let this = Rc::new(PerformanceEditor { | ||||||
|             backend, |             handle, | ||||||
|             editor, |             editor, | ||||||
|             person_row, |             person_row, | ||||||
|             ensemble_row, |             ensemble_row, | ||||||
|  | @ -77,79 +76,51 @@ impl PerformanceEditor { | ||||||
|             person: RefCell::new(None), |             person: RefCell::new(None), | ||||||
|             ensemble: RefCell::new(None), |             ensemble: RefCell::new(None), | ||||||
|             role: RefCell::new(None), |             role: RefCell::new(None), | ||||||
|             selected_cb: RefCell::new(None), |  | ||||||
|             navigator: RefCell::new(None), |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         this.editor.set_back_cb(clone!(@strong this => move || { |         this.editor.set_back_cb(clone!(@weak this => move || { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(None); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.editor.set_save_cb(clone!(@weak this => move || { |         this.editor.set_save_cb(clone!(@weak this => move || { | ||||||
|             if let Some(cb) = &*this.selected_cb.borrow() { |             let performance = Performance { | ||||||
|                 cb(Performance { |                 person: this.person.borrow().clone(), | ||||||
|                     person: this.person.borrow().clone(), |                 ensemble: this.ensemble.borrow().clone(), | ||||||
|                     ensemble: this.ensemble.borrow().clone(), |                 role: this.role.borrow().clone(), | ||||||
|                     role: this.role.borrow().clone(), |             }; | ||||||
|                 }); |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(Some(performance)); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.person_row.set_cb(clone!(@weak this => move || { |         this.person_row.set_cb(clone!(@weak this => move || { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             spawn!(@clone this, async move { | ||||||
|             if let Some(navigator) = navigator { |                 if let Some(person) = push!(this.handle, PersonSelector).await { | ||||||
|                 let selector = PersonSelector::new(this.backend.clone()); |  | ||||||
| 
 |  | ||||||
|                 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.clone())); |                     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(); |                 } | ||||||
|                 })); |             }); | ||||||
| 
 |  | ||||||
|                 navigator.push(selector); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.ensemble_row.set_cb(clone!(@weak this => move || { |         this.ensemble_row.set_cb(clone!(@weak this => move || { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             spawn!(@clone this, async move { | ||||||
|             if let Some(navigator) = navigator { |                 if let Some(ensemble) = push!(this.handle, EnsembleSelector).await { | ||||||
|                 let selector = EnsembleSelector::new(this.backend.clone()); |  | ||||||
| 
 |  | ||||||
|                 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.clone())); |                     this.ensemble.replace(None); | ||||||
|                     navigator.clone().pop(); |                 } | ||||||
|                 })); |             }); | ||||||
| 
 |  | ||||||
|                 navigator.push(selector); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.role_row.set_cb(clone!(@weak this => move || { |         this.role_row.set_cb(clone!(@weak this => move || { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             spawn!(@clone this, async move { | ||||||
|                 if let Some(navigator) = navigator { |                 if let Some(role) = push!(this.handle, InstrumentSelector).await { | ||||||
|                 let selector = InstrumentSelector::new(this.backend.clone()); |  | ||||||
| 
 |  | ||||||
|                 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.clone())); |                     this.role.replace(Some(role)); | ||||||
|                     navigator.clone().pop(); |                 } | ||||||
|                 })); |             }); | ||||||
| 
 |  | ||||||
|                 navigator.push(selector); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.reset_role_button.connect_clicked(clone!(@weak this => move |_| { |         this.reset_role_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|  | @ -176,12 +147,9 @@ impl PerformanceEditor { | ||||||
| 
 | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     /// Set a closure to be called when the user has chosen to save the performance.
 | impl PerformanceEditor { | ||||||
|     pub fn set_selected_cb<F: Fn(Performance) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.selected_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// 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 { | ||||||
|  | @ -214,16 +182,8 @@ impl PerformanceEditor { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl NavigatorScreen for PerformanceEditor { | impl Widget for PerformanceEditor { | ||||||
|     fn attach_navigator(&self, navigator: Rc<Navigator>) { |  | ||||||
|         self.navigator.replace(Some(navigator)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_widget(&self) -> gtk::Widget { |     fn get_widget(&self) -> gtk::Widget { | ||||||
|         self.editor.widget.clone().upcast() |         self.editor.widget.clone().upcast() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     fn detach_navigator(&self) { |  | ||||||
|         self.navigator.replace(None); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,7 +1,8 @@ | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::database::generate_id; | use crate::database::generate_id; | ||||||
| use crate::database::Person; | use crate::database::Person; | ||||||
| use crate::widgets::{Editor, EntryRow, Navigator, NavigatorScreen, Section, UploadSection}; | use crate::navigator::{NavigationHandle, Screen}; | ||||||
|  | use crate::widgets::{Editor, EntryRow, Section, UploadSection, Widget}; | ||||||
| use anyhow::Result; | use anyhow::Result; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
|  | @ -11,7 +12,7 @@ use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
| /// A dialog for creating or editing a person.
 | /// A dialog for creating or editing a person.
 | ||||||
| pub struct PersonEditor { | pub struct PersonEditor { | ||||||
|     backend: Rc<Backend>, |     handle: NavigationHandle<Person>, | ||||||
| 
 | 
 | ||||||
|     /// The ID of the person that is edited or a newly generated one.
 |     /// The ID of the person that is edited or a newly generated one.
 | ||||||
|     id: String, |     id: String, | ||||||
|  | @ -20,13 +21,11 @@ pub struct PersonEditor { | ||||||
|     first_name: EntryRow, |     first_name: EntryRow, | ||||||
|     last_name: EntryRow, |     last_name: EntryRow, | ||||||
|     upload: UploadSection, |     upload: UploadSection, | ||||||
|     saved_cb: RefCell<Option<Box<dyn Fn(Person) -> ()>>>, |  | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl PersonEditor { | impl Screen<Option<Person>, Person> for PersonEditor { | ||||||
|     /// Create a new person editor and optionally initialize it.
 |     /// Create a new person editor and optionally initialize it.
 | ||||||
|     pub fn new(backend: Rc<Backend>, person: Option<Person>) -> Rc<Self> { |     fn new(person: Option<Person>, handle: NavigationHandle<Person>) -> Rc<Self> { | ||||||
|         let editor = Editor::new(); |         let editor = Editor::new(); | ||||||
|         editor.set_title("Person"); |         editor.set_title("Person"); | ||||||
| 
 | 
 | ||||||
|  | @ -57,56 +56,42 @@ impl PersonEditor { | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|             backend, |             handle, | ||||||
|             id, |             id, | ||||||
|             editor, |             editor, | ||||||
|             first_name, |             first_name, | ||||||
|             last_name, |             last_name, | ||||||
|             upload, |             upload, | ||||||
|             saved_cb: RefCell::new(None), |  | ||||||
|             navigator: RefCell::new(None), |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         this.editor.set_back_cb(clone!(@strong this => move || { |         this.editor.set_back_cb(clone!(@weak this => move || { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(None); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.editor.set_save_cb(clone!(@strong this => move || { |         this.editor.set_save_cb(clone!(@strong this => move || { | ||||||
|             let context = glib::MainContext::default(); |             spawn!(@clone this, async move { | ||||||
|             let clone = this.clone(); |                 this.editor.loading(); | ||||||
|             context.spawn_local(async move { |                 match this.save().await { | ||||||
|                 clone.editor.loading(); |                     Ok(person) => { | ||||||
|                 match clone.clone().save().await { |                         this.handle.pop(Some(person)); | ||||||
|                     Ok(_) => { |  | ||||||
|                         let navigator = clone.navigator.borrow().clone(); |  | ||||||
|                         if let Some(navigator) = navigator { |  | ||||||
|                             navigator.pop(); |  | ||||||
|                         } |  | ||||||
|                     } |                     } | ||||||
|                     Err(err) => { |                     Err(err) => { | ||||||
|                         let description = gettext!("Cause: {}", err); |                         let description = gettext!("Cause: {}", err); | ||||||
|                         clone.editor.error(&gettext("Failed to save person!"), &description); |                         this.editor.error(&gettext("Failed to save person!"), &description); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 |  | ||||||
|             }); |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     /// Set the closure to be called if the person was saved.
 | impl PersonEditor { | ||||||
|     pub fn set_saved_cb<F: Fn(Person) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.saved_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// 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<Person> { | ||||||
|         let first_name = self.first_name.get_text(); |         let first_name = self.first_name.get_text(); | ||||||
|         let last_name = self.last_name.get_text(); |         let last_name = self.last_name.get_text(); | ||||||
| 
 | 
 | ||||||
|  | @ -117,31 +102,19 @@ impl PersonEditor { | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         if self.upload.get_active() { |         if self.upload.get_active() { | ||||||
|             self.backend.post_person(&person).await?; |             self.handle.backend.post_person(&person).await?; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         self.backend.db().update_person(person.clone()).await?; |         self.handle.backend.db().update_person(person.clone()).await?; | ||||||
|         self.backend.library_changed(); |         self.handle.backend.library_changed(); | ||||||
| 
 | 
 | ||||||
|         if let Some(cb) = &*self.saved_cb.borrow() { |         Ok(person) | ||||||
|             cb(person.clone()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         Ok(()) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl NavigatorScreen for PersonEditor { | impl Widget for PersonEditor { | ||||||
|     fn attach_navigator(&self, navigator: Rc<Navigator>) { |  | ||||||
|         self.navigator.replace(Some(navigator)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_widget(&self) -> gtk::Widget { |     fn get_widget(&self) -> gtk::Widget { | ||||||
|         self.editor.widget.clone().upcast() |         self.editor.widget.clone().upcast() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     fn detach_navigator(&self) { |  | ||||||
|         self.navigator.replace(None); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,8 +1,9 @@ | ||||||
| use super::performance::PerformanceEditor; | use super::performance::PerformanceEditor; | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
| use crate::selectors::{PersonSelector, WorkSelector}; | use crate::selectors::PersonSelector; | ||||||
| use crate::widgets::{List, Navigator, NavigatorScreen}; | use crate::widgets::{List, Widget}; | ||||||
|  | use crate::navigator::{NavigationHandle, Screen}; | ||||||
| use anyhow::Result; | use anyhow::Result; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
|  | @ -14,8 +15,8 @@ use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
| /// A widget for creating or editing a recording.
 | /// A widget for creating or editing a recording.
 | ||||||
| pub struct RecordingEditor { | pub struct RecordingEditor { | ||||||
|     pub widget: gtk::Stack, |     handle: NavigationHandle<Recording>, | ||||||
|     backend: Rc<Backend>, |     widget: gtk::Stack, | ||||||
|     save_button: gtk::Button, |     save_button: gtk::Button, | ||||||
|     info_bar: gtk::InfoBar, |     info_bar: gtk::InfoBar, | ||||||
|     work_row: libadwaita::ActionRow, |     work_row: libadwaita::ActionRow, | ||||||
|  | @ -25,13 +26,11 @@ pub struct RecordingEditor { | ||||||
|     id: String, |     id: String, | ||||||
|     work: RefCell<Option<Work>>, |     work: RefCell<Option<Work>>, | ||||||
|     performances: RefCell<Vec<Performance>>, |     performances: RefCell<Vec<Performance>>, | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(Recording) -> ()>>>, |  | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl RecordingEditor { | impl Screen<Option<Recording>, Recording> for RecordingEditor { | ||||||
|     /// Create a new recording editor widget and optionally initialize it.
 |     /// Create a new recording editor widget and optionally initialize it.
 | ||||||
|     pub fn new(backend: Rc<Backend>, recording: Option<Recording>) -> Rc<Self> { |     fn new(recording: Option<Recording>, handle: NavigationHandle<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"); | ||||||
|  | @ -59,8 +58,8 @@ impl RecordingEditor { | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(RecordingEditor { |         let this = Rc::new(RecordingEditor { | ||||||
|  |             handle, | ||||||
|             widget, |             widget, | ||||||
|             backend, |  | ||||||
|             save_button, |             save_button, | ||||||
|             info_bar, |             info_bar, | ||||||
|             work_row, |             work_row, | ||||||
|  | @ -70,107 +69,67 @@ impl RecordingEditor { | ||||||
|             id, |             id, | ||||||
|             work: RefCell::new(work), |             work: RefCell::new(work), | ||||||
|             performances: RefCell::new(performances), |             performances: RefCell::new(performances), | ||||||
|             selected_cb: RefCell::new(None), |  | ||||||
|             navigator: RefCell::new(None), |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         back_button.connect_clicked(clone!(@strong this => move |_| { |         back_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(None); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.clone().pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.save_button |         this.save_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             .connect_clicked(clone!(@strong this => move |_| { |             spawn!(@clone this, async move { | ||||||
|                 let context = glib::MainContext::default(); |                 this.widget.set_visible_child_name("loading"); | ||||||
|                 let clone = this.clone(); |                 match this.save().await { | ||||||
|                 context.spawn_local(async move { |                     Ok(recording) => { | ||||||
|                     clone.widget.set_visible_child_name("loading"); |                         this.handle.pop(Some(recording)); | ||||||
|                     match clone.clone().save().await { |  | ||||||
|                         Ok(_) => { |  | ||||||
|                             let navigator = clone.navigator.borrow().clone(); |  | ||||||
|                             if let Some(navigator) = navigator { |  | ||||||
|                                 navigator.clone().pop(); |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                         Err(_) => { |  | ||||||
|                             clone.info_bar.set_revealed(true); |  | ||||||
|                             clone.widget.set_visible_child_name("content"); |  | ||||||
|                         } |  | ||||||
|                     } |                     } | ||||||
| 
 |                     Err(_) => { | ||||||
|                 }); |                         this.info_bar.set_revealed(true); | ||||||
|             })); |                         this.widget.set_visible_child_name("content"); | ||||||
| 
 |                     } | ||||||
|         work_button.connect_clicked(clone!(@strong this => move |_| { |                 } | ||||||
|             let navigator = this.navigator.borrow().clone(); |             }); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 let person_selector = PersonSelector::new(this.backend.clone()); |  | ||||||
| 
 |  | ||||||
|                 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.replace(Some(work.clone())); |  | ||||||
| 
 |  | ||||||
|                         navigator.clone().pop(); |  | ||||||
|                         navigator.clone().pop(); |  | ||||||
|                     })); |  | ||||||
| 
 |  | ||||||
|                     navigator.clone().push(work_selector); |  | ||||||
|                 })); |  | ||||||
| 
 |  | ||||||
|                 navigator.push(person_selector); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.performance_list.set_make_widget_cb(clone!(@strong this => move |index| { |         work_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|  |             spawn!(@clone this, async move { | ||||||
|  |                 // TODO: We need the pushed screen to return a work here.
 | ||||||
|  |             }); | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         this.performance_list.set_make_widget_cb(clone!(@weak this => move |index| { | ||||||
|             let performance = &this.performances.borrow()[index]; |             let performance = &this.performances.borrow()[index]; | ||||||
| 
 | 
 | ||||||
|             let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic")); |             let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic")); | ||||||
|             delete_button.set_valign(gtk::Align::Center); |             delete_button.set_valign(gtk::Align::Center); | ||||||
| 
 | 
 | ||||||
|             delete_button.connect_clicked(clone!(@strong this => move |_| { |             delete_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|                     let length = { |                 let length = { | ||||||
|                         let mut performances = this.performances.borrow_mut(); |                     let mut performances = this.performances.borrow_mut(); | ||||||
|                         performances.remove(index); |                     performances.remove(index); | ||||||
|                         performances.len() |                     performances.len() | ||||||
|                     }; |                 }; | ||||||
| 
 | 
 | ||||||
|                     this.performance_list.update(length); |                 this.performance_list.update(length); | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|             let edit_button = gtk::Button::from_icon_name(Some("document-edit-symbolic")); |             let edit_button = gtk::Button::from_icon_name(Some("document-edit-symbolic")); | ||||||
|             edit_button.set_valign(gtk::Align::Center); |             edit_button.set_valign(gtk::Align::Center); | ||||||
| 
 | 
 | ||||||
|             edit_button.connect_clicked(clone!(@strong this => move |_| { |             edit_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|                     let navigator = this.navigator.borrow().clone(); |                 spawn!(@clone this, async move { | ||||||
|                     if let Some(navigator) = navigator { |                     let performance = &this.performances.borrow()[index]; | ||||||
|                         let performance = &this.performances.borrow()[index]; |                     if let Some(performance) = push!(this.handle, PerformanceEditor, Some(performance.to_owned())).await { | ||||||
|  |                         let length = { | ||||||
|  |                             let mut performances = this.performances.borrow_mut(); | ||||||
|  |                             performances[index] = performance; | ||||||
|  |                             performances.len() | ||||||
|  |                         }; | ||||||
| 
 | 
 | ||||||
|                         let editor = PerformanceEditor::new( |                         this.performance_list.update(length); | ||||||
|                             this.backend.clone(), |  | ||||||
|                             Some(performance.clone()), |  | ||||||
|                         ); |  | ||||||
| 
 |  | ||||||
|                         editor.set_selected_cb(clone!(@strong this, @strong navigator => move |performance| { |  | ||||||
|                             let length = { |  | ||||||
|                                 let mut performances = this.performances.borrow_mut(); |  | ||||||
|                                 performances[index] = performance; |  | ||||||
|                                 performances.len() |  | ||||||
|                             }; |  | ||||||
| 
 |  | ||||||
|                             this.performance_list.update(length); |  | ||||||
| 
 |  | ||||||
|                             navigator.clone().pop(); |  | ||||||
|                         })); |  | ||||||
| 
 |  | ||||||
|                         navigator.push(editor); |  | ||||||
|                     } |                     } | ||||||
|  |                 }); | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|             let row = libadwaita::ActionRow::new(); |             let row = libadwaita::ActionRow::new(); | ||||||
|  | @ -184,11 +143,8 @@ impl RecordingEditor { | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         add_performer_button.connect_clicked(clone!(@strong this => move |_| { |         add_performer_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             spawn!(@clone this, async move { | ||||||
|             if let Some(navigator) = navigator { |                 if let Some(performance) = push!(this.handle, PerformanceEditor, None).await { | ||||||
|                 let editor = PerformanceEditor::new(this.backend.clone(), None); |  | ||||||
| 
 |  | ||||||
|                 editor.set_selected_cb(clone!(@strong this, @strong navigator => move |performance| { |  | ||||||
|                     let length = { |                     let length = { | ||||||
|                         let mut performances = this.performances.borrow_mut(); |                         let mut performances = this.performances.borrow_mut(); | ||||||
|                         performances.push(performance); |                         performances.push(performance); | ||||||
|  | @ -196,12 +152,8 @@ impl RecordingEditor { | ||||||
|                     }; |                     }; | ||||||
| 
 | 
 | ||||||
|                     this.performance_list.update(length); |                     this.performance_list.update(length); | ||||||
| 
 |                 } | ||||||
|                     navigator.clone().pop(); |             }); | ||||||
|                 })); |  | ||||||
| 
 |  | ||||||
|                 navigator.push(editor); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         // Initialize
 |         // Initialize
 | ||||||
|  | @ -215,12 +167,9 @@ impl RecordingEditor { | ||||||
| 
 | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     /// Set the closure to be called if the recording was created.
 | impl RecordingEditor { | ||||||
|     pub fn set_selected_cb<F: Fn(Recording) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.selected_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Update the UI according to work.    
 |     /// Update the UI according to work.    
 | ||||||
|     fn work_selected(&self, work: &Work) { |     fn work_selected(&self, work: &Work) { | ||||||
|         self.work_row.set_title(Some(&gettext("Work"))); |         self.work_row.set_title(Some(&gettext("Work"))); | ||||||
|  | @ -229,7 +178,7 @@ impl RecordingEditor { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Save the recording and possibly upload it to the server.
 |     /// Save the recording and possibly upload it to the server.
 | ||||||
|     async fn save(self: Rc<Self>) -> Result<()> { |     async fn save(self: &Rc<Self>) -> Result<Recording> { | ||||||
|         let recording = Recording { |         let recording = Recording { | ||||||
|             id: self.id.clone(), |             id: self.id.clone(), | ||||||
|             work: self |             work: self | ||||||
|  | @ -243,40 +192,23 @@ impl RecordingEditor { | ||||||
| 
 | 
 | ||||||
|         let upload = self.upload_switch.get_active(); |         let upload = self.upload_switch.get_active(); | ||||||
|         if upload { |         if upload { | ||||||
|             self.backend.post_recording(&recording).await?; |             self.handle.backend.post_recording(&recording).await?; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         self.backend |         self.handle.backend | ||||||
|             .db() |             .db() | ||||||
|             .update_recording(recording.clone().into()) |             .update_recording(recording.clone().into()) | ||||||
|             .await |             .await | ||||||
|             .unwrap(); |             .unwrap(); | ||||||
| 
 | 
 | ||||||
|         self.backend.library_changed(); |         self.handle.backend.library_changed(); | ||||||
| 
 | 
 | ||||||
|         if let Some(cb) = &*self.selected_cb.borrow() { |         Ok(recording) | ||||||
|             cb(recording.clone()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         let navigator = self.navigator.borrow().clone(); |  | ||||||
|         if let Some(navigator) = navigator { |  | ||||||
|             navigator.clone().pop(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         Ok(()) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl NavigatorScreen for RecordingEditor { | impl Widget for RecordingEditor { | ||||||
|     fn attach_navigator(&self, navigator: Rc<Navigator>) { |  | ||||||
|         self.navigator.replace(Some(navigator)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_widget(&self) -> gtk::Widget { |     fn get_widget(&self) -> gtk::Widget { | ||||||
|         self.widget.clone().upcast() |         self.widget.clone().upcast() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     fn detach_navigator(&self) { |  | ||||||
|         self.navigator.replace(None); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| use super::work_part::WorkPartEditor; | use super::work_part::WorkPartEditor; | ||||||
| use super::work_section::WorkSectionEditor; | use super::work_section::WorkSectionEditor; | ||||||
| use crate::backend::Backend; |  | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
| use crate::selectors::{InstrumentSelector, PersonSelector}; | use crate::selectors::{InstrumentSelector, PersonSelector}; | ||||||
| use crate::widgets::{List, Navigator, NavigatorScreen}; | use crate::navigator::{NavigationHandle, Screen}; | ||||||
|  | use crate::widgets::{List, Widget}; | ||||||
| use anyhow::Result; | use anyhow::Result; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
|  | @ -32,8 +32,8 @@ impl PartOrSection { | ||||||
| 
 | 
 | ||||||
| /// A widget for editing and creating works.
 | /// A widget for editing and creating works.
 | ||||||
| pub struct WorkEditor { | pub struct WorkEditor { | ||||||
|  |     handle: NavigationHandle<Work>, | ||||||
|     widget: gtk::Stack, |     widget: gtk::Stack, | ||||||
|     backend: Rc<Backend>, |  | ||||||
|     save_button: gtk::Button, |     save_button: gtk::Button, | ||||||
|     title_entry: gtk::Entry, |     title_entry: gtk::Entry, | ||||||
|     info_bar: gtk::InfoBar, |     info_bar: gtk::InfoBar, | ||||||
|  | @ -45,13 +45,11 @@ 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>>, | ||||||
|     saved_cb: RefCell<Option<Box<dyn Fn(Work) -> ()>>>, |  | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl WorkEditor { | impl Screen<Option<Work>, Work> for WorkEditor { | ||||||
|     /// Create a new work editor widget and optionally initialize it.
 |     /// Create a new work editor widget and optionally initialize it.
 | ||||||
|     pub fn new(backend: Rc<Backend>, work: Option<Work>) -> Rc<Self> { |     fn new(work: Option<Work>, handle: NavigationHandle<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"); | ||||||
|  | @ -100,8 +98,8 @@ impl WorkEditor { | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|  |             handle, | ||||||
|             widget, |             widget, | ||||||
|             backend, |  | ||||||
|             save_button, |             save_button, | ||||||
|             id, |             id, | ||||||
|             info_bar, |             info_bar, | ||||||
|  | @ -113,57 +111,39 @@ 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), | ||||||
|             saved_cb: RefCell::new(None), |  | ||||||
|             navigator: RefCell::new(None), |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         back_button.connect_clicked(clone!(@strong this => move |_| { |         back_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(None); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.save_button |         this.save_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             .connect_clicked(clone!(@strong this => move |_| { |             spawn!(@clone this, async move { | ||||||
|                 let context = glib::MainContext::default(); |                 this.widget.set_visible_child_name("loading"); | ||||||
|                 let clone = this.clone(); |                 match this.save().await { | ||||||
|                 context.spawn_local(async move { |                     Ok(work) => { | ||||||
|                     clone.widget.set_visible_child_name("loading"); |                         this.handle.pop(Some(work)); | ||||||
|                     match clone.clone().save().await { |  | ||||||
|                         Ok(_) => { |  | ||||||
|                             let navigator = clone.navigator.borrow().clone(); |  | ||||||
|                             if let Some(navigator) = navigator { |  | ||||||
|                                 navigator.pop(); |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                         Err(_) => { |  | ||||||
|                             clone.info_bar.set_revealed(true); |  | ||||||
|                             clone.widget.set_visible_child_name("content"); |  | ||||||
|                         } |  | ||||||
|                     } |                     } | ||||||
| 
 |                     Err(_) => { | ||||||
|                 }); |                         this.info_bar.set_revealed(true); | ||||||
|             })); |                         this.widget.set_visible_child_name("content"); | ||||||
| 
 |                     } | ||||||
|         composer_button.connect_clicked(clone!(@strong this => move |_| { |                 } | ||||||
|             let navigator = this.navigator.borrow().clone(); |             }); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 let selector = PersonSelector::new(this.backend.clone()); |  | ||||||
| 
 |  | ||||||
|                 selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| { |  | ||||||
|                     this.show_composer(person); |  | ||||||
|                     this.composer.replace(Some(person.clone())); |  | ||||||
|                     navigator.clone().pop(); |  | ||||||
|                 })); |  | ||||||
| 
 |  | ||||||
|                 navigator.push(selector); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.instrument_list.set_make_widget_cb(clone!(@strong this => move |index| { |         composer_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|  |             spawn!(@clone this, async move { | ||||||
|  |                 if let Some(person) = push!(this.handle, PersonSelector).await { | ||||||
|  |                     this.show_composer(&person); | ||||||
|  |                     this.composer.replace(Some(person.to_owned())); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         this.instrument_list.set_make_widget_cb(clone!(@weak this => move |index| { | ||||||
|             let instrument = &this.instruments.borrow()[index]; |             let instrument = &this.instruments.borrow()[index]; | ||||||
| 
 | 
 | ||||||
|             let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic")); |             let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic")); | ||||||
|  | @ -186,12 +166,9 @@ impl WorkEditor { | ||||||
|             row.upcast() |             row.upcast() | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         add_instrument_button.connect_clicked(clone!(@strong this => move |_| { |         add_instrument_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             spawn!(@clone this, async move { | ||||||
|             if let Some(navigator) = navigator { |                 if let Some(instrument) = push!(this.handle, InstrumentSelector).await { | ||||||
|                 let selector = InstrumentSelector::new(this.backend.clone()); |  | ||||||
| 
 |  | ||||||
|                 selector.set_selected_cb(clone!(@strong this, @strong navigator => move |instrument| { |  | ||||||
|                     let length = { |                     let length = { | ||||||
|                         let mut instruments = this.instruments.borrow_mut(); |                         let mut instruments = this.instruments.borrow_mut(); | ||||||
|                         instruments.push(instrument.clone()); |                         instruments.push(instrument.clone()); | ||||||
|  | @ -199,20 +176,17 @@ impl WorkEditor { | ||||||
|                     }; |                     }; | ||||||
| 
 | 
 | ||||||
|                     this.instrument_list.update(length); |                     this.instrument_list.update(length); | ||||||
|                     navigator.clone().pop(); |                 } | ||||||
|                 })); |             }); | ||||||
| 
 |  | ||||||
|                 navigator.push(selector); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.part_list.set_make_widget_cb(clone!(@strong this => move |index| { |         this.part_list.set_make_widget_cb(clone!(@weak this => move |index| { | ||||||
|             let pos = &this.structure.borrow()[index]; |             let pos = &this.structure.borrow()[index]; | ||||||
| 
 | 
 | ||||||
|             let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic")); |             let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic")); | ||||||
|             delete_button.set_valign(gtk::Align::Center); |             delete_button.set_valign(gtk::Align::Center); | ||||||
| 
 | 
 | ||||||
|             delete_button.connect_clicked(clone!(@strong this => move |_| { |             delete_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|                 let length = { |                 let length = { | ||||||
|                     let mut structure = this.structure.borrow_mut(); |                     let mut structure = this.structure.borrow_mut(); | ||||||
|                     structure.remove(index); |                     structure.remove(index); | ||||||
|  | @ -225,14 +199,11 @@ impl WorkEditor { | ||||||
|             let edit_button = gtk::Button::from_icon_name(Some("document-edit-symbolic")); |             let edit_button = gtk::Button::from_icon_name(Some("document-edit-symbolic")); | ||||||
|             edit_button.set_valign(gtk::Align::Center); |             edit_button.set_valign(gtk::Align::Center); | ||||||
| 
 | 
 | ||||||
|             edit_button.connect_clicked(clone!(@strong this => move |_| { |             edit_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|                 let navigator = this.navigator.borrow().clone(); |                 spawn!(@clone this, async move { | ||||||
|                 if let Some(navigator) = navigator { |  | ||||||
|                     match this.structure.borrow()[index].clone() { |                     match this.structure.borrow()[index].clone() { | ||||||
|                         PartOrSection::Part(part) => { |                         PartOrSection::Part(part) => { | ||||||
|                             let editor = WorkPartEditor::new(this.backend.clone(), Some(part)); |                             if let Some(part) = push!(this.handle, WorkPartEditor, Some(part)).await { | ||||||
| 
 |  | ||||||
|                             editor.set_ready_cb(clone!(@strong this, @strong navigator => move |part| { |  | ||||||
|                                 let length = { |                                 let length = { | ||||||
|                                     let mut structure = this.structure.borrow_mut(); |                                     let mut structure = this.structure.borrow_mut(); | ||||||
|                                     structure[index] = PartOrSection::Part(part); |                                     structure[index] = PartOrSection::Part(part); | ||||||
|  | @ -240,15 +211,10 @@ impl WorkEditor { | ||||||
|                                 }; |                                 }; | ||||||
| 
 | 
 | ||||||
|                                 this.part_list.update(length); |                                 this.part_list.update(length); | ||||||
|                                 navigator.clone().pop(); |                             } | ||||||
|                             })); |  | ||||||
| 
 |  | ||||||
|                             navigator.push(editor); |  | ||||||
|                         } |                         } | ||||||
|                         PartOrSection::Section(section) => { |                         PartOrSection::Section(section) => { | ||||||
|                             let editor = WorkSectionEditor::new(Some(section)); |                             if let Some(section) = push!(this.handle, WorkSectionEditor, Some(section)).await { | ||||||
| 
 |  | ||||||
|                             editor.set_ready_cb(clone!(@strong this, @strong navigator => move |section| { |  | ||||||
|                                 let length = { |                                 let length = { | ||||||
|                                     let mut structure = this.structure.borrow_mut(); |                                     let mut structure = this.structure.borrow_mut(); | ||||||
|                                     structure[index] = PartOrSection::Section(section); |                                     structure[index] = PartOrSection::Section(section); | ||||||
|  | @ -256,13 +222,10 @@ impl WorkEditor { | ||||||
|                                 }; |                                 }; | ||||||
| 
 | 
 | ||||||
|                                 this.part_list.update(length); |                                 this.part_list.update(length); | ||||||
|                                 navigator.clone().pop(); |                             } | ||||||
|                             })); |  | ||||||
| 
 |  | ||||||
|                             navigator.push(editor); |  | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 }); | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|             let row = libadwaita::ActionRow::new(); |             let row = libadwaita::ActionRow::new(); | ||||||
|  | @ -280,7 +243,7 @@ impl WorkEditor { | ||||||
|             row.upcast() |             row.upcast() | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.part_list.set_move_cb(clone!(@strong this => move |old_index, new_index| { |         this.part_list.set_move_cb(clone!(@weak this => move |old_index, new_index| { | ||||||
|             let length = { |             let length = { | ||||||
|                 let mut structure = this.structure.borrow_mut(); |                 let mut structure = this.structure.borrow_mut(); | ||||||
|                 structure.swap(old_index, new_index); |                 structure.swap(old_index, new_index); | ||||||
|  | @ -290,12 +253,9 @@ impl WorkEditor { | ||||||
|             this.part_list.update(length); |             this.part_list.update(length); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         add_part_button.connect_clicked(clone!(@strong this => move |_| { |         add_part_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             spawn!(@clone this, async move { | ||||||
|             if let Some(navigator) = navigator { |                 if let Some(part) = push!(this.handle, WorkPartEditor, None).await { | ||||||
|                 let editor = WorkPartEditor::new(this.backend.clone(), None); |  | ||||||
| 
 |  | ||||||
|                 editor.set_ready_cb(clone!(@strong this, @strong navigator => move |part| { |  | ||||||
|                     let length = { |                     let length = { | ||||||
|                         let mut structure = this.structure.borrow_mut(); |                         let mut structure = this.structure.borrow_mut(); | ||||||
|                         structure.push(PartOrSection::Part(part)); |                         structure.push(PartOrSection::Part(part)); | ||||||
|  | @ -303,19 +263,13 @@ impl WorkEditor { | ||||||
|                     }; |                     }; | ||||||
| 
 | 
 | ||||||
|                     this.part_list.update(length); |                     this.part_list.update(length); | ||||||
|                     navigator.clone().pop(); |                 } | ||||||
|                 })); |             }); | ||||||
| 
 |  | ||||||
|                 navigator.push(editor); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         add_section_button.connect_clicked(clone!(@strong this => move |_| { |         add_section_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             spawn!(@clone this, async move { | ||||||
|             if let Some(navigator) = navigator { |                 if let Some(section) = push!(this.handle, WorkSectionEditor, None).await { | ||||||
|                 let editor = WorkSectionEditor::new(None); |  | ||||||
| 
 |  | ||||||
|                 editor.set_ready_cb(clone!(@strong this, @strong navigator => move |section| { |  | ||||||
|                     let length = { |                     let length = { | ||||||
|                         let mut structure = this.structure.borrow_mut(); |                         let mut structure = this.structure.borrow_mut(); | ||||||
|                         structure.push(PartOrSection::Section(section)); |                         structure.push(PartOrSection::Section(section)); | ||||||
|  | @ -323,11 +277,8 @@ impl WorkEditor { | ||||||
|                     }; |                     }; | ||||||
| 
 | 
 | ||||||
|                     this.part_list.update(length); |                     this.part_list.update(length); | ||||||
|                     navigator.clone().pop(); |                 } | ||||||
|                 })); |             }); | ||||||
| 
 |  | ||||||
|                 navigator.push(editor); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         // Initialization
 |         // Initialization
 | ||||||
|  | @ -341,12 +292,9 @@ impl WorkEditor { | ||||||
| 
 | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     /// The closure to call when a work was created.
 | impl WorkEditor { | ||||||
|     pub fn set_saved_cb<F: Fn(Work) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.saved_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Update the UI according to person.
 |     /// Update the UI according to person.
 | ||||||
|     fn show_composer(&self, person: &Person) { |     fn show_composer(&self, person: &Person) { | ||||||
|         self.composer_row.set_title(Some(&gettext("Composer"))); |         self.composer_row.set_title(Some(&gettext("Composer"))); | ||||||
|  | @ -355,7 +303,7 @@ impl WorkEditor { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Save the work and possibly upload it to the server.
 |     /// Save the work and possibly upload it to the server.
 | ||||||
|     async fn save(self: Rc<Self>) -> Result<()> { |     async fn save(self: &Rc<Self>) -> Result<Work> { | ||||||
|         let mut section_count: usize = 0; |         let mut section_count: usize = 0; | ||||||
|         let mut parts = Vec::new(); |         let mut parts = Vec::new(); | ||||||
|         let mut sections = Vec::new(); |         let mut sections = Vec::new(); | ||||||
|  | @ -387,35 +335,23 @@ impl WorkEditor { | ||||||
| 
 | 
 | ||||||
|         let upload = self.upload_switch.get_active(); |         let upload = self.upload_switch.get_active(); | ||||||
|         if upload { |         if upload { | ||||||
|             self.backend.post_work(&work).await?; |             self.handle.backend.post_work(&work).await?; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         self.backend |         self.handle.backend | ||||||
|             .db() |             .db() | ||||||
|             .update_work(work.clone().into()) |             .update_work(work.clone().into()) | ||||||
|             .await |             .await | ||||||
|             .unwrap(); |             .unwrap(); | ||||||
| 
 | 
 | ||||||
|         self.backend.library_changed(); |         self.handle.backend.library_changed(); | ||||||
| 
 | 
 | ||||||
|         if let Some(cb) = &*self.saved_cb.borrow() { |         Ok(work) | ||||||
|             cb(work.clone()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         Ok(()) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl NavigatorScreen for WorkEditor { | impl Widget for WorkEditor { | ||||||
|     fn attach_navigator(&self, navigator: Rc<Navigator>) { |  | ||||||
|         self.navigator.replace(Some(navigator)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_widget(&self) -> gtk::Widget { |     fn get_widget(&self) -> gtk::Widget { | ||||||
|         self.widget.clone().upcast() |         self.widget.clone().upcast() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     fn detach_navigator(&self) { |  | ||||||
|         self.navigator.replace(None); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| use crate::backend::Backend; |  | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
| use crate::selectors::PersonSelector; | use crate::selectors::PersonSelector; | ||||||
| use crate::widgets::{Navigator, NavigatorScreen}; | use crate::navigator::{NavigationHandle, Screen}; | ||||||
|  | use crate::widgets::Widget; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
|  | @ -12,19 +12,17 @@ use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
| /// A dialog for creating or editing a work part.
 | /// A dialog for creating or editing a work part.
 | ||||||
| pub struct WorkPartEditor { | pub struct WorkPartEditor { | ||||||
|     backend: Rc<Backend>, |     handle: NavigationHandle<WorkPart>, | ||||||
|     widget: gtk::Box, |     widget: gtk::Box, | ||||||
|     title_entry: gtk::Entry, |     title_entry: gtk::Entry, | ||||||
|     composer_row: libadwaita::ActionRow, |     composer_row: libadwaita::ActionRow, | ||||||
|     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) -> ()>>>, |  | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl WorkPartEditor { | impl Screen<Option<WorkPart>, WorkPart> for WorkPartEditor { | ||||||
|     /// Create a new part editor and optionally initialize it.
 |     /// Create a new part editor and optionally initialize it.
 | ||||||
|     pub fn new(backend: Rc<Backend>, part: Option<WorkPart>) -> Rc<Self> { |     fn new(part: Option<WorkPart>, handle: NavigationHandle<WorkPart>) -> Rc<Self> { | ||||||
|         // Create UI
 |         // Create UI
 | ||||||
| 
 | 
 | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_part_editor.ui"); |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_part_editor.ui"); | ||||||
|  | @ -46,53 +44,36 @@ impl WorkPartEditor { | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|             backend, |             handle, | ||||||
|             widget, |             widget, | ||||||
|             title_entry, |             title_entry, | ||||||
|             composer_row, |             composer_row, | ||||||
|             reset_composer_button, |             reset_composer_button, | ||||||
|             composer: RefCell::new(composer), |             composer: RefCell::new(composer), | ||||||
|             ready_cb: RefCell::new(None), |  | ||||||
|             navigator: RefCell::new(None), |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         back_button.connect_clicked(clone!(@strong this => move |_| { |         back_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(None); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         save_button.connect_clicked(clone!(@strong this => move |_| { |         save_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             if let Some(cb) = &*this.ready_cb.borrow() { |             let part = WorkPart { | ||||||
|                 cb(WorkPart { |                 title: this.title_entry.get_text().unwrap().to_string(), | ||||||
|                     title: this.title_entry.get_text().unwrap().to_string(), |                 composer: this.composer.borrow().clone(), | ||||||
|                     composer: this.composer.borrow().clone(), |             }; | ||||||
|                 }); |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(Some(part)); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         composer_button.connect_clicked(clone!(@strong this => move |_| { |         composer_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             spawn!(@clone this, async move { | ||||||
|             if let Some(navigator) = navigator { |                 if let Some(person) = push!(this.handle, PersonSelector).await { | ||||||
|                 let selector = PersonSelector::new(this.backend.clone()); |                     this.show_composer(Some(&person)); | ||||||
| 
 |                     this.composer.replace(Some(person.to_owned())); | ||||||
|                 selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| { |                 } | ||||||
|                     this.show_composer(Some(person)); |             }); | ||||||
|                     this.composer.replace(Some(person.clone())); |  | ||||||
|                     navigator.clone().pop(); |  | ||||||
|                 })); |  | ||||||
| 
 |  | ||||||
|                 navigator.push(selector); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.reset_composer_button |         this.reset_composer_button | ||||||
|  | @ -109,12 +90,9 @@ impl WorkPartEditor { | ||||||
| 
 | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     /// Set the closure to be called when the user wants to save the part.
 | impl WorkPartEditor { | ||||||
|     pub fn set_ready_cb<F: Fn(WorkPart) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.ready_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// 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 { | ||||||
|  | @ -129,16 +107,8 @@ impl WorkPartEditor { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl NavigatorScreen for WorkPartEditor { | impl Widget for WorkPartEditor { | ||||||
|     fn attach_navigator(&self, navigator: Rc<Navigator>) { |  | ||||||
|         self.navigator.replace(Some(navigator)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_widget(&self) -> gtk::Widget { |     fn get_widget(&self) -> gtk::Widget { | ||||||
|         self.widget.clone().upcast() |         self.widget.clone().upcast() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     fn detach_navigator(&self) { |  | ||||||
|         self.navigator.replace(None); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| use crate::database::*; | use crate::database::*; | ||||||
| use crate::widgets::{Navigator, NavigatorScreen}; | use crate::navigator::{NavigationHandle, Screen}; | ||||||
|  | use crate::widgets::Widget; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
| use gtk_macros::get_widget; | use gtk_macros::get_widget; | ||||||
|  | @ -8,15 +9,14 @@ use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
| /// A dialog for creating or editing a work section.
 | /// A dialog for creating or editing a work section.
 | ||||||
| pub struct WorkSectionEditor { | pub struct WorkSectionEditor { | ||||||
|  |     handle: NavigationHandle<WorkSection>, | ||||||
|     widget: gtk::Box, |     widget: gtk::Box, | ||||||
|     title_entry: gtk::Entry, |     title_entry: gtk::Entry, | ||||||
|     ready_cb: RefCell<Option<Box<dyn Fn(WorkSection) -> ()>>>, |  | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl WorkSectionEditor { | impl Screen<Option<WorkSection>, WorkSection> for  WorkSectionEditor { | ||||||
|     /// Create a new section editor and optionally initialize it.
 |     /// Create a new section editor and optionally initialize it.
 | ||||||
|     pub fn new(section: Option<WorkSection>) -> Rc<Self> { |     fn new(section: Option<WorkSection>, handle: NavigationHandle<WorkSection>) -> Rc<Self> { | ||||||
|         // Create UI
 |         // Create UI
 | ||||||
| 
 | 
 | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_section_editor.ui"); |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_section_editor.ui"); | ||||||
|  | @ -31,56 +31,32 @@ impl WorkSectionEditor { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|  |             handle, | ||||||
|             widget, |             widget, | ||||||
|             title_entry, |             title_entry, | ||||||
|             ready_cb: RefCell::new(None), |  | ||||||
|             navigator: RefCell::new(None), |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         back_button.connect_clicked(clone!(@strong this => move |_| { |         back_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(None); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         save_button.connect_clicked(clone!(@strong this => move |_| { |         save_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             if let Some(cb) = &*this.ready_cb.borrow() { |             let section = WorkSection { | ||||||
|                 cb(WorkSection { |                 before_index: 0, | ||||||
|                     before_index: 0, |                 title: this.title_entry.get_text().unwrap().to_string(), | ||||||
|                     title: this.title_entry.get_text().unwrap().to_string(), |             }; | ||||||
|                 }); |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(Some(section)); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     /// Set the closure to be called when the user wants to save the section. Note that the
 |  | ||||||
|     /// resulting object will always have `before_index` set to 0. The caller is expected to
 |  | ||||||
|     /// change that later before adding the section to the database.
 |  | ||||||
|     pub fn set_ready_cb<F: Fn(WorkSection) -> () + 'static>(&self, cb: F) { |  | ||||||
|         self.ready_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl NavigatorScreen for WorkSectionEditor { | impl Widget for WorkSectionEditor { | ||||||
|     fn attach_navigator(&self, navigator: Rc<Navigator>) { |  | ||||||
|         self.navigator.replace(Some(navigator)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_widget(&self) -> gtk::Widget { |     fn get_widget(&self) -> gtk::Widget { | ||||||
|         self.widget.clone().upcast() |         self.widget.clone().upcast() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     fn detach_navigator(&self) { |  | ||||||
|         self.navigator.replace(None); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,7 +2,8 @@ use super::source::Source; | ||||||
| use super::track_set_editor::{TrackSetData, TrackSetEditor}; | use super::track_set_editor::{TrackSetData, TrackSetEditor}; | ||||||
| use crate::database::{generate_id, Medium, Track, TrackSet}; | use crate::database::{generate_id, Medium, Track, TrackSet}; | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::widgets::{List, Navigator, NavigatorScreen}; | use crate::navigator::{NavigationHandle, Screen}; | ||||||
|  | use crate::widgets::{List, Widget}; | ||||||
| use anyhow::{anyhow, Result}; | use anyhow::{anyhow, Result}; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use glib::prelude::*; | use glib::prelude::*; | ||||||
|  | @ -14,7 +15,7 @@ use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
| /// A dialog for editing metadata while importing music into the music library.
 | /// A dialog for editing metadata while importing music into the music library.
 | ||||||
| pub struct MediumEditor { | pub struct MediumEditor { | ||||||
|     backend: Rc<Backend>, |     handle: NavigationHandle<()>, | ||||||
|     source: Rc<Box<dyn Source>>, |     source: Rc<Box<dyn Source>>, | ||||||
|     widget: gtk::Stack, |     widget: gtk::Stack, | ||||||
|     done_button: gtk::Button, |     done_button: gtk::Button, | ||||||
|  | @ -24,12 +25,11 @@ pub struct MediumEditor { | ||||||
|     publish_switch: gtk::Switch, |     publish_switch: gtk::Switch, | ||||||
|     track_set_list: Rc<List>, |     track_set_list: Rc<List>, | ||||||
|     track_sets: RefCell<Vec<TrackSetData>>, |     track_sets: RefCell<Vec<TrackSetData>>, | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl MediumEditor { | impl Screen<Rc<Box<dyn Source>>, ()> for MediumEditor { | ||||||
|     /// Create a new medium editor.
 |     /// Create a new medium editor.
 | ||||||
|     pub fn new(backend: Rc<Backend>, source: Rc<Box<dyn Source>>) -> Rc<Self> { |     fn new(source: Rc<Box<dyn Source>>, handle: NavigationHandle<()>) -> Rc<Self> { | ||||||
|         // Create UI
 |         // Create UI
 | ||||||
| 
 | 
 | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/medium_editor.ui"); |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/medium_editor.ui"); | ||||||
|  | @ -48,7 +48,7 @@ impl MediumEditor { | ||||||
|         frame.set_child(Some(&list.widget)); |         frame.set_child(Some(&list.widget)); | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|             backend, |             handle, | ||||||
|             source, |             source, | ||||||
|             widget, |             widget, | ||||||
|             done_button, |             done_button, | ||||||
|  | @ -58,40 +58,30 @@ impl MediumEditor { | ||||||
|             publish_switch, |             publish_switch, | ||||||
|             track_set_list: list, |             track_set_list: list, | ||||||
|             track_sets: RefCell::new(Vec::new()), |             track_sets: RefCell::new(Vec::new()), | ||||||
|             navigator: RefCell::new(None), |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         back_button.connect_clicked(clone!(@strong this => move |_| { |         back_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(None); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.done_button.connect_clicked(clone!(@strong this => move |_| { |         this.done_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             let context = glib::MainContext::default(); |             this.widget.set_visible_child_name("loading"); | ||||||
|             let clone = this.clone(); |             spawn!(@clone this, async move { | ||||||
|             context.spawn_local(async move { |                 match this.save().await { | ||||||
|                 clone.widget.set_visible_child_name("loading"); |  | ||||||
|                 match clone.clone().save().await { |  | ||||||
|                     Ok(_) => (), |                     Ok(_) => (), | ||||||
|                     Err(err) => { |                     Err(err) => { | ||||||
|  |                         // TODO: Display errors.
 | ||||||
|                         println!("{:?}", err); |                         println!("{:?}", err); | ||||||
|                         // clone.info_bar.set_revealed(true);
 |  | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 |  | ||||||
|             }); |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         add_button.connect_clicked(clone!(@strong this => move |_| { |         add_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             spawn!(@clone this, async move { | ||||||
|             if let Some(navigator) = navigator { |                 if let Some(track_set) = push!(this.handle, TrackSetEditor, Rc::clone(&this.source)).await { | ||||||
|                 let editor = TrackSetEditor::new(this.backend.clone(), Rc::clone(&this.source)); |  | ||||||
| 
 |  | ||||||
|                 editor.set_done_cb(clone!(@strong this => move |track_set| { |  | ||||||
|                     let length = { |                     let length = { | ||||||
|                         let mut track_sets = this.track_sets.borrow_mut(); |                         let mut track_sets = this.track_sets.borrow_mut(); | ||||||
|                         track_sets.push(track_set); |                         track_sets.push(track_set); | ||||||
|  | @ -99,13 +89,11 @@ impl MediumEditor { | ||||||
|                     }; |                     }; | ||||||
| 
 | 
 | ||||||
|                     this.track_set_list.update(length); |                     this.track_set_list.update(length); | ||||||
|                 })); |                 } | ||||||
| 
 |             }); | ||||||
|                 navigator.push(editor); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.track_set_list.set_make_widget_cb(clone!(@strong this => move |index| { |         this.track_set_list.set_make_widget_cb(clone!(@weak this => move |index| { | ||||||
|             let track_set = &this.track_sets.borrow()[index]; |             let track_set = &this.track_sets.borrow()[index]; | ||||||
| 
 | 
 | ||||||
|             let title = track_set.recording.work.get_title(); |             let title = track_set.recording.work.get_title(); | ||||||
|  | @ -124,39 +112,38 @@ impl MediumEditor { | ||||||
|             row.add_suffix(&edit_button); |             row.add_suffix(&edit_button); | ||||||
|             row.set_activatable_widget(Some(&edit_button)); |             row.set_activatable_widget(Some(&edit_button)); | ||||||
| 
 | 
 | ||||||
|             edit_button.connect_clicked(clone!(@strong this => move |_| { |             edit_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
| 
 |                 // TODO: Implement editing.
 | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|             row.upcast() |             row.upcast() | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         // Copy the source in the background.
 |         spawn!(@clone this, async move { | ||||||
|         let context = glib::MainContext::default(); |             match this.source.copy().await { | ||||||
|         let clone = this.clone(); |  | ||||||
|         context.spawn_local(async move { |  | ||||||
|             match clone.source.copy().await { |  | ||||||
|                 Err(error) => { |                 Err(error) => { | ||||||
|                     // TODO: Present error.
 |                     // TODO: Present error.
 | ||||||
|                     println!("Failed to copy source: {}", error); |                     println!("Failed to copy source: {}", error); | ||||||
|                 }, |                 }, | ||||||
|                 Ok(_) => { |                 Ok(_) => { | ||||||
|                     clone.done_stack.set_visible_child(&clone.done); |                     this.done_stack.set_visible_child(&this.done); | ||||||
|                     clone.done_button.set_sensitive(true); |                     this.done_button.set_sensitive(true); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|  | impl MediumEditor { | ||||||
|     /// Save the medium and possibly upload it to the server.
 |     /// Save the medium and possibly upload it to the server.
 | ||||||
|     async fn save(self: Rc<Self>) -> Result<()> { |     async fn save(&self) -> Result<()> { | ||||||
|         let name = self.name_entry.get_text().unwrap().to_string(); |         let name = self.name_entry.get_text().unwrap().to_string(); | ||||||
| 
 | 
 | ||||||
|         // Create a new directory in the music library path for the imported medium.
 |         // Create a new directory in the music library path for the imported medium.
 | ||||||
| 
 | 
 | ||||||
|         let mut path = self.backend.get_music_library_path().unwrap().clone(); |         let mut path = self.handle.backend.get_music_library_path().unwrap().clone(); | ||||||
|         path.push(&name); |         path.push(&name); | ||||||
|         std::fs::create_dir(&path)?; |         std::fs::create_dir(&path)?; | ||||||
| 
 | 
 | ||||||
|  | @ -205,35 +192,22 @@ impl MediumEditor { | ||||||
| 
 | 
 | ||||||
|         let upload = self.publish_switch.get_active(); |         let upload = self.publish_switch.get_active(); | ||||||
|         if upload { |         if upload { | ||||||
|             self.backend.post_medium(&medium).await?; |             self.handle.backend.post_medium(&medium).await?; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         self.backend |         self.handle.backend | ||||||
|             .db() |             .db() | ||||||
|             .update_medium(medium.clone()) |             .update_medium(medium.clone()) | ||||||
|             .await?; |             .await?; | ||||||
| 
 | 
 | ||||||
|         self.backend.library_changed(); |         self.handle.backend.library_changed(); | ||||||
| 
 |  | ||||||
|         let navigator = self.navigator.borrow().clone(); |  | ||||||
|         if let Some(navigator) = navigator { |  | ||||||
|             navigator.clone().pop(); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl NavigatorScreen for MediumEditor { | impl Widget for MediumEditor { | ||||||
|     fn attach_navigator(&self, navigator: Rc<Navigator>) { |  | ||||||
|         self.navigator.replace(Some(navigator)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_widget(&self) -> gtk::Widget { |     fn get_widget(&self) -> gtk::Widget { | ||||||
|         self.widget.clone().upcast() |         self.widget.clone().upcast() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     fn detach_navigator(&self) { |  | ||||||
|         self.navigator.replace(None); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,7 +3,8 @@ use super::disc_source::DiscSource; | ||||||
| use super::folder_source::FolderSource; | use super::folder_source::FolderSource; | ||||||
| use super::source::Source; | use super::source::Source; | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::widgets::{Navigator, NavigatorScreen}; | use crate::navigator::{NavigationHandle, Screen}; | ||||||
|  | use crate::widgets::Widget; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
|  | @ -14,16 +15,15 @@ use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
| /// A dialog for starting to import music.
 | /// A dialog for starting to import music.
 | ||||||
| pub struct SourceSelector { | pub struct SourceSelector { | ||||||
|     backend: Rc<Backend>, |     handle: NavigationHandle<()>, | ||||||
|     widget: gtk::Box, |     widget: gtk::Box, | ||||||
|     stack: gtk::Stack, |     stack: gtk::Stack, | ||||||
|     info_bar: gtk::InfoBar, |     info_bar: gtk::InfoBar, | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl SourceSelector { | impl Screen<(), ()> for SourceSelector { | ||||||
|     /// Create a new source selector.
 |     /// Create a new source selector.
 | ||||||
|     pub fn new(backend: Rc<Backend>) -> Rc<Self> { |     fn new(_: (), handle: NavigationHandle<()>) -> Rc<Self> { | ||||||
|         // Create UI
 |         // Create UI
 | ||||||
| 
 | 
 | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/source_selector.ui"); |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/source_selector.ui"); | ||||||
|  | @ -36,60 +36,47 @@ impl SourceSelector { | ||||||
|         get_widget!(builder, gtk::Button, disc_button); |         get_widget!(builder, gtk::Button, disc_button); | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|             backend, |             handle, | ||||||
|             widget, |             widget, | ||||||
|             stack, |             stack, | ||||||
|             info_bar, |             info_bar, | ||||||
|             navigator: RefCell::new(None), |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         back_button.connect_clicked(clone!(@strong this => move |_| { |         back_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(None); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         folder_button.connect_clicked(clone!(@strong this => move |_| { |         folder_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             let window = this.navigator.borrow().clone().unwrap().window.clone(); |  | ||||||
|             let dialog = gtk::FileChooserDialog::new( |             let dialog = gtk::FileChooserDialog::new( | ||||||
|                 Some(&gettext("Select folder")), |                 Some(&gettext("Select folder")), | ||||||
|                 Some(&window), |                 Some(&this.handle.window), | ||||||
|                 gtk::FileChooserAction::SelectFolder, |                 gtk::FileChooserAction::SelectFolder, | ||||||
|                 &[ |                 &[ | ||||||
|                     (&gettext("Cancel"), gtk::ResponseType::Cancel), |                     (&gettext("Cancel"), gtk::ResponseType::Cancel), | ||||||
|                     (&gettext("Select"), gtk::ResponseType::Accept), |                     (&gettext("Select"), gtk::ResponseType::Accept), | ||||||
|                 ]); |                 ]); | ||||||
| 
 | 
 | ||||||
|             dialog.connect_response(clone!(@strong this => move |dialog, response| { |             dialog.connect_response(clone!(@weak this => move |dialog, response| { | ||||||
|                 this.stack.set_visible_child_name("loading"); |                 this.stack.set_visible_child_name("loading"); | ||||||
|                 dialog.hide(); |                 dialog.hide(); | ||||||
| 
 | 
 | ||||||
|                 if let gtk::ResponseType::Accept = response { |                 if let gtk::ResponseType::Accept = response { | ||||||
|                     if let Some(file) = dialog.get_file() { |                     if let Some(file) = dialog.get_file() { | ||||||
|                         if let Some(path) = file.get_path() { |                         if let Some(path) = file.get_path() { | ||||||
|                             let context = glib::MainContext::default(); |                             spawn!(@clone this, async move { | ||||||
|                             let clone = this.clone(); |  | ||||||
|                             context.spawn_local(async move { |  | ||||||
|                                 let folder = FolderSource::new(PathBuf::from(path)); |                                 let folder = FolderSource::new(PathBuf::from(path)); | ||||||
|                                 match folder.load().await { |                                 match folder.load().await { | ||||||
|                                     Ok(_) => { |                                     Ok(_) => { | ||||||
|                                         let navigator = clone.navigator.borrow().clone(); |                                         let source = Rc::new(Box::new(folder) as Box<dyn Source>); | ||||||
|                                         if let Some(navigator) = navigator { |                                         push!(this.handle, MediumEditor, source).await; | ||||||
|                                             let source = Rc::new(Box::new(folder) as Box<dyn Source>); |                                         this.handle.pop(Some(())); | ||||||
|                                             let editor = MediumEditor::new(clone.backend.clone(), source); |  | ||||||
|                                             navigator.push(editor); |  | ||||||
|                                         } |  | ||||||
| 
 |  | ||||||
|                                         clone.info_bar.set_revealed(false); |  | ||||||
|                                         clone.stack.set_visible_child_name("start"); |  | ||||||
|                                     } |                                     } | ||||||
|                                     Err(_) => { |                                     Err(_) => { | ||||||
|                                         // TODO: Present error.
 |                                         // TODO: Present error.
 | ||||||
|                                         clone.info_bar.set_revealed(true); |                                         this.info_bar.set_revealed(true); | ||||||
|                                         clone.stack.set_visible_child_name("start"); |                                         this.stack.set_visible_child_name("start"); | ||||||
|                                     } |                                     } | ||||||
|                                 } |                                 } | ||||||
|                             }); |                             }); | ||||||
|  | @ -101,29 +88,21 @@ impl SourceSelector { | ||||||
|             dialog.show(); |             dialog.show(); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         disc_button.connect_clicked(clone!(@strong this => move |_| { |         disc_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             this.stack.set_visible_child_name("loading"); |             this.stack.set_visible_child_name("loading"); | ||||||
| 
 | 
 | ||||||
|             let context = glib::MainContext::default(); |             spawn!(@clone this, async move { | ||||||
|             let clone = this.clone(); |  | ||||||
|             context.spawn_local(async move { |  | ||||||
|                 let disc = DiscSource::new().unwrap(); |                 let disc = DiscSource::new().unwrap(); | ||||||
|                 match disc.load().await { |                 match disc.load().await { | ||||||
|                     Ok(_) => { |                     Ok(_) => { | ||||||
|                         let navigator = clone.navigator.borrow().clone(); |                         let source = Rc::new(Box::new(disc) as Box<dyn Source>); | ||||||
|                         if let Some(navigator) = navigator { |                         push!(this.handle, MediumEditor, source).await; | ||||||
|                             let source = Rc::new(Box::new(disc) as Box<dyn Source>); |                         this.handle.pop(Some(())); | ||||||
|                             let editor = MediumEditor::new(clone.backend.clone(), source); |  | ||||||
|                             navigator.push(editor); |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         clone.info_bar.set_revealed(false); |  | ||||||
|                         clone.stack.set_visible_child_name("start"); |  | ||||||
|                     } |                     } | ||||||
|                     Err(_) => { |                     Err(_) => { | ||||||
|                         // TODO: Present error.
 |                         // TODO: Present error.
 | ||||||
|                         clone.info_bar.set_revealed(true); |                         this.info_bar.set_revealed(true); | ||||||
|                         clone.stack.set_visible_child_name("start"); |                         this.stack.set_visible_child_name("start"); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|  | @ -133,16 +112,8 @@ impl SourceSelector { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl NavigatorScreen for SourceSelector { | impl Widget for SourceSelector { | ||||||
|     fn attach_navigator(&self, navigator: Rc<Navigator>) { |  | ||||||
|         self.navigator.replace(Some(navigator)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_widget(&self) -> gtk::Widget { |     fn get_widget(&self) -> gtk::Widget { | ||||||
|         self.widget.clone().upcast() |         self.widget.clone().upcast() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     fn detach_navigator(&self) { |  | ||||||
|         self.navigator.replace(None); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| use crate::database::Recording; | use crate::database::Recording; | ||||||
| use crate::widgets::{Navigator, NavigatorScreen}; | use crate::navigator::{NavigationHandle, Screen}; | ||||||
|  | use crate::widgets::Widget; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
| use gtk_macros::get_widget; | use gtk_macros::get_widget; | ||||||
|  | @ -9,15 +10,14 @@ use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
| /// A screen for editing a single track.
 | /// A screen for editing a single track.
 | ||||||
| pub struct TrackEditor { | pub struct TrackEditor { | ||||||
|  |     handle: NavigationHandle<Vec<usize>>, | ||||||
|     widget: gtk::Box, |     widget: gtk::Box, | ||||||
|     selection: RefCell<Vec<usize>>, |     selection: RefCell<Vec<usize>>, | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(Vec<usize>)>>>, |  | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl TrackEditor { | impl Screen<(Recording, Vec<usize>), Vec<usize>> for TrackEditor { | ||||||
|     /// Create a new track editor.
 |     /// Create a new track editor.
 | ||||||
|     pub fn new(recording: Recording, selection: Vec<usize>) -> Rc<Self> { |     fn new((recording, selection): (Recording, Vec<usize>), handle: NavigationHandle<Vec<usize>>) -> Rc<Self> { | ||||||
|         // Create UI
 |         // Create UI
 | ||||||
| 
 | 
 | ||||||
|         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"); | ||||||
|  | @ -34,38 +34,27 @@ impl TrackEditor { | ||||||
|         parts_frame.set_child(Some(&parts_list)); |         parts_frame.set_child(Some(&parts_list)); | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|  |             handle, | ||||||
|             widget, |             widget, | ||||||
|             selection: RefCell::new(selection), |             selection: RefCell::new(selection), | ||||||
|             selected_cb: RefCell::new(None), |  | ||||||
|             navigator: RefCell::new(None), |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         back_button.connect_clicked(clone!(@strong this => move |_| { |         back_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(None); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         select_button.connect_clicked(clone!(@strong this => move |_| { |         select_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             let selection = this.selection.borrow().clone(); | ||||||
|             if let Some(navigator) = navigator { |             this.handle.pop(Some(selection)); | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if let Some(cb) = &*this.selected_cb.borrow() { |  | ||||||
|                 let selection = this.selection.borrow().clone(); |  | ||||||
|                 cb(selection); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         for (index, part) in recording.work.parts.iter().enumerate() { |         for (index, part) in recording.work.parts.iter().enumerate() { | ||||||
|             let check = gtk::CheckButton::new(); |             let check = gtk::CheckButton::new(); | ||||||
|             check.set_active(this.selection.borrow().contains(&index)); |             check.set_active(this.selection.borrow().contains(&index)); | ||||||
| 
 | 
 | ||||||
|             check.connect_toggled(clone!(@strong this => move |check| { |             check.connect_toggled(clone!(@weak this => move |check| { | ||||||
|                 let mut selection = this.selection.borrow_mut(); |                 let mut selection = this.selection.borrow_mut(); | ||||||
|                 if check.get_active() { |                 if check.get_active() { | ||||||
|                     selection.push(index); |                     selection.push(index); | ||||||
|  | @ -86,23 +75,10 @@ impl TrackEditor { | ||||||
| 
 | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     /// Set the closure to be called when the user has edited the track.
 |  | ||||||
|     pub fn set_selected_cb<F: Fn(Vec<usize>) + 'static>(&self, cb: F) { |  | ||||||
|         self.selected_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl NavigatorScreen for TrackEditor { | impl Widget for TrackEditor { | ||||||
|     fn attach_navigator(&self, navigator: Rc<Navigator>) { |  | ||||||
|         self.navigator.replace(Some(navigator)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_widget(&self) -> gtk::Widget { |     fn get_widget(&self) -> gtk::Widget { | ||||||
|         self.widget.clone().upcast() |         self.widget.clone().upcast() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     fn detach_navigator(&self) { |  | ||||||
|         self.navigator.replace(None); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| use super::source::Source; | use super::source::Source; | ||||||
| use crate::widgets::{Navigator, NavigatorScreen}; | use crate::navigator::{NavigationHandle, Screen}; | ||||||
|  | use crate::widgets::Widget; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
| use gtk_macros::get_widget; | use gtk_macros::get_widget; | ||||||
|  | @ -9,17 +10,16 @@ use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
| /// A screen for selecting tracks from a source.
 | /// A screen for selecting tracks from a source.
 | ||||||
| pub struct TrackSelector { | pub struct TrackSelector { | ||||||
|  |     handle: NavigationHandle<Vec<usize>>, | ||||||
|     source: Rc<Box<dyn Source>>, |     source: Rc<Box<dyn Source>>, | ||||||
|     widget: gtk::Box, |     widget: gtk::Box, | ||||||
|     select_button: gtk::Button, |     select_button: gtk::Button, | ||||||
|     selection: RefCell<Vec<usize>>, |     selection: RefCell<Vec<usize>>, | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(Vec<usize>)>>>, |  | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl TrackSelector { | impl Screen<Rc<Box<dyn Source>>, Vec<usize>> for TrackSelector { | ||||||
|     /// Create a new track selector.
 |     /// Create a new track selector.
 | ||||||
|     pub fn new(source: Rc<Box<dyn Source>>) -> Rc<Self> { |     fn new(source: Rc<Box<dyn Source>>, handle: NavigationHandle<Vec<usize>>) -> Rc<Self> { | ||||||
|         // Create UI
 |         // Create UI
 | ||||||
| 
 | 
 | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/track_selector.ui"); |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/track_selector.ui"); | ||||||
|  | @ -36,33 +36,22 @@ impl TrackSelector { | ||||||
|         tracks_frame.set_child(Some(&track_list)); |         tracks_frame.set_child(Some(&track_list)); | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|  |             handle, | ||||||
|             source, |             source, | ||||||
|             widget, |             widget, | ||||||
|             select_button, |             select_button, | ||||||
|             selection: RefCell::new(Vec::new()), |             selection: RefCell::new(Vec::new()), | ||||||
|             selected_cb: RefCell::new(None), |  | ||||||
|             navigator: RefCell::new(None), |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         back_button.connect_clicked(clone!(@strong this => move |_| { |         back_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(None); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.select_button.connect_clicked(clone!(@strong this => move |_| { |         this.select_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             let selection = this.selection.borrow().clone(); | ||||||
|             if let Some(navigator) = navigator { |             this.handle.pop(Some(selection)); | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if let Some(cb) = &*this.selected_cb.borrow() { |  | ||||||
|                 let selection = this.selection.borrow().clone(); |  | ||||||
|                 cb(selection); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         let tracks = this.source.tracks().unwrap(); |         let tracks = this.source.tracks().unwrap(); | ||||||
|  | @ -70,7 +59,7 @@ impl TrackSelector { | ||||||
|         for (index, track) in tracks.iter().enumerate() { |         for (index, track) in tracks.iter().enumerate() { | ||||||
|             let check = gtk::CheckButton::new(); |             let check = gtk::CheckButton::new(); | ||||||
| 
 | 
 | ||||||
|             check.connect_toggled(clone!(@strong this => move |check| { |             check.connect_toggled(clone!(@weak this => move |check| { | ||||||
|                 let mut selection = this.selection.borrow_mut(); |                 let mut selection = this.selection.borrow_mut(); | ||||||
|                 if check.get_active() { |                 if check.get_active() { | ||||||
|                     selection.push(index); |                     selection.push(index); | ||||||
|  | @ -98,25 +87,10 @@ impl TrackSelector { | ||||||
| 
 | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     /// Set the closure to be called when the user has selected tracks. The
 |  | ||||||
|     /// closure will be called with the indices of the selected tracks as its
 |  | ||||||
|     /// argument.
 |  | ||||||
|     pub fn set_selected_cb<F: Fn(Vec<usize>) + 'static>(&self, cb: F) { |  | ||||||
|         self.selected_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl NavigatorScreen for TrackSelector { | impl Widget for TrackSelector { | ||||||
|     fn attach_navigator(&self, navigator: Rc<Navigator>) { |  | ||||||
|         self.navigator.replace(Some(navigator)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_widget(&self) -> gtk::Widget { |     fn get_widget(&self) -> gtk::Widget { | ||||||
|         self.widget.clone().upcast() |         self.widget.clone().upcast() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     fn detach_navigator(&self) { |  | ||||||
|         self.navigator.replace(None); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,8 +3,9 @@ use super::track_editor::TrackEditor; | ||||||
| use super::track_selector::TrackSelector; | use super::track_selector::TrackSelector; | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::database::Recording; | use crate::database::Recording; | ||||||
| use crate::selectors::{PersonSelector, RecordingSelector, WorkSelector}; | use crate::navigator::{NavigationHandle, Screen}; | ||||||
| use crate::widgets::{List, Navigator, NavigatorScreen}; | use crate::selectors::PersonSelector; | ||||||
|  | use crate::widgets::{List, Widget}; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
|  | @ -32,7 +33,7 @@ pub struct TrackData { | ||||||
| 
 | 
 | ||||||
| /// A screen for editing a set of tracks for one recording.
 | /// A screen for editing a set of tracks for one recording.
 | ||||||
| pub struct TrackSetEditor { | pub struct TrackSetEditor { | ||||||
|     backend: Rc<Backend>, |     handle: NavigationHandle<TrackSetData>, | ||||||
|     source: Rc<Box<dyn Source>>, |     source: Rc<Box<dyn Source>>, | ||||||
|     widget: gtk::Box, |     widget: gtk::Box, | ||||||
|     save_button: gtk::Button, |     save_button: gtk::Button, | ||||||
|  | @ -40,13 +41,11 @@ pub struct TrackSetEditor { | ||||||
|     track_list: Rc<List>, |     track_list: Rc<List>, | ||||||
|     recording: RefCell<Option<Recording>>, |     recording: RefCell<Option<Recording>>, | ||||||
|     tracks: RefCell<Vec<TrackData>>, |     tracks: RefCell<Vec<TrackData>>, | ||||||
|     done_cb: RefCell<Option<Box<dyn Fn(TrackSetData)>>>, |  | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl TrackSetEditor { | impl Screen<Rc<Box<dyn Source>>, TrackSetData> for TrackSetEditor { | ||||||
|     /// Create a new track set editor.
 |     /// Create a new track set editor.
 | ||||||
|     pub fn new(backend: Rc<Backend>, source: Rc<Box<dyn Source>>) -> Rc<Self> { |     fn new(source: Rc<Box<dyn Source>>, handle: NavigationHandle<TrackSetData>) -> Rc<Self> { | ||||||
|         // Create UI
 |         // Create UI
 | ||||||
| 
 | 
 | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/track_set_editor.ui"); |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/track_set_editor.ui"); | ||||||
|  | @ -63,7 +62,7 @@ impl TrackSetEditor { | ||||||
|         tracks_frame.set_child(Some(&track_list.widget)); |         tracks_frame.set_child(Some(&track_list.widget)); | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|             backend, |             handle, | ||||||
|             source, |             source, | ||||||
|             widget, |             widget, | ||||||
|             save_button, |             save_button, | ||||||
|  | @ -71,71 +70,30 @@ impl TrackSetEditor { | ||||||
|             track_list, |             track_list, | ||||||
|             recording: RefCell::new(None), |             recording: RefCell::new(None), | ||||||
|             tracks: RefCell::new(Vec::new()), |             tracks: RefCell::new(Vec::new()), | ||||||
|             done_cb: RefCell::new(None), |  | ||||||
|             navigator: RefCell::new(None), |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         back_button.connect_clicked(clone!(@strong this => move |_| { |         back_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(None); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.save_button.connect_clicked(clone!(@strong this => move |_| { |         this.save_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             if let Some(cb) = &*this.done_cb.borrow() { |             let data = TrackSetData { | ||||||
|                 let data = TrackSetData { |                 recording: this.recording.borrow().clone().unwrap(), | ||||||
|                     recording: this.recording.borrow().clone().unwrap(), |                 tracks: this.tracks.borrow().clone(), | ||||||
|                     tracks: this.tracks.borrow().clone(), |             }; | ||||||
|                 }; |  | ||||||
| 
 | 
 | ||||||
|                 cb(data); |             this.handle.pop(Some(data)); | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             let navigator = this.navigator.borrow().clone(); |  | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         select_recording_button.connect_clicked(clone!(@strong this => move |_| { |         select_recording_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             // TODO: We need to push a screen returning a recording here.
 | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 let person_selector = PersonSelector::new(this.backend.clone()); |  | ||||||
| 
 |  | ||||||
|                 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| { |  | ||||||
|                         let recording_selector = RecordingSelector::new(this.backend.clone(), work.clone()); |  | ||||||
| 
 |  | ||||||
|                         recording_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |recording| { |  | ||||||
|                             this.recording.replace(Some(recording.clone())); |  | ||||||
|                             this.recording_selected(); |  | ||||||
| 
 |  | ||||||
|                             navigator.clone().pop(); |  | ||||||
|                             navigator.clone().pop(); |  | ||||||
|                             navigator.clone().pop(); |  | ||||||
|                         })); |  | ||||||
| 
 |  | ||||||
|                         navigator.clone().push(recording_selector); |  | ||||||
|                     })); |  | ||||||
| 
 |  | ||||||
|                     navigator.clone().push(work_selector); |  | ||||||
|                 })); |  | ||||||
| 
 |  | ||||||
|                 navigator.clone().push(person_selector); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         edit_tracks_button.connect_clicked(clone!(@strong this => move |_| { |         edit_tracks_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             spawn!(@clone this, async move { | ||||||
|             if let Some(navigator) = navigator { |                 if let Some(selection) = push!(this.handle, TrackSelector, Rc::clone(&this.source)).await { | ||||||
|                 let selector = TrackSelector::new(Rc::clone(&this.source)); |  | ||||||
| 
 |  | ||||||
|                 selector.set_selected_cb(clone!(@strong this => move |selection| { |  | ||||||
|                     let mut tracks = Vec::new(); |                     let mut tracks = Vec::new(); | ||||||
| 
 | 
 | ||||||
|                     for index in selection { |                     for index in selection { | ||||||
|  | @ -151,13 +109,11 @@ impl TrackSetEditor { | ||||||
|                     this.tracks.replace(tracks); |                     this.tracks.replace(tracks); | ||||||
|                     this.track_list.update(length); |                     this.track_list.update(length); | ||||||
|                     this.autofill_parts(); |                     this.autofill_parts(); | ||||||
|                 })); |                 } | ||||||
| 
 |             }); | ||||||
|                 navigator.push(selector); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.track_list.set_make_widget_cb(clone!(@strong this => move |index| { |         this.track_list.set_make_widget_cb(clone!(@weak this => move |index| { | ||||||
|             let track = &this.tracks.borrow()[index]; |             let track = &this.tracks.borrow()[index]; | ||||||
| 
 | 
 | ||||||
|             let mut title_parts = Vec::<String>::new(); |             let mut title_parts = Vec::<String>::new(); | ||||||
|  | @ -190,26 +146,21 @@ impl TrackSetEditor { | ||||||
|             row.add_suffix(&edit_button); |             row.add_suffix(&edit_button); | ||||||
|             row.set_activatable_widget(Some(&edit_button)); |             row.set_activatable_widget(Some(&edit_button)); | ||||||
| 
 | 
 | ||||||
|             edit_button.connect_clicked(clone!(@strong this => move |_| { |             edit_button.connect_clicked(clone!(@weak this => move |_| { | ||||||
|                 let recording = this.recording.borrow().clone(); |                 let recording = this.recording.borrow().clone(); | ||||||
|                 let navigator = this.navigator.borrow().clone(); |                 if let Some(recording) = recording { | ||||||
|  |                     spawn!(@clone this, async move { | ||||||
|  |                         let track = &this.tracks.borrow()[index]; | ||||||
|  |                         if let Some(selection) = push!(this.handle, TrackEditor, (recording, track.work_parts.clone())).await { | ||||||
|  |                             { | ||||||
|  |                                 let mut tracks = this.tracks.borrow_mut(); | ||||||
|  |                                 let mut track = &mut tracks[index]; | ||||||
|  |                                 track.work_parts = selection; | ||||||
|  |                             }; | ||||||
| 
 | 
 | ||||||
|                 if let (Some(recording), Some(navigator)) = (recording, navigator) { |                             this.update_tracks(); | ||||||
|                     let track = &this.tracks.borrow()[index]; |                         } | ||||||
| 
 |                     }); | ||||||
|                     let editor = TrackEditor::new(recording, track.work_parts.clone()); |  | ||||||
| 
 |  | ||||||
|                     editor.set_selected_cb(clone!(@strong this => move |selection| { |  | ||||||
|                         { |  | ||||||
|                             let mut tracks = this.tracks.borrow_mut(); |  | ||||||
|                             let mut track = &mut tracks[index]; |  | ||||||
|                             track.work_parts = selection; |  | ||||||
|                         }; |  | ||||||
| 
 |  | ||||||
|                         this.update_tracks(); |  | ||||||
|                     })); |  | ||||||
| 
 |  | ||||||
|                     navigator.push(editor); |  | ||||||
|                 } |                 } | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|  | @ -218,12 +169,9 @@ impl TrackSetEditor { | ||||||
| 
 | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     /// Set the closure to be called when the user has created the track set.
 | impl TrackSetEditor { | ||||||
|     pub fn set_done_cb<F: Fn(TrackSetData) + 'static>(&self, cb: F) { |  | ||||||
|         self.done_cb.replace(Some(Box::new(cb))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Set everything up after selecting a recording.
 |     /// Set everything up after selecting a recording.
 | ||||||
|     fn recording_selected(&self) { |     fn recording_selected(&self) { | ||||||
|         if let Some(recording) = &*self.recording.borrow() { |         if let Some(recording) = &*self.recording.borrow() { | ||||||
|  | @ -260,21 +208,9 @@ impl TrackSetEditor { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl NavigatorScreen for TrackSetEditor { | impl Widget for TrackSetEditor { | ||||||
|     fn attach_navigator(&self, navigator: Rc<Navigator>) { |  | ||||||
|         self.navigator.replace(Some(navigator)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_widget(&self) -> gtk::Widget { |     fn get_widget(&self) -> gtk::Widget { | ||||||
|         self.widget.clone().upcast() |         self.widget.clone().upcast() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     fn detach_navigator(&self) { |  | ||||||
|         self.navigator.replace(None); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ macro_rules! push { | ||||||
|     ($handle:expr, $screen:ty) => { |     ($handle:expr, $screen:ty) => { | ||||||
|         $handle.push::<_, _, $screen>(()) |         $handle.push::<_, _, $screen>(()) | ||||||
|     }; |     }; | ||||||
|     ($handle:expr, $screen:ty, $input:ident) => { |     ($handle:expr, $screen:ty, $input:expr) => { | ||||||
|         $handle.push::<_, _, $screen>($input) |         $handle.push::<_, _, $screen>($input) | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  | @ -43,7 +43,7 @@ macro_rules! replace { | ||||||
|     ($navigator:expr, $screen:ty) => { |     ($navigator:expr, $screen:ty) => { | ||||||
|         $navigator.replace::<_, _, $screen>(()) |         $navigator.replace::<_, _, $screen>(()) | ||||||
|     }; |     }; | ||||||
|     ($navigator:expr, $screen:ty, $input:ident) => { |     ($navigator:expr, $screen:ty, $input:expr) => { | ||||||
|         $navigator.replace::<_, _, $screen>($input) |         $navigator.replace::<_, _, $screen>($input) | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,7 +3,8 @@ use super::RecordingScreen; | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::database::{Ensemble, Recording}; | use crate::database::{Ensemble, Recording}; | ||||||
| use crate::editors::EnsembleEditor; | use crate::editors::EnsembleEditor; | ||||||
| use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow, Screen, Section}; | use crate::navigator::NavigatorWindow; | ||||||
|  | use crate::widgets::{List, Navigator, NavigatorScreen, Screen, Section}; | ||||||
| 
 | 
 | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
|  | @ -49,9 +50,10 @@ impl EnsembleScreen { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         this.widget.add_action(&gettext("Edit ensemble"), clone!(@strong this => move || { |         this.widget.add_action(&gettext("Edit ensemble"), clone!(@strong this => move || { | ||||||
|             let editor = EnsembleEditor::new(this.backend.clone(), Some(this.ensemble.clone())); |             spawn!(@clone this, async move { | ||||||
|             let window = NavigatorWindow::new(editor); |                 let window = NavigatorWindow::new(this.backend.clone()); | ||||||
|             window.show(); |                 replace!(window.navigator, EnsembleEditor, None).await; | ||||||
|  |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.widget.add_action(&gettext("Delete ensemble"), clone!(@strong this => move || { |         this.widget.add_action(&gettext("Delete ensemble"), clone!(@strong this => move || { | ||||||
|  |  | ||||||
|  | @ -3,7 +3,8 @@ use super::{WorkScreen, RecordingScreen}; | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::database::{Person, Recording, Work}; | use crate::database::{Person, Recording, Work}; | ||||||
| use crate::editors::PersonEditor; | use crate::editors::PersonEditor; | ||||||
| use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow, Screen, Section}; | use crate::navigator::NavigatorWindow; | ||||||
|  | use crate::widgets::{List, Navigator, NavigatorScreen, Screen, Section}; | ||||||
| 
 | 
 | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
|  | @ -54,9 +55,10 @@ impl PersonScreen { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         this.widget.add_action(&gettext("Edit person"), clone!(@strong this => move || { |         this.widget.add_action(&gettext("Edit person"), clone!(@strong this => move || { | ||||||
|             let editor = PersonEditor::new(this.backend.clone(), Some(this.person.clone())); |             spawn!(@clone this, async move { | ||||||
|             let window = NavigatorWindow::new(editor); |                 let window = NavigatorWindow::new(this.backend.clone()); | ||||||
|             window.show(); |                 replace!(window.navigator, PersonEditor, None).await; | ||||||
|  |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.widget.add_action(&gettext("Delete person"), clone!(@strong this => move || { |         this.widget.add_action(&gettext("Delete person"), clone!(@strong this => move || { | ||||||
|  |  | ||||||
|  | @ -1,7 +1,8 @@ | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::database::Recording; | use crate::database::Recording; | ||||||
| use crate::editors::RecordingEditor; | use crate::editors::RecordingEditor; | ||||||
| use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow, Screen, Section}; | use crate::navigator::NavigatorWindow; | ||||||
|  | use crate::widgets::{List, Navigator, NavigatorScreen, Screen, Section}; | ||||||
| 
 | 
 | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
|  | @ -48,9 +49,10 @@ impl RecordingScreen { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         this.widget.add_action(&gettext("Edit recording"), clone!(@strong this => move || { |         this.widget.add_action(&gettext("Edit recording"), clone!(@strong this => move || { | ||||||
|             let editor = RecordingEditor::new(this.backend.clone(), Some(this.recording.clone())); |             spawn!(@clone this, async move { | ||||||
|             let window = NavigatorWindow::new(editor); |                 let window = NavigatorWindow::new(this.backend.clone()); | ||||||
|             window.show(); |                 replace!(window.navigator, RecordingEditor, None).await; | ||||||
|  |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.widget.add_action(&gettext("Delete recording"), clone!(@strong this => move || { |         this.widget.add_action(&gettext("Delete recording"), clone!(@strong this => move || { | ||||||
|  |  | ||||||
|  | @ -3,7 +3,8 @@ use super::RecordingScreen; | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::database::{Work, Recording}; | use crate::database::{Work, Recording}; | ||||||
| use crate::editors::WorkEditor; | use crate::editors::WorkEditor; | ||||||
| use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow, Screen, Section}; | use crate::navigator::NavigatorWindow; | ||||||
|  | use crate::widgets::{List, Navigator, NavigatorScreen, Screen, Section}; | ||||||
| 
 | 
 | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
|  | @ -50,9 +51,10 @@ impl WorkScreen { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         this.widget.add_action(&gettext("Edit work"), clone!(@strong this => move || { |         this.widget.add_action(&gettext("Edit work"), clone!(@strong this => move || { | ||||||
|             let editor = WorkEditor::new(this.backend.clone(), Some(this.work.clone())); |             spawn!(@clone this, async move { | ||||||
|             let window = NavigatorWindow::new(editor); |                 let window = NavigatorWindow::new(this.backend.clone()); | ||||||
|             window.show(); |                 replace!(window.navigator, WorkEditor, None).await; | ||||||
|  |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.widget.add_action(&gettext("Delete work"), clone!(@strong this => move || { |         this.widget.add_action(&gettext("Delete work"), clone!(@strong this => move || { | ||||||
|  |  | ||||||
|  | @ -2,7 +2,8 @@ use super::selector::Selector; | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::database::Ensemble; | use crate::database::Ensemble; | ||||||
| use crate::editors::EnsembleEditor; | use crate::editors::EnsembleEditor; | ||||||
| use crate::widgets::{Navigator, NavigatorScreen}; | use crate::navigator::{NavigationHandle, Screen}; | ||||||
|  | use crate::widgets::Widget; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
|  | @ -12,66 +13,55 @@ use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
| /// A screen for selecting a ensemble.
 | /// A screen for selecting a ensemble.
 | ||||||
| pub struct EnsembleSelector { | pub struct EnsembleSelector { | ||||||
|     backend: Rc<Backend>, |     handle: NavigationHandle<Ensemble>, | ||||||
|     selector: Rc<Selector<Ensemble>>, |     selector: Rc<Selector<Ensemble>>, | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(&Ensemble) -> ()>>>, |  | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl EnsembleSelector { | impl Screen<(), Ensemble> for EnsembleSelector { | ||||||
|     /// Create a new ensemble selector.
 |     /// Create a new ensemble selector.
 | ||||||
|     pub fn new(backend: Rc<Backend>) -> Rc<Self> { |     fn new(_: (), handle: NavigationHandle<Ensemble>) -> Rc<Self> { | ||||||
|         // Create UI
 |         // Create UI
 | ||||||
| 
 | 
 | ||||||
|         let selector = Selector::<Ensemble>::new(); |         let selector = Selector::<Ensemble>::new(); | ||||||
|         selector.set_title(&gettext("Select ensemble")); |         selector.set_title(&gettext("Select ensemble")); | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|             backend, |             handle, | ||||||
|             selector, |             selector, | ||||||
|             selected_cb: RefCell::new(None), |  | ||||||
|             navigator: RefCell::new(None), |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         this.selector.set_back_cb(clone!(@strong this => move || { |         this.selector.set_back_cb(clone!(@weak this => move || { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(None); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.selector.set_add_cb(clone!(@strong this => move || { |         this.selector.set_add_cb(clone!(@weak this => move || { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             spawn!(@clone this, async move { | ||||||
|             if let Some(navigator) = navigator { |                 if let Some(ensemble) = push!(this.handle, EnsembleEditor, None).await { | ||||||
|                 let editor = EnsembleEditor::new(this.backend.clone(), None); |                     this.handle.pop(Some(ensemble)); | ||||||
|                 editor |                 } | ||||||
|                     .set_saved_cb(clone!(@strong this => move |ensemble| this.select(&ensemble))); |             }); | ||||||
|                 navigator.push(editor); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.selector |         this.selector.set_load_online(clone!(@weak this => move || { | ||||||
|             .set_load_online(clone!(@strong this => move || { |             let clone = this.clone(); | ||||||
|                 let clone = this.clone(); |             async move { clone.handle.backend.get_ensembles().await } | ||||||
|                 async move { clone.backend.get_ensembles().await } |         })); | ||||||
|             })); |  | ||||||
| 
 | 
 | ||||||
|         this.selector |         this.selector.set_load_local(clone!(@weak this => move || { | ||||||
|             .set_load_local(clone!(@strong this => move || { |             let clone = this.clone(); | ||||||
|                 let clone = this.clone(); |             async move { clone.handle.backend.db().get_ensembles().await.unwrap() } | ||||||
|                 async move { clone.backend.db().get_ensembles().await.unwrap() } |         })); | ||||||
|             })); |  | ||||||
| 
 | 
 | ||||||
|         this.selector.set_make_widget(clone!(@strong this => move |ensemble| { |         this.selector.set_make_widget(clone!(@weak this => move |ensemble| { | ||||||
|             let row = libadwaita::ActionRow::new(); |             let row = libadwaita::ActionRow::new(); | ||||||
|             row.set_activatable(true); |             row.set_activatable(true); | ||||||
|             row.set_title(Some(&ensemble.name)); |             row.set_title(Some(&ensemble.name)); | ||||||
| 
 | 
 | ||||||
|             let ensemble = ensemble.to_owned(); |             let ensemble = ensemble.to_owned(); | ||||||
|             row.connect_activated(clone!(@strong this => move |_| { |             row.connect_activated(clone!(@weak this => move |_| { | ||||||
|                 this.select(&ensemble); |                 this.handle.pop(Some(ensemble.clone())) | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|             row.upcast() |             row.upcast() | ||||||
|  | @ -82,31 +72,10 @@ impl EnsembleSelector { | ||||||
| 
 | 
 | ||||||
|         this |         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 an ensemble.
 |  | ||||||
|     fn select(&self, ensemble: &Ensemble) {        
 |  | ||||||
|         if let Some(cb) = &*self.selected_cb.borrow() { |  | ||||||
|             cb(&ensemble); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl NavigatorScreen for EnsembleSelector { | impl Widget for EnsembleSelector { | ||||||
|     fn attach_navigator(&self, navigator: Rc<Navigator>) { |  | ||||||
|         self.navigator.replace(Some(navigator)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_widget(&self) -> gtk::Widget { |     fn get_widget(&self) -> gtk::Widget { | ||||||
|         self.selector.widget.clone().upcast() |         self.selector.widget.clone().upcast() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     fn detach_navigator(&self) { |  | ||||||
|         self.navigator.replace(None); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,7 +2,8 @@ use super::selector::Selector; | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::database::Instrument; | use crate::database::Instrument; | ||||||
| use crate::editors::InstrumentEditor; | use crate::editors::InstrumentEditor; | ||||||
| use crate::widgets::{Navigator, NavigatorScreen}; | use crate::navigator::{NavigationHandle, Screen}; | ||||||
|  | use crate::widgets::Widget; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
|  | @ -12,66 +13,55 @@ use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
| /// A screen for selecting a instrument.
 | /// A screen for selecting a instrument.
 | ||||||
| pub struct InstrumentSelector { | pub struct InstrumentSelector { | ||||||
|     backend: Rc<Backend>, |     handle: NavigationHandle<Instrument>, | ||||||
|     selector: Rc<Selector<Instrument>>, |     selector: Rc<Selector<Instrument>>, | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(&Instrument) -> ()>>>, |  | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl InstrumentSelector { | impl Screen<(), Instrument> for InstrumentSelector { | ||||||
|     /// Create a new instrument selector.
 |     /// Create a new instrument selector.
 | ||||||
|     pub fn new(backend: Rc<Backend>) -> Rc<Self> { |     fn new(_: (), handle: NavigationHandle<Instrument>) -> Rc<Self> { | ||||||
|         // Create UI
 |         // Create UI
 | ||||||
| 
 | 
 | ||||||
|         let selector = Selector::<Instrument>::new(); |         let selector = Selector::<Instrument>::new(); | ||||||
|         selector.set_title(&gettext("Select instrument")); |         selector.set_title(&gettext("Select instrument")); | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|             backend, |             handle, | ||||||
|             selector, |             selector, | ||||||
|             selected_cb: RefCell::new(None), |  | ||||||
|             navigator: RefCell::new(None), |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         this.selector.set_back_cb(clone!(@strong this => move || { |         this.selector.set_back_cb(clone!(@weak this => move || { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(None); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.selector.set_add_cb(clone!(@strong this => move || { |         this.selector.set_add_cb(clone!(@weak this => move || { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             spawn!(@clone this, async move { | ||||||
|             if let Some(navigator) = navigator { |                 if let Some(instrument) = push!(this.handle, InstrumentEditor, None).await { | ||||||
|                 let editor = InstrumentEditor::new(this.backend.clone(), None); |                     this.handle.pop(Some(instrument)); | ||||||
|                 editor |                 } | ||||||
|                     .set_saved_cb(clone!(@strong this => move |instrument| this.select(&instrument))); |             }); | ||||||
|                 navigator.push(editor); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.selector |         this.selector.set_load_online(clone!(@weak this => move || { | ||||||
|             .set_load_online(clone!(@strong this => move || { |             let clone = this.clone(); | ||||||
|                 let clone = this.clone(); |             async move { clone.handle.backend.get_instruments().await } | ||||||
|                 async move { clone.backend.get_instruments().await } |         })); | ||||||
|             })); |  | ||||||
| 
 | 
 | ||||||
|         this.selector |         this.selector.set_load_local(clone!(@weak this => move || { | ||||||
|             .set_load_local(clone!(@strong this => move || { |             let clone = this.clone(); | ||||||
|                 let clone = this.clone(); |             async move { clone.handle.backend.db().get_instruments().await.unwrap() } | ||||||
|                 async move { clone.backend.db().get_instruments().await.unwrap() } |         })); | ||||||
|             })); |  | ||||||
| 
 | 
 | ||||||
|         this.selector.set_make_widget(clone!(@strong this => move |instrument| { |         this.selector.set_make_widget(clone!(@weak this => move |instrument| { | ||||||
|             let row = libadwaita::ActionRow::new(); |             let row = libadwaita::ActionRow::new(); | ||||||
|             row.set_activatable(true); |             row.set_activatable(true); | ||||||
|             row.set_title(Some(&instrument.name)); |             row.set_title(Some(&instrument.name)); | ||||||
| 
 | 
 | ||||||
|             let instrument = instrument.to_owned(); |             let instrument = instrument.to_owned(); | ||||||
|             row.connect_activated(clone!(@strong this => move |_| { |             row.connect_activated(clone!(@weak this => move |_| { | ||||||
|                 this.select(&instrument); |                 this.handle.pop(Some(instrument.clone())) | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|             row.upcast() |             row.upcast() | ||||||
|  | @ -82,30 +72,10 @@ impl InstrumentSelector { | ||||||
| 
 | 
 | ||||||
|         this |         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 an instrument.
 |  | ||||||
|     fn select(&self, instrument: &Instrument) { |  | ||||||
|         if let Some(cb) = &*self.selected_cb.borrow() { |  | ||||||
|             cb(&instrument); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl NavigatorScreen for InstrumentSelector { | impl Widget for InstrumentSelector { | ||||||
|     fn attach_navigator(&self, navigator: Rc<Navigator>) { |  | ||||||
|         self.navigator.replace(Some(navigator)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_widget(&self) -> gtk::Widget { |     fn get_widget(&self) -> gtk::Widget { | ||||||
|         self.selector.widget.clone().upcast() |         self.selector.widget.clone().upcast() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     fn detach_navigator(&self) { |  | ||||||
|         self.navigator.replace(None); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -7,10 +7,12 @@ pub use instrument::*; | ||||||
| pub mod person; | pub mod person; | ||||||
| pub use person::*; | pub use person::*; | ||||||
| 
 | 
 | ||||||
| pub mod recording; | // TODO: Readd a better version of these.
 | ||||||
| pub use recording::*; | //
 | ||||||
| 
 | // pub mod recording;
 | ||||||
| pub mod work; | // pub use recording::*;
 | ||||||
| pub use work::*; | //
 | ||||||
|  | // pub mod work;
 | ||||||
|  | // pub use work::*;
 | ||||||
| 
 | 
 | ||||||
| mod selector; | mod selector; | ||||||
|  | @ -2,7 +2,8 @@ use super::selector::Selector; | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::database::Person; | use crate::database::Person; | ||||||
| use crate::editors::PersonEditor; | use crate::editors::PersonEditor; | ||||||
| use crate::widgets::{Navigator, NavigatorScreen}; | use crate::navigator::{NavigationHandle, Screen}; | ||||||
|  | use crate::widgets::Widget; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
|  | @ -12,66 +13,55 @@ use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
| /// A screen for selecting a person.
 | /// A screen for selecting a person.
 | ||||||
| pub struct PersonSelector { | pub struct PersonSelector { | ||||||
|     backend: Rc<Backend>, |     handle: NavigationHandle<Person>, | ||||||
|     selector: Rc<Selector<Person>>, |     selector: Rc<Selector<Person>>, | ||||||
|     selected_cb: RefCell<Option<Box<dyn Fn(&Person) -> ()>>>, |  | ||||||
|     navigator: RefCell<Option<Rc<Navigator>>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl PersonSelector { | impl Screen<(), Person> for PersonSelector { | ||||||
|     /// Create a new person selector.
 |     /// Create a new person selector.
 | ||||||
|     pub fn new(backend: Rc<Backend>) -> Rc<Self> { |     fn new(_: (), handle: NavigationHandle<Person>) -> Rc<Self> { | ||||||
|         // Create UI
 |         // Create UI
 | ||||||
| 
 | 
 | ||||||
|         let selector = Selector::<Person>::new(); |         let selector = Selector::<Person>::new(); | ||||||
|         selector.set_title(&gettext("Select person")); |         selector.set_title(&gettext("Select person")); | ||||||
| 
 | 
 | ||||||
|         let this = Rc::new(Self { |         let this = Rc::new(Self { | ||||||
|             backend, |             handle, | ||||||
|             selector, |             selector, | ||||||
|             selected_cb: RefCell::new(None), |  | ||||||
|             navigator: RefCell::new(None), |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Connect signals and callbacks
 |         // Connect signals and callbacks
 | ||||||
| 
 | 
 | ||||||
|         this.selector.set_back_cb(clone!(@strong this => move || { |         this.selector.set_back_cb(clone!(@weak this => move || { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             this.handle.pop(None); | ||||||
|             if let Some(navigator) = navigator { |  | ||||||
|                 navigator.pop(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.selector.set_add_cb(clone!(@strong this => move || { |         this.selector.set_add_cb(clone!(@weak this => move || { | ||||||
|             let navigator = this.navigator.borrow().clone(); |             spawn!(@clone this, async move { | ||||||
|             if let Some(navigator) = navigator { |                 if let Some(person) = push!(this.handle, PersonEditor, None).await { | ||||||
|                 let editor = PersonEditor::new(this.backend.clone(), None); |                     this.handle.pop(Some(person)); | ||||||
|                 editor |                 } | ||||||
|                     .set_saved_cb(clone!(@strong this => move |person| this.select(&person))); |             }); | ||||||
|                 navigator.push(editor); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.selector |         this.selector.set_load_online(clone!(@weak this => move || { | ||||||
|             .set_load_online(clone!(@strong this => move || { |             let clone = this.clone(); | ||||||
|                 let clone = this.clone(); |             async move { clone.handle.backend.get_persons().await } | ||||||
|                 async move { clone.backend.get_persons().await } |         })); | ||||||
|             })); |  | ||||||
| 
 | 
 | ||||||
|         this.selector |         this.selector.set_load_local(clone!(@weak this => move || { | ||||||
|             .set_load_local(clone!(@strong this => move || { |             let clone = this.clone(); | ||||||
|                 let clone = this.clone(); |             async move { clone.handle.backend.db().get_persons().await.unwrap() } | ||||||
|                 async move { clone.backend.db().get_persons().await.unwrap() } |         })); | ||||||
|             })); |  | ||||||
| 
 | 
 | ||||||
|         this.selector.set_make_widget(clone!(@strong this => move |person| { |         this.selector.set_make_widget(clone!(@weak this => move |person| { | ||||||
|             let row = libadwaita::ActionRow::new(); |             let row = libadwaita::ActionRow::new(); | ||||||
|             row.set_activatable(true); |             row.set_activatable(true); | ||||||
|             row.set_title(Some(&person.name_lf())); |             row.set_title(Some(&person.name_lf())); | ||||||
| 
 | 
 | ||||||
|             let person = person.to_owned(); |             let person = person.to_owned(); | ||||||
|             row.connect_activated(clone!(@strong this => move |_| { |             row.connect_activated(clone!(@weak this => move |_| { | ||||||
|                 this.select(&person); |                 this.handle.pop(Some(person.clone())); | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|             row.upcast() |             row.upcast() | ||||||
|  | @ -82,30 +72,10 @@ impl PersonSelector { | ||||||
| 
 | 
 | ||||||
|         this |         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 { | impl Widget for PersonSelector { | ||||||
|     fn attach_navigator(&self, navigator: Rc<Navigator>) { |  | ||||||
|         self.navigator.replace(Some(navigator)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_widget(&self) -> gtk::Widget { |     fn get_widget(&self) -> gtk::Widget { | ||||||
|         self.selector.widget.clone().upcast() |         self.selector.widget.clone().upcast() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     fn detach_navigator(&self) { |  | ||||||
|         self.navigator.replace(None); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ use crate::import::SourceSelector; | ||||||
| use crate::preferences::Preferences; | use crate::preferences::Preferences; | ||||||
| use crate::screens::*; | use crate::screens::*; | ||||||
| use crate::widgets::*; | use crate::widgets::*; | ||||||
|  | use crate::navigator::NavigatorWindow; | ||||||
| use futures::prelude::*; | use futures::prelude::*; | ||||||
| use gettextrs::gettext; | use gettextrs::gettext; | ||||||
| use gio::prelude::*; | use gio::prelude::*; | ||||||
|  | @ -95,18 +96,10 @@ 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(), None, Vec::new());
 |             spawn!(@clone result, async move { | ||||||
| 
 |                 let window = NavigatorWindow::new(result.backend.clone()); | ||||||
|             // editor.set_callback(clone!(@strong result => move || {
 |                 replace!(window.navigator, SourceSelector).await; | ||||||
|             //     result.reload();
 |             }); | ||||||
|             // }));
 |  | ||||||
| 
 |  | ||||||
|             // let window = NavigatorWindow::new(editor);
 |  | ||||||
|             // window.show();
 |  | ||||||
| 
 |  | ||||||
|             let dialog = SourceSelector::new(result.backend.clone()); |  | ||||||
|             let window = NavigatorWindow::new(dialog); |  | ||||||
|             window.show(); |  | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         result |         result | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Elias Projahn
						Elias Projahn