mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 19:57:25 +01:00
Add work and recording editor
This commit is contained in:
parent
36b2f1097e
commit
364557d959
30 changed files with 3308 additions and 418 deletions
112
src/editor/ensemble_editor.rs
Normal file
112
src/editor/ensemble_editor.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
use std::cell::OnceCell;
|
||||
|
||||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gettextrs::gettext;
|
||||
use gtk::glib::{self, subclass::Signal};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{
|
||||
db::models::Ensemble, editor::translation_editor::MusicusTranslationEditor,
|
||||
library::MusicusLibrary,
|
||||
};
|
||||
|
||||
mod imp {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default, gtk::CompositeTemplate)]
|
||||
#[template(file = "data/ui/ensemble_editor.blp")]
|
||||
pub struct MusicusEnsembleEditor {
|
||||
pub navigation: OnceCell<adw::NavigationView>,
|
||||
pub library: OnceCell<MusicusLibrary>,
|
||||
pub ensemble_id: OnceCell<String>,
|
||||
|
||||
#[template_child]
|
||||
pub name_editor: TemplateChild<MusicusTranslationEditor>,
|
||||
#[template_child]
|
||||
pub save_button: TemplateChild<gtk::Button>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for MusicusEnsembleEditor {
|
||||
const NAME: &'static str = "MusicusEnsembleEditor";
|
||||
type Type = super::MusicusEnsembleEditor;
|
||||
type ParentType = adw::NavigationPage;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
MusicusTranslationEditor::static_type();
|
||||
klass.bind_template();
|
||||
klass.bind_template_instance_callbacks();
|
||||
}
|
||||
|
||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for MusicusEnsembleEditor {
|
||||
fn signals() -> &'static [Signal] {
|
||||
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
||||
vec![Signal::builder("created")
|
||||
.param_types([Ensemble::static_type()])
|
||||
.build()]
|
||||
});
|
||||
|
||||
SIGNALS.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for MusicusEnsembleEditor {}
|
||||
impl NavigationPageImpl for MusicusEnsembleEditor {}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct MusicusEnsembleEditor(ObjectSubclass<imp::MusicusEnsembleEditor>)
|
||||
@extends gtk::Widget, adw::NavigationPage;
|
||||
}
|
||||
|
||||
#[gtk::template_callbacks]
|
||||
impl MusicusEnsembleEditor {
|
||||
pub fn new(
|
||||
navigation: &adw::NavigationView,
|
||||
library: &MusicusLibrary,
|
||||
ensemble: Option<&Ensemble>,
|
||||
) -> Self {
|
||||
let obj: Self = glib::Object::new();
|
||||
|
||||
obj.imp().navigation.set(navigation.to_owned()).unwrap();
|
||||
obj.imp().library.set(library.to_owned()).unwrap();
|
||||
|
||||
if let Some(ensemble) = ensemble {
|
||||
obj.imp().save_button.set_label(&gettext("Save changes"));
|
||||
obj.imp().ensemble_id.set(ensemble.ensemble_id.clone()).unwrap();
|
||||
obj.imp().name_editor.set_translation(&ensemble.name);
|
||||
}
|
||||
|
||||
obj
|
||||
}
|
||||
|
||||
pub fn connect_created<F: Fn(&Self, Ensemble) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
self.connect_local("created", true, move |values| {
|
||||
let obj = values[0].get::<Self>().unwrap();
|
||||
let ensemble = values[1].get::<Ensemble>().unwrap();
|
||||
f(&obj, ensemble);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn save(&self, _: >k::Button) {
|
||||
let library = self.imp().library.get().unwrap();
|
||||
let name = self.imp().name_editor.translation();
|
||||
|
||||
if let Some(ensemble_id) = self.imp().ensemble_id.get() {
|
||||
library.update_ensemble(ensemble_id, name).unwrap();
|
||||
} else {
|
||||
let ensemble = library.create_ensemble(name).unwrap();
|
||||
self.emit_by_name::<()>("created", &[&ensemble]);
|
||||
}
|
||||
|
||||
self.imp().navigation.get().unwrap().pop();
|
||||
}
|
||||
}
|
||||
204
src/editor/ensemble_selector_popover.rs
Normal file
204
src/editor/ensemble_selector_popover.rs
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
use crate::{db::models::Ensemble, library::MusicusLibrary};
|
||||
|
||||
use gettextrs::gettext;
|
||||
use gtk::{
|
||||
glib::{self, subclass::Signal, Properties},
|
||||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use std::cell::{OnceCell, RefCell};
|
||||
|
||||
use super::activatable_row::MusicusActivatableRow;
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default, gtk::CompositeTemplate, Properties)]
|
||||
#[properties(wrapper_type = super::MusicusEnsembleSelectorPopover)]
|
||||
#[template(file = "data/ui/ensemble_selector_popover.blp")]
|
||||
pub struct MusicusEnsembleSelectorPopover {
|
||||
#[property(get, construct_only)]
|
||||
pub library: OnceCell<MusicusLibrary>,
|
||||
|
||||
pub ensembles: RefCell<Vec<Ensemble>>,
|
||||
|
||||
#[template_child]
|
||||
pub search_entry: TemplateChild<gtk::SearchEntry>,
|
||||
#[template_child]
|
||||
pub scrolled_window: TemplateChild<gtk::ScrolledWindow>,
|
||||
#[template_child]
|
||||
pub list_box: TemplateChild<gtk::ListBox>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for MusicusEnsembleSelectorPopover {
|
||||
const NAME: &'static str = "MusicusEnsembleSelectorPopover";
|
||||
type Type = super::MusicusEnsembleSelectorPopover;
|
||||
type ParentType = gtk::Popover;
|
||||
|
||||
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]
|
||||
impl ObjectImpl for MusicusEnsembleSelectorPopover {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
self.obj()
|
||||
.connect_visible_notify(|obj: &super::MusicusEnsembleSelectorPopover| {
|
||||
if obj.is_visible() {
|
||||
obj.imp().search_entry.set_text("");
|
||||
obj.imp().search_entry.grab_focus();
|
||||
obj.imp().scrolled_window.vadjustment().set_value(0.0);
|
||||
}
|
||||
});
|
||||
|
||||
self.obj().search("");
|
||||
}
|
||||
|
||||
fn signals() -> &'static [Signal] {
|
||||
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
||||
vec![
|
||||
Signal::builder("ensemble-selected")
|
||||
.param_types([Ensemble::static_type()])
|
||||
.build(),
|
||||
Signal::builder("create").build(),
|
||||
]
|
||||
});
|
||||
|
||||
SIGNALS.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for MusicusEnsembleSelectorPopover {
|
||||
// TODO: Fix focus.
|
||||
fn focus(&self, direction_type: gtk::DirectionType) -> bool {
|
||||
if direction_type == gtk::DirectionType::Down {
|
||||
self.list_box.child_focus(direction_type)
|
||||
} else {
|
||||
self.parent_focus(direction_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PopoverImpl for MusicusEnsembleSelectorPopover {}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct MusicusEnsembleSelectorPopover(ObjectSubclass<imp::MusicusEnsembleSelectorPopover>)
|
||||
@extends gtk::Widget, gtk::Popover;
|
||||
}
|
||||
|
||||
#[gtk::template_callbacks]
|
||||
impl MusicusEnsembleSelectorPopover {
|
||||
pub fn new(library: &MusicusLibrary) -> Self {
|
||||
glib::Object::builder().property("library", library).build()
|
||||
}
|
||||
|
||||
pub fn connect_ensemble_selected<F: Fn(&Self, Ensemble) + 'static>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> glib::SignalHandlerId {
|
||||
self.connect_local("ensemble-selected", true, move |values| {
|
||||
let obj = values[0].get::<Self>().unwrap();
|
||||
let ensemble = values[1].get::<Ensemble>().unwrap();
|
||||
f(&obj, ensemble);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn connect_create<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
self.connect_local("create", true, move |values| {
|
||||
let obj = values[0].get::<Self>().unwrap();
|
||||
f(&obj);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn search_changed(&self, entry: >k::SearchEntry) {
|
||||
self.search(&entry.text());
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn activate(&self, _: >k::SearchEntry) {
|
||||
if let Some(ensemble) = self.imp().ensembles.borrow().first() {
|
||||
self.select(ensemble.clone());
|
||||
} else {
|
||||
self.create();
|
||||
}
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn stop_search(&self, _: >k::SearchEntry) {
|
||||
self.popdown();
|
||||
}
|
||||
|
||||
fn search(&self, search: &str) {
|
||||
let imp = self.imp();
|
||||
|
||||
let ensembles = imp
|
||||
.library
|
||||
.get()
|
||||
.unwrap()
|
||||
.search_ensembles(search)
|
||||
.unwrap();
|
||||
|
||||
imp.list_box.remove_all();
|
||||
|
||||
for ensemble in &ensembles {
|
||||
let row = MusicusActivatableRow::new(
|
||||
>k::Label::builder()
|
||||
.label(ensemble.to_string())
|
||||
.halign(gtk::Align::Start)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let ensemble = ensemble.clone();
|
||||
let obj = self.clone();
|
||||
row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||
obj.select(ensemble.clone());
|
||||
});
|
||||
|
||||
imp.list_box.append(&row);
|
||||
}
|
||||
|
||||
let create_box = gtk::Box::builder().spacing(12).build();
|
||||
create_box.append(>k::Image::builder().icon_name("list-add-symbolic").build());
|
||||
create_box.append(
|
||||
>k::Label::builder()
|
||||
.label(gettext("Create new ensemble"))
|
||||
.halign(gtk::Align::Start)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let create_row = MusicusActivatableRow::new(&create_box);
|
||||
let obj = self.clone();
|
||||
create_row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||
obj.create();
|
||||
});
|
||||
|
||||
imp.list_box.append(&create_row);
|
||||
|
||||
imp.ensembles.replace(ensembles);
|
||||
}
|
||||
|
||||
fn select(&self, ensemble: Ensemble) {
|
||||
self.emit_by_name::<()>("ensemble-selected", &[&ensemble]);
|
||||
self.popdown();
|
||||
}
|
||||
|
||||
fn create(&self) {
|
||||
self.emit_by_name::<()>("create", &[]);
|
||||
self.popdown();
|
||||
}
|
||||
}
|
||||
112
src/editor/instrument_editor.rs
Normal file
112
src/editor/instrument_editor.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
use std::cell::OnceCell;
|
||||
|
||||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gettextrs::gettext;
|
||||
use gtk::glib::{self, subclass::Signal};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{
|
||||
db::models::Instrument, editor::translation_editor::MusicusTranslationEditor,
|
||||
library::MusicusLibrary,
|
||||
};
|
||||
|
||||
mod imp {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default, gtk::CompositeTemplate)]
|
||||
#[template(file = "data/ui/instrument_editor.blp")]
|
||||
pub struct MusicusInstrumentEditor {
|
||||
pub navigation: OnceCell<adw::NavigationView>,
|
||||
pub library: OnceCell<MusicusLibrary>,
|
||||
pub instrument_id: OnceCell<String>,
|
||||
|
||||
#[template_child]
|
||||
pub name_editor: TemplateChild<MusicusTranslationEditor>,
|
||||
#[template_child]
|
||||
pub save_button: TemplateChild<gtk::Button>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for MusicusInstrumentEditor {
|
||||
const NAME: &'static str = "MusicusInstrumentEditor";
|
||||
type Type = super::MusicusInstrumentEditor;
|
||||
type ParentType = adw::NavigationPage;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
MusicusTranslationEditor::static_type();
|
||||
klass.bind_template();
|
||||
klass.bind_template_instance_callbacks();
|
||||
}
|
||||
|
||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for MusicusInstrumentEditor {
|
||||
fn signals() -> &'static [Signal] {
|
||||
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
||||
vec![Signal::builder("created")
|
||||
.param_types([Instrument::static_type()])
|
||||
.build()]
|
||||
});
|
||||
|
||||
SIGNALS.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for MusicusInstrumentEditor {}
|
||||
impl NavigationPageImpl for MusicusInstrumentEditor {}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct MusicusInstrumentEditor(ObjectSubclass<imp::MusicusInstrumentEditor>)
|
||||
@extends gtk::Widget, adw::NavigationPage;
|
||||
}
|
||||
|
||||
#[gtk::template_callbacks]
|
||||
impl MusicusInstrumentEditor {
|
||||
pub fn new(
|
||||
navigation: &adw::NavigationView,
|
||||
library: &MusicusLibrary,
|
||||
instrument: Option<&Instrument>,
|
||||
) -> Self {
|
||||
let obj: Self = glib::Object::new();
|
||||
|
||||
obj.imp().navigation.set(navigation.to_owned()).unwrap();
|
||||
obj.imp().library.set(library.to_owned()).unwrap();
|
||||
|
||||
if let Some(instrument) = instrument {
|
||||
obj.imp().save_button.set_label(&gettext("Save changes"));
|
||||
obj.imp().instrument_id.set(instrument.instrument_id.clone()).unwrap();
|
||||
obj.imp().name_editor.set_translation(&instrument.name);
|
||||
}
|
||||
|
||||
obj
|
||||
}
|
||||
|
||||
pub fn connect_created<F: Fn(&Self, Instrument) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
self.connect_local("created", true, move |values| {
|
||||
let obj = values[0].get::<Self>().unwrap();
|
||||
let instrument = values[1].get::<Instrument>().unwrap();
|
||||
f(&obj, instrument);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn save(&self, _: >k::Button) {
|
||||
let library = self.imp().library.get().unwrap();
|
||||
let name = self.imp().name_editor.translation();
|
||||
|
||||
if let Some(instrument_id) = self.imp().instrument_id.get() {
|
||||
library.update_instrument(instrument_id, name).unwrap();
|
||||
} else {
|
||||
let instrument = library.create_instrument(name).unwrap();
|
||||
self.emit_by_name::<()>("created", &[&instrument]);
|
||||
}
|
||||
|
||||
self.imp().navigation.get().unwrap().pop();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,18 @@
|
|||
pub mod activatable_row;
|
||||
pub mod ensemble_editor;
|
||||
pub mod ensemble_selector_popover;
|
||||
pub mod instrument_editor;
|
||||
pub mod instrument_selector_popover;
|
||||
pub mod performer_role_selector_popover;
|
||||
pub mod person_editor;
|
||||
pub mod person_selector_popover;
|
||||
pub mod recording_editor;
|
||||
pub mod recording_editor_ensemble_row;
|
||||
pub mod recording_editor_performer_row;
|
||||
pub mod role_editor;
|
||||
pub mod role_selector_popover;
|
||||
pub mod translation_editor;
|
||||
pub mod translation_entry;
|
||||
pub mod work_editor;
|
||||
pub mod work_editor_composer_row;
|
||||
pub mod work_editor_composer_row;
|
||||
pub mod work_selector_popover;
|
||||
|
|
|
|||
328
src/editor/performer_role_selector_popover.rs
Normal file
328
src/editor/performer_role_selector_popover.rs
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
use crate::{
|
||||
db::models::{Instrument, Role},
|
||||
library::MusicusLibrary,
|
||||
};
|
||||
|
||||
use gettextrs::gettext;
|
||||
use gtk::{
|
||||
glib::{self, subclass::Signal, Properties},
|
||||
pango,
|
||||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use std::cell::{OnceCell, RefCell};
|
||||
|
||||
use super::activatable_row::MusicusActivatableRow;
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default, gtk::CompositeTemplate, Properties)]
|
||||
#[properties(wrapper_type = super::MusicusPerformerRoleSelectorPopover)]
|
||||
#[template(file = "data/ui/performer_role_selector_popover.blp")]
|
||||
pub struct MusicusPerformerRoleSelectorPopover {
|
||||
#[property(get, construct_only)]
|
||||
pub library: OnceCell<MusicusLibrary>,
|
||||
|
||||
pub roles: RefCell<Vec<Role>>,
|
||||
pub instruments: RefCell<Vec<Instrument>>,
|
||||
|
||||
#[template_child]
|
||||
pub stack: TemplateChild<gtk::Stack>,
|
||||
#[template_child]
|
||||
pub role_view: TemplateChild<adw::ToolbarView>,
|
||||
#[template_child]
|
||||
pub role_search_entry: TemplateChild<gtk::SearchEntry>,
|
||||
#[template_child]
|
||||
pub role_scrolled_window: TemplateChild<gtk::ScrolledWindow>,
|
||||
#[template_child]
|
||||
pub role_list: TemplateChild<gtk::ListBox>,
|
||||
#[template_child]
|
||||
pub instrument_view: TemplateChild<adw::ToolbarView>,
|
||||
#[template_child]
|
||||
pub instrument_search_entry: TemplateChild<gtk::SearchEntry>,
|
||||
#[template_child]
|
||||
pub instrument_scrolled_window: TemplateChild<gtk::ScrolledWindow>,
|
||||
#[template_child]
|
||||
pub instrument_list: TemplateChild<gtk::ListBox>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for MusicusPerformerRoleSelectorPopover {
|
||||
const NAME: &'static str = "MusicusPerformerRoleSelectorPopover";
|
||||
type Type = super::MusicusPerformerRoleSelectorPopover;
|
||||
type ParentType = gtk::Popover;
|
||||
|
||||
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]
|
||||
impl ObjectImpl for MusicusPerformerRoleSelectorPopover {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
self.obj().connect_visible_notify(
|
||||
|obj: &super::MusicusPerformerRoleSelectorPopover| {
|
||||
if obj.is_visible() {
|
||||
obj.imp().stack.set_visible_child(&*obj.imp().role_view);
|
||||
obj.imp().role_search_entry.set_text("");
|
||||
obj.imp().role_search_entry.grab_focus();
|
||||
obj.imp().role_scrolled_window.vadjustment().set_value(0.0);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
self.obj().search_roles("");
|
||||
}
|
||||
|
||||
fn signals() -> &'static [Signal] {
|
||||
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
||||
vec![
|
||||
Signal::builder("selected")
|
||||
.param_types([Role::static_type(), Instrument::static_type()])
|
||||
.build(),
|
||||
Signal::builder("create-role").build(),
|
||||
Signal::builder("create-instrument").build(),
|
||||
]
|
||||
});
|
||||
|
||||
SIGNALS.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for MusicusPerformerRoleSelectorPopover {
|
||||
// TODO: Fix focus.
|
||||
fn focus(&self, direction_type: gtk::DirectionType) -> bool {
|
||||
if direction_type == gtk::DirectionType::Down {
|
||||
if self.stack.visible_child() == Some(self.role_list.get().upcast()) {
|
||||
self.role_list.child_focus(direction_type)
|
||||
} else {
|
||||
self.instrument_list.child_focus(direction_type)
|
||||
}
|
||||
} else {
|
||||
self.parent_focus(direction_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PopoverImpl for MusicusPerformerRoleSelectorPopover {}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct MusicusPerformerRoleSelectorPopover(ObjectSubclass<imp::MusicusPerformerRoleSelectorPopover>)
|
||||
@extends gtk::Widget, gtk::Popover;
|
||||
}
|
||||
|
||||
#[gtk::template_callbacks]
|
||||
impl MusicusPerformerRoleSelectorPopover {
|
||||
pub fn new(library: &MusicusLibrary) -> Self {
|
||||
glib::Object::builder().property("library", library).build()
|
||||
}
|
||||
|
||||
pub fn connect_selected<F: Fn(&Self, Role, Option<Instrument>) + 'static>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> glib::SignalHandlerId {
|
||||
self.connect_local("selected", true, move |values| {
|
||||
let obj = values[0].get::<Self>().unwrap();
|
||||
let role = values[1].get::<Role>().unwrap();
|
||||
let instrument = values[2].get::<Option<Instrument>>().unwrap();
|
||||
f(&obj, role, instrument);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn connect_create_role<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
self.connect_local("create-role", true, move |values| {
|
||||
let obj = values[0].get::<Self>().unwrap();
|
||||
f(&obj);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn connect_create_instrument<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
self.connect_local("create-instrument", true, move |values| {
|
||||
let obj = values[0].get::<Self>().unwrap();
|
||||
f(&obj);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn role_search_changed(&self, entry: >k::SearchEntry) {
|
||||
self.search_roles(&entry.text());
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn role_activate(&self, _: >k::SearchEntry) {
|
||||
if let Some(role) = self.imp().roles.borrow().first() {
|
||||
self.select_role(role.to_owned());
|
||||
} else {
|
||||
self.create_role();
|
||||
}
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn back_button_clicked(&self, _: >k::Button) {
|
||||
self.imp().stack.set_visible_child(&*self.imp().role_view);
|
||||
self.imp().role_search_entry.grab_focus();
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn instrument_search_changed(&self, entry: >k::SearchEntry) {
|
||||
self.search_instruments(&entry.text());
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn instrument_activate(&self, _: >k::SearchEntry) {
|
||||
if let Some(instrument) = self.imp().instruments.borrow().first() {
|
||||
self.select_instrument(instrument.clone());
|
||||
} else {
|
||||
self.create_instrument();
|
||||
}
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn stop_search(&self, _: >k::SearchEntry) {
|
||||
self.popdown();
|
||||
}
|
||||
|
||||
fn search_roles(&self, search: &str) {
|
||||
let imp = self.imp();
|
||||
|
||||
let roles = imp.library.get().unwrap().search_roles(search).unwrap();
|
||||
|
||||
imp.role_list.remove_all();
|
||||
|
||||
for role in &roles {
|
||||
let row = MusicusActivatableRow::new(
|
||||
>k::Label::builder()
|
||||
.label(role.to_string())
|
||||
.halign(gtk::Align::Start)
|
||||
.ellipsize(pango::EllipsizeMode::Middle)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let role = role.clone();
|
||||
let obj = self.clone();
|
||||
row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||
obj.select_role(role.clone());
|
||||
});
|
||||
|
||||
imp.role_list.append(&row);
|
||||
}
|
||||
|
||||
let create_box = gtk::Box::builder().spacing(12).build();
|
||||
create_box.append(>k::Image::builder().icon_name("list-add-symbolic").build());
|
||||
create_box.append(
|
||||
>k::Label::builder()
|
||||
.label(gettext("Create new role"))
|
||||
.halign(gtk::Align::Start)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let create_row = MusicusActivatableRow::new(&create_box);
|
||||
let obj = self.clone();
|
||||
create_row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||
obj.create_role();
|
||||
});
|
||||
|
||||
imp.role_list.append(&create_row);
|
||||
|
||||
imp.roles.replace(roles);
|
||||
}
|
||||
|
||||
fn search_instruments(&self, search: &str) {
|
||||
let imp = self.imp();
|
||||
|
||||
let instruments = imp
|
||||
.library
|
||||
.get()
|
||||
.unwrap()
|
||||
.search_instruments(search)
|
||||
.unwrap();
|
||||
|
||||
imp.instrument_list.remove_all();
|
||||
|
||||
for instrument in &instruments {
|
||||
let row = MusicusActivatableRow::new(
|
||||
>k::Label::builder()
|
||||
.label(instrument.name.get())
|
||||
.halign(gtk::Align::Start)
|
||||
.ellipsize(pango::EllipsizeMode::Middle)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let instrument = instrument.clone();
|
||||
let obj = self.clone();
|
||||
row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||
obj.select_instrument(instrument.clone());
|
||||
});
|
||||
|
||||
imp.instrument_list.append(&row);
|
||||
}
|
||||
|
||||
let create_box = gtk::Box::builder().spacing(12).build();
|
||||
create_box.append(>k::Image::builder().icon_name("list-add-symbolic").build());
|
||||
create_box.append(
|
||||
>k::Label::builder()
|
||||
.label(gettext("Create new instrument"))
|
||||
.halign(gtk::Align::Start)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let create_row = MusicusActivatableRow::new(&create_box);
|
||||
let obj = self.clone();
|
||||
create_row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||
obj.create_instrument();
|
||||
});
|
||||
|
||||
imp.instrument_list.append(&create_row);
|
||||
|
||||
imp.instruments.replace(instruments);
|
||||
}
|
||||
|
||||
fn select_role(&self, role: Role) {
|
||||
if role == self.library().performer_default_role().unwrap() {
|
||||
self.imp().instrument_search_entry.set_text("");
|
||||
self.imp().instrument_search_entry.grab_focus();
|
||||
self.imp()
|
||||
.instrument_scrolled_window
|
||||
.vadjustment()
|
||||
.set_value(0.0);
|
||||
self.imp()
|
||||
.stack
|
||||
.set_visible_child(&*self.imp().instrument_view);
|
||||
|
||||
self.search_instruments("");
|
||||
} else {
|
||||
self.emit_by_name::<()>("selected", &[&role, &None::<Instrument>]);
|
||||
self.popdown();
|
||||
}
|
||||
}
|
||||
|
||||
fn select_instrument(&self, instrument: Instrument) {
|
||||
let role = self.library().performer_default_role().unwrap();
|
||||
self.emit_by_name::<()>("selected", &[&role, &instrument]);
|
||||
self.popdown();
|
||||
}
|
||||
|
||||
fn create_role(&self) {
|
||||
self.emit_by_name::<()>("create-role", &[]);
|
||||
self.popdown();
|
||||
}
|
||||
|
||||
fn create_instrument(&self) {
|
||||
self.emit_by_name::<()>("create-instrument", &[]);
|
||||
self.popdown();
|
||||
}
|
||||
}
|
||||
317
src/editor/recording_editor.rs
Normal file
317
src/editor/recording_editor.rs
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
use std::cell::{OnceCell, RefCell};
|
||||
|
||||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gettextrs::gettext;
|
||||
use gtk::glib::{self, clone, subclass::Signal, Properties};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{
|
||||
db::models::{Ensemble, EnsemblePerformer, Performer, Person, Recording, Work},
|
||||
editor::{
|
||||
ensemble_editor::MusicusEnsembleEditor,
|
||||
ensemble_selector_popover::MusicusEnsembleSelectorPopover,
|
||||
person_editor::MusicusPersonEditor, person_selector_popover::MusicusPersonSelectorPopover,
|
||||
recording_editor_ensemble_row::MusicusRecordingEditorEnsembleRow,
|
||||
recording_editor_performer_row::MusicusRecordingEditorPerformerRow,
|
||||
work_selector_popover::MusicusWorkSelectorPopover,
|
||||
},
|
||||
library::MusicusLibrary,
|
||||
};
|
||||
|
||||
mod imp {
|
||||
use crate::editor::work_editor::MusicusWorkEditor;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default, gtk::CompositeTemplate, Properties)]
|
||||
#[properties(wrapper_type = super::MusicusRecordingEditor)]
|
||||
#[template(file = "data/ui/recording_editor.blp")]
|
||||
pub struct MusicusRecordingEditor {
|
||||
#[property(get, construct_only)]
|
||||
pub navigation: OnceCell<adw::NavigationView>,
|
||||
|
||||
#[property(get, construct_only)]
|
||||
pub library: OnceCell<MusicusLibrary>,
|
||||
|
||||
pub recording_id: OnceCell<String>,
|
||||
|
||||
pub work: RefCell<Option<Work>>,
|
||||
pub performer_rows: RefCell<Vec<MusicusRecordingEditorPerformerRow>>,
|
||||
pub ensemble_rows: RefCell<Vec<MusicusRecordingEditorEnsembleRow>>,
|
||||
|
||||
pub work_selector_popover: OnceCell<MusicusWorkSelectorPopover>,
|
||||
pub persons_popover: OnceCell<MusicusPersonSelectorPopover>,
|
||||
pub ensembles_popover: OnceCell<MusicusEnsembleSelectorPopover>,
|
||||
|
||||
#[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]
|
||||
pub performer_list: TemplateChild<gtk::ListBox>,
|
||||
#[template_child]
|
||||
pub select_person_box: TemplateChild<gtk::Box>,
|
||||
#[template_child]
|
||||
pub ensemble_list: TemplateChild<gtk::ListBox>,
|
||||
#[template_child]
|
||||
pub select_ensemble_box: TemplateChild<gtk::Box>,
|
||||
#[template_child]
|
||||
pub save_button: TemplateChild<gtk::Button>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for MusicusRecordingEditor {
|
||||
const NAME: &'static str = "MusicusRecordingEditor";
|
||||
type Type = super::MusicusRecordingEditor;
|
||||
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]
|
||||
impl ObjectImpl for MusicusRecordingEditor {
|
||||
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();
|
||||
|
||||
let work_selector_popover =
|
||||
MusicusWorkSelectorPopover::new(self.library.get().unwrap());
|
||||
|
||||
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 |_| {
|
||||
let editor = MusicusWorkEditor::new(&obj.navigation(), &obj.library(), None);
|
||||
|
||||
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();
|
||||
|
||||
let persons_popover = MusicusPersonSelectorPopover::new(self.library.get().unwrap());
|
||||
|
||||
let obj = self.obj().clone();
|
||||
persons_popover.connect_person_selected(move |_, person| {
|
||||
obj.add_performer(person);
|
||||
});
|
||||
|
||||
let obj = self.obj().clone();
|
||||
persons_popover.connect_create(move |_| {
|
||||
let editor = MusicusPersonEditor::new(&obj.navigation(), &obj.library(), None);
|
||||
|
||||
editor.connect_created(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_, person| {
|
||||
obj.add_performer(person);
|
||||
}
|
||||
));
|
||||
|
||||
obj.navigation().push(&editor);
|
||||
});
|
||||
|
||||
self.select_person_box.append(&persons_popover);
|
||||
self.persons_popover.set(persons_popover).unwrap();
|
||||
|
||||
let ensembles_popover =
|
||||
MusicusEnsembleSelectorPopover::new(self.library.get().unwrap());
|
||||
|
||||
let obj = self.obj().clone();
|
||||
ensembles_popover.connect_ensemble_selected(move |_, ensemble| {
|
||||
obj.add_ensemble(ensemble);
|
||||
});
|
||||
|
||||
let obj = self.obj().clone();
|
||||
ensembles_popover.connect_create(move |_| {
|
||||
let editor = MusicusEnsembleEditor::new(&obj.navigation(), &obj.library(), None);
|
||||
|
||||
editor.connect_created(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_, ensemble| {
|
||||
obj.add_ensemble(ensemble);
|
||||
}
|
||||
));
|
||||
|
||||
obj.navigation().push(&editor);
|
||||
});
|
||||
|
||||
self.select_ensemble_box.append(&ensembles_popover);
|
||||
self.ensembles_popover.set(ensembles_popover).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for MusicusRecordingEditor {}
|
||||
impl NavigationPageImpl for MusicusRecordingEditor {}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct MusicusRecordingEditor(ObjectSubclass<imp::MusicusRecordingEditor>)
|
||||
@extends gtk::Widget, adw::NavigationPage;
|
||||
}
|
||||
|
||||
#[gtk::template_callbacks]
|
||||
impl MusicusRecordingEditor {
|
||||
pub fn new(
|
||||
navigation: &adw::NavigationView,
|
||||
library: &MusicusLibrary,
|
||||
recording: Option<&Recording>,
|
||||
) -> Self {
|
||||
let obj: Self = glib::Object::builder()
|
||||
.property("navigation", navigation)
|
||||
.property("library", library)
|
||||
.build();
|
||||
|
||||
if let Some(recording) = recording {
|
||||
obj.imp().save_button.set_label(&gettext("Save changes"));
|
||||
obj.imp()
|
||||
.recording_id
|
||||
.set(recording.recording_id.clone())
|
||||
.unwrap();
|
||||
// TODO: Initialize data.
|
||||
}
|
||||
|
||||
obj
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn select_work(&self, _: &adw::ActionRow) {
|
||||
self.imp().work_selector_popover.get().unwrap().popup();
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn select_person(&self, _: &adw::ActionRow) {
|
||||
self.imp().persons_popover.get().unwrap().popup();
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn select_ensemble(&self, _: &adw::ActionRow) {
|
||||
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());
|
||||
self.imp().work.replace(Some(work));
|
||||
}
|
||||
|
||||
fn add_performer(&self, person: Person) {
|
||||
let role = self.library().performer_default_role().unwrap();
|
||||
let performer = Performer {
|
||||
person,
|
||||
role,
|
||||
instrument: None,
|
||||
};
|
||||
|
||||
let row =
|
||||
MusicusRecordingEditorPerformerRow::new(&self.navigation(), &self.library(), performer);
|
||||
|
||||
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 add_ensemble(&self, ensemble: Ensemble) {
|
||||
let role = self.library().performer_default_role().unwrap();
|
||||
let performer = EnsemblePerformer { ensemble, role };
|
||||
|
||||
let row =
|
||||
MusicusRecordingEditorEnsembleRow::new(&self.navigation(), &self.library(), performer);
|
||||
|
||||
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, _: >k::Button) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
149
src/editor/recording_editor_ensemble_row.rs
Normal file
149
src/editor/recording_editor_ensemble_row.rs
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
use std::cell::{OnceCell, RefCell};
|
||||
|
||||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gtk::glib::{self, clone, subclass::Signal, Properties};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{
|
||||
db::models::EnsemblePerformer,
|
||||
editor::{role_editor::MusicusRoleEditor, role_selector_popover::MusicusRoleSelectorPopover},
|
||||
library::MusicusLibrary,
|
||||
};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
#[derive(Properties, Debug, Default, gtk::CompositeTemplate)]
|
||||
#[properties(wrapper_type = super::MusicusRecordingEditorEnsembleRow)]
|
||||
#[template(file = "data/ui/recording_editor_ensemble_row.blp")]
|
||||
pub struct MusicusRecordingEditorEnsembleRow {
|
||||
#[property(get, construct_only)]
|
||||
pub navigation: OnceCell<adw::NavigationView>,
|
||||
|
||||
#[property(get, construct_only)]
|
||||
pub library: OnceCell<MusicusLibrary>,
|
||||
|
||||
pub ensemble: RefCell<Option<EnsemblePerformer>>,
|
||||
pub role_popover: OnceCell<MusicusRoleSelectorPopover>,
|
||||
|
||||
#[template_child]
|
||||
pub role_label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
pub role_box: TemplateChild<gtk::Box>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for MusicusRecordingEditorEnsembleRow {
|
||||
const NAME: &'static str = "MusicusRecordingEditorEnsembleRow";
|
||||
type Type = super::MusicusRecordingEditorEnsembleRow;
|
||||
type ParentType = adw::ActionRow;
|
||||
|
||||
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]
|
||||
impl ObjectImpl for MusicusRecordingEditorEnsembleRow {
|
||||
fn signals() -> &'static [Signal] {
|
||||
static SIGNALS: Lazy<Vec<Signal>> =
|
||||
Lazy::new(|| vec![Signal::builder("remove").build()]);
|
||||
|
||||
SIGNALS.as_ref()
|
||||
}
|
||||
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
let role_popover = MusicusRoleSelectorPopover::new(self.library.get().unwrap());
|
||||
|
||||
let obj = self.obj().to_owned();
|
||||
role_popover.connect_role_selected(move |_, role| {
|
||||
if let Some(ensemble) = &mut *obj.imp().ensemble.borrow_mut() {
|
||||
obj.imp().role_label.set_label(&role.to_string());
|
||||
ensemble.role = role;
|
||||
}
|
||||
});
|
||||
|
||||
let obj = self.obj().to_owned();
|
||||
role_popover.connect_create(move |_| {
|
||||
let editor = MusicusRoleEditor::new(&obj.navigation(), &obj.library(), None);
|
||||
|
||||
editor.connect_created(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_, role| {
|
||||
if let Some(ensemble) = &mut *obj.imp().ensemble.borrow_mut() {
|
||||
obj.imp().role_label.set_label(&role.to_string());
|
||||
ensemble.role = role;
|
||||
};
|
||||
}
|
||||
));
|
||||
|
||||
obj.navigation().push(&editor);
|
||||
});
|
||||
|
||||
self.role_box.append(&role_popover);
|
||||
self.role_popover.set(role_popover).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for MusicusRecordingEditorEnsembleRow {}
|
||||
impl ListBoxRowImpl for MusicusRecordingEditorEnsembleRow {}
|
||||
impl PreferencesRowImpl for MusicusRecordingEditorEnsembleRow {}
|
||||
impl ActionRowImpl for MusicusRecordingEditorEnsembleRow {}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct MusicusRecordingEditorEnsembleRow(ObjectSubclass<imp::MusicusRecordingEditorEnsembleRow>)
|
||||
@extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow, adw::ActionRow;
|
||||
}
|
||||
|
||||
#[gtk::template_callbacks]
|
||||
impl MusicusRecordingEditorEnsembleRow {
|
||||
pub fn new(
|
||||
navigation: &adw::NavigationView,
|
||||
library: &MusicusLibrary,
|
||||
ensemble: EnsemblePerformer,
|
||||
) -> Self {
|
||||
let obj: Self = glib::Object::builder()
|
||||
.property("navigation", navigation)
|
||||
.property("library", library)
|
||||
.build();
|
||||
obj.set_ensemble(ensemble);
|
||||
obj
|
||||
}
|
||||
|
||||
pub fn connect_remove<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
self.connect_local("remove", true, move |values| {
|
||||
let obj = values[0].get::<Self>().unwrap();
|
||||
f(&obj);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ensemble(&self) -> EnsemblePerformer {
|
||||
self.imp().ensemble.borrow().to_owned().unwrap()
|
||||
}
|
||||
|
||||
fn set_ensemble(&self, ensemble: EnsemblePerformer) {
|
||||
self.set_title(&ensemble.ensemble.to_string());
|
||||
self.imp().role_label.set_label(&ensemble.role.to_string());
|
||||
self.imp().ensemble.replace(Some(ensemble));
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn open_role_popover(&self, _: >k::Button) {
|
||||
self.imp().role_popover.get().unwrap().popup();
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn remove(&self, _: >k::Button) {
|
||||
self.emit_by_name::<()>("remove", &[]);
|
||||
}
|
||||
}
|
||||
188
src/editor/recording_editor_performer_row.rs
Normal file
188
src/editor/recording_editor_performer_row.rs
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
use std::cell::{OnceCell, RefCell};
|
||||
|
||||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gtk::glib::{self, clone, subclass::Signal, Properties};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{
|
||||
db::models::Performer,
|
||||
editor::{
|
||||
performer_role_selector_popover::MusicusPerformerRoleSelectorPopover,
|
||||
role_editor::MusicusRoleEditor,
|
||||
},
|
||||
library::MusicusLibrary,
|
||||
};
|
||||
|
||||
mod imp {
|
||||
use crate::editor::instrument_editor::MusicusInstrumentEditor;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Properties, Debug, Default, gtk::CompositeTemplate)]
|
||||
#[properties(wrapper_type = super::MusicusRecordingEditorPerformerRow)]
|
||||
#[template(file = "data/ui/recording_editor_performer_row.blp")]
|
||||
pub struct MusicusRecordingEditorPerformerRow {
|
||||
#[property(get, construct_only)]
|
||||
pub navigation: OnceCell<adw::NavigationView>,
|
||||
|
||||
#[property(get, construct_only)]
|
||||
pub library: OnceCell<MusicusLibrary>,
|
||||
|
||||
pub performer: RefCell<Option<Performer>>,
|
||||
pub role_popover: OnceCell<MusicusPerformerRoleSelectorPopover>,
|
||||
|
||||
#[template_child]
|
||||
pub role_label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
pub role_box: TemplateChild<gtk::Box>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for MusicusRecordingEditorPerformerRow {
|
||||
const NAME: &'static str = "MusicusRecordingEditorPerformerRow";
|
||||
type Type = super::MusicusRecordingEditorPerformerRow;
|
||||
type ParentType = adw::ActionRow;
|
||||
|
||||
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]
|
||||
impl ObjectImpl for MusicusRecordingEditorPerformerRow {
|
||||
fn signals() -> &'static [Signal] {
|
||||
static SIGNALS: Lazy<Vec<Signal>> =
|
||||
Lazy::new(|| vec![Signal::builder("remove").build()]);
|
||||
|
||||
SIGNALS.as_ref()
|
||||
}
|
||||
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
let role_popover =
|
||||
MusicusPerformerRoleSelectorPopover::new(self.library.get().unwrap());
|
||||
|
||||
let obj = self.obj().to_owned();
|
||||
role_popover.connect_selected(move |_, role, instrument| {
|
||||
if let Some(performer) = &mut *obj.imp().performer.borrow_mut() {
|
||||
let label = match &instrument {
|
||||
Some(instrument) => instrument.to_string(),
|
||||
None => role.to_string(),
|
||||
};
|
||||
|
||||
obj.imp().role_label.set_label(&label);
|
||||
|
||||
performer.role = role;
|
||||
performer.instrument = instrument;
|
||||
}
|
||||
});
|
||||
|
||||
let obj = self.obj().to_owned();
|
||||
role_popover.connect_create_role(move |_| {
|
||||
let editor = MusicusRoleEditor::new(&obj.navigation(), &obj.library(), None);
|
||||
|
||||
editor.connect_created(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_, role| {
|
||||
if let Some(performer) = &mut *obj.imp().performer.borrow_mut() {
|
||||
obj.imp().role_label.set_label(&role.to_string());
|
||||
performer.role = role;
|
||||
performer.instrument = None;
|
||||
};
|
||||
}
|
||||
));
|
||||
|
||||
obj.navigation().push(&editor);
|
||||
});
|
||||
|
||||
let obj = self.obj().to_owned();
|
||||
role_popover.connect_create_instrument(move |_| {
|
||||
let editor = MusicusInstrumentEditor::new(&obj.navigation(), &obj.library(), None);
|
||||
|
||||
editor.connect_created(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_, instrument| {
|
||||
if let Some(performer) = &mut *obj.imp().performer.borrow_mut() {
|
||||
obj.imp().role_label.set_label(&instrument.to_string());
|
||||
performer.role = obj.library().performer_default_role().unwrap();
|
||||
performer.instrument = Some(instrument);
|
||||
};
|
||||
}
|
||||
));
|
||||
|
||||
obj.navigation().push(&editor);
|
||||
});
|
||||
|
||||
self.role_box.append(&role_popover);
|
||||
self.role_popover.set(role_popover).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for MusicusRecordingEditorPerformerRow {}
|
||||
impl ListBoxRowImpl for MusicusRecordingEditorPerformerRow {}
|
||||
impl PreferencesRowImpl for MusicusRecordingEditorPerformerRow {}
|
||||
impl ActionRowImpl for MusicusRecordingEditorPerformerRow {}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct MusicusRecordingEditorPerformerRow(ObjectSubclass<imp::MusicusRecordingEditorPerformerRow>)
|
||||
@extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow, adw::ActionRow;
|
||||
}
|
||||
|
||||
#[gtk::template_callbacks]
|
||||
impl MusicusRecordingEditorPerformerRow {
|
||||
pub fn new(
|
||||
navigation: &adw::NavigationView,
|
||||
library: &MusicusLibrary,
|
||||
performer: Performer,
|
||||
) -> Self {
|
||||
let obj: Self = glib::Object::builder()
|
||||
.property("navigation", navigation)
|
||||
.property("library", library)
|
||||
.build();
|
||||
obj.set_performer(performer);
|
||||
obj
|
||||
}
|
||||
|
||||
pub fn connect_remove<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
self.connect_local("remove", true, move |values| {
|
||||
let obj = values[0].get::<Self>().unwrap();
|
||||
f(&obj);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn performer(&self) -> Performer {
|
||||
self.imp().performer.borrow().to_owned().unwrap()
|
||||
}
|
||||
|
||||
fn set_performer(&self, performer: Performer) {
|
||||
self.set_title(&performer.person.to_string());
|
||||
|
||||
let label = match &performer.instrument {
|
||||
Some(instrument) => instrument.to_string(),
|
||||
None => performer.role.to_string(),
|
||||
};
|
||||
|
||||
self.imp().role_label.set_label(&label.to_string());
|
||||
self.imp().performer.replace(Some(performer));
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn open_role_popover(&self, _: >k::Button) {
|
||||
self.imp().role_popover.get().unwrap().popup();
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn remove(&self, _: >k::Button) {
|
||||
self.emit_by_name::<()>("remove", &[]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,20 @@
|
|||
use std::cell::{OnceCell, RefCell};
|
||||
|
||||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gettextrs::gettext;
|
||||
use gtk::glib::{
|
||||
clone, Properties,
|
||||
{self, subclass::Signal},
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{
|
||||
db::{
|
||||
self,
|
||||
models::{Composer, Instrument, Person, Work, WorkPart},
|
||||
},
|
||||
editor::{
|
||||
instrument_editor::MusicusInstrumentEditor,
|
||||
instrument_selector_popover::MusicusInstrumentSelectorPopover,
|
||||
person_editor::MusicusPersonEditor, person_selector_popover::MusicusPersonSelectorPopover,
|
||||
translation_editor::MusicusTranslationEditor,
|
||||
|
|
@ -12,12 +23,6 @@ use crate::{
|
|||
library::MusicusLibrary,
|
||||
};
|
||||
|
||||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gettextrs::gettext;
|
||||
use gtk::glib::{self, clone, Properties};
|
||||
|
||||
use std::cell::{OnceCell, RefCell};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
|
|
@ -31,10 +36,13 @@ mod imp {
|
|||
#[property(get, construct_only)]
|
||||
pub library: OnceCell<MusicusLibrary>,
|
||||
|
||||
pub work_id: OnceCell<String>,
|
||||
|
||||
// Holding a reference to each composer row is the simplest way to enumerate all
|
||||
// results when finishing the process of editing the work. The composer rows
|
||||
// handle all state related to the composer.
|
||||
pub composer_rows: RefCell<Vec<MusicusWorkEditorComposerRow>>,
|
||||
// TODO: These need to be PartRows!
|
||||
pub parts: RefCell<Vec<WorkPart>>,
|
||||
pub instruments: RefCell<Vec<Instrument>>,
|
||||
|
||||
|
|
@ -53,6 +61,8 @@ mod imp {
|
|||
pub instrument_list: TemplateChild<gtk::ListBox>,
|
||||
#[template_child]
|
||||
pub select_instrument_box: TemplateChild<gtk::Box>,
|
||||
#[template_child]
|
||||
pub save_button: TemplateChild<gtk::Button>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
|
@ -74,6 +84,16 @@ mod imp {
|
|||
|
||||
#[glib::derived_properties]
|
||||
impl ObjectImpl for MusicusWorkEditor {
|
||||
fn signals() -> &'static [Signal] {
|
||||
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
||||
vec![Signal::builder("created")
|
||||
.param_types([Work::static_type()])
|
||||
.build()]
|
||||
});
|
||||
|
||||
SIGNALS.as_ref()
|
||||
}
|
||||
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
|
|
@ -106,43 +126,24 @@ mod imp {
|
|||
MusicusInstrumentSelectorPopover::new(self.library.get().unwrap());
|
||||
|
||||
let obj = self.obj().clone();
|
||||
instruments_popover.connect_instrument_selected(
|
||||
move |_: &MusicusInstrumentSelectorPopover, instrument: Instrument| {
|
||||
let row = adw::ActionRow::builder()
|
||||
.title(instrument.to_string())
|
||||
.build();
|
||||
instruments_popover.connect_instrument_selected(move |_, instrument| {
|
||||
obj.add_instrument_row(instrument);
|
||||
});
|
||||
|
||||
let remove_button = gtk::Button::builder()
|
||||
.icon_name("user-trash-symbolic")
|
||||
.valign(gtk::Align::Center)
|
||||
.css_classes(["flat"])
|
||||
.build();
|
||||
let obj = self.obj().clone();
|
||||
instruments_popover.connect_create(move |_| {
|
||||
let editor = MusicusInstrumentEditor::new(&obj.navigation(), &obj.library(), None);
|
||||
|
||||
remove_button.connect_clicked(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
#[weak]
|
||||
row,
|
||||
#[strong]
|
||||
instrument,
|
||||
move |_| {
|
||||
obj.imp().instrument_list.remove(&row);
|
||||
obj.imp()
|
||||
.instruments
|
||||
.borrow_mut()
|
||||
.retain(|i| *i != instrument);
|
||||
}
|
||||
));
|
||||
editor.connect_created(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_, instrument| {
|
||||
obj.add_instrument_row(instrument);
|
||||
}
|
||||
));
|
||||
|
||||
row.add_suffix(&remove_button);
|
||||
|
||||
obj.imp()
|
||||
.instrument_list
|
||||
.insert(&row, obj.imp().instruments.borrow().len() as i32);
|
||||
|
||||
obj.imp().instruments.borrow_mut().push(instrument);
|
||||
},
|
||||
);
|
||||
obj.navigation().push(&editor);
|
||||
});
|
||||
|
||||
self.select_instrument_box.append(&instruments_popover);
|
||||
self.instruments_popover.set(instruments_popover).unwrap();
|
||||
|
|
@ -170,13 +171,24 @@ impl MusicusWorkEditor {
|
|||
.property("library", library)
|
||||
.build();
|
||||
|
||||
if let Some(_work) = work {
|
||||
if let Some(work) = work {
|
||||
obj.imp().save_button.set_label(&gettext("Save changes"));
|
||||
obj.imp().work_id.set(work.work_id.clone()).unwrap();
|
||||
// TODO: Initialize work data.
|
||||
}
|
||||
|
||||
obj
|
||||
}
|
||||
|
||||
pub fn connect_created<F: Fn(&Self, Work) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
self.connect_local("created", true, move |values| {
|
||||
let obj = values[0].get::<Self>().unwrap();
|
||||
let work = values[1].get::<Work>().unwrap();
|
||||
f(&obj, work);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn add_person(&self, _: &adw::ActionRow) {
|
||||
self.imp().persons_popover.get().unwrap().popup();
|
||||
|
|
@ -246,4 +258,69 @@ impl MusicusWorkEditor {
|
|||
|
||||
self.imp().composer_rows.borrow_mut().push(row);
|
||||
}
|
||||
|
||||
fn add_instrument_row(&self, instrument: Instrument) {
|
||||
let row = adw::ActionRow::builder()
|
||||
.title(instrument.to_string())
|
||||
.build();
|
||||
|
||||
let remove_button = gtk::Button::builder()
|
||||
.icon_name("user-trash-symbolic")
|
||||
.valign(gtk::Align::Center)
|
||||
.css_classes(["flat"])
|
||||
.build();
|
||||
|
||||
remove_button.connect_clicked(clone!(
|
||||
#[weak(rename_to = this)]
|
||||
self,
|
||||
#[weak]
|
||||
row,
|
||||
#[strong]
|
||||
instrument,
|
||||
move |_| {
|
||||
this.imp().instrument_list.remove(&row);
|
||||
this.imp()
|
||||
.instruments
|
||||
.borrow_mut()
|
||||
.retain(|i| *i != instrument);
|
||||
}
|
||||
));
|
||||
|
||||
row.add_suffix(&remove_button);
|
||||
|
||||
self.imp()
|
||||
.instrument_list
|
||||
.insert(&row, self.imp().instruments.borrow().len() as i32);
|
||||
|
||||
self.imp().instruments.borrow_mut().push(instrument);
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn save(&self, _: >k::Button) {
|
||||
let library = self.imp().library.get().unwrap();
|
||||
|
||||
let name = self.imp().name_editor.translation();
|
||||
let parts = self.imp().parts.borrow().clone();
|
||||
let composers = self
|
||||
.imp()
|
||||
.composer_rows
|
||||
.borrow()
|
||||
.iter()
|
||||
.map(|c| c.composer())
|
||||
.collect::<Vec<Composer>>();
|
||||
let instruments = self.imp().instruments.borrow().clone();
|
||||
|
||||
if let Some(work_id) = self.imp().work_id.get() {
|
||||
library
|
||||
.update_work(work_id, name, parts, composers, instruments)
|
||||
.unwrap();
|
||||
} else {
|
||||
let work = library
|
||||
.create_work(name, parts, composers, instruments)
|
||||
.unwrap();
|
||||
self.emit_by_name::<()>("created", &[&work]);
|
||||
}
|
||||
|
||||
self.imp().navigation.get().unwrap().pop();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use gtk::glib::{self, clone, subclass::Signal, Properties};
|
|||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{
|
||||
db::models::{Composer, Role},
|
||||
db::models::Composer,
|
||||
editor::{role_editor::MusicusRoleEditor, role_selector_popover::MusicusRoleSelectorPopover},
|
||||
library::MusicusLibrary,
|
||||
};
|
||||
|
|
@ -142,14 +142,6 @@ impl MusicusWorkEditorComposerRow {
|
|||
self.imp().role_popover.get().unwrap().popup();
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn role_selected(&self, role: Role) {
|
||||
if let Some(composer) = &mut *self.imp().composer.borrow_mut() {
|
||||
self.imp().role_label.set_label(&role.to_string());
|
||||
composer.role = role;
|
||||
}
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn remove(&self, _: >k::Button) {
|
||||
self.emit_by_name::<()>("remove", &[]);
|
||||
|
|
|
|||
308
src/editor/work_selector_popover.rs
Normal file
308
src/editor/work_selector_popover.rs
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
use crate::{
|
||||
db::models::{Person, Work},
|
||||
library::MusicusLibrary,
|
||||
};
|
||||
|
||||
use gettextrs::gettext;
|
||||
use gtk::{
|
||||
glib::{self, subclass::Signal, Properties},
|
||||
pango,
|
||||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use std::cell::{OnceCell, RefCell};
|
||||
|
||||
use super::activatable_row::MusicusActivatableRow;
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default, gtk::CompositeTemplate, Properties)]
|
||||
#[properties(wrapper_type = super::MusicusWorkSelectorPopover)]
|
||||
#[template(file = "data/ui/work_selector_popover.blp")]
|
||||
pub struct MusicusWorkSelectorPopover {
|
||||
#[property(get, construct_only)]
|
||||
pub library: OnceCell<MusicusLibrary>,
|
||||
|
||||
pub composers: RefCell<Vec<Person>>,
|
||||
pub composer: RefCell<Option<Person>>,
|
||||
pub works: RefCell<Vec<Work>>,
|
||||
|
||||
#[template_child]
|
||||
pub stack: TemplateChild<gtk::Stack>,
|
||||
#[template_child]
|
||||
pub composer_view: TemplateChild<adw::ToolbarView>,
|
||||
#[template_child]
|
||||
pub composer_search_entry: TemplateChild<gtk::SearchEntry>,
|
||||
#[template_child]
|
||||
pub composer_scrolled_window: TemplateChild<gtk::ScrolledWindow>,
|
||||
#[template_child]
|
||||
pub composer_list: TemplateChild<gtk::ListBox>,
|
||||
#[template_child]
|
||||
pub work_view: TemplateChild<adw::ToolbarView>,
|
||||
#[template_child]
|
||||
pub composer_label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
pub work_search_entry: TemplateChild<gtk::SearchEntry>,
|
||||
#[template_child]
|
||||
pub work_scrolled_window: TemplateChild<gtk::ScrolledWindow>,
|
||||
#[template_child]
|
||||
pub work_list: TemplateChild<gtk::ListBox>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for MusicusWorkSelectorPopover {
|
||||
const NAME: &'static str = "MusicusWorkSelectorPopover";
|
||||
type Type = super::MusicusWorkSelectorPopover;
|
||||
type ParentType = gtk::Popover;
|
||||
|
||||
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]
|
||||
impl ObjectImpl for MusicusWorkSelectorPopover {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
self.obj()
|
||||
.connect_visible_notify(|obj: &super::MusicusWorkSelectorPopover| {
|
||||
if obj.is_visible() {
|
||||
obj.imp().stack.set_visible_child(&*obj.imp().composer_view);
|
||||
obj.imp().composer_search_entry.set_text("");
|
||||
obj.imp().composer_search_entry.grab_focus();
|
||||
obj.imp()
|
||||
.composer_scrolled_window
|
||||
.vadjustment()
|
||||
.set_value(0.0);
|
||||
}
|
||||
});
|
||||
|
||||
self.obj().search_composers("");
|
||||
}
|
||||
|
||||
fn signals() -> &'static [Signal] {
|
||||
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
||||
vec![
|
||||
Signal::builder("selected")
|
||||
.param_types([Work::static_type()])
|
||||
.build(),
|
||||
Signal::builder("create").build(),
|
||||
]
|
||||
});
|
||||
|
||||
SIGNALS.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for MusicusWorkSelectorPopover {
|
||||
// TODO: Fix focus.
|
||||
fn focus(&self, direction_type: gtk::DirectionType) -> bool {
|
||||
if direction_type == gtk::DirectionType::Down {
|
||||
if self.stack.visible_child() == Some(self.composer_list.get().upcast()) {
|
||||
self.composer_list.child_focus(direction_type)
|
||||
} else {
|
||||
self.work_list.child_focus(direction_type)
|
||||
}
|
||||
} else {
|
||||
self.parent_focus(direction_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PopoverImpl for MusicusWorkSelectorPopover {}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct MusicusWorkSelectorPopover(ObjectSubclass<imp::MusicusWorkSelectorPopover>)
|
||||
@extends gtk::Widget, gtk::Popover;
|
||||
}
|
||||
|
||||
#[gtk::template_callbacks]
|
||||
impl MusicusWorkSelectorPopover {
|
||||
pub fn new(library: &MusicusLibrary) -> Self {
|
||||
glib::Object::builder().property("library", library).build()
|
||||
}
|
||||
|
||||
pub fn connect_selected<F: Fn(&Self, Work) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
self.connect_local("selected", true, move |values| {
|
||||
let obj = values[0].get::<Self>().unwrap();
|
||||
let work = values[1].get::<Work>().unwrap();
|
||||
f(&obj, work);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn connect_create<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
self.connect_local("create", true, move |values| {
|
||||
let obj = values[0].get::<Self>().unwrap();
|
||||
f(&obj);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn composer_search_changed(&self, entry: >k::SearchEntry) {
|
||||
self.search_composers(&entry.text());
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn composer_activate(&self, _: >k::SearchEntry) {
|
||||
if let Some(composer) = self.imp().composers.borrow().first() {
|
||||
self.select_composer(composer.to_owned());
|
||||
} else {
|
||||
self.create();
|
||||
}
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn back_button_clicked(&self, _: >k::Button) {
|
||||
self.imp()
|
||||
.stack
|
||||
.set_visible_child(&*self.imp().composer_view);
|
||||
self.imp().composer_search_entry.grab_focus();
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn work_search_changed(&self, entry: >k::SearchEntry) {
|
||||
self.search_works(&entry.text());
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn work_activate(&self, _: >k::SearchEntry) {
|
||||
if let Some(work) = self.imp().works.borrow().first() {
|
||||
self.select(work.clone());
|
||||
} else {
|
||||
self.create();
|
||||
}
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn stop_search(&self, _: >k::SearchEntry) {
|
||||
self.popdown();
|
||||
}
|
||||
|
||||
fn search_composers(&self, search: &str) {
|
||||
let imp = self.imp();
|
||||
|
||||
let persons = imp.library.get().unwrap().search_persons(search).unwrap();
|
||||
|
||||
imp.composer_list.remove_all();
|
||||
|
||||
for person in &persons {
|
||||
let row = MusicusActivatableRow::new(
|
||||
>k::Label::builder()
|
||||
.label(person.to_string())
|
||||
.halign(gtk::Align::Start)
|
||||
.ellipsize(pango::EllipsizeMode::Middle)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let person = person.clone();
|
||||
let obj = self.clone();
|
||||
row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||
obj.select_composer(person.clone());
|
||||
});
|
||||
|
||||
imp.composer_list.append(&row);
|
||||
}
|
||||
|
||||
let create_box = gtk::Box::builder().spacing(12).build();
|
||||
create_box.append(>k::Image::builder().icon_name("list-add-symbolic").build());
|
||||
create_box.append(
|
||||
>k::Label::builder()
|
||||
.label(gettext("Create new work"))
|
||||
.halign(gtk::Align::Start)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let create_row = MusicusActivatableRow::new(&create_box);
|
||||
let obj = self.clone();
|
||||
create_row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||
obj.create();
|
||||
});
|
||||
|
||||
imp.composer_list.append(&create_row);
|
||||
|
||||
imp.composers.replace(persons);
|
||||
}
|
||||
|
||||
fn search_works(&self, search: &str) {
|
||||
let imp = self.imp();
|
||||
|
||||
let works = imp
|
||||
.library
|
||||
.get()
|
||||
.unwrap()
|
||||
.search_works(imp.composer.borrow().as_ref().unwrap(), search)
|
||||
.unwrap();
|
||||
|
||||
imp.work_list.remove_all();
|
||||
|
||||
for work in &works {
|
||||
let row = MusicusActivatableRow::new(
|
||||
>k::Label::builder()
|
||||
.label(work.name.get())
|
||||
.halign(gtk::Align::Start)
|
||||
.ellipsize(pango::EllipsizeMode::Middle)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let work = work.clone();
|
||||
let obj = self.clone();
|
||||
row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||
obj.select(work.clone());
|
||||
});
|
||||
|
||||
imp.work_list.append(&row);
|
||||
}
|
||||
|
||||
let create_box = gtk::Box::builder().spacing(12).build();
|
||||
create_box.append(>k::Image::builder().icon_name("list-add-symbolic").build());
|
||||
create_box.append(
|
||||
>k::Label::builder()
|
||||
.label(gettext("Create new work"))
|
||||
.halign(gtk::Align::Start)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let create_row = MusicusActivatableRow::new(&create_box);
|
||||
let obj = self.clone();
|
||||
create_row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||
obj.create();
|
||||
});
|
||||
|
||||
imp.work_list.append(&create_row);
|
||||
|
||||
imp.works.replace(works);
|
||||
}
|
||||
|
||||
fn select_composer(&self, person: Person) {
|
||||
self.imp().composer_label.set_text(person.name.get());
|
||||
self.imp().work_search_entry.set_text("");
|
||||
self.imp().work_search_entry.grab_focus();
|
||||
self.imp().work_scrolled_window.vadjustment().set_value(0.0);
|
||||
self.imp().stack.set_visible_child(&*self.imp().work_view);
|
||||
|
||||
self.imp().composer.replace(Some(person.clone()));
|
||||
self.search_works("");
|
||||
}
|
||||
|
||||
fn select(&self, work: Work) {
|
||||
self.emit_by_name::<()>("selected", &[&work]);
|
||||
self.popdown();
|
||||
}
|
||||
|
||||
fn create(&self) {
|
||||
self.emit_by_name::<()>("create", &[]);
|
||||
self.popdown();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue