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::database::generate_id;
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 gettextrs::gettext;
use glib::clone;
@ -11,7 +12,7 @@ use std::rc::Rc;
/// A dialog for creating or editing a ensemble.
pub struct EnsembleEditor {
backend: Rc<Backend>,
handle: NavigationHandle<Ensemble>,
/// The ID of the ensemble that is edited or a newly generated one.
id: String,
@ -19,15 +20,13 @@ pub struct EnsembleEditor {
editor: Editor,
name: EntryRow,
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.
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();
editor.set_title("Ensemble");
editor.set_title("Ensemble/Role");
let list = gtk::ListBoxBuilder::new()
.selection_mode(gtk::SelectionMode::None)
@ -51,55 +50,41 @@ impl EnsembleEditor {
};
let this = Rc::new(Self {
backend,
handle,
id,
editor,
name,
upload,
saved_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
this.editor.set_back_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
this.editor.set_back_cb(clone!(@weak this => move || {
this.handle.pop(None);
}));
this.editor.set_save_cb(clone!(@strong this => move || {
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
clone.editor.loading();
match clone.clone().save().await {
Ok(_) => {
let navigator = clone.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
this.editor.set_save_cb(clone!(@weak this => move || {
spawn!(@clone this, async move {
this.editor.loading();
match this.save().await {
Ok(ensemble) => {
this.handle.pop(Some(ensemble));
}
Err(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
}
}
/// 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.
async fn save(self: Rc<Self>) -> Result<()> {
async fn save(&self) -> Result<Ensemble> {
let name = self.name.get_text();
let ensemble = Ensemble {
@ -108,31 +93,19 @@ impl EnsembleEditor {
};
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.backend.library_changed();
self.handle.backend.db().update_ensemble(ensemble.clone()).await?;
self.handle.backend.library_changed();
if let Some(cb) = &*self.saved_cb.borrow() {
cb(ensemble.clone());
}
Ok(())
Ok(ensemble)
}
}
impl NavigatorScreen for EnsembleEditor {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
}
impl Widget for EnsembleEditor {
fn get_widget(&self) -> gtk::Widget {
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::database::generate_id;
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 gettextrs::gettext;
use glib::clone;
@ -11,7 +12,7 @@ use std::rc::Rc;
/// A dialog for creating or editing a instrument.
pub struct InstrumentEditor {
backend: Rc<Backend>,
handle: NavigationHandle<Instrument>,
/// The ID of the instrument that is edited or a newly generated one.
id: String,
@ -19,13 +20,11 @@ pub struct InstrumentEditor {
editor: Editor,
name: EntryRow,
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.
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();
editor.set_title("Instrument/Role");
@ -51,55 +50,41 @@ impl InstrumentEditor {
};
let this = Rc::new(Self {
backend,
handle,
id,
editor,
name,
upload,
saved_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
this.editor.set_back_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
this.editor.set_back_cb(clone!(@weak this => move || {
this.handle.pop(None);
}));
this.editor.set_save_cb(clone!(@strong this => move || {
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
clone.editor.loading();
match clone.clone().save().await {
Ok(_) => {
let navigator = clone.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
this.editor.set_save_cb(clone!(@weak this => move || {
spawn!(@clone this, async move {
this.editor.loading();
match this.save().await {
Ok(instrument) => {
this.handle.pop(Some(instrument));
}
Err(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
}
}
/// 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.
async fn save(self: Rc<Self>) -> Result<()> {
async fn save(&self) -> Result<Instrument> {
let name = self.name.get_text();
let instrument = Instrument {
@ -108,31 +93,19 @@ impl InstrumentEditor {
};
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.backend.library_changed();
self.handle.backend.db().update_instrument(instrument.clone()).await?;
self.handle.backend.library_changed();
if let Some(cb) = &*self.saved_cb.borrow() {
cb(instrument.clone());
}
Ok(())
Ok(instrument)
}
}
impl NavigatorScreen for InstrumentEditor {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
}
impl Widget for InstrumentEditor {
fn get_widget(&self) -> gtk::Widget {
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::database::*;
use crate::navigator::{NavigationHandle, Screen};
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 glib::clone;
use gtk::prelude::*;
@ -11,7 +12,7 @@ use std::rc::Rc;
/// A dialog for editing a performance within a recording.
pub struct PerformanceEditor {
backend: Rc<Backend>,
handle: NavigationHandle<Performance>,
editor: Editor,
person_row: ButtonRow,
ensemble_row: ButtonRow,
@ -20,13 +21,11 @@ pub struct PerformanceEditor {
person: RefCell<Option<Person>>,
ensemble: RefCell<Option<Ensemble>>,
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.
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();
editor.set_title("Performance");
editor.set_may_save(false);
@ -68,7 +67,7 @@ impl PerformanceEditor {
editor.add_content(&role_section);
let this = Rc::new(PerformanceEditor {
backend,
handle,
editor,
person_row,
ensemble_row,
@ -77,79 +76,51 @@ impl PerformanceEditor {
person: RefCell::new(None),
ensemble: 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 || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
this.editor.set_back_cb(clone!(@weak this => move || {
this.handle.pop(None);
}));
this.editor.set_save_cb(clone!(@weak this => move || {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(Performance {
person: this.person.borrow().clone(),
ensemble: this.ensemble.borrow().clone(),
role: this.role.borrow().clone(),
});
}
let performance = Performance {
person: this.person.borrow().clone(),
ensemble: this.ensemble.borrow().clone(),
role: this.role.borrow().clone(),
};
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
this.handle.pop(Some(performance));
}));
this.person_row.set_cb(clone!(@weak this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let selector = PersonSelector::new(this.backend.clone());
selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| {
spawn!(@clone this, async move {
if let Some(person) = push!(this.handle, PersonSelector).await {
this.show_person(Some(&person));
this.person.replace(Some(person.clone()));
this.show_ensemble(None);
this.ensemble.replace(None);
navigator.clone().pop();
}));
navigator.push(selector);
}
}
});
}));
this.ensemble_row.set_cb(clone!(@weak this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let selector = EnsembleSelector::new(this.backend.clone());
selector.set_selected_cb(clone!(@strong this, @strong navigator => move |ensemble| {
spawn!(@clone this, async move {
if let Some(ensemble) = push!(this.handle, EnsembleSelector).await {
this.show_person(None);
this.person.replace(None);
this.show_ensemble(Some(&ensemble));
this.ensemble.replace(Some(ensemble.clone()));
navigator.clone().pop();
}));
navigator.push(selector);
}
this.ensemble.replace(None);
}
});
}));
this.role_row.set_cb(clone!(@weak this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let selector = InstrumentSelector::new(this.backend.clone());
selector.set_selected_cb(clone!(@strong this, @strong navigator => move |role| {
spawn!(@clone this, async move {
if let Some(role) = push!(this.handle, InstrumentSelector).await {
this.show_role(Some(&role));
this.role.replace(Some(role.clone()));
navigator.clone().pop();
}));
navigator.push(selector);
}
this.role.replace(Some(role));
}
});
}));
this.reset_role_button.connect_clicked(clone!(@weak this => move |_| {
@ -176,12 +147,9 @@ impl PerformanceEditor {
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.
fn show_person(&self, person: Option<&Person>) {
if let Some(person) = person {
@ -214,16 +182,8 @@ impl PerformanceEditor {
}
}
impl NavigatorScreen for PerformanceEditor {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
}
impl Widget for PerformanceEditor {
fn get_widget(&self) -> gtk::Widget {
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::database::generate_id;
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 gettextrs::gettext;
use glib::clone;
@ -11,7 +12,7 @@ use std::rc::Rc;
/// A dialog for creating or editing a person.
pub struct PersonEditor {
backend: Rc<Backend>,
handle: NavigationHandle<Person>,
/// The ID of the person that is edited or a newly generated one.
id: String,
@ -20,13 +21,11 @@ pub struct PersonEditor {
first_name: EntryRow,
last_name: EntryRow,
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.
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();
editor.set_title("Person");
@ -57,56 +56,42 @@ impl PersonEditor {
};
let this = Rc::new(Self {
backend,
handle,
id,
editor,
first_name,
last_name,
upload,
saved_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
this.editor.set_back_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
this.editor.set_back_cb(clone!(@weak this => move || {
this.handle.pop(None);
}));
this.editor.set_save_cb(clone!(@strong this => move || {
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
clone.editor.loading();
match clone.clone().save().await {
Ok(_) => {
let navigator = clone.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
spawn!(@clone this, async move {
this.editor.loading();
match this.save().await {
Ok(person) => {
this.handle.pop(Some(person));
}
Err(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
}
}
/// 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.
async fn save(self: Rc<Self>) -> Result<()> {
async fn save(self: &Rc<Self>) -> Result<Person> {
let first_name = self.first_name.get_text();
let last_name = self.last_name.get_text();
@ -117,31 +102,19 @@ impl PersonEditor {
};
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.backend.library_changed();
self.handle.backend.db().update_person(person.clone()).await?;
self.handle.backend.library_changed();
if let Some(cb) = &*self.saved_cb.borrow() {
cb(person.clone());
}
Ok(())
Ok(person)
}
}
impl NavigatorScreen for PersonEditor {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
}
impl Widget for PersonEditor {
fn get_widget(&self) -> gtk::Widget {
self.editor.widget.clone().upcast()
}
fn detach_navigator(&self) {
self.navigator.replace(None);
}
}

View file

@ -1,8 +1,9 @@
use super::performance::PerformanceEditor;
use crate::backend::Backend;
use crate::database::*;
use crate::selectors::{PersonSelector, WorkSelector};
use crate::widgets::{List, Navigator, NavigatorScreen};
use crate::selectors::PersonSelector;
use crate::widgets::{List, Widget};
use crate::navigator::{NavigationHandle, Screen};
use anyhow::Result;
use gettextrs::gettext;
use glib::clone;
@ -14,8 +15,8 @@ use std::rc::Rc;
/// A widget for creating or editing a recording.
pub struct RecordingEditor {
pub widget: gtk::Stack,
backend: Rc<Backend>,
handle: NavigationHandle<Recording>,
widget: gtk::Stack,
save_button: gtk::Button,
info_bar: gtk::InfoBar,
work_row: libadwaita::ActionRow,
@ -25,13 +26,11 @@ pub struct RecordingEditor {
id: String,
work: RefCell<Option<Work>>,
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.
pub fn new(backend: Rc<Backend>, recording: Option<Recording>) -> Rc<Self> {
fn new(recording: Option<Recording>, handle: NavigationHandle<Recording>) -> Rc<Self> {
// Create 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 {
handle,
widget,
backend,
save_button,
info_bar,
work_row,
@ -70,107 +69,67 @@ impl RecordingEditor {
id,
work: RefCell::new(work),
performances: RefCell::new(performances),
selected_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
back_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.clone().pop();
}
back_button.connect_clicked(clone!(@weak this => move |_| {
this.handle.pop(None);
}));
this.save_button
.connect_clicked(clone!(@strong this => move |_| {
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
clone.widget.set_visible_child_name("loading");
match clone.clone().save().await {
Ok(_) => {
let navigator = clone.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.clone().pop();
}
}
Err(_) => {
clone.info_bar.set_revealed(true);
clone.widget.set_visible_child_name("content");
}
this.save_button.connect_clicked(clone!(@weak this => move |_| {
spawn!(@clone this, async move {
this.widget.set_visible_child_name("loading");
match this.save().await {
Ok(recording) => {
this.handle.pop(Some(recording));
}
});
}));
work_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let person_selector = PersonSelector::new(this.backend.clone());
person_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| {
let work_selector = WorkSelector::new(this.backend.clone(), person.clone());
work_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |work| {
this.work_selected(&work);
this.work.replace(Some(work.clone()));
navigator.clone().pop();
navigator.clone().pop();
}));
navigator.clone().push(work_selector);
}));
navigator.push(person_selector);
}
Err(_) => {
this.info_bar.set_revealed(true);
this.widget.set_visible_child_name("content");
}
}
});
}));
this.performance_list.set_make_widget_cb(clone!(@strong this => move |index| {
work_button.connect_clicked(clone!(@weak this => move |_| {
spawn!(@clone this, async move {
// TODO: We need the pushed screen to return a work here.
});
}));
this.performance_list.set_make_widget_cb(clone!(@weak this => move |index| {
let performance = &this.performances.borrow()[index];
let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic"));
delete_button.set_valign(gtk::Align::Center);
delete_button.connect_clicked(clone!(@strong this => move |_| {
let length = {
let mut performances = this.performances.borrow_mut();
performances.remove(index);
performances.len()
};
delete_button.connect_clicked(clone!(@weak this => move |_| {
let length = {
let mut performances = this.performances.borrow_mut();
performances.remove(index);
performances.len()
};
this.performance_list.update(length);
this.performance_list.update(length);
}));
let edit_button = gtk::Button::from_icon_name(Some("document-edit-symbolic"));
edit_button.set_valign(gtk::Align::Center);
edit_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let performance = &this.performances.borrow()[index];
edit_button.connect_clicked(clone!(@weak this => move |_| {
spawn!(@clone this, async move {
let performance = &this.performances.borrow()[index];
if let Some(performance) = push!(this.handle, PerformanceEditor, Some(performance.to_owned())).await {
let length = {
let mut performances = this.performances.borrow_mut();
performances[index] = performance;
performances.len()
};
let editor = PerformanceEditor::new(
this.backend.clone(),
Some(performance.clone()),
);
editor.set_selected_cb(clone!(@strong this, @strong navigator => move |performance| {
let length = {
let mut performances = this.performances.borrow_mut();
performances[index] = performance;
performances.len()
};
this.performance_list.update(length);
navigator.clone().pop();
}));
navigator.push(editor);
this.performance_list.update(length);
}
});
}));
let row = libadwaita::ActionRow::new();
@ -184,11 +143,8 @@ impl RecordingEditor {
}));
add_performer_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let editor = PerformanceEditor::new(this.backend.clone(), None);
editor.set_selected_cb(clone!(@strong this, @strong navigator => move |performance| {
spawn!(@clone this, async move {
if let Some(performance) = push!(this.handle, PerformanceEditor, None).await {
let length = {
let mut performances = this.performances.borrow_mut();
performances.push(performance);
@ -196,12 +152,8 @@ impl RecordingEditor {
};
this.performance_list.update(length);
navigator.clone().pop();
}));
navigator.push(editor);
}
}
});
}));
// Initialize
@ -215,12 +167,9 @@ impl RecordingEditor {
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.
fn work_selected(&self, work: &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.
async fn save(self: Rc<Self>) -> Result<()> {
async fn save(self: &Rc<Self>) -> Result<Recording> {
let recording = Recording {
id: self.id.clone(),
work: self
@ -243,40 +192,23 @@ impl RecordingEditor {
let upload = self.upload_switch.get_active();
if upload {
self.backend.post_recording(&recording).await?;
self.handle.backend.post_recording(&recording).await?;
}
self.backend
self.handle.backend
.db()
.update_recording(recording.clone().into())
.await
.unwrap();
self.backend.library_changed();
self.handle.backend.library_changed();
if let Some(cb) = &*self.selected_cb.borrow() {
cb(recording.clone());
}
let navigator = self.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.clone().pop();
}
Ok(())
Ok(recording)
}
}
impl NavigatorScreen for RecordingEditor {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
}
impl Widget for RecordingEditor {
fn get_widget(&self) -> gtk::Widget {
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_section::WorkSectionEditor;
use crate::backend::Backend;
use crate::database::*;
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 gettextrs::gettext;
use glib::clone;
@ -32,8 +32,8 @@ impl PartOrSection {
/// A widget for editing and creating works.
pub struct WorkEditor {
handle: NavigationHandle<Work>,
widget: gtk::Stack,
backend: Rc<Backend>,
save_button: gtk::Button,
title_entry: gtk::Entry,
info_bar: gtk::InfoBar,
@ -45,13 +45,11 @@ pub struct WorkEditor {
composer: RefCell<Option<Person>>,
instruments: RefCell<Vec<Instrument>>,
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.
pub fn new(backend: Rc<Backend>, work: Option<Work>) -> Rc<Self> {
fn new(work: Option<Work>, handle: NavigationHandle<Work>) -> Rc<Self> {
// Create 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 {
handle,
widget,
backend,
save_button,
id,
info_bar,
@ -113,57 +111,39 @@ impl WorkEditor {
composer: RefCell::new(composer),
instruments: RefCell::new(instruments),
structure: RefCell::new(structure),
saved_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
back_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
back_button.connect_clicked(clone!(@weak this => move |_| {
this.handle.pop(None);
}));
this.save_button
.connect_clicked(clone!(@strong this => move |_| {
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
clone.widget.set_visible_child_name("loading");
match clone.clone().save().await {
Ok(_) => {
let navigator = clone.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
}
Err(_) => {
clone.info_bar.set_revealed(true);
clone.widget.set_visible_child_name("content");
}
this.save_button.connect_clicked(clone!(@weak this => move |_| {
spawn!(@clone this, async move {
this.widget.set_visible_child_name("loading");
match this.save().await {
Ok(work) => {
this.handle.pop(Some(work));
}
});
}));
composer_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let selector = PersonSelector::new(this.backend.clone());
selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| {
this.show_composer(person);
this.composer.replace(Some(person.clone()));
navigator.clone().pop();
}));
navigator.push(selector);
}
Err(_) => {
this.info_bar.set_revealed(true);
this.widget.set_visible_child_name("content");
}
}
});
}));
this.instrument_list.set_make_widget_cb(clone!(@strong this => move |index| {
composer_button.connect_clicked(clone!(@weak this => move |_| {
spawn!(@clone this, async move {
if let Some(person) = push!(this.handle, PersonSelector).await {
this.show_composer(&person);
this.composer.replace(Some(person.to_owned()));
}
});
}));
this.instrument_list.set_make_widget_cb(clone!(@weak this => move |index| {
let instrument = &this.instruments.borrow()[index];
let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic"));
@ -186,12 +166,9 @@ impl WorkEditor {
row.upcast()
}));
add_instrument_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let selector = InstrumentSelector::new(this.backend.clone());
selector.set_selected_cb(clone!(@strong this, @strong navigator => move |instrument| {
add_instrument_button.connect_clicked(clone!(@weak this => move |_| {
spawn!(@clone this, async move {
if let Some(instrument) = push!(this.handle, InstrumentSelector).await {
let length = {
let mut instruments = this.instruments.borrow_mut();
instruments.push(instrument.clone());
@ -199,20 +176,17 @@ impl WorkEditor {
};
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 delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic"));
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 mut structure = this.structure.borrow_mut();
structure.remove(index);
@ -225,14 +199,11 @@ impl WorkEditor {
let edit_button = gtk::Button::from_icon_name(Some("document-edit-symbolic"));
edit_button.set_valign(gtk::Align::Center);
edit_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
edit_button.connect_clicked(clone!(@weak this => move |_| {
spawn!(@clone this, async move {
match this.structure.borrow()[index].clone() {
PartOrSection::Part(part) => {
let editor = WorkPartEditor::new(this.backend.clone(), Some(part));
editor.set_ready_cb(clone!(@strong this, @strong navigator => move |part| {
if let Some(part) = push!(this.handle, WorkPartEditor, Some(part)).await {
let length = {
let mut structure = this.structure.borrow_mut();
structure[index] = PartOrSection::Part(part);
@ -240,15 +211,10 @@ impl WorkEditor {
};
this.part_list.update(length);
navigator.clone().pop();
}));
navigator.push(editor);
}
}
PartOrSection::Section(section) => {
let editor = WorkSectionEditor::new(Some(section));
editor.set_ready_cb(clone!(@strong this, @strong navigator => move |section| {
if let Some(section) = push!(this.handle, WorkSectionEditor, Some(section)).await {
let length = {
let mut structure = this.structure.borrow_mut();
structure[index] = PartOrSection::Section(section);
@ -256,13 +222,10 @@ impl WorkEditor {
};
this.part_list.update(length);
navigator.clone().pop();
}));
navigator.push(editor);
}
}
}
}
});
}));
let row = libadwaita::ActionRow::new();
@ -280,7 +243,7 @@ impl WorkEditor {
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 mut structure = this.structure.borrow_mut();
structure.swap(old_index, new_index);
@ -290,12 +253,9 @@ impl WorkEditor {
this.part_list.update(length);
}));
add_part_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let editor = WorkPartEditor::new(this.backend.clone(), None);
editor.set_ready_cb(clone!(@strong this, @strong navigator => move |part| {
add_part_button.connect_clicked(clone!(@weak this => move |_| {
spawn!(@clone this, async move {
if let Some(part) = push!(this.handle, WorkPartEditor, None).await {
let length = {
let mut structure = this.structure.borrow_mut();
structure.push(PartOrSection::Part(part));
@ -303,19 +263,13 @@ impl WorkEditor {
};
this.part_list.update(length);
navigator.clone().pop();
}));
navigator.push(editor);
}
}
});
}));
add_section_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let editor = WorkSectionEditor::new(None);
editor.set_ready_cb(clone!(@strong this, @strong navigator => move |section| {
spawn!(@clone this, async move {
if let Some(section) = push!(this.handle, WorkSectionEditor, None).await {
let length = {
let mut structure = this.structure.borrow_mut();
structure.push(PartOrSection::Section(section));
@ -323,11 +277,8 @@ impl WorkEditor {
};
this.part_list.update(length);
navigator.clone().pop();
}));
navigator.push(editor);
}
}
});
}));
// Initialization
@ -341,12 +292,9 @@ impl WorkEditor {
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.
fn show_composer(&self, person: &Person) {
self.composer_row.set_title(Some(&gettext("Composer")));
@ -355,7 +303,7 @@ impl WorkEditor {
}
/// 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 parts = Vec::new();
let mut sections = Vec::new();
@ -387,35 +335,23 @@ impl WorkEditor {
let upload = self.upload_switch.get_active();
if upload {
self.backend.post_work(&work).await?;
self.handle.backend.post_work(&work).await?;
}
self.backend
self.handle.backend
.db()
.update_work(work.clone().into())
.await
.unwrap();
self.backend.library_changed();
self.handle.backend.library_changed();
if let Some(cb) = &*self.saved_cb.borrow() {
cb(work.clone());
}
Ok(())
Ok(work)
}
}
impl NavigatorScreen for WorkEditor {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
}
impl Widget for WorkEditor {
fn get_widget(&self) -> gtk::Widget {
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::selectors::PersonSelector;
use crate::widgets::{Navigator, NavigatorScreen};
use crate::navigator::{NavigationHandle, Screen};
use crate::widgets::Widget;
use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
@ -12,19 +12,17 @@ use std::rc::Rc;
/// A dialog for creating or editing a work part.
pub struct WorkPartEditor {
backend: Rc<Backend>,
handle: NavigationHandle<WorkPart>,
widget: gtk::Box,
title_entry: gtk::Entry,
composer_row: libadwaita::ActionRow,
reset_composer_button: gtk::Button,
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.
pub fn new(backend: Rc<Backend>, part: Option<WorkPart>) -> Rc<Self> {
fn new(part: Option<WorkPart>, handle: NavigationHandle<WorkPart>) -> Rc<Self> {
// Create 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 {
backend,
handle,
widget,
title_entry,
composer_row,
reset_composer_button,
composer: RefCell::new(composer),
ready_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
back_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
back_button.connect_clicked(clone!(@weak this => move |_| {
this.handle.pop(None);
}));
save_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(cb) = &*this.ready_cb.borrow() {
cb(WorkPart {
title: this.title_entry.get_text().unwrap().to_string(),
composer: this.composer.borrow().clone(),
});
}
save_button.connect_clicked(clone!(@weak this => move |_| {
let part = WorkPart {
title: this.title_entry.get_text().unwrap().to_string(),
composer: this.composer.borrow().clone(),
};
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
this.handle.pop(Some(part));
}));
composer_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let selector = PersonSelector::new(this.backend.clone());
selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| {
this.show_composer(Some(person));
this.composer.replace(Some(person.clone()));
navigator.clone().pop();
}));
navigator.push(selector);
}
spawn!(@clone this, async move {
if let Some(person) = push!(this.handle, PersonSelector).await {
this.show_composer(Some(&person));
this.composer.replace(Some(person.to_owned()));
}
});
}));
this.reset_composer_button
@ -109,12 +90,9 @@ impl WorkPartEditor {
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.
fn show_composer(&self, person: Option<&Person>) {
if let Some(person) = person {
@ -129,16 +107,8 @@ impl WorkPartEditor {
}
}
impl NavigatorScreen for WorkPartEditor {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
}
impl Widget for WorkPartEditor {
fn get_widget(&self) -> gtk::Widget {
self.widget.clone().upcast()
}
fn detach_navigator(&self) {
self.navigator.replace(None);
}
}

View file

@ -1,5 +1,6 @@
use crate::database::*;
use crate::widgets::{Navigator, NavigatorScreen};
use crate::navigator::{NavigationHandle, Screen};
use crate::widgets::Widget;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
@ -8,15 +9,14 @@ use std::rc::Rc;
/// A dialog for creating or editing a work section.
pub struct WorkSectionEditor {
handle: NavigationHandle<WorkSection>,
widget: gtk::Box,
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.
pub fn new(section: Option<WorkSection>) -> Rc<Self> {
fn new(section: Option<WorkSection>, handle: NavigationHandle<WorkSection>) -> Rc<Self> {
// Create 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 {
handle,
widget,
title_entry,
ready_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
back_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
back_button.connect_clicked(clone!(@weak this => move |_| {
this.handle.pop(None);
}));
save_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(cb) = &*this.ready_cb.borrow() {
cb(WorkSection {
before_index: 0,
title: this.title_entry.get_text().unwrap().to_string(),
});
}
save_button.connect_clicked(clone!(@weak this => move |_| {
let section = WorkSection {
before_index: 0,
title: this.title_entry.get_text().unwrap().to_string(),
};
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
this.handle.pop(Some(section));
}));
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 {
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 crate::database::{generate_id, Medium, Track, TrackSet};
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 glib::clone;
use glib::prelude::*;
@ -14,7 +15,7 @@ use std::rc::Rc;
/// A dialog for editing metadata while importing music into the music library.
pub struct MediumEditor {
backend: Rc<Backend>,
handle: NavigationHandle<()>,
source: Rc<Box<dyn Source>>,
widget: gtk::Stack,
done_button: gtk::Button,
@ -24,12 +25,11 @@ pub struct MediumEditor {
publish_switch: gtk::Switch,
track_set_list: Rc<List>,
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.
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
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));
let this = Rc::new(Self {
backend,
handle,
source,
widget,
done_button,
@ -58,40 +58,30 @@ impl MediumEditor {
publish_switch,
track_set_list: list,
track_sets: RefCell::new(Vec::new()),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
back_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
back_button.connect_clicked(clone!(@weak this => move |_| {
this.handle.pop(None);
}));
this.done_button.connect_clicked(clone!(@strong this => move |_| {
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
clone.widget.set_visible_child_name("loading");
match clone.clone().save().await {
this.done_button.connect_clicked(clone!(@weak this => move |_| {
this.widget.set_visible_child_name("loading");
spawn!(@clone this, async move {
match this.save().await {
Ok(_) => (),
Err(err) => {
// TODO: Display errors.
println!("{:?}", err);
// clone.info_bar.set_revealed(true);
}
}
});
}));
add_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let editor = TrackSetEditor::new(this.backend.clone(), Rc::clone(&this.source));
editor.set_done_cb(clone!(@strong this => move |track_set| {
add_button.connect_clicked(clone!(@weak this => move |_| {
spawn!(@clone this, async move {
if let Some(track_set) = push!(this.handle, TrackSetEditor, Rc::clone(&this.source)).await {
let length = {
let mut track_sets = this.track_sets.borrow_mut();
track_sets.push(track_set);
@ -99,13 +89,11 @@ impl MediumEditor {
};
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 title = track_set.recording.work.get_title();
@ -124,39 +112,38 @@ impl MediumEditor {
row.add_suffix(&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()
}));
// Copy the source in the background.
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
match clone.source.copy().await {
spawn!(@clone this, async move {
match this.source.copy().await {
Err(error) => {
// TODO: Present error.
println!("Failed to copy source: {}", error);
},
Ok(_) => {
clone.done_stack.set_visible_child(&clone.done);
clone.done_button.set_sensitive(true);
this.done_stack.set_visible_child(&this.done);
this.done_button.set_sensitive(true);
}
}
});
this
}
}
impl MediumEditor {
/// 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();
// 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);
std::fs::create_dir(&path)?;
@ -205,35 +192,22 @@ impl MediumEditor {
let upload = self.publish_switch.get_active();
if upload {
self.backend.post_medium(&medium).await?;
self.handle.backend.post_medium(&medium).await?;
}
self.backend
self.handle.backend
.db()
.update_medium(medium.clone())
.await?;
self.backend.library_changed();
let navigator = self.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.clone().pop();
}
self.handle.backend.library_changed();
Ok(())
}
}
impl NavigatorScreen for MediumEditor {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
}
impl Widget for MediumEditor {
fn get_widget(&self) -> gtk::Widget {
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::source::Source;
use crate::backend::Backend;
use crate::widgets::{Navigator, NavigatorScreen};
use crate::navigator::{NavigationHandle, Screen};
use crate::widgets::Widget;
use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
@ -14,16 +15,15 @@ use std::rc::Rc;
/// A dialog for starting to import music.
pub struct SourceSelector {
backend: Rc<Backend>,
handle: NavigationHandle<()>,
widget: gtk::Box,
stack: gtk::Stack,
info_bar: gtk::InfoBar,
navigator: RefCell<Option<Rc<Navigator>>>,
}
impl SourceSelector {
impl Screen<(), ()> for SourceSelector {
/// Create a new source selector.
pub fn new(backend: Rc<Backend>) -> Rc<Self> {
fn new(_: (), handle: NavigationHandle<()>) -> Rc<Self> {
// Create 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);
let this = Rc::new(Self {
backend,
handle,
widget,
stack,
info_bar,
navigator: RefCell::new(None),
});
// Connect signals and callbacks
back_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
back_button.connect_clicked(clone!(@weak this => move |_| {
this.handle.pop(None);
}));
folder_button.connect_clicked(clone!(@strong this => move |_| {
let window = this.navigator.borrow().clone().unwrap().window.clone();
folder_button.connect_clicked(clone!(@weak this => move |_| {
let dialog = gtk::FileChooserDialog::new(
Some(&gettext("Select folder")),
Some(&window),
Some(&this.handle.window),
gtk::FileChooserAction::SelectFolder,
&[
(&gettext("Cancel"), gtk::ResponseType::Cancel),
(&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");
dialog.hide();
if let gtk::ResponseType::Accept = response {
if let Some(file) = dialog.get_file() {
if let Some(path) = file.get_path() {
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
spawn!(@clone this, async move {
let folder = FolderSource::new(PathBuf::from(path));
match folder.load().await {
Ok(_) => {
let navigator = clone.navigator.borrow().clone();
if let Some(navigator) = navigator {
let source = Rc::new(Box::new(folder) as Box<dyn Source>);
let editor = MediumEditor::new(clone.backend.clone(), source);
navigator.push(editor);
}
clone.info_bar.set_revealed(false);
clone.stack.set_visible_child_name("start");
let source = Rc::new(Box::new(folder) as Box<dyn Source>);
push!(this.handle, MediumEditor, source).await;
this.handle.pop(Some(()));
}
Err(_) => {
// TODO: Present error.
clone.info_bar.set_revealed(true);
clone.stack.set_visible_child_name("start");
this.info_bar.set_revealed(true);
this.stack.set_visible_child_name("start");
}
}
});
@ -101,29 +88,21 @@ impl SourceSelector {
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");
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
spawn!(@clone this, async move {
let disc = DiscSource::new().unwrap();
match disc.load().await {
Ok(_) => {
let navigator = clone.navigator.borrow().clone();
if let Some(navigator) = navigator {
let source = Rc::new(Box::new(disc) as Box<dyn Source>);
let editor = MediumEditor::new(clone.backend.clone(), source);
navigator.push(editor);
}
clone.info_bar.set_revealed(false);
clone.stack.set_visible_child_name("start");
let source = Rc::new(Box::new(disc) as Box<dyn Source>);
push!(this.handle, MediumEditor, source).await;
this.handle.pop(Some(()));
}
Err(_) => {
// TODO: Present error.
clone.info_bar.set_revealed(true);
clone.stack.set_visible_child_name("start");
this.info_bar.set_revealed(true);
this.stack.set_visible_child_name("start");
}
}
});
@ -133,16 +112,8 @@ impl SourceSelector {
}
}
impl NavigatorScreen for SourceSelector {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
}
impl Widget for SourceSelector {
fn get_widget(&self) -> gtk::Widget {
self.widget.clone().upcast()
}
fn detach_navigator(&self) {
self.navigator.replace(None);
}
}

View file

@ -1,5 +1,6 @@
use crate::database::Recording;
use crate::widgets::{Navigator, NavigatorScreen};
use crate::navigator::{NavigationHandle, Screen};
use crate::widgets::Widget;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
@ -9,15 +10,14 @@ use std::rc::Rc;
/// A screen for editing a single track.
pub struct TrackEditor {
handle: NavigationHandle<Vec<usize>>,
widget: gtk::Box,
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.
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
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));
let this = Rc::new(Self {
handle,
widget,
selection: RefCell::new(selection),
selected_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
back_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
back_button.connect_clicked(clone!(@weak this => move |_| {
this.handle.pop(None);
}));
select_button.connect_clicked(clone!(@strong 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();
cb(selection);
}
select_button.connect_clicked(clone!(@weak this => move |_| {
let selection = this.selection.borrow().clone();
this.handle.pop(Some(selection));
}));
for (index, part) in recording.work.parts.iter().enumerate() {
let check = gtk::CheckButton::new();
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();
if check.get_active() {
selection.push(index);
@ -86,23 +75,10 @@ impl TrackEditor {
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 {
self.widget.clone().upcast()
}
fn detach_navigator(&self) {
self.navigator.replace(None);
}
}

View file

@ -1,5 +1,6 @@
use super::source::Source;
use crate::widgets::{Navigator, NavigatorScreen};
use crate::navigator::{NavigationHandle, Screen};
use crate::widgets::Widget;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
@ -9,17 +10,16 @@ use std::rc::Rc;
/// A screen for selecting tracks from a source.
pub struct TrackSelector {
handle: NavigationHandle<Vec<usize>>,
source: Rc<Box<dyn Source>>,
widget: gtk::Box,
select_button: gtk::Button,
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.
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
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));
let this = Rc::new(Self {
handle,
source,
widget,
select_button,
selection: RefCell::new(Vec::new()),
selected_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
back_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
back_button.connect_clicked(clone!(@weak this => move |_| {
this.handle.pop(None);
}));
this.select_button.connect_clicked(clone!(@strong 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();
cb(selection);
}
this.select_button.connect_clicked(clone!(@weak this => move |_| {
let selection = this.selection.borrow().clone();
this.handle.pop(Some(selection));
}));
let tracks = this.source.tracks().unwrap();
@ -70,7 +59,7 @@ impl TrackSelector {
for (index, track) in tracks.iter().enumerate() {
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();
if check.get_active() {
selection.push(index);
@ -98,25 +87,10 @@ impl TrackSelector {
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 {
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 crate::backend::Backend;
use crate::database::Recording;
use crate::selectors::{PersonSelector, RecordingSelector, WorkSelector};
use crate::widgets::{List, Navigator, NavigatorScreen};
use crate::navigator::{NavigationHandle, Screen};
use crate::selectors::PersonSelector;
use crate::widgets::{List, Widget};
use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
@ -32,7 +33,7 @@ pub struct TrackData {
/// A screen for editing a set of tracks for one recording.
pub struct TrackSetEditor {
backend: Rc<Backend>,
handle: NavigationHandle<TrackSetData>,
source: Rc<Box<dyn Source>>,
widget: gtk::Box,
save_button: gtk::Button,
@ -40,13 +41,11 @@ pub struct TrackSetEditor {
track_list: Rc<List>,
recording: RefCell<Option<Recording>>,
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.
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
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));
let this = Rc::new(Self {
backend,
handle,
source,
widget,
save_button,
@ -71,71 +70,30 @@ impl TrackSetEditor {
track_list,
recording: RefCell::new(None),
tracks: RefCell::new(Vec::new()),
done_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
back_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
back_button.connect_clicked(clone!(@weak this => move |_| {
this.handle.pop(None);
}));
this.save_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(cb) = &*this.done_cb.borrow() {
let data = TrackSetData {
recording: this.recording.borrow().clone().unwrap(),
tracks: this.tracks.borrow().clone(),
};
this.save_button.connect_clicked(clone!(@weak this => move |_| {
let data = TrackSetData {
recording: this.recording.borrow().clone().unwrap(),
tracks: this.tracks.borrow().clone(),
};
cb(data);
}
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
this.handle.pop(Some(data));
}));
select_recording_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let person_selector = PersonSelector::new(this.backend.clone());
person_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| {
let work_selector = WorkSelector::new(this.backend.clone(), person.clone());
work_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |work| {
let recording_selector = RecordingSelector::new(this.backend.clone(), work.clone());
recording_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |recording| {
this.recording.replace(Some(recording.clone()));
this.recording_selected();
navigator.clone().pop();
navigator.clone().pop();
navigator.clone().pop();
}));
navigator.clone().push(recording_selector);
}));
navigator.clone().push(work_selector);
}));
navigator.clone().push(person_selector);
}
select_recording_button.connect_clicked(clone!(@weak this => move |_| {
// TODO: We need to push a screen returning a recording here.
}));
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| {
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 {
let mut tracks = Vec::new();
for index in selection {
@ -151,13 +109,11 @@ impl TrackSetEditor {
this.tracks.replace(tracks);
this.track_list.update(length);
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 mut title_parts = Vec::<String>::new();
@ -190,26 +146,21 @@ impl TrackSetEditor {
row.add_suffix(&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 navigator = this.navigator.borrow().clone();
if let Some(recording) = recording {
spawn!(@clone this, async move {
let track = &this.tracks.borrow()[index];
if let Some(selection) = push!(this.handle, TrackEditor, (recording, track.work_parts.clone())).await {
{
let mut tracks = this.tracks.borrow_mut();
let mut track = &mut tracks[index];
track.work_parts = selection;
};
if let (Some(recording), Some(navigator)) = (recording, navigator) {
let track = &this.tracks.borrow()[index];
let editor = TrackEditor::new(recording, track.work_parts.clone());
editor.set_selected_cb(clone!(@strong this => move |selection| {
{
let mut tracks = this.tracks.borrow_mut();
let mut track = &mut tracks[index];
track.work_parts = selection;
};
this.update_tracks();
}));
navigator.push(editor);
this.update_tracks();
}
});
}
}));
@ -218,12 +169,9 @@ impl TrackSetEditor {
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.
fn recording_selected(&self) {
if let Some(recording) = &*self.recording.borrow() {
@ -260,21 +208,9 @@ impl TrackSetEditor {
}
}
impl NavigatorScreen for TrackSetEditor {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
}
impl Widget for TrackSetEditor {
fn get_widget(&self) -> gtk::Widget {
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.push::<_, _, $screen>(())
};
($handle:expr, $screen:ty, $input:ident) => {
($handle:expr, $screen:ty, $input:expr) => {
$handle.push::<_, _, $screen>($input)
};
}
@ -43,7 +43,7 @@ macro_rules! replace {
($navigator:expr, $screen:ty) => {
$navigator.replace::<_, _, $screen>(())
};
($navigator:expr, $screen:ty, $input:ident) => {
($navigator:expr, $screen:ty, $input:expr) => {
$navigator.replace::<_, _, $screen>($input)
};
}

View file

@ -3,7 +3,8 @@ use super::RecordingScreen;
use crate::backend::Backend;
use crate::database::{Ensemble, Recording};
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 glib::clone;
@ -49,9 +50,10 @@ impl EnsembleScreen {
this.widget.add_action(&gettext("Edit ensemble"), clone!(@strong this => move || {
let editor = EnsembleEditor::new(this.backend.clone(), Some(this.ensemble.clone()));
let window = NavigatorWindow::new(editor);
window.show();
spawn!(@clone this, async move {
let window = NavigatorWindow::new(this.backend.clone());
replace!(window.navigator, EnsembleEditor, None).await;
});
}));
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::database::{Person, Recording, Work};
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 glib::clone;
@ -54,9 +55,10 @@ impl PersonScreen {
this.widget.add_action(&gettext("Edit person"), clone!(@strong this => move || {
let editor = PersonEditor::new(this.backend.clone(), Some(this.person.clone()));
let window = NavigatorWindow::new(editor);
window.show();
spawn!(@clone this, async move {
let window = NavigatorWindow::new(this.backend.clone());
replace!(window.navigator, PersonEditor, None).await;
});
}));
this.widget.add_action(&gettext("Delete person"), clone!(@strong this => move || {

View file

@ -1,7 +1,8 @@
use crate::backend::Backend;
use crate::database::Recording;
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 glib::clone;
@ -48,9 +49,10 @@ impl RecordingScreen {
this.widget.add_action(&gettext("Edit recording"), clone!(@strong this => move || {
let editor = RecordingEditor::new(this.backend.clone(), Some(this.recording.clone()));
let window = NavigatorWindow::new(editor);
window.show();
spawn!(@clone this, async move {
let window = NavigatorWindow::new(this.backend.clone());
replace!(window.navigator, RecordingEditor, None).await;
});
}));
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::database::{Work, Recording};
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 glib::clone;
@ -50,9 +51,10 @@ impl WorkScreen {
this.widget.add_action(&gettext("Edit work"), clone!(@strong this => move || {
let editor = WorkEditor::new(this.backend.clone(), Some(this.work.clone()));
let window = NavigatorWindow::new(editor);
window.show();
spawn!(@clone this, async move {
let window = NavigatorWindow::new(this.backend.clone());
replace!(window.navigator, WorkEditor, None).await;
});
}));
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::database::Ensemble;
use crate::editors::EnsembleEditor;
use crate::widgets::{Navigator, NavigatorScreen};
use crate::navigator::{NavigationHandle, Screen};
use crate::widgets::Widget;
use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
@ -12,66 +13,55 @@ use std::rc::Rc;
/// A screen for selecting a ensemble.
pub struct EnsembleSelector {
backend: Rc<Backend>,
handle: NavigationHandle<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.
pub fn new(backend: Rc<Backend>) -> Rc<Self> {
fn new(_: (), handle: NavigationHandle<Ensemble>) -> Rc<Self> {
// Create UI
let selector = Selector::<Ensemble>::new();
selector.set_title(&gettext("Select ensemble"));
let this = Rc::new(Self {
backend,
handle,
selector,
selected_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
this.selector.set_back_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
this.selector.set_back_cb(clone!(@weak this => move || {
this.handle.pop(None);
}));
this.selector.set_add_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let editor = EnsembleEditor::new(this.backend.clone(), None);
editor
.set_saved_cb(clone!(@strong this => move |ensemble| this.select(&ensemble)));
navigator.push(editor);
}
this.selector.set_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_load_online(clone!(@strong this => move || {
let clone = this.clone();
async move { clone.backend.get_ensembles().await }
}));
this.selector.set_load_online(clone!(@weak this => move || {
let clone = this.clone();
async move { clone.handle.backend.get_ensembles().await }
}));
this.selector
.set_load_local(clone!(@strong this => move || {
let clone = this.clone();
async move { clone.backend.db().get_ensembles().await.unwrap() }
}));
this.selector.set_load_local(clone!(@weak this => move || {
let clone = this.clone();
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();
row.set_activatable(true);
row.set_title(Some(&ensemble.name));
let ensemble = ensemble.to_owned();
row.connect_activated(clone!(@strong this => move |_| {
this.select(&ensemble);
row.connect_activated(clone!(@weak this => move |_| {
this.handle.pop(Some(ensemble.clone()))
}));
row.upcast()
@ -82,31 +72,10 @@ impl EnsembleSelector {
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 {
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::database::Instrument;
use crate::editors::InstrumentEditor;
use crate::widgets::{Navigator, NavigatorScreen};
use crate::navigator::{NavigationHandle, Screen};
use crate::widgets::Widget;
use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
@ -12,66 +13,55 @@ use std::rc::Rc;
/// A screen for selecting a instrument.
pub struct InstrumentSelector {
backend: Rc<Backend>,
handle: NavigationHandle<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.
pub fn new(backend: Rc<Backend>) -> Rc<Self> {
fn new(_: (), handle: NavigationHandle<Instrument>) -> Rc<Self> {
// Create UI
let selector = Selector::<Instrument>::new();
selector.set_title(&gettext("Select instrument"));
let this = Rc::new(Self {
backend,
handle,
selector,
selected_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
this.selector.set_back_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
this.selector.set_back_cb(clone!(@weak this => move || {
this.handle.pop(None);
}));
this.selector.set_add_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let editor = InstrumentEditor::new(this.backend.clone(), None);
editor
.set_saved_cb(clone!(@strong this => move |instrument| this.select(&instrument)));
navigator.push(editor);
}
this.selector.set_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_load_online(clone!(@strong this => move || {
let clone = this.clone();
async move { clone.backend.get_instruments().await }
}));
this.selector.set_load_online(clone!(@weak this => move || {
let clone = this.clone();
async move { clone.handle.backend.get_instruments().await }
}));
this.selector
.set_load_local(clone!(@strong this => move || {
let clone = this.clone();
async move { clone.backend.db().get_instruments().await.unwrap() }
}));
this.selector.set_load_local(clone!(@weak this => move || {
let clone = this.clone();
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();
row.set_activatable(true);
row.set_title(Some(&instrument.name));
let instrument = instrument.to_owned();
row.connect_activated(clone!(@strong this => move |_| {
this.select(&instrument);
row.connect_activated(clone!(@weak this => move |_| {
this.handle.pop(Some(instrument.clone()))
}));
row.upcast()
@ -82,30 +72,10 @@ impl InstrumentSelector {
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 {
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 use person::*;
pub mod recording;
pub use recording::*;
pub mod work;
pub use work::*;
// TODO: Readd a better version of these.
//
// pub mod recording;
// pub use recording::*;
//
// pub mod work;
// pub use work::*;
mod selector;

View file

@ -2,7 +2,8 @@ use super::selector::Selector;
use crate::backend::Backend;
use crate::database::Person;
use crate::editors::PersonEditor;
use crate::widgets::{Navigator, NavigatorScreen};
use crate::navigator::{NavigationHandle, Screen};
use crate::widgets::Widget;
use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
@ -12,66 +13,55 @@ use std::rc::Rc;
/// A screen for selecting a person.
pub struct PersonSelector {
backend: Rc<Backend>,
handle: NavigationHandle<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.
pub fn new(backend: Rc<Backend>) -> Rc<Self> {
fn new(_: (), handle: NavigationHandle<Person>) -> Rc<Self> {
// Create UI
let selector = Selector::<Person>::new();
selector.set_title(&gettext("Select person"));
let this = Rc::new(Self {
backend,
handle,
selector,
selected_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
this.selector.set_back_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
this.selector.set_back_cb(clone!(@weak this => move || {
this.handle.pop(None);
}));
this.selector.set_add_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let editor = PersonEditor::new(this.backend.clone(), None);
editor
.set_saved_cb(clone!(@strong this => move |person| this.select(&person)));
navigator.push(editor);
}
this.selector.set_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_load_online(clone!(@strong this => move || {
let clone = this.clone();
async move { clone.backend.get_persons().await }
}));
this.selector.set_load_online(clone!(@weak this => move || {
let clone = this.clone();
async move { clone.handle.backend.get_persons().await }
}));
this.selector
.set_load_local(clone!(@strong this => move || {
let clone = this.clone();
async move { clone.backend.db().get_persons().await.unwrap() }
}));
this.selector.set_load_local(clone!(@weak this => move || {
let clone = this.clone();
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();
row.set_activatable(true);
row.set_title(Some(&person.name_lf()));
let person = person.to_owned();
row.connect_activated(clone!(@strong this => move |_| {
this.select(&person);
row.connect_activated(clone!(@weak this => move |_| {
this.handle.pop(Some(person.clone()));
}));
row.upcast()
@ -82,30 +72,10 @@ impl PersonSelector {
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 {
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::screens::*;
use crate::widgets::*;
use crate::navigator::NavigatorWindow;
use futures::prelude::*;
use gettextrs::gettext;
use gio::prelude::*;
@ -95,18 +96,10 @@ impl Window {
}));
add_button.connect_clicked(clone!(@strong result => move |_| {
// let editor = TracksEditor::new(result.backend.clone(), None, Vec::new());
// editor.set_callback(clone!(@strong result => move || {
// result.reload();
// }));
// let window = NavigatorWindow::new(editor);
// window.show();
let dialog = SourceSelector::new(result.backend.clone());
let window = NavigatorWindow::new(dialog);
window.show();
spawn!(@clone result, async move {
let window = NavigatorWindow::new(result.backend.clone());
replace!(window.navigator, SourceSelector).await;
});
}));
result