mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 11:47:25 +01:00
Merge recording editor and selector into one dialog
This commit is contained in:
parent
3e34658b25
commit
99bbd9e58f
22 changed files with 1167 additions and 1032 deletions
|
|
@ -16,9 +16,6 @@ pub use instrument_selector::*;
|
|||
pub mod part_editor;
|
||||
pub use part_editor::*;
|
||||
|
||||
pub mod performance_editor;
|
||||
pub use performance_editor::*;
|
||||
|
||||
pub mod person_editor;
|
||||
pub use person_editor::*;
|
||||
|
||||
|
|
@ -28,11 +25,8 @@ pub use person_selector::*;
|
|||
pub mod preferences;
|
||||
pub use preferences::*;
|
||||
|
||||
pub mod recording_editor;
|
||||
pub use recording_editor::*;
|
||||
|
||||
pub mod recording_selector;
|
||||
pub use recording_selector::*;
|
||||
pub mod recording;
|
||||
pub use recording::*;
|
||||
|
||||
pub mod section_editor;
|
||||
pub use section_editor::*;
|
||||
|
|
|
|||
|
|
@ -1,147 +0,0 @@
|
|||
use super::*;
|
||||
use crate::backend::Backend;
|
||||
use crate::database::*;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct PerformanceEditor<F>
|
||||
where
|
||||
F: Fn(PerformanceDescription) -> () + 'static,
|
||||
{
|
||||
backend: Rc<Backend>,
|
||||
window: libhandy::Window,
|
||||
callback: F,
|
||||
save_button: gtk::Button,
|
||||
person_label: gtk::Label,
|
||||
ensemble_label: gtk::Label,
|
||||
role_label: gtk::Label,
|
||||
person: RefCell<Option<Person>>,
|
||||
ensemble: RefCell<Option<Ensemble>>,
|
||||
role: RefCell<Option<Instrument>>,
|
||||
}
|
||||
|
||||
impl<F> PerformanceEditor<F>
|
||||
where
|
||||
F: Fn(PerformanceDescription) -> () + 'static,
|
||||
{
|
||||
pub fn new<P: IsA<gtk::Window>>(
|
||||
backend: Rc<Backend>,
|
||||
parent: &P,
|
||||
performance: Option<PerformanceDescription>,
|
||||
callback: F,
|
||||
) -> Rc<Self> {
|
||||
let builder =
|
||||
gtk::Builder::from_resource("/de/johrpan/musicus/ui/performance_editor.ui");
|
||||
|
||||
get_widget!(builder, libhandy::Window, window);
|
||||
get_widget!(builder, gtk::Button, cancel_button);
|
||||
get_widget!(builder, gtk::Button, save_button);
|
||||
get_widget!(builder, gtk::Button, person_button);
|
||||
get_widget!(builder, gtk::Button, ensemble_button);
|
||||
get_widget!(builder, gtk::Button, role_button);
|
||||
get_widget!(builder, gtk::Button, reset_role_button);
|
||||
get_widget!(builder, gtk::Label, person_label);
|
||||
get_widget!(builder, gtk::Label, ensemble_label);
|
||||
get_widget!(builder, gtk::Label, role_label);
|
||||
|
||||
let (person, ensemble, role) = match performance {
|
||||
Some(performance) => {
|
||||
match performance.person.clone() {
|
||||
Some(person) => {
|
||||
person_label.set_text(&person.name_fl());
|
||||
save_button.set_sensitive(true);
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
|
||||
match performance.ensemble.clone() {
|
||||
Some(ensemble) => {
|
||||
ensemble_label.set_text(&ensemble.name);
|
||||
save_button.set_sensitive(true);
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
|
||||
match performance.role.clone() {
|
||||
Some(role) => role_label.set_text(&role.name),
|
||||
None => (),
|
||||
}
|
||||
|
||||
(performance.person, performance.ensemble, performance.role)
|
||||
}
|
||||
None => (None, None, None),
|
||||
};
|
||||
|
||||
let result = Rc::new(PerformanceEditor {
|
||||
backend: backend,
|
||||
window: window,
|
||||
callback: callback,
|
||||
save_button: save_button,
|
||||
person_label: person_label,
|
||||
ensemble_label: ensemble_label,
|
||||
role_label: role_label,
|
||||
person: RefCell::new(person),
|
||||
ensemble: RefCell::new(ensemble),
|
||||
role: RefCell::new(role),
|
||||
});
|
||||
|
||||
cancel_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
result.window.close();
|
||||
}));
|
||||
|
||||
result
|
||||
.save_button
|
||||
.connect_clicked(clone!(@strong result => move |_| {
|
||||
(result.callback)(PerformanceDescription {
|
||||
person: result.person.borrow().clone(),
|
||||
ensemble: result.ensemble.borrow().clone(),
|
||||
role: result.role.borrow().clone(),
|
||||
});
|
||||
result.window.close();
|
||||
}));
|
||||
|
||||
person_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
PersonSelector::new(result.backend.clone(), &result.window, clone!(@strong result => move |person| {
|
||||
result.person.replace(Some(person.clone()));
|
||||
result.person_label.set_text(&person.name_fl());
|
||||
result.ensemble.replace(None);
|
||||
result.ensemble_label.set_text(&gettext("Select …"));
|
||||
result.save_button.set_sensitive(true);
|
||||
})).show();
|
||||
}));
|
||||
|
||||
ensemble_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
EnsembleSelector::new(result.backend.clone(), &result.window, clone!(@strong result => move |ensemble| {
|
||||
result.ensemble.replace(Some(ensemble.clone()));
|
||||
result.ensemble_label.set_text(&ensemble.name);
|
||||
result.person.replace(None);
|
||||
result.person_label.set_text(&gettext("Select …"));
|
||||
result.save_button.set_sensitive(true);
|
||||
})).show();
|
||||
}));
|
||||
|
||||
role_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
InstrumentSelector::new(result.backend.clone(), &result.window, clone!(@strong result => move |role| {
|
||||
result.role.replace(Some(role.clone()));
|
||||
result.role_label.set_text(&role.name);
|
||||
})).show();
|
||||
}));
|
||||
|
||||
reset_role_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
result.role.replace(None);
|
||||
result.role_label.set_text(&gettext("Select …"));
|
||||
}));
|
||||
|
||||
result.window.set_transient_for(Some(parent));
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
11
src/dialogs/recording/mod.rs
Normal file
11
src/dialogs/recording/mod.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
pub mod recording_dialog;
|
||||
pub use recording_dialog::*;
|
||||
|
||||
pub mod recording_editor_dialog;
|
||||
pub use recording_editor_dialog::*;
|
||||
|
||||
mod performance_editor;
|
||||
mod recording_editor;
|
||||
mod recording_selector;
|
||||
mod recording_selector_person_screen;
|
||||
mod recording_selector_work_screen;
|
||||
174
src/dialogs/recording/performance_editor.rs
Normal file
174
src/dialogs/recording/performance_editor.rs
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use crate::dialogs::*;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A dialog for editing a performance within a recording.
|
||||
pub struct PerformanceEditor {
|
||||
backend: Rc<Backend>,
|
||||
window: libhandy::Window,
|
||||
save_button: gtk::Button,
|
||||
person_label: gtk::Label,
|
||||
ensemble_label: gtk::Label,
|
||||
role_label: gtk::Label,
|
||||
reset_role_button: gtk::Button,
|
||||
person: RefCell<Option<Person>>,
|
||||
ensemble: RefCell<Option<Ensemble>>,
|
||||
role: RefCell<Option<Instrument>>,
|
||||
selected_cb: RefCell<Option<Box<dyn Fn(PerformanceDescription) -> ()>>>,
|
||||
}
|
||||
|
||||
impl PerformanceEditor {
|
||||
/// Create a new performance editor.
|
||||
pub fn new<P: IsA<gtk::Window>>(
|
||||
backend: Rc<Backend>,
|
||||
parent: &P,
|
||||
performance: Option<PerformanceDescription>,
|
||||
) -> Rc<Self> {
|
||||
// Create UI
|
||||
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/performance_editor.ui");
|
||||
|
||||
get_widget!(builder, libhandy::Window, window);
|
||||
get_widget!(builder, gtk::Button, cancel_button);
|
||||
get_widget!(builder, gtk::Button, save_button);
|
||||
get_widget!(builder, gtk::Button, person_button);
|
||||
get_widget!(builder, gtk::Button, ensemble_button);
|
||||
get_widget!(builder, gtk::Button, role_button);
|
||||
get_widget!(builder, gtk::Button, reset_role_button);
|
||||
get_widget!(builder, gtk::Label, person_label);
|
||||
get_widget!(builder, gtk::Label, ensemble_label);
|
||||
get_widget!(builder, gtk::Label, role_label);
|
||||
|
||||
window.set_transient_for(Some(parent));
|
||||
|
||||
let this = Rc::new(PerformanceEditor {
|
||||
backend,
|
||||
window,
|
||||
save_button,
|
||||
person_label,
|
||||
ensemble_label,
|
||||
role_label,
|
||||
reset_role_button,
|
||||
person: RefCell::new(None),
|
||||
ensemble: RefCell::new(None),
|
||||
role: RefCell::new(None),
|
||||
selected_cb: RefCell::new(None),
|
||||
});
|
||||
|
||||
// Connect signals and callbacks
|
||||
|
||||
cancel_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
this.window.close();
|
||||
}));
|
||||
|
||||
this.save_button
|
||||
.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(cb) = &*this.selected_cb.borrow() {
|
||||
cb(PerformanceDescription {
|
||||
person: this.person.borrow().clone(),
|
||||
ensemble: this.ensemble.borrow().clone(),
|
||||
role: this.role.borrow().clone(),
|
||||
});
|
||||
|
||||
this.window.close();
|
||||
}
|
||||
}));
|
||||
|
||||
person_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
PersonSelector::new(this.backend.clone(), &this.window, clone!(@strong this => move |person| {
|
||||
this.show_person(Some(&person));
|
||||
this.person.replace(Some(person));
|
||||
this.show_ensemble(None);
|
||||
this.ensemble.replace(None);
|
||||
})).show();
|
||||
}));
|
||||
|
||||
ensemble_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
EnsembleSelector::new(this.backend.clone(), &this.window, clone!(@strong this => move |ensemble| {
|
||||
this.show_person(None);
|
||||
this.person.replace(None);
|
||||
this.show_ensemble(Some(&ensemble));
|
||||
this.ensemble.replace(Some(ensemble));
|
||||
})).show();
|
||||
}));
|
||||
|
||||
role_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
InstrumentSelector::new(this.backend.clone(), &this.window, clone!(@strong this => move |role| {
|
||||
this.show_role(Some(&role));
|
||||
this.role.replace(Some(role));
|
||||
})).show();
|
||||
}));
|
||||
|
||||
this.reset_role_button
|
||||
.connect_clicked(clone!(@strong this => move |_| {
|
||||
this.show_role(None);
|
||||
this.role.replace(None);
|
||||
}));
|
||||
|
||||
// Initialize
|
||||
|
||||
if let Some(performance) = performance {
|
||||
if let Some(person) = performance.person {
|
||||
this.show_person(Some(&person));
|
||||
this.person.replace(Some(person));
|
||||
} else if let Some(ensemble) = performance.ensemble {
|
||||
this.show_ensemble(Some(&ensemble));
|
||||
this.ensemble.replace(Some(ensemble));
|
||||
}
|
||||
|
||||
if let Some(role) = performance.role {
|
||||
this.show_role(Some(&role));
|
||||
this.role.replace(Some(role));
|
||||
}
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Set a closure to be called when the user has chosen to save the performance.
|
||||
pub fn set_selected_cb<F: Fn(PerformanceDescription) -> () + 'static>(&self, cb: F) {
|
||||
self.selected_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Show the performance editor.
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
|
||||
/// Update the UI according to person.
|
||||
fn show_person(&self, person: Option<&Person>) {
|
||||
if let Some(person) = person {
|
||||
self.person_label.set_text(&person.name_fl());
|
||||
self.save_button.set_sensitive(true);
|
||||
} else {
|
||||
self.person_label.set_text(&gettext("Select …"));
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the UI according to ensemble.
|
||||
fn show_ensemble(&self, ensemble: Option<&Ensemble>) {
|
||||
if let Some(ensemble) = ensemble {
|
||||
self.ensemble_label.set_text(&ensemble.name);
|
||||
self.save_button.set_sensitive(true);
|
||||
} else {
|
||||
self.ensemble_label.set_text(&gettext("Select …"));
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the UI according to role.
|
||||
fn show_role(&self, role: Option<&Instrument>) {
|
||||
if let Some(role) = role {
|
||||
self.role_label.set_text(&role.name);
|
||||
self.reset_role_button.show();
|
||||
} else {
|
||||
self.role_label.set_text(&gettext("Select …"));
|
||||
self.reset_role_button.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
86
src/dialogs/recording/recording_dialog.rs
Normal file
86
src/dialogs/recording/recording_dialog.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
use super::recording_editor::*;
|
||||
use super::recording_selector::*;
|
||||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A dialog for selecting and creating a recording.
|
||||
pub struct RecordingDialog {
|
||||
pub window: libhandy::Window,
|
||||
stack: gtk::Stack,
|
||||
selector: Rc<RecordingSelector>,
|
||||
editor: Rc<RecordingEditor>,
|
||||
selected_cb: RefCell<Option<Box<dyn Fn(RecordingDescription) -> ()>>>,
|
||||
}
|
||||
|
||||
impl RecordingDialog {
|
||||
/// Create a new recording dialog.
|
||||
pub fn new<W: IsA<gtk::Window>>(backend: Rc<Backend>, parent: &W) -> Rc<Self> {
|
||||
// Create UI
|
||||
|
||||
let window = libhandy::Window::new();
|
||||
window.set_type_hint(gdk::WindowTypeHint::Dialog);
|
||||
window.set_modal(true);
|
||||
window.set_transient_for(Some(parent));
|
||||
window.set_default_size(600, 424);
|
||||
|
||||
let selector = RecordingSelector::new(backend.clone());
|
||||
let editor = RecordingEditor::new(backend.clone(), &window, None);
|
||||
|
||||
let stack = gtk::Stack::new();
|
||||
stack.set_transition_type(gtk::StackTransitionType::Crossfade);
|
||||
stack.add(&selector.widget);
|
||||
stack.add(&editor.widget);
|
||||
window.add(&stack);
|
||||
window.show_all();
|
||||
|
||||
let this = Rc::new(Self {
|
||||
window,
|
||||
stack,
|
||||
selector,
|
||||
editor,
|
||||
selected_cb: RefCell::new(None),
|
||||
});
|
||||
|
||||
// Connect signals and callbacks
|
||||
|
||||
this.selector.set_add_cb(clone!(@strong this => move || {
|
||||
this.stack.set_visible_child(&this.editor.widget);
|
||||
}));
|
||||
|
||||
this.selector
|
||||
.set_selected_cb(clone!(@strong this => move |recording| {
|
||||
if let Some(cb) = &*this.selected_cb.borrow() {
|
||||
cb(recording);
|
||||
this.window.close();
|
||||
}
|
||||
}));
|
||||
|
||||
this.editor.set_back_cb(clone!(@strong this => move || {
|
||||
this.stack.set_visible_child(&this.selector.widget);
|
||||
}));
|
||||
|
||||
this.editor
|
||||
.set_selected_cb(clone!(@strong this => move |recording| {
|
||||
if let Some(cb) = &*this.selected_cb.borrow() {
|
||||
cb(recording);
|
||||
this.window.close();
|
||||
}
|
||||
}));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Set the closure to be called when the user has selected or created a recording.
|
||||
pub fn set_selected_cb<F: Fn(RecordingDescription) -> () + 'static>(&self, cb: F) {
|
||||
self.selected_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Show the recording dialog.
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
197
src/dialogs/recording/recording_editor.rs
Normal file
197
src/dialogs/recording/recording_editor.rs
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
use super::performance_editor::*;
|
||||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use crate::dialogs::*;
|
||||
use crate::widgets::*;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A widget for creating or editing a recording.
|
||||
// TODO: Disable buttons if no performance is selected.
|
||||
pub struct RecordingEditor {
|
||||
pub widget: gtk::Box,
|
||||
backend: Rc<Backend>,
|
||||
parent: gtk::Window,
|
||||
save_button: gtk::Button,
|
||||
work_label: gtk::Label,
|
||||
comment_entry: gtk::Entry,
|
||||
performance_list: Rc<List<PerformanceDescription>>,
|
||||
id: i64,
|
||||
work: RefCell<Option<WorkDescription>>,
|
||||
performances: RefCell<Vec<PerformanceDescription>>,
|
||||
selected_cb: RefCell<Option<Box<dyn Fn(RecordingDescription) -> ()>>>,
|
||||
back_cb: RefCell<Option<Box<dyn Fn() -> ()>>>,
|
||||
}
|
||||
|
||||
impl RecordingEditor {
|
||||
/// Create a new recording editor widget and optionally initialize it. The parent window is
|
||||
/// used as the parent for newly created dialogs.
|
||||
pub fn new<W: IsA<gtk::Window>>(
|
||||
backend: Rc<Backend>,
|
||||
parent: &W,
|
||||
recording: Option<RecordingDescription>,
|
||||
) -> Rc<Self> {
|
||||
// Create UI
|
||||
|
||||
let builder =
|
||||
gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_editor.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, gtk::Button, cancel_button);
|
||||
get_widget!(builder, gtk::Button, save_button);
|
||||
get_widget!(builder, gtk::Button, work_button);
|
||||
get_widget!(builder, gtk::Label, work_label);
|
||||
get_widget!(builder, gtk::Entry, comment_entry);
|
||||
get_widget!(builder, gtk::ScrolledWindow, scroll);
|
||||
get_widget!(builder, gtk::Button, add_performer_button);
|
||||
get_widget!(builder, gtk::Button, edit_performer_button);
|
||||
get_widget!(builder, gtk::Button, remove_performer_button);
|
||||
|
||||
let performance_list = List::new(&gettext("No performers added."));
|
||||
scroll.add(&performance_list.widget);
|
||||
|
||||
let (id, work, performances) = match recording {
|
||||
Some(recording) => (recording.id, Some(recording.work), recording.performances),
|
||||
None => (rand::random::<u32>().into(), None, Vec::new()),
|
||||
};
|
||||
|
||||
let this = Rc::new(RecordingEditor {
|
||||
widget,
|
||||
backend,
|
||||
parent: parent.clone().upcast(),
|
||||
save_button,
|
||||
work_label,
|
||||
comment_entry,
|
||||
performance_list,
|
||||
id,
|
||||
work: RefCell::new(work),
|
||||
performances: RefCell::new(performances),
|
||||
selected_cb: RefCell::new(None),
|
||||
back_cb: RefCell::new(None),
|
||||
});
|
||||
|
||||
// Connect signals and callbacks
|
||||
|
||||
cancel_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(cb) = &*this.back_cb.borrow() {
|
||||
cb();
|
||||
}
|
||||
}));
|
||||
|
||||
this.save_button
|
||||
.connect_clicked(clone!(@strong this => move |_| {
|
||||
let recording = RecordingDescription {
|
||||
id: this.id,
|
||||
work: this.work.borrow().clone().expect("Tried to create recording without work!"),
|
||||
comment: this.comment_entry.get_text().to_string(),
|
||||
performances: this.performances.borrow().clone(),
|
||||
};
|
||||
|
||||
let c = glib::MainContext::default();
|
||||
let clone = this.clone();
|
||||
c.spawn_local(async move {
|
||||
clone.backend.update_recording(recording.clone().into()).await.unwrap();
|
||||
if let Some(cb) = &*clone.selected_cb.borrow() {
|
||||
cb(recording.clone());
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
work_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
WorkSelector::new(this.backend.clone(), &this.parent, clone!(@strong this => move |work| {
|
||||
this.work_selected(&work);
|
||||
this.work.replace(Some(work));
|
||||
})).show();
|
||||
}));
|
||||
|
||||
this.performance_list.set_make_widget(|performance| {
|
||||
let label = gtk::Label::new(Some(&performance.get_title()));
|
||||
label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
label.set_halign(gtk::Align::Start);
|
||||
label.set_margin_start(6);
|
||||
label.set_margin_end(6);
|
||||
label.set_margin_top(6);
|
||||
label.set_margin_bottom(6);
|
||||
label.upcast()
|
||||
});
|
||||
|
||||
add_performer_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
let editor = PerformanceEditor::new(this.backend.clone(), &this.parent, None);
|
||||
|
||||
editor.set_selected_cb(clone!(@strong this => move |performance| {
|
||||
let mut performances = this.performances.borrow_mut();
|
||||
|
||||
let index = match this.performance_list.get_selected_index() {
|
||||
Some(index) => index + 1,
|
||||
None => performances.len(),
|
||||
};
|
||||
|
||||
performances.push(performance);
|
||||
this.performance_list.show_items(performances.clone());
|
||||
this.performance_list.select_index(index);
|
||||
}));
|
||||
|
||||
editor.show();
|
||||
}));
|
||||
|
||||
edit_performer_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(index) = this.performance_list.get_selected_index() {
|
||||
let performance = &this.performances.borrow()[index];
|
||||
|
||||
let editor = PerformanceEditor::new(
|
||||
this.backend.clone(),
|
||||
&this.parent,
|
||||
Some(performance.clone()),
|
||||
);
|
||||
|
||||
editor.set_selected_cb(clone!(@strong this => move |performance| {
|
||||
let mut performances = this.performances.borrow_mut();
|
||||
performances[index] = performance;
|
||||
this.performance_list.show_items(performances.clone());
|
||||
this.performance_list.select_index(index);
|
||||
}));
|
||||
|
||||
editor.show();
|
||||
}
|
||||
}));
|
||||
|
||||
remove_performer_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(index) = this.performance_list.get_selected_index() {
|
||||
let mut performances = this.performances.borrow_mut();
|
||||
performances.remove(index);
|
||||
this.performance_list.show_items(performances.clone());
|
||||
this.performance_list.select_index(index);
|
||||
}
|
||||
}));
|
||||
|
||||
// Initialize
|
||||
|
||||
if let Some(work) = &*this.work.borrow() {
|
||||
this.work_selected(work);
|
||||
}
|
||||
|
||||
this.performance_list.show_items(this.performances.borrow().clone());
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Set the closure to be called if the editor is canceled.
|
||||
pub fn set_back_cb<F: Fn() -> () + 'static>(&self, cb: F) {
|
||||
self.back_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Set the closure to be called if the recording was created.
|
||||
pub fn set_selected_cb<F: Fn(RecordingDescription) -> () + 'static>(&self, cb: F) {
|
||||
self.selected_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Update the UI according to work.
|
||||
fn work_selected(&self, work: &WorkDescription) {
|
||||
self.work_label.set_text(&format!("{}: {}", work.composer.name_fl(), work.title));
|
||||
self.save_button.set_sensitive(true);
|
||||
}
|
||||
}
|
||||
63
src/dialogs/recording/recording_editor_dialog.rs
Normal file
63
src/dialogs/recording/recording_editor_dialog.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
use super::recording_editor::*;
|
||||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A dialog for creating or editing a recording.
|
||||
pub struct RecordingEditorDialog {
|
||||
pub window: libhandy::Window,
|
||||
selected_cb: RefCell<Option<Box<dyn Fn(RecordingDescription) -> ()>>>,
|
||||
}
|
||||
|
||||
impl RecordingEditorDialog {
|
||||
/// Create a new recording editor dialog and optionally initialize it.
|
||||
pub fn new<W: IsA<gtk::Window>>(
|
||||
backend: Rc<Backend>,
|
||||
parent: &W,
|
||||
recording: Option<RecordingDescription>,
|
||||
) -> Rc<Self> {
|
||||
// Create UI
|
||||
|
||||
let window = libhandy::Window::new();
|
||||
window.set_type_hint(gdk::WindowTypeHint::Dialog);
|
||||
window.set_modal(true);
|
||||
window.set_transient_for(Some(parent));
|
||||
|
||||
let editor = RecordingEditor::new(backend.clone(), &window, recording);
|
||||
window.add(&editor.widget);
|
||||
window.show_all();
|
||||
|
||||
let this = Rc::new(Self {
|
||||
window,
|
||||
selected_cb: RefCell::new(None),
|
||||
});
|
||||
|
||||
// Connect signals and callbacks
|
||||
|
||||
editor.set_back_cb(clone!(@strong this => move || {
|
||||
this.window.close();
|
||||
}));
|
||||
|
||||
editor.set_selected_cb(clone!(@strong this => move |recording| {
|
||||
if let Some(cb) = &*this.selected_cb.borrow() {
|
||||
cb(recording);
|
||||
this.window.close();
|
||||
}
|
||||
}));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Set the closure to be called when the user edited or created a recording.
|
||||
pub fn set_selected_cb<F: Fn(RecordingDescription) -> () + 'static>(&self, cb: F) {
|
||||
self.selected_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Show the recording editor dialog.
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
89
src/dialogs/recording/recording_selector.rs
Normal file
89
src/dialogs/recording/recording_selector.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
use super::recording_selector_person_screen::*;
|
||||
use crate::backend::Backend;
|
||||
use crate::database::*;
|
||||
use crate::widgets::*;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libhandy::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A widget for selecting a recording from a list of existing ones.
|
||||
pub struct RecordingSelector {
|
||||
pub widget: libhandy::Leaflet,
|
||||
backend: Rc<Backend>,
|
||||
sidebar_box: gtk::Box,
|
||||
selected_cb: RefCell<Option<Box<dyn Fn(RecordingDescription) -> ()>>>,
|
||||
add_cb: RefCell<Option<Box<dyn Fn() -> ()>>>,
|
||||
navigator: Rc<Navigator>,
|
||||
}
|
||||
|
||||
impl RecordingSelector {
|
||||
/// Create a new recording selector.
|
||||
pub fn new(backend: Rc<Backend>) -> Rc<Self> {
|
||||
// Create UI
|
||||
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_selector.ui");
|
||||
|
||||
get_widget!(builder, libhandy::Leaflet, widget);
|
||||
get_widget!(builder, gtk::Button, add_button);
|
||||
get_widget!(builder, gtk::Box, sidebar_box);
|
||||
get_widget!(builder, gtk::Box, empty_screen);
|
||||
|
||||
let person_list = PersonList::new(backend.clone());
|
||||
sidebar_box.pack_start(&person_list.widget, true, true, 0);
|
||||
|
||||
let navigator = Navigator::new(&empty_screen);
|
||||
widget.add(&navigator.widget);
|
||||
|
||||
let this = Rc::new(Self {
|
||||
widget,
|
||||
backend,
|
||||
sidebar_box,
|
||||
selected_cb: RefCell::new(None),
|
||||
add_cb: RefCell::new(None),
|
||||
navigator,
|
||||
});
|
||||
|
||||
// Connect signals and callbacks
|
||||
|
||||
add_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(cb) = &*this.add_cb.borrow() {
|
||||
cb();
|
||||
}
|
||||
}));
|
||||
|
||||
person_list.set_selected(clone!(@strong this => move |person| {
|
||||
let person_screen = RecordingSelectorPersonScreen::new(
|
||||
this.backend.clone(),
|
||||
person.clone(),
|
||||
);
|
||||
|
||||
person_screen.set_selected_cb(clone!(@strong this => move |recording| {
|
||||
if let Some(cb) = &*this.selected_cb.borrow() {
|
||||
cb(recording);
|
||||
}
|
||||
}));
|
||||
|
||||
this.navigator.clone().push(person_screen);
|
||||
this.widget.set_visible_child(&this.navigator.widget);
|
||||
}));
|
||||
|
||||
this.navigator.set_back_cb(clone!(@strong this => move || {
|
||||
this.widget.set_visible_child(&this.sidebar_box);
|
||||
}));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Set the closure to be called if the editor is user wants to add a new recording.
|
||||
pub fn set_add_cb<F: Fn() -> () + 'static>(&self, cb: F) {
|
||||
self.add_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Set the closure to be called when the user has selected a recording.
|
||||
pub fn set_selected_cb<F: Fn(RecordingDescription) -> () + 'static>(&self, cb: F) {
|
||||
self.selected_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
}
|
||||
126
src/dialogs/recording/recording_selector_person_screen.rs
Normal file
126
src/dialogs/recording/recording_selector_person_screen.rs
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
use super::recording_selector_work_screen::*;
|
||||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use crate::widgets::*;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libhandy::HeaderBarExt;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A screen within the recording selector that presents a list of persons
|
||||
/// and switches to a work screen on selection.
|
||||
pub struct RecordingSelectorPersonScreen {
|
||||
backend: Rc<Backend>,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
work_list: Rc<List<WorkDescription>>,
|
||||
selected_cb: RefCell<Option<Box<dyn Fn(RecordingDescription) -> ()>>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
impl RecordingSelectorPersonScreen {
|
||||
/// Create a new recording selector person screen.
|
||||
pub fn new(backend: Rc<Backend>, person: Person) -> Rc<Self> {
|
||||
// Create UI
|
||||
|
||||
let builder =
|
||||
gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_selector_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, libhandy::HeaderBar, header);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::Stack, stack);
|
||||
|
||||
header.set_title(Some(&person.name_fl()));
|
||||
|
||||
let work_list = List::new(&gettext("No works found."));
|
||||
stack.add_named(&work_list.widget, "content");
|
||||
|
||||
let this = Rc::new(Self {
|
||||
backend,
|
||||
widget,
|
||||
stack,
|
||||
work_list,
|
||||
selected_cb: RefCell::new(None),
|
||||
navigator: RefCell::new(None),
|
||||
});
|
||||
|
||||
// Connect signals and callbacks
|
||||
|
||||
back_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
let navigator = this.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.pop();
|
||||
}
|
||||
}));
|
||||
|
||||
this.work_list.set_make_widget(|work: &WorkDescription| {
|
||||
let label = gtk::Label::new(Some(&work.title));
|
||||
label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
label.set_halign(gtk::Align::Start);
|
||||
label.set_margin_start(6);
|
||||
label.set_margin_end(6);
|
||||
label.set_margin_top(6);
|
||||
label.set_margin_bottom(6);
|
||||
label.upcast()
|
||||
});
|
||||
|
||||
this.work_list
|
||||
.set_selected(clone!(@strong this => move |work| {
|
||||
let navigator = this.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
let work_screen = RecordingSelectorWorkScreen::new(
|
||||
this.backend.clone(),
|
||||
work.clone(),
|
||||
);
|
||||
|
||||
work_screen.set_selected_cb(clone!(@strong this => move |recording| {
|
||||
if let Some(cb) = &*this.selected_cb.borrow() {
|
||||
cb(recording);
|
||||
}
|
||||
}));
|
||||
|
||||
navigator.push(work_screen);
|
||||
}
|
||||
}));
|
||||
|
||||
// Initialize
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = this.clone();
|
||||
context.spawn_local(async move {
|
||||
let works = clone
|
||||
.backend
|
||||
.get_work_descriptions(person.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
clone.work_list.show_items(works);
|
||||
clone.stack.set_visible_child_name("content");
|
||||
});
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Sets a closure to be called when the user has selected a recording.
|
||||
pub fn set_selected_cb<F: Fn(RecordingDescription) -> () + 'static>(&self, cb: F) {
|
||||
self.selected_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
}
|
||||
|
||||
impl NavigatorScreen for RecordingSelectorPersonScreen {
|
||||
fn attach_navigator(&self, navigator: Rc<Navigator>) {
|
||||
self.navigator.replace(Some(navigator));
|
||||
}
|
||||
|
||||
fn get_widget(&self) -> gtk::Widget {
|
||||
self.widget.clone().upcast()
|
||||
}
|
||||
|
||||
fn detach_navigator(&self) {
|
||||
self.navigator.replace(None);
|
||||
}
|
||||
}
|
||||
120
src/dialogs/recording/recording_selector_work_screen.rs
Normal file
120
src/dialogs/recording/recording_selector_work_screen.rs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use crate::widgets::*;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libhandy::HeaderBarExt;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A screen within the recording selector presenting a list of recordings for a work.
|
||||
pub struct RecordingSelectorWorkScreen {
|
||||
backend: Rc<Backend>,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
recording_list: Rc<List<RecordingDescription>>,
|
||||
selected_cb: RefCell<Option<Box<dyn Fn(RecordingDescription) -> ()>>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
impl RecordingSelectorWorkScreen {
|
||||
/// Create a new recording selector work screen.
|
||||
pub fn new(backend: Rc<Backend>, work: WorkDescription) -> Rc<Self> {
|
||||
// Create UI
|
||||
|
||||
let builder =
|
||||
gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_selector_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, libhandy::HeaderBar, header);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::Stack, stack);
|
||||
|
||||
header.set_title(Some(&work.title));
|
||||
header.set_subtitle(Some(&work.composer.name_fl()));
|
||||
|
||||
let recording_list = List::new(&gettext("No recordings found."));
|
||||
stack.add_named(&recording_list.widget, "content");
|
||||
|
||||
let this = Rc::new(Self {
|
||||
backend,
|
||||
widget,
|
||||
stack,
|
||||
recording_list,
|
||||
selected_cb: RefCell::new(None),
|
||||
navigator: RefCell::new(None),
|
||||
});
|
||||
|
||||
// Connect signals and callbacks
|
||||
|
||||
back_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
let navigator = this.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.pop();
|
||||
}
|
||||
}));
|
||||
|
||||
this.recording_list.set_make_widget(|recording: &RecordingDescription| {
|
||||
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
||||
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
work_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
||||
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
performers_label.set_opacity(0.5);
|
||||
performers_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
vbox.set_border_width(6);
|
||||
vbox.add(&work_label);
|
||||
vbox.add(&performers_label);
|
||||
|
||||
vbox.upcast()
|
||||
});
|
||||
|
||||
this.recording_list
|
||||
.set_selected(clone!(@strong this => move |recording| {
|
||||
if let Some(cb) = &*this.selected_cb.borrow() {
|
||||
cb(recording.clone());
|
||||
}
|
||||
}));
|
||||
|
||||
// Initialize
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = this.clone();
|
||||
context.spawn_local(async move {
|
||||
let recordings = clone
|
||||
.backend
|
||||
.get_recordings_for_work(work.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
clone.recording_list.show_items(recordings);
|
||||
clone.stack.set_visible_child_name("content");
|
||||
});
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Sets a closure to be called when the user has selected a recording.
|
||||
pub fn set_selected_cb<F: Fn(RecordingDescription) -> () + 'static>(&self, cb: F) {
|
||||
self.selected_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
}
|
||||
|
||||
impl NavigatorScreen for RecordingSelectorWorkScreen {
|
||||
fn attach_navigator(&self, navigator: Rc<Navigator>) {
|
||||
self.navigator.replace(Some(navigator));
|
||||
}
|
||||
|
||||
fn get_widget(&self) -> gtk::Widget {
|
||||
self.widget.clone().upcast()
|
||||
}
|
||||
|
||||
fn detach_navigator(&self) {
|
||||
self.navigator.replace(None);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
use super::*;
|
||||
use crate::backend::Backend;
|
||||
use crate::database::*;
|
||||
use crate::widgets::*;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::convert::TryInto;
|
||||
|
||||
pub struct RecordingEditor<F>
|
||||
where
|
||||
F: Fn(RecordingDescription) -> () + 'static,
|
||||
{
|
||||
backend: Rc<Backend>,
|
||||
window: libhandy::Window,
|
||||
callback: F,
|
||||
id: i64,
|
||||
save_button: gtk::Button,
|
||||
work_label: gtk::Label,
|
||||
work: RefCell<Option<WorkDescription>>,
|
||||
comment_entry: gtk::Entry,
|
||||
performers: RefCell<Vec<PerformanceDescription>>,
|
||||
performer_list: gtk::ListBox,
|
||||
}
|
||||
|
||||
impl<F> RecordingEditor<F>
|
||||
where
|
||||
F: Fn(RecordingDescription) -> () + 'static,
|
||||
{
|
||||
pub fn new<P: IsA<gtk::Window>>(
|
||||
backend: Rc<Backend>,
|
||||
parent: &P,
|
||||
recording: Option<RecordingDescription>,
|
||||
callback: F,
|
||||
) -> Rc<Self> {
|
||||
let builder =
|
||||
gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_editor.ui");
|
||||
|
||||
get_widget!(builder, libhandy::Window, window);
|
||||
get_widget!(builder, gtk::Button, cancel_button);
|
||||
get_widget!(builder, gtk::Button, save_button);
|
||||
get_widget!(builder, gtk::Button, work_button);
|
||||
get_widget!(builder, gtk::Label, work_label);
|
||||
get_widget!(builder, gtk::Entry, comment_entry);
|
||||
get_widget!(builder, gtk::ListBox, performer_list);
|
||||
get_widget!(builder, gtk::Button, add_performer_button);
|
||||
get_widget!(builder, gtk::Button, edit_performer_button);
|
||||
get_widget!(builder, gtk::Button, remove_performer_button);
|
||||
|
||||
let (id, work, performers) = match recording {
|
||||
Some(recording) => {
|
||||
save_button.set_sensitive(true);
|
||||
work_label.set_text(&recording.work.get_title());
|
||||
comment_entry.set_text(&recording.comment);
|
||||
(recording.id, Some(recording.work), recording.performances)
|
||||
}
|
||||
None => (rand::random::<u32>().into(), None, Vec::new()),
|
||||
};
|
||||
|
||||
let result = Rc::new(RecordingEditor {
|
||||
backend: backend,
|
||||
window: window,
|
||||
callback: callback,
|
||||
id: id,
|
||||
save_button: save_button,
|
||||
work_label: work_label,
|
||||
work: RefCell::new(work),
|
||||
comment_entry: comment_entry,
|
||||
performers: RefCell::new(performers),
|
||||
performer_list: performer_list,
|
||||
});
|
||||
|
||||
cancel_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
result.window.close();
|
||||
}));
|
||||
|
||||
result
|
||||
.save_button
|
||||
.connect_clicked(clone!(@strong result => move |_| {
|
||||
let recording = RecordingDescription {
|
||||
id: result.id,
|
||||
work: result.work.borrow().clone().expect("Tried to create recording without work!"),
|
||||
comment: result.comment_entry.get_text().to_string(),
|
||||
performances: result.performers.borrow().to_vec(),
|
||||
};
|
||||
|
||||
let c = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
c.spawn_local(async move {
|
||||
clone.backend.update_recording(recording.clone().into()).await.unwrap();
|
||||
clone.window.close();
|
||||
(clone.callback)(recording.clone());
|
||||
});
|
||||
}));
|
||||
|
||||
work_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
WorkSelector::new(result.backend.clone(), &result.window, clone!(@strong result => move |work| {
|
||||
result.work.replace(Some(work.clone()));
|
||||
result.work_label.set_text(&format!("{}: {}", work.composer.name_fl(), work.title));
|
||||
result.save_button.set_sensitive(true);
|
||||
})).show();
|
||||
}));
|
||||
|
||||
add_performer_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
PerformanceEditor::new(result.backend.clone(), &result.window, None, clone!(@strong result => move |performance| {
|
||||
{
|
||||
let mut performers = result.performers.borrow_mut();
|
||||
performers.push(performance);
|
||||
}
|
||||
|
||||
result.show_performers();
|
||||
})).show();
|
||||
}));
|
||||
|
||||
edit_performer_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let row = result.get_selected_performer_row();
|
||||
match row {
|
||||
Some(row) => {
|
||||
let index = row.get_index();
|
||||
let index: usize = index.try_into().unwrap();
|
||||
let performer = result.performers.borrow()[index].clone();
|
||||
|
||||
PerformanceEditor::new(result.backend.clone(), &result.window, Some(performer), clone!(@strong result => move |performer| {
|
||||
result.performers.borrow_mut()[index] = performer;
|
||||
result.show_performers();
|
||||
})).show();
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}));
|
||||
|
||||
remove_performer_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let row = result.get_selected_performer_row();
|
||||
match row {
|
||||
Some(row) => {
|
||||
let index = row.get_index();
|
||||
let index: usize = index.try_into().unwrap();
|
||||
result.performers.borrow_mut().remove(index);
|
||||
result.show_performers();
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}));
|
||||
|
||||
result.window.set_transient_for(Some(parent));
|
||||
|
||||
result.show_performers();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
|
||||
fn show_performers(&self) {
|
||||
for child in self.performer_list.get_children() {
|
||||
self.performer_list.remove(&child);
|
||||
}
|
||||
|
||||
for (index, performer) in self.performers.borrow().iter().enumerate() {
|
||||
let label = gtk::Label::new(Some(&performer.get_title()));
|
||||
label.set_halign(gtk::Align::Start);
|
||||
label.set_margin_start(6);
|
||||
label.set_margin_end(6);
|
||||
label.set_margin_top(6);
|
||||
label.set_margin_bottom(6);
|
||||
|
||||
let row = SelectorRow::new(index.try_into().unwrap(), &label);
|
||||
row.show_all();
|
||||
self.performer_list.insert(&row, -1);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_selected_performer_row(&self) -> Option<SelectorRow> {
|
||||
match self.performer_list.get_selected_rows().first() {
|
||||
Some(row) => match row.get_child() {
|
||||
Some(child) => Some(child.downcast().unwrap()),
|
||||
None => None,
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,279 +0,0 @@
|
|||
use super::*;
|
||||
use crate::backend::Backend;
|
||||
use crate::database::*;
|
||||
use crate::widgets::*;
|
||||
use gettextrs::gettext;
|
||||
use gio::prelude::*;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libhandy::prelude::*;
|
||||
use libhandy::HeaderBarExt;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct RecordingSelector {
|
||||
backend: Rc<Backend>,
|
||||
window: libhandy::Window,
|
||||
callback: Box<dyn Fn(RecordingDescription) -> () + 'static>,
|
||||
leaflet: libhandy::Leaflet,
|
||||
navigator: Rc<Navigator>,
|
||||
}
|
||||
|
||||
impl RecordingSelector {
|
||||
pub fn new<P, F>(backend: Rc<Backend>, parent: &P, callback: F) -> Rc<Self>
|
||||
where
|
||||
P: IsA<gtk::Window>,
|
||||
F: Fn(RecordingDescription) -> () + 'static,
|
||||
{
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_selector.ui");
|
||||
|
||||
get_widget!(builder, libhandy::Window, window);
|
||||
get_widget!(builder, libhandy::Leaflet, leaflet);
|
||||
get_widget!(builder, gtk::Button, add_button);
|
||||
get_widget!(builder, gtk::Box, sidebar_box);
|
||||
get_widget!(builder, gtk::Box, empty_screen);
|
||||
|
||||
let person_list = PersonList::new(backend.clone());
|
||||
sidebar_box.pack_start(&person_list.widget, true, true, 0);
|
||||
|
||||
let navigator = Navigator::new(&empty_screen);
|
||||
leaflet.add(&navigator.widget);
|
||||
|
||||
navigator.set_back_cb(clone!(@strong leaflet, @strong sidebar_box => move || {
|
||||
leaflet.set_visible_child(&sidebar_box);
|
||||
}));
|
||||
|
||||
let result = Rc::new(Self {
|
||||
backend: backend,
|
||||
window: window,
|
||||
callback: Box::new(callback),
|
||||
leaflet: leaflet,
|
||||
navigator: navigator,
|
||||
});
|
||||
|
||||
add_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let editor = RecordingEditor::new(
|
||||
result.backend.clone(),
|
||||
&result.window,
|
||||
None,
|
||||
clone!(@strong result => move |recording| {
|
||||
result.select(recording);
|
||||
}),
|
||||
);
|
||||
|
||||
editor.show();
|
||||
}));
|
||||
|
||||
person_list.set_selected(clone!(@strong result => move |person| {
|
||||
result.navigator.clone().replace(RecordingSelectorPersonScreen::new(result.backend.clone(), result.clone(), person.clone()));
|
||||
result.leaflet.set_visible_child(&result.navigator.widget);
|
||||
}));
|
||||
|
||||
result.window.set_transient_for(Some(parent));
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
|
||||
pub fn select(&self, recording: RecordingDescription) {
|
||||
self.window.close();
|
||||
(self.callback)(recording);
|
||||
}
|
||||
}
|
||||
|
||||
struct RecordingSelectorPersonScreen {
|
||||
backend: Rc<Backend>,
|
||||
selector: Rc<RecordingSelector>,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
work_list: Rc<List<WorkDescription>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
impl RecordingSelectorPersonScreen {
|
||||
pub fn new(backend: Rc<Backend>, selector: Rc<RecordingSelector>, person: Person) -> Rc<Self> {
|
||||
let builder =
|
||||
gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_selector_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, libhandy::HeaderBar, header);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::Stack, stack);
|
||||
|
||||
header.set_title(Some(&person.name_fl()));
|
||||
|
||||
let work_list = List::new(&gettext("No works found."));
|
||||
|
||||
work_list.set_make_widget(|work: &WorkDescription| {
|
||||
let label = gtk::Label::new(Some(&work.title));
|
||||
label.set_halign(gtk::Align::Start);
|
||||
label.set_margin_start(6);
|
||||
label.set_margin_end(6);
|
||||
label.set_margin_top(6);
|
||||
label.set_margin_bottom(6);
|
||||
label.upcast()
|
||||
});
|
||||
|
||||
stack.add_named(&work_list.widget, "content");
|
||||
|
||||
let result = Rc::new(Self {
|
||||
backend,
|
||||
selector,
|
||||
widget,
|
||||
stack,
|
||||
work_list,
|
||||
navigator: RefCell::new(None),
|
||||
});
|
||||
|
||||
back_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.clone().pop();
|
||||
}
|
||||
}));
|
||||
|
||||
result
|
||||
.work_list
|
||||
.set_selected(clone!(@strong result => move |work| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.push(RecordingSelectorWorkScreen::new(result.backend.clone(), result.selector.clone(), work.clone()));
|
||||
}
|
||||
}));
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
context.spawn_local(async move {
|
||||
let works = clone
|
||||
.backend
|
||||
.get_work_descriptions(person.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
clone.work_list.show_items(works);
|
||||
clone.stack.set_visible_child_name("content");
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl NavigatorScreen for RecordingSelectorPersonScreen {
|
||||
fn attach_navigator(&self, navigator: Rc<Navigator>) {
|
||||
self.navigator.replace(Some(navigator));
|
||||
}
|
||||
|
||||
fn get_widget(&self) -> gtk::Widget {
|
||||
self.widget.clone().upcast()
|
||||
}
|
||||
|
||||
fn detach_navigator(&self) {
|
||||
self.navigator.replace(None);
|
||||
}
|
||||
}
|
||||
|
||||
struct RecordingSelectorWorkScreen {
|
||||
backend: Rc<Backend>,
|
||||
selector: Rc<RecordingSelector>,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
recording_list: Rc<List<RecordingDescription>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
impl RecordingSelectorWorkScreen {
|
||||
pub fn new(
|
||||
backend: Rc<Backend>,
|
||||
selector: Rc<RecordingSelector>,
|
||||
work: WorkDescription,
|
||||
) -> Rc<Self> {
|
||||
let builder =
|
||||
gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_selector_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, libhandy::HeaderBar, header);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::Stack, stack);
|
||||
|
||||
header.set_title(Some(&work.title));
|
||||
header.set_subtitle(Some(&work.composer.name_fl()));
|
||||
|
||||
let recording_list = List::new(&gettext("No recordings found."));
|
||||
|
||||
recording_list.set_make_widget(|recording: &RecordingDescription| {
|
||||
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
||||
|
||||
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
work_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
||||
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
performers_label.set_opacity(0.5);
|
||||
performers_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
vbox.set_border_width(6);
|
||||
vbox.add(&work_label);
|
||||
vbox.add(&performers_label);
|
||||
|
||||
vbox.upcast()
|
||||
});
|
||||
|
||||
stack.add_named(&recording_list.widget, "content");
|
||||
|
||||
let result = Rc::new(Self {
|
||||
backend,
|
||||
selector,
|
||||
widget,
|
||||
stack,
|
||||
recording_list,
|
||||
navigator: RefCell::new(None),
|
||||
});
|
||||
|
||||
back_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.clone().pop();
|
||||
}
|
||||
}));
|
||||
|
||||
result
|
||||
.recording_list
|
||||
.set_selected(clone!(@strong result => move |recording| {
|
||||
result.selector.select(recording.clone());
|
||||
}));
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
context.spawn_local(async move {
|
||||
let recordings = clone
|
||||
.backend
|
||||
.get_recordings_for_work(work.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
clone.recording_list.show_items(recordings);
|
||||
clone.stack.set_visible_child_name("content");
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl NavigatorScreen for RecordingSelectorWorkScreen {
|
||||
fn attach_navigator(&self, navigator: Rc<Navigator>) {
|
||||
self.navigator.replace(Some(navigator));
|
||||
}
|
||||
|
||||
fn get_widget(&self) -> gtk::Widget {
|
||||
self.widget.clone().upcast()
|
||||
}
|
||||
|
||||
fn detach_navigator(&self) {
|
||||
self.navigator.replace(None);
|
||||
}
|
||||
}
|
||||
|
|
@ -95,14 +95,14 @@ impl TracksEditor {
|
|||
}));
|
||||
|
||||
recording_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
RecordingSelector::new(
|
||||
this.backend.clone(),
|
||||
&this.window,
|
||||
clone!(@strong this => move |recording| {
|
||||
this.recording_selected(&recording);
|
||||
this.recording.replace(Some(recording));
|
||||
}),
|
||||
).show();
|
||||
let dialog = RecordingDialog::new(this.backend.clone(), &this.window);
|
||||
|
||||
dialog.set_selected_cb(clone!(@strong this => move |recording| {
|
||||
this.recording_selected(&recording);
|
||||
this.recording.replace(Some(recording));
|
||||
}));
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
));
|
||||
|
||||
|
|
|
|||
|
|
@ -45,12 +45,17 @@ sources = files(
|
|||
'dialogs/instrument_selector.rs',
|
||||
'dialogs/mod.rs',
|
||||
'dialogs/part_editor.rs',
|
||||
'dialogs/performance_editor.rs',
|
||||
'dialogs/person_editor.rs',
|
||||
'dialogs/person_selector.rs',
|
||||
'dialogs/preferences.rs',
|
||||
'dialogs/recording_editor.rs',
|
||||
'dialogs/recording_selector.rs',
|
||||
'dialogs/recording/mod.rs',
|
||||
'dialogs/recording/performance_editor.rs',
|
||||
'dialogs/recording/recording_dialog.rs',
|
||||
'dialogs/recording/recording_editor_dialog.rs',
|
||||
'dialogs/recording/recording_editor.rs',
|
||||
'dialogs/recording/recording_selector_person_screen.rs',
|
||||
'dialogs/recording/recording_selector.rs',
|
||||
'dialogs/recording/recording_selector_work_screen.rs',
|
||||
'dialogs/section_editor.rs',
|
||||
'dialogs/track_editor.rs',
|
||||
'dialogs/tracks_editor.rs',
|
||||
|
|
|
|||
|
|
@ -166,9 +166,13 @@ impl Window {
|
|||
result.window,
|
||||
"add-recording",
|
||||
clone!(@strong result => move |_, _| {
|
||||
RecordingEditor::new(result.backend.clone(), &result.window, None, clone!(@strong result => move |_| {
|
||||
let dialog = RecordingDialog::new(result.backend.clone(), &result.window);
|
||||
|
||||
dialog.set_selected_cb(clone!(@strong result => move |_| {
|
||||
result.reload();
|
||||
})).show();
|
||||
}));
|
||||
|
||||
dialog.show();
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -292,9 +296,13 @@ impl Window {
|
|||
let c = glib::MainContext::default();
|
||||
c.spawn_local(async move {
|
||||
let recording = result.backend.get_recording_description(id).await.unwrap();
|
||||
RecordingEditor::new(result.backend.clone(), &result.window, Some(recording), clone!(@strong result => move |_| {
|
||||
let dialog = RecordingEditorDialog::new(result.backend.clone(), &result.window, Some(recording));
|
||||
|
||||
dialog.set_selected_cb(clone!(@strong result => move |_| {
|
||||
result.reload();
|
||||
})).show();
|
||||
}));
|
||||
|
||||
dialog.show();
|
||||
});
|
||||
})
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue