musicus/src/editor/recording.rs

383 lines
12 KiB
Rust
Raw Normal View History

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-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| {
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| {
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| {
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| {
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();
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]
fn select_work(&self) {
2025-01-15 11:23:04 +01:00
self.imp().work_selector_popover.get().unwrap().popup();
}
#[template_callback]
fn select_person(&self) {
2025-01-15 11:23:04 +01:00
self.imp().persons_popover.get().unwrap().popup();
}
#[template_callback]
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());
self.imp().work_row.set_subtitle(
&work
.composers_string()
.unwrap_or_else(|| gettext("No composers")),
);
2025-01-15 11:23:04 +01:00
self.imp().work.replace(Some(work));
}
fn new_performer(&self, person: Person) {
2025-01-15 11:23:04 +01:00
let performer = Performer {
person,
role: None,
2025-01-15 11:23:04 +01:00
instrument: None,
};
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
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);
}
fn new_ensemble_performer(&self, ensemble: Ensemble) {
let performer = EnsemblePerformer {
ensemble,
role: None,
};
self.add_ensemble_row(performer);
}
2025-01-15 11:23:04 +01:00
fn add_ensemble_row(&self, ensemble_performer: EnsemblePerformer) {
2025-03-01 09:57:01 +01:00
let row = RecordingEditorEnsembleRow::new(
&self.navigation(),
&self.library(),
ensemble_performer,
);
2025-01-15 11:23:04 +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]
fn save(&self) {
2025-01-15 11:23:04 +01:00
let library = self.imp().library.get().unwrap();
// TODO: No work selected?
let work = self.imp().work.borrow().as_ref().unwrap().clone();
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>>();
if let Some(recording_id) = self.imp().recording_id.get() {
library
.update_recording(recording_id, work, Some(year), performers, ensembles)
.unwrap();
} else {
let recording = library
.create_recording(work, Some(year), performers, ensembles)
.unwrap();
self.emit_by_name::<()>("created", &[&recording]);
}
self.imp().navigation.get().unwrap().pop();
}
}