2025-03-01 09:57:01 +01:00
|
|
|
mod ensemble_row;
|
|
|
|
|
mod performer_row;
|
|
|
|
|
|
2025-01-15 11:23:04 +01:00
|
|
|
use std::cell::{OnceCell, RefCell};
|
|
|
|
|
|
|
|
|
|
use adw::{prelude::*, subclass::prelude::*};
|
2025-03-01 09:57:01 +01:00
|
|
|
use ensemble_row::RecordingEditorEnsembleRow;
|
2025-01-15 11:23:04 +01:00
|
|
|
use gettextrs::gettext;
|
|
|
|
|
use gtk::glib::{self, clone, subclass::Signal, Properties};
|
|
|
|
|
use once_cell::sync::Lazy;
|
2025-03-01 09:57:01 +01:00
|
|
|
use performer_row::RecordingEditorPerformerRow;
|
2025-01-15 11:23:04 +01:00
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
|
db::models::{Ensemble, EnsemblePerformer, Performer, Person, Recording, Work},
|
2025-03-01 09:57:01 +01:00
|
|
|
editor::{ensemble::EnsembleEditor, person::PersonEditor, work::WorkEditor},
|
|
|
|
|
library::Library,
|
|
|
|
|
selector::{
|
|
|
|
|
ensemble::EnsembleSelectorPopover, person::PersonSelectorPopover, work::WorkSelectorPopover,
|
2025-01-15 11:23:04 +01:00
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mod imp {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Default, gtk::CompositeTemplate, Properties)]
|
2025-03-01 09:57:01 +01:00
|
|
|
#[properties(wrapper_type = super::RecordingEditor)]
|
|
|
|
|
#[template(file = "data/ui/editor/recording.blp")]
|
|
|
|
|
pub struct RecordingEditor {
|
2025-01-15 11:23:04 +01:00
|
|
|
#[property(get, construct_only)]
|
|
|
|
|
pub navigation: OnceCell<adw::NavigationView>,
|
|
|
|
|
|
|
|
|
|
#[property(get, construct_only)]
|
2025-03-01 09:57:01 +01:00
|
|
|
pub library: OnceCell<Library>,
|
2025-01-15 11:23:04 +01:00
|
|
|
|
|
|
|
|
pub recording_id: OnceCell<String>,
|
|
|
|
|
|
|
|
|
|
pub work: RefCell<Option<Work>>,
|
2025-03-01 09:57:01 +01:00
|
|
|
pub performer_rows: RefCell<Vec<RecordingEditorPerformerRow>>,
|
|
|
|
|
pub ensemble_rows: RefCell<Vec<RecordingEditorEnsembleRow>>,
|
2025-01-15 11:23:04 +01:00
|
|
|
|
2025-03-01 09:57:01 +01:00
|
|
|
pub work_selector_popover: OnceCell<WorkSelectorPopover>,
|
|
|
|
|
pub persons_popover: OnceCell<PersonSelectorPopover>,
|
|
|
|
|
pub ensembles_popover: OnceCell<EnsembleSelectorPopover>,
|
2025-01-15 11:23:04 +01:00
|
|
|
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub work_row: TemplateChild<adw::ActionRow>,
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub select_work_box: TemplateChild<gtk::Box>,
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub year_row: TemplateChild<adw::SpinRow>,
|
|
|
|
|
#[template_child]
|
2025-03-01 11:13:29 +01:00
|
|
|
pub performers_box: TemplateChild<gtk::Box>,
|
|
|
|
|
#[template_child]
|
2025-01-15 11:23:04 +01:00
|
|
|
pub performer_list: TemplateChild<gtk::ListBox>,
|
|
|
|
|
#[template_child]
|
2025-03-01 11:13:29 +01:00
|
|
|
pub ensembles_box: TemplateChild<gtk::Box>,
|
2025-01-15 11:23:04 +01:00
|
|
|
#[template_child]
|
|
|
|
|
pub ensemble_list: TemplateChild<gtk::ListBox>,
|
|
|
|
|
#[template_child]
|
2025-04-27 15:22:04 +02:00
|
|
|
pub enable_updates_row: TemplateChild<adw::SwitchRow>,
|
|
|
|
|
#[template_child]
|
2025-01-17 18:33:31 +01:00
|
|
|
pub save_row: TemplateChild<adw::ButtonRow>,
|
2025-01-15 11:23:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[glib::object_subclass]
|
2025-03-01 09:57:01 +01:00
|
|
|
impl ObjectSubclass for RecordingEditor {
|
2025-01-15 11:23:04 +01:00
|
|
|
const NAME: &'static str = "MusicusRecordingEditor";
|
2025-03-01 09:57:01 +01:00
|
|
|
type Type = super::RecordingEditor;
|
2025-01-15 11:23:04 +01:00
|
|
|
type ParentType = adw::NavigationPage;
|
|
|
|
|
|
|
|
|
|
fn class_init(klass: &mut Self::Class) {
|
|
|
|
|
klass.bind_template();
|
|
|
|
|
klass.bind_template_instance_callbacks();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
|
|
|
|
obj.init_template();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[glib::derived_properties]
|
2025-03-01 09:57:01 +01:00
|
|
|
impl ObjectImpl for RecordingEditor {
|
2025-01-15 11:23:04 +01:00
|
|
|
fn signals() -> &'static [Signal] {
|
|
|
|
|
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
|
|
|
|
vec![Signal::builder("created")
|
|
|
|
|
.param_types([Recording::static_type()])
|
|
|
|
|
.build()]
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
SIGNALS.as_ref()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn constructed(&self) {
|
|
|
|
|
self.parent_constructed();
|
|
|
|
|
|
2025-03-01 09:57:01 +01:00
|
|
|
let work_selector_popover = WorkSelectorPopover::new(self.library.get().unwrap());
|
2025-01-15 11:23:04 +01:00
|
|
|
|
|
|
|
|
let obj = self.obj().clone();
|
|
|
|
|
work_selector_popover.connect_selected(move |_, work| {
|
|
|
|
|
obj.set_work(work);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let obj = self.obj().clone();
|
|
|
|
|
work_selector_popover.connect_create(move |_| {
|
2025-03-01 09:57:01 +01:00
|
|
|
let editor = WorkEditor::new(&obj.navigation(), &obj.library(), None, false);
|
2025-01-15 11:23:04 +01:00
|
|
|
|
|
|
|
|
editor.connect_created(clone!(
|
|
|
|
|
#[weak]
|
|
|
|
|
obj,
|
|
|
|
|
move |_, work| {
|
|
|
|
|
obj.set_work(work);
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
obj.navigation().push(&editor);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
self.select_work_box.append(&work_selector_popover);
|
|
|
|
|
self.work_selector_popover
|
|
|
|
|
.set(work_selector_popover)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2025-03-01 09:57:01 +01:00
|
|
|
let persons_popover = PersonSelectorPopover::new(self.library.get().unwrap());
|
2025-01-15 11:23:04 +01:00
|
|
|
|
|
|
|
|
let obj = self.obj().clone();
|
|
|
|
|
persons_popover.connect_person_selected(move |_, person| {
|
2025-01-17 17:54:16 +01:00
|
|
|
obj.new_performer(person);
|
2025-01-15 11:23:04 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let obj = self.obj().clone();
|
|
|
|
|
persons_popover.connect_create(move |_| {
|
2025-03-01 09:57:01 +01:00
|
|
|
let editor = PersonEditor::new(&obj.navigation(), &obj.library(), None);
|
2025-01-15 11:23:04 +01:00
|
|
|
|
|
|
|
|
editor.connect_created(clone!(
|
|
|
|
|
#[weak]
|
|
|
|
|
obj,
|
|
|
|
|
move |_, person| {
|
2025-01-17 17:54:16 +01:00
|
|
|
obj.new_performer(person);
|
2025-01-15 11:23:04 +01:00
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
obj.navigation().push(&editor);
|
|
|
|
|
});
|
|
|
|
|
|
2025-03-01 11:13:29 +01:00
|
|
|
self.performers_box.append(&persons_popover);
|
2025-01-15 11:23:04 +01:00
|
|
|
self.persons_popover.set(persons_popover).unwrap();
|
|
|
|
|
|
2025-03-01 09:57:01 +01:00
|
|
|
let ensembles_popover = EnsembleSelectorPopover::new(self.library.get().unwrap());
|
2025-01-15 11:23:04 +01:00
|
|
|
|
|
|
|
|
let obj = self.obj().clone();
|
|
|
|
|
ensembles_popover.connect_ensemble_selected(move |_, ensemble| {
|
2025-01-17 17:54:16 +01:00
|
|
|
obj.new_ensemble_performer(ensemble);
|
2025-01-15 11:23:04 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let obj = self.obj().clone();
|
|
|
|
|
ensembles_popover.connect_create(move |_| {
|
2025-03-01 09:57:01 +01:00
|
|
|
let editor = EnsembleEditor::new(&obj.navigation(), &obj.library(), None);
|
2025-01-15 11:23:04 +01:00
|
|
|
|
|
|
|
|
editor.connect_created(clone!(
|
|
|
|
|
#[weak]
|
|
|
|
|
obj,
|
|
|
|
|
move |_, ensemble| {
|
2025-01-17 17:54:16 +01:00
|
|
|
obj.new_ensemble_performer(ensemble);
|
2025-01-15 11:23:04 +01:00
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
obj.navigation().push(&editor);
|
|
|
|
|
});
|
|
|
|
|
|
2025-03-01 11:13:29 +01:00
|
|
|
self.ensembles_box.append(&ensembles_popover);
|
2025-01-15 11:23:04 +01:00
|
|
|
self.ensembles_popover.set(ensembles_popover).unwrap();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-01 09:57:01 +01:00
|
|
|
impl WidgetImpl for RecordingEditor {}
|
|
|
|
|
impl NavigationPageImpl for RecordingEditor {}
|
2025-01-15 11:23:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glib::wrapper! {
|
2025-03-01 09:57:01 +01:00
|
|
|
pub struct RecordingEditor(ObjectSubclass<imp::RecordingEditor>)
|
2025-01-15 11:23:04 +01:00
|
|
|
@extends gtk::Widget, adw::NavigationPage;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[gtk::template_callbacks]
|
2025-03-01 09:57:01 +01:00
|
|
|
impl RecordingEditor {
|
2025-01-15 11:23:04 +01:00
|
|
|
pub fn new(
|
|
|
|
|
navigation: &adw::NavigationView,
|
2025-03-01 09:57:01 +01:00
|
|
|
library: &Library,
|
2025-01-15 11:23:04 +01:00
|
|
|
recording: Option<&Recording>,
|
|
|
|
|
) -> Self {
|
|
|
|
|
let obj: Self = glib::Object::builder()
|
|
|
|
|
.property("navigation", navigation)
|
|
|
|
|
.property("library", library)
|
|
|
|
|
.build();
|
|
|
|
|
|
|
|
|
|
if let Some(recording) = recording {
|
2025-03-01 08:34:53 +01:00
|
|
|
obj.imp().save_row.set_title(&gettext("_Save changes"));
|
2025-01-15 11:23:04 +01:00
|
|
|
obj.imp()
|
|
|
|
|
.recording_id
|
|
|
|
|
.set(recording.recording_id.clone())
|
|
|
|
|
.unwrap();
|
2025-01-17 17:54:16 +01:00
|
|
|
|
|
|
|
|
obj.set_work(recording.work.clone());
|
|
|
|
|
|
|
|
|
|
if let Some(year) = recording.year {
|
|
|
|
|
obj.imp().year_row.set_value(year as f64);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for performer in recording.persons.clone() {
|
|
|
|
|
obj.add_performer_row(performer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for ensemble_performer in recording.ensembles.clone() {
|
|
|
|
|
obj.add_ensemble_row(ensemble_performer);
|
|
|
|
|
}
|
2025-01-15 11:23:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
obj
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 08:14:46 +01:00
|
|
|
pub fn connect_created<F: Fn(&Self, Recording) + 'static>(
|
|
|
|
|
&self,
|
|
|
|
|
f: F,
|
|
|
|
|
) -> glib::SignalHandlerId {
|
|
|
|
|
self.connect_local("created", true, move |values| {
|
|
|
|
|
let obj = values[0].get::<Self>().unwrap();
|
|
|
|
|
let recording = values[1].get::<Recording>().unwrap();
|
|
|
|
|
f(&obj, recording);
|
|
|
|
|
None
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-15 11:23:04 +01:00
|
|
|
#[template_callback]
|
2025-03-01 08:55:04 +01:00
|
|
|
fn select_work(&self) {
|
2025-01-15 11:23:04 +01:00
|
|
|
self.imp().work_selector_popover.get().unwrap().popup();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[template_callback]
|
2025-03-01 08:55:04 +01:00
|
|
|
fn select_person(&self) {
|
2025-01-15 11:23:04 +01:00
|
|
|
self.imp().persons_popover.get().unwrap().popup();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[template_callback]
|
2025-03-01 08:55:04 +01:00
|
|
|
fn select_ensemble(&self) {
|
2025-01-15 11:23:04 +01:00
|
|
|
self.imp().ensembles_popover.get().unwrap().popup();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_work(&self, work: Work) {
|
|
|
|
|
self.imp().work_row.set_title(&work.name.get());
|
2025-02-16 08:46:40 +01:00
|
|
|
self.imp().work_row.set_subtitle(
|
|
|
|
|
&work
|
|
|
|
|
.composers_string()
|
|
|
|
|
.unwrap_or_else(|| gettext("No composers")),
|
|
|
|
|
);
|
2025-04-27 15:22:04 +02:00
|
|
|
|
|
|
|
|
self.imp()
|
|
|
|
|
.enable_updates_row
|
|
|
|
|
.set_active(work.enable_updates);
|
|
|
|
|
|
2025-03-29 17:53:04 +01:00
|
|
|
self.imp().save_row.set_sensitive(true);
|
2025-01-15 11:23:04 +01:00
|
|
|
self.imp().work.replace(Some(work));
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-17 17:54:16 +01:00
|
|
|
fn new_performer(&self, person: Person) {
|
2025-01-15 11:23:04 +01:00
|
|
|
let performer = Performer {
|
|
|
|
|
person,
|
2025-03-29 17:29:29 +01:00
|
|
|
role: None,
|
2025-01-15 11:23:04 +01:00
|
|
|
instrument: None,
|
|
|
|
|
};
|
|
|
|
|
|
2025-01-17 17:54:16 +01:00
|
|
|
self.add_performer_row(performer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn add_performer_row(&self, performer: Performer) {
|
2025-03-01 09:57:01 +01:00
|
|
|
let row = RecordingEditorPerformerRow::new(&self.navigation(), &self.library(), performer);
|
2025-01-15 11:23:04 +01:00
|
|
|
|
2025-03-01 15:52:59 +01:00
|
|
|
row.connect_move(clone!(
|
|
|
|
|
#[weak(rename_to = this)]
|
|
|
|
|
self,
|
|
|
|
|
move |target, source| {
|
|
|
|
|
let mut performer_rows = this.imp().performer_rows.borrow_mut();
|
|
|
|
|
if let Some(index) = performer_rows.iter().position(|p| p == target) {
|
|
|
|
|
this.imp().performer_list.remove(&source);
|
|
|
|
|
performer_rows.retain(|p| p != &source);
|
|
|
|
|
this.imp().performer_list.insert(&source, index as i32);
|
|
|
|
|
performer_rows.insert(index, source);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
|
2025-01-15 11:23:04 +01:00
|
|
|
row.connect_remove(clone!(
|
|
|
|
|
#[weak(rename_to = this)]
|
|
|
|
|
self,
|
|
|
|
|
move |row| {
|
|
|
|
|
this.imp().performer_list.remove(row);
|
|
|
|
|
this.imp().performer_rows.borrow_mut().retain(|c| c != row);
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
self.imp()
|
|
|
|
|
.performer_list
|
|
|
|
|
.insert(&row, self.imp().performer_rows.borrow().len() as i32);
|
|
|
|
|
|
|
|
|
|
self.imp().performer_rows.borrow_mut().push(row);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-17 17:54:16 +01:00
|
|
|
fn new_ensemble_performer(&self, ensemble: Ensemble) {
|
|
|
|
|
let performer = EnsemblePerformer {
|
|
|
|
|
ensemble,
|
2025-03-29 17:29:29 +01:00
|
|
|
role: None,
|
2025-01-17 17:54:16 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
self.add_ensemble_row(performer);
|
|
|
|
|
}
|
2025-01-15 11:23:04 +01:00
|
|
|
|
2025-01-17 17:54:16 +01:00
|
|
|
fn add_ensemble_row(&self, ensemble_performer: EnsemblePerformer) {
|
2025-03-01 09:57:01 +01:00
|
|
|
let row = RecordingEditorEnsembleRow::new(
|
2025-01-17 17:54:16 +01:00
|
|
|
&self.navigation(),
|
|
|
|
|
&self.library(),
|
|
|
|
|
ensemble_performer,
|
|
|
|
|
);
|
2025-01-15 11:23:04 +01:00
|
|
|
|
2025-03-01 15:52:59 +01:00
|
|
|
row.connect_move(clone!(
|
|
|
|
|
#[weak(rename_to = this)]
|
|
|
|
|
self,
|
|
|
|
|
move |target, source| {
|
|
|
|
|
let mut ensemble_rows = this.imp().ensemble_rows.borrow_mut();
|
|
|
|
|
if let Some(index) = ensemble_rows.iter().position(|p| p == target) {
|
|
|
|
|
this.imp().ensemble_list.remove(&source);
|
|
|
|
|
ensemble_rows.retain(|p| p != &source);
|
|
|
|
|
this.imp().ensemble_list.insert(&source, index as i32);
|
|
|
|
|
ensemble_rows.insert(index, source);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
|
2025-01-15 11:23:04 +01:00
|
|
|
row.connect_remove(clone!(
|
|
|
|
|
#[weak(rename_to = this)]
|
|
|
|
|
self,
|
|
|
|
|
move |row| {
|
|
|
|
|
this.imp().ensemble_list.remove(row);
|
|
|
|
|
this.imp().ensemble_rows.borrow_mut().retain(|c| c != row);
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
self.imp()
|
|
|
|
|
.ensemble_list
|
|
|
|
|
.insert(&row, self.imp().ensemble_rows.borrow().len() as i32);
|
|
|
|
|
|
|
|
|
|
self.imp().ensemble_rows.borrow_mut().push(row);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[template_callback]
|
2025-02-16 16:30:24 +01:00
|
|
|
fn save(&self) {
|
2025-03-29 17:53:04 +01:00
|
|
|
if let Some(work) = &*self.imp().work.borrow() {
|
|
|
|
|
let library = self.imp().library.get().unwrap();
|
|
|
|
|
|
|
|
|
|
let work = work.to_owned();
|
|
|
|
|
let year = self.imp().year_row.value() as i32;
|
|
|
|
|
|
|
|
|
|
let performers = self
|
|
|
|
|
.imp()
|
|
|
|
|
.performer_rows
|
|
|
|
|
.borrow()
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|p| p.performer())
|
|
|
|
|
.collect::<Vec<Performer>>();
|
|
|
|
|
|
|
|
|
|
let ensembles = self
|
|
|
|
|
.imp()
|
|
|
|
|
.ensemble_rows
|
|
|
|
|
.borrow()
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|e| e.ensemble())
|
|
|
|
|
.collect::<Vec<EnsemblePerformer>>();
|
|
|
|
|
|
2025-04-27 15:22:04 +02:00
|
|
|
let enable_updates = self.imp().enable_updates_row.is_active();
|
|
|
|
|
|
2025-03-29 17:53:04 +01:00
|
|
|
if let Some(recording_id) = self.imp().recording_id.get() {
|
|
|
|
|
library
|
2025-04-27 15:22:04 +02:00
|
|
|
.update_recording(
|
|
|
|
|
recording_id,
|
|
|
|
|
work,
|
|
|
|
|
Some(year),
|
|
|
|
|
performers,
|
|
|
|
|
ensembles,
|
|
|
|
|
enable_updates,
|
|
|
|
|
)
|
2025-03-29 17:53:04 +01:00
|
|
|
.unwrap();
|
|
|
|
|
} else {
|
|
|
|
|
let recording = library
|
2025-04-27 15:22:04 +02:00
|
|
|
.create_recording(work, Some(year), performers, ensembles, enable_updates)
|
2025-03-29 17:53:04 +01:00
|
|
|
.unwrap();
|
|
|
|
|
self.emit_by_name::<()>("created", &[&recording]);
|
|
|
|
|
}
|
2025-01-15 11:23:04 +01:00
|
|
|
|
2025-03-29 17:53:04 +01:00
|
|
|
self.imp().navigation.get().unwrap().pop();
|
|
|
|
|
}
|
2025-01-15 11:23:04 +01:00
|
|
|
}
|
|
|
|
|
}
|