Port most screens to the new navigator

This commit is contained in:
Elias Projahn 2021-02-03 14:09:17 +01:00
parent 7eff62b5a4
commit 18e33c3d0d
23 changed files with 499 additions and 1063 deletions

View file

@ -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.
pub fn set_saved_cb<F: Fn(Ensemble) -> () + 'static>(&self, cb: F) {
self.saved_cb.replace(Some(Box::new(cb)));
} }
impl EnsembleEditor {
/// 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);
}
} }

View file

@ -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.
pub fn set_saved_cb<F: Fn(Instrument) -> () + 'static>(&self, cb: F) {
self.saved_cb.replace(Some(Box::new(cb)));
} }
impl InstrumentEditor {
/// 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);
}
} }

View file

@ -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.
pub fn set_selected_cb<F: Fn(Performance) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
} }
impl PerformanceEditor {
/// 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);
}
} }

View file

@ -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.
pub fn set_saved_cb<F: Fn(Person) -> () + 'static>(&self, cb: F) {
self.saved_cb.replace(Some(Box::new(cb)));
} }
impl PersonEditor {
/// 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);
}
} }

View file

@ -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,71 +69,42 @@ 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(_) => { Err(_) => {
clone.info_bar.set_revealed(true); this.info_bar.set_revealed(true);
clone.widget.set_visible_child_name("content"); this.widget.set_visible_child_name("content");
} }
} }
}); });
})); }));
work_button.connect_clicked(clone!(@strong this => move |_| { work_button.connect_clicked(clone!(@weak this => move |_| {
let navigator = this.navigator.borrow().clone(); spawn!(@clone this, async move {
if let Some(navigator) = navigator { // TODO: We need the pushed screen to return a work here.
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); this.performance_list.set_make_widget_cb(clone!(@weak this => move |index| {
}));
navigator.push(person_selector);
}
}));
this.performance_list.set_make_widget_cb(clone!(@strong 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);
@ -147,17 +117,10 @@ impl RecordingEditor {
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 editor = PerformanceEditor::new(
this.backend.clone(),
Some(performance.clone()),
);
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[index] = performance; performances[index] = performance;
@ -165,12 +128,8 @@ impl RecordingEditor {
}; };
this.performance_list.update(length); 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.
pub fn set_selected_cb<F: Fn(Recording) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
} }
impl RecordingEditor {
/// 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);
}
} }

View file

@ -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(_) => { Err(_) => {
clone.info_bar.set_revealed(true); this.info_bar.set_revealed(true);
clone.widget.set_visible_child_name("content"); this.widget.set_visible_child_name("content");
} }
} }
}); });
})); }));
composer_button.connect_clicked(clone!(@strong this => move |_| { composer_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(person) = push!(this.handle, PersonSelector).await {
let selector = PersonSelector::new(this.backend.clone()); this.show_composer(&person);
this.composer.replace(Some(person.to_owned()));
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| { 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.
pub fn set_saved_cb<F: Fn(Work) -> () + 'static>(&self, cb: F) {
self.saved_cb.replace(Some(Box::new(cb)));
} }
impl WorkEditor {
/// 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);
}
} }

View file

@ -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.
pub fn set_ready_cb<F: Fn(WorkPart) -> () + 'static>(&self, cb: F) {
self.ready_cb.replace(Some(Box::new(cb)));
} }
impl WorkPartEditor {
/// 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);
}
} }

View file

@ -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 {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
} }
impl Widget for WorkSectionEditor {
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);
}
} }

View file

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

View file

@ -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();
if let Some(navigator) = navigator {
let source = Rc::new(Box::new(folder) as Box<dyn Source>); let source = Rc::new(Box::new(folder) as Box<dyn Source>);
let editor = MediumEditor::new(clone.backend.clone(), source); push!(this.handle, MediumEditor, source).await;
navigator.push(editor); this.handle.pop(Some(()));
}
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();
if let Some(navigator) = navigator {
let source = Rc::new(Box::new(disc) as Box<dyn Source>); let source = Rc::new(Box::new(disc) as Box<dyn Source>);
let editor = MediumEditor::new(clone.backend.clone(), source); push!(this.handle, MediumEditor, source).await;
navigator.push(editor); this.handle.pop(Some(()));
}
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);
}
} }

View file

@ -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();
if let Some(navigator) = navigator {
navigator.pop();
}
if let Some(cb) = &*this.selected_cb.borrow() {
let selection = this.selection.borrow().clone(); let selection = this.selection.borrow().clone();
cb(selection); this.handle.pop(Some(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 {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
} }
impl Widget for TrackEditor {
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);
}
} }

View file

@ -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();
if let Some(navigator) = navigator {
navigator.pop();
}
if let Some(cb) = &*this.selected_cb.borrow() {
let selection = this.selection.borrow().clone(); let selection = this.selection.borrow().clone();
cb(selection); this.handle.pop(Some(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 {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
} }
impl Widget for TrackSelector {
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);
}
} }

View file

@ -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); edit_tracks_button.connect_clicked(clone!(@weak this => move |_| {
})); spawn!(@clone this, async move {
if let Some(selection) = push!(this.handle, TrackSelector, Rc::clone(&this.source)).await {
navigator.clone().push(work_selector);
}));
navigator.clone().push(person_selector);
}
}));
edit_tracks_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
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,16 +146,12 @@ 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 {
if let (Some(recording), Some(navigator)) = (recording, navigator) {
let track = &this.tracks.borrow()[index]; let track = &this.tracks.borrow()[index];
if let Some(selection) = push!(this.handle, TrackEditor, (recording, track.work_parts.clone())).await {
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 tracks = this.tracks.borrow_mut();
let mut track = &mut tracks[index]; let mut track = &mut tracks[index];
@ -207,9 +159,8 @@ impl TrackSetEditor {
}; };
this.update_tracks(); 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.
pub fn set_done_cb<F: Fn(TrackSetData) + 'static>(&self, cb: F) {
self.done_cb.replace(Some(Box::new(cb)));
} }
impl TrackSetEditor {
/// 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);
}
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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!(@weak this => move || {
spawn!(@clone this, async move {
if let Some(ensemble) = push!(this.handle, EnsembleEditor, None).await {
this.handle.pop(Some(ensemble));
} }
});
})); }));
this.selector.set_add_cb(clone!(@strong this => move || { this.selector.set_load_online(clone!(@weak this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let editor = EnsembleEditor::new(this.backend.clone(), None);
editor
.set_saved_cb(clone!(@strong this => move |ensemble| this.select(&ensemble)));
navigator.push(editor);
}
}));
this.selector
.set_load_online(clone!(@strong this => move || {
let clone = this.clone(); let clone = this.clone();
async move { clone.backend.get_ensembles().await } async move { clone.handle.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.backend.db().get_ensembles().await.unwrap() } async move { clone.handle.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 {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
} }
impl Widget for EnsembleSelector {
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);
}
} }

View file

@ -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!(@weak this => move || {
spawn!(@clone this, async move {
if let Some(instrument) = push!(this.handle, InstrumentEditor, None).await {
this.handle.pop(Some(instrument));
} }
});
})); }));
this.selector.set_add_cb(clone!(@strong this => move || { this.selector.set_load_online(clone!(@weak this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let editor = InstrumentEditor::new(this.backend.clone(), None);
editor
.set_saved_cb(clone!(@strong this => move |instrument| this.select(&instrument)));
navigator.push(editor);
}
}));
this.selector
.set_load_online(clone!(@strong this => move || {
let clone = this.clone(); let clone = this.clone();
async move { clone.backend.get_instruments().await } async move { clone.handle.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.backend.db().get_instruments().await.unwrap() } async move { clone.handle.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 {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
} }
impl Widget for InstrumentSelector {
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);
}
} }

View file

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

View file

@ -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!(@weak this => move || {
spawn!(@clone this, async move {
if let Some(person) = push!(this.handle, PersonEditor, None).await {
this.handle.pop(Some(person));
} }
});
})); }));
this.selector.set_add_cb(clone!(@strong this => move || { this.selector.set_load_online(clone!(@weak this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let editor = PersonEditor::new(this.backend.clone(), None);
editor
.set_saved_cb(clone!(@strong this => move |person| this.select(&person)));
navigator.push(editor);
}
}));
this.selector
.set_load_online(clone!(@strong this => move || {
let clone = this.clone(); let clone = this.clone();
async move { clone.backend.get_persons().await } async move { clone.handle.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.backend.db().get_persons().await.unwrap() } async move { clone.handle.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 {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
} }
impl Widget for PersonSelector {
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);
}
} }

View file

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