Use nullable roles instead of default roles

This commit is contained in:
Elias Projahn 2025-03-29 17:29:29 +01:00
parent 130df0d60e
commit a371e356f7
16 changed files with 380 additions and 186 deletions

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
adw = { package = "libadwaita", version = "0.7", features = ["v1_6"] }
adw = { package = "libadwaita", version = "0.7", features = ["v1_7"] }
anyhow = "1"
async-channel = "2.3"
chrono = "0.4"

View file

@ -3,91 +3,98 @@ using Adw 1;
template $MusicusPerformerRoleSelectorPopover: Gtk.Popover {
styles [
"selector"
"selector",
]
Gtk.Stack stack {
transition-type: slide_left_right;
Gtk.Box {
orientation: vertical;
Adw.ToolbarView role_view {
[top]
Gtk.SearchEntry role_search_entry {
placeholder-text: _("Search roles…");
margin-start: 8;
margin-end: 8;
margin-top: 8;
margin-bottom: 6;
search-changed => $role_search_changed() swapped;
activate => $role_activate() swapped;
stop-search => $stop_search() swapped;
Gtk.CenterBox {
margin-start: 6;
margin-end: 6;
margin-top: 6;
[center]
Adw.InlineViewSwitcher {
stack: stack;
}
Gtk.ScrolledWindow role_scrolled_window {
height-request: 200;
[end]
Gtk.Button {
icon-name: "edit-clear-symbolic";
tooltip-text: _("Reset to default role");
margin-start: 6;
clicked => $reset_button_clicked() swapped;
Gtk.ListBox role_list {
styles [
"selector-list"
]
selection-mode: none;
activate-on-single-click: true;
}
styles [
"flat",
]
}
}
Adw.ToolbarView instrument_view {
[top]
Gtk.Box {
margin-start: 8;
margin-end: 8;
margin-top: 8;
margin-bottom: 6;
orientation: vertical;
Adw.ViewStack stack {
Adw.ViewStackPage {
name: "role";
title: _("Role");
Gtk.CenterBox {
[start]
Gtk.Button {
styles [
"flat"
]
icon-name: "go-previous-symbolic";
clicked => $back_button_clicked() swapped;
child: Adw.ToolbarView role_view {
[top]
Gtk.SearchEntry role_search_entry {
placeholder-text: _("Search roles…");
margin-start: 8;
margin-end: 8;
margin-top: 8;
margin-bottom: 6;
search-changed => $role_search_changed() swapped;
activate => $role_activate() swapped;
stop-search => $stop_search() swapped;
}
[center]
Gtk.Label {
styles [
"heading"
]
Gtk.ScrolledWindow role_scrolled_window {
height-request: 200;
label: _("Performer");
ellipsize: end;
margin-start: 6;
Gtk.ListBox role_list {
styles [
"selector-list",
]
selection-mode: none;
activate-on-single-click: true;
}
}
}
Gtk.SearchEntry instrument_search_entry {
placeholder-text: _("Search instruments…");
margin-top: 6;
search-changed => $instrument_search_changed() swapped;
activate => $instrument_activate() swapped;
stop-search => $stop_search() swapped;
}
};
}
Gtk.ScrolledWindow instrument_scrolled_window {
height-request: 200;
Adw.ViewStackPage {
name: "instrument";
title: _("Instrument");
Gtk.ListBox instrument_list {
styles [
"selector-list"
]
child: Adw.ToolbarView instrument_view {
[top]
Gtk.SearchEntry instrument_search_entry {
placeholder-text: _("Search instruments…");
margin-start: 8;
margin-end: 8;
margin-top: 8;
margin-bottom: 6;
search-changed => $instrument_search_changed() swapped;
activate => $instrument_activate() swapped;
stop-search => $stop_search() swapped;
}
selection-mode: none;
activate-on-single-click: true;
}
Gtk.ScrolledWindow instrument_scrolled_window {
height-request: 200;
Gtk.ListBox instrument_list {
styles [
"selector-list",
]
selection-mode: none;
activate-on-single-click: true;
}
}
};
}
}
}

View file

@ -3,20 +3,35 @@ using Adw 1;
template $MusicusRoleSelectorPopover: Gtk.Popover {
styles [
"selector"
"selector",
]
Adw.ToolbarView {
[top]
Gtk.SearchEntry search_entry {
placeholder-text: _("Search roles…");
Gtk.Box {
spacing: 6;
margin-start: 8;
margin-end: 8;
margin-top: 8;
margin-bottom: 6;
search-changed => $search_changed() swapped;
activate => $activate() swapped;
stop-search => $stop_search() swapped;
Gtk.SearchEntry search_entry {
placeholder-text: _("Search roles…");
hexpand: true;
search-changed => $search_changed() swapped;
activate => $activate() swapped;
stop-search => $stop_search() swapped;
}
Gtk.Button {
icon-name: "edit-clear-symbolic";
tooltip-text: _("Reset to default role");
clicked => $reset_button_clicked() swapped;
styles [
"flat",
]
}
}
Gtk.ScrolledWindow scrolled_window {
@ -24,7 +39,7 @@ template $MusicusRoleSelectorPopover: Gtk.Popover {
Gtk.ListBox list_box {
styles [
"selector-list"
"selector-list",
]
selection-mode: none;

View file

@ -0,0 +1,43 @@
CREATE TABLE work_persons_old (
work_id TEXT NOT NULL REFERENCES works(work_id) ON DELETE CASCADE,
person_id TEXT NOT NULL REFERENCES persons(person_id),
role_id TEXT NOT NULL REFERENCES roles(role_id),
sequence_number INTEGER NOT NULL,
PRIMARY KEY (work_id, person_id, role_id)
);
CREATE TABLE recording_persons_old (
recording_id TEXT NOT NULL REFERENCES recordings(recording_id) ON DELETE CASCADE,
person_id TEXT NOT NULL REFERENCES persons(person_id),
role_id TEXT NOT NULL REFERENCES roles(role_id),
instrument_id TEXT REFERENCES instruments(instrument_id),
sequence_number INTEGER NOT NULL,
PRIMARY KEY (recording_id, person_id, role_id, instrument_id)
);
CREATE TABLE recording_ensembles_old (
recording_id TEXT NOT NULL REFERENCES recordings(recording_id) ON DELETE CASCADE,
ensemble_id TEXT NOT NULL REFERENCES ensembles(ensemble_id),
role_id TEXT NOT NULL REFERENCES roles(role_id),
sequence_number INTEGER NOT NULL,
PRIMARY KEY (recording_id, ensemble_id, role_id)
);
INSERT INTO roles (role_id, name) VALUES ('380d7e09eb2f49c1a90db2ba4acb6ffd', '{"generic":"Composer"}');
INSERT INTO roles (role_id, name) VALUES ('28ff0aeb11c041a6916d93e9b4884eef', '{"generic":"Performer"}');
UPDATE work_persons SET role_id = '380d7e09eb2f49c1a90db2ba4acb6ffd' WHERE role_id IS NULL;
UPDATE recording_persons SET role_id = '28ff0aeb11c041a6916d93e9b4884eef' WHERE role_id IS NULL;
UPDATE recording_ensembles SET role_id = '28ff0aeb11c041a6916d93e9b4884eef' WHERE role_id IS NULL;
INSERT INTO work_persons_old SELECT * FROM work_persons;
DROP TABLE work_persons;
ALTER TABLE work_persons_old RENAME TO work_persons;
INSERT INTO recording_persons_old SELECT * FROM recording_persons;
DROP TABLE recording_persons;
ALTER TABLE recording_persons_old RENAME TO recording_persons;
INSERT INTO recording_ensembles_old SELECT * FROM recording_ensembles;
DROP TABLE recording_ensembles;
ALTER TABLE recording_ensembles_old RENAME TO recording_ensembles;

View file

@ -0,0 +1,41 @@
CREATE TABLE work_persons_new (
work_id TEXT NOT NULL REFERENCES works(work_id) ON DELETE CASCADE,
person_id TEXT NOT NULL REFERENCES persons(person_id),
role_id TEXT REFERENCES roles(role_id),
sequence_number INTEGER NOT NULL,
PRIMARY KEY (work_id, person_id, sequence_number)
);
CREATE TABLE recording_persons_new (
recording_id TEXT NOT NULL REFERENCES recordings(recording_id) ON DELETE CASCADE,
person_id TEXT NOT NULL REFERENCES persons(person_id),
role_id TEXT REFERENCES roles(role_id),
instrument_id TEXT REFERENCES instruments(instrument_id),
sequence_number INTEGER NOT NULL,
PRIMARY KEY (recording_id, person_id, sequence_number)
);
CREATE TABLE recording_ensembles_new (
recording_id TEXT NOT NULL REFERENCES recordings(recording_id) ON DELETE CASCADE,
ensemble_id TEXT NOT NULL REFERENCES ensembles(ensemble_id),
role_id TEXT REFERENCES roles(role_id),
sequence_number INTEGER NOT NULL,
PRIMARY KEY (recording_id, ensemble_id, sequence_number)
);
INSERT OR IGNORE INTO work_persons_new SELECT * FROM work_persons;
UPDATE work_persons_new SET role_id = NULL WHERE role_id = '380d7e09eb2f49c1a90db2ba4acb6ffd';
DROP TABLE work_persons;
ALTER TABLE work_persons_new RENAME TO work_persons;
INSERT OR IGNORE INTO recording_persons_new SELECT * FROM recording_persons;
UPDATE recording_persons_new SET role_id = NULL WHERE role_id = '28ff0aeb11c041a6916d93e9b4884eef';
DROP TABLE recording_persons;
ALTER TABLE recording_persons_new RENAME TO recording_persons;
INSERT OR IGNORE INTO recording_ensembles_new SELECT * FROM recording_ensembles;
UPDATE recording_ensembles_new SET role_id = NULL WHERE role_id = '28ff0aeb11c041a6916d93e9b4884eef';
DROP TABLE recording_ensembles;
ALTER TABLE recording_ensembles_new RENAME TO recording_ensembles;
DELETE FROM roles WHERE role_id IN ('380d7e09eb2f49c1a90db2ba4acb6ffd', '28ff0aeb11c041a6916d93e9b4884eef');

View file

@ -21,12 +21,10 @@ pub struct Work {
pub instruments: Vec<Instrument>,
}
#[derive(Queryable, Selectable, Clone, Debug)]
#[derive(Clone, Debug)]
pub struct Composer {
#[diesel(embed)]
pub person: Person,
#[diesel(embed)]
pub role: Role,
pub role: Option<Role>,
}
#[derive(Boxed, Clone, Debug)]
@ -50,14 +48,14 @@ pub struct Recording {
#[derive(Clone, Debug)]
pub struct Performer {
pub person: Person,
pub role: Role,
pub role: Option<Role>,
pub instrument: Option<Instrument>,
}
#[derive(Clone, Debug)]
pub struct EnsemblePerformer {
pub ensemble: Ensemble,
pub role: Role,
pub role: Option<Role>,
}
#[derive(Clone, Debug)]
@ -133,12 +131,13 @@ impl Work {
.map(|w| Work::from_table(w, connection))
.collect::<Result<Vec<Work>>>()?;
let persons: Vec<Composer> = persons::table
.inner_join(work_persons::table.inner_join(roles::table))
let persons = work_persons::table
.order(work_persons::sequence_number)
.filter(work_persons::work_id.eq(&data.work_id))
.select(Composer::as_select())
.load(connection)?;
.load::<tables::WorkPerson>(connection)?
.into_iter()
.map(|r| Composer::from_table(r, connection))
.collect::<Result<Vec<Composer>>>()?;
let instruments: Vec<Instrument> = instruments::table
.inner_join(work_instruments::table)
@ -157,11 +156,10 @@ impl Work {
}
pub fn composers_string(&self) -> Option<String> {
// TODO: Include roles except default composer.
let composers_string = self
.persons
.iter()
.map(|p| p.person.name.get().to_string())
.map(ToString::to_string)
.collect::<Vec<String>>()
.join(", ");
@ -190,6 +188,34 @@ impl Display for Work {
}
}
impl Composer {
pub fn from_table(data: tables::WorkPerson, connection: &mut SqliteConnection) -> Result<Self> {
let person: Person = persons::table
.filter(persons::person_id.eq(&data.person_id))
.first(connection)?;
let role = match &data.role_id {
Some(role_id) => Some(
roles::table
.filter(roles::role_id.eq(role_id))
.first::<Role>(connection)?,
),
None => None,
};
Ok(Self { person, role })
}
}
impl Display for Composer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.role {
Some(role) => format!("{} ({})", self.person.name.get(), role.name.get()).fmt(f),
None => self.person.name.get().fmt(f),
}
}
}
impl Ensemble {
pub fn from_table(data: tables::Ensemble, connection: &mut SqliteConnection) -> Result<Self> {
let persons: Vec<(Person, Instrument)> = persons::table
@ -297,9 +323,14 @@ impl Performer {
.filter(persons::person_id.eq(&data.person_id))
.first(connection)?;
let role: Role = roles::table
.filter(roles::role_id.eq(&data.role_id))
.first(connection)?;
let role = match &data.role_id {
Some(role_id) => Some(
roles::table
.filter(roles::role_id.eq(role_id))
.first::<Role>(connection)?,
),
None => None,
};
let instrument = match &data.instrument_id {
Some(instrument_id) => Some(
@ -320,11 +351,12 @@ impl Performer {
impl Display for Performer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.instrument {
Some(instrument) => {
match (&self.role, &self.instrument) {
(_, Some(instrument)) => {
format!("{} ({})", self.person.name.get(), instrument.name.get()).fmt(f)
}
None => self.person.name.get().fmt(f),
(Some(role), _) => format!("{} ({})", self.person.name.get(), role.name.get()).fmt(f),
(None, None) => self.person.name.get().fmt(f),
}
}
}
@ -340,9 +372,14 @@ impl EnsemblePerformer {
let ensemble = Ensemble::from_table(ensemble_data, connection)?;
let role: Role = roles::table
.filter(roles::role_id.eq(&data.role_id))
.first(connection)?;
let role = match &data.role_id {
Some(role_id) => Some(
roles::table
.filter(roles::role_id.eq(role_id))
.first::<Role>(connection)?,
),
None => None,
};
Ok(Self { ensemble, role })
}

View file

@ -81,19 +81,19 @@ diesel::table! {
}
diesel::table! {
recording_ensembles (recording_id, ensemble_id, role_id) {
recording_ensembles (recording_id, ensemble_id, sequence_number) {
recording_id -> Text,
ensemble_id -> Text,
role_id -> Text,
role_id -> Nullable<Text>,
sequence_number -> Integer,
}
}
diesel::table! {
recording_persons (recording_id, person_id, role_id, instrument_id) {
recording_persons (recording_id, person_id, sequence_number) {
recording_id -> Text,
person_id -> Text,
role_id -> Text,
role_id -> Nullable<Text>,
instrument_id -> Nullable<Text>,
sequence_number -> Integer,
}
@ -153,10 +153,10 @@ diesel::table! {
}
diesel::table! {
work_persons (work_id, person_id, role_id) {
work_persons (work_id, person_id, sequence_number) {
work_id -> Text,
person_id -> Text,
role_id -> Text,
role_id -> Nullable<Text>,
sequence_number -> Integer,
}
}

View file

@ -60,7 +60,7 @@ pub struct Work {
pub struct WorkPerson {
pub work_id: String,
pub person_id: String,
pub role_id: String,
pub role_id: Option<String>,
pub sequence_number: i32,
}
@ -109,7 +109,7 @@ pub struct Recording {
pub struct RecordingPerson {
pub recording_id: String,
pub person_id: String,
pub role_id: String,
pub role_id: Option<String>,
pub instrument_id: Option<String>,
pub sequence_number: i32,
}
@ -119,7 +119,7 @@ pub struct RecordingPerson {
pub struct RecordingEnsemble {
pub recording_id: String,
pub ensemble_id: String,
pub role_id: String,
pub role_id: Option<String>,
pub sequence_number: i32,
}

View file

@ -256,7 +256,7 @@ impl RecordingEditor {
fn new_performer(&self, person: Person) {
let performer = Performer {
person,
role: self.library().performer_default_role().unwrap(),
role: None,
instrument: None,
};
@ -299,7 +299,7 @@ impl RecordingEditor {
fn new_ensemble_performer(&self, ensemble: Ensemble) {
let performer = EnsemblePerformer {
ensemble,
role: self.library().performer_default_role().unwrap(),
role: None,
};
self.add_ensemble_row(performer);

View file

@ -1,6 +1,7 @@
use std::cell::{OnceCell, RefCell};
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{
gdk,
glib::{self, clone, subclass::Signal, Properties},
@ -112,7 +113,7 @@ mod imp {
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;
ensemble.role = Some(role);
}
});
@ -126,7 +127,7 @@ mod imp {
move |_, role| {
if let Some(ensemble) = &mut *obj.imp().ensemble.borrow_mut() {
obj.imp().role_label.set_label(&role.to_string());
ensemble.role = role;
ensemble.role = Some(role);
};
}
));
@ -188,7 +189,13 @@ impl RecordingEditorEnsembleRow {
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().role_label.set_label(
&ensemble
.role
.as_ref()
.map(ToString::to_string)
.unwrap_or_else(|| gettext("Performer")),
);
self.imp().ensemble.replace(Some(ensemble));
}

View file

@ -1,6 +1,7 @@
use std::cell::{OnceCell, RefCell};
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{
gdk,
glib::{self, clone, subclass::Signal, Properties},
@ -110,17 +111,29 @@ mod imp {
let role_popover = PerformerRoleSelectorPopover::new(self.library.get().unwrap());
let obj = self.obj().to_owned();
role_popover.connect_selected(move |_, role, instrument| {
role_popover.connect_reset(move |_| {
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(&gettext("Performer"));
performer.role = None;
performer.instrument = None;
}
});
obj.imp().role_label.set_label(&label);
let obj = self.obj().to_owned();
role_popover.connect_role_selected(move |_, role| {
if let Some(performer) = &mut *obj.imp().performer.borrow_mut() {
obj.imp().role_label.set_label(&role.to_string());
performer.role = Some(role);
performer.instrument = None;
}
});
performer.role = role;
performer.instrument = instrument;
let obj = self.obj().to_owned();
role_popover.connect_instrument_selected(move |_, instrument| {
if let Some(performer) = &mut *obj.imp().performer.borrow_mut() {
obj.imp().role_label.set_label(&instrument.to_string());
performer.role = None;
performer.instrument = Some(instrument);
}
});
@ -134,7 +147,7 @@ mod imp {
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.role = Some(role);
performer.instrument = None;
};
}
@ -153,7 +166,7 @@ mod imp {
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.role = None;
performer.instrument = Some(instrument);
};
}
@ -215,7 +228,11 @@ impl RecordingEditorPerformerRow {
let label = match &performer.instrument {
Some(instrument) => instrument.to_string(),
None => performer.role.to_string(),
None => performer
.role
.as_ref()
.map(ToString::to_string)
.unwrap_or_else(|| gettext("Performer")),
};
self.imp().role_label.set_label(&label.to_string());

View file

@ -233,8 +233,7 @@ impl WorkEditor {
}
fn add_composer(&self, person: Person) {
let role = self.library().composer_default_role().unwrap();
let composer = Composer { person, role };
let composer = Composer { person, role: None };
self.add_composer_row(composer);
}

View file

@ -1,6 +1,7 @@
use std::cell::{OnceCell, RefCell};
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{
gdk,
glib::{self, clone, subclass::Signal, Properties},
@ -108,11 +109,19 @@ mod imp {
let role_popover = RoleSelectorPopover::new(self.library.get().unwrap());
let obj = self.obj().to_owned();
role_popover.connect_reset(move |_| {
if let Some(composer) = &mut *obj.imp().composer.borrow_mut() {
obj.imp().role_label.set_label(&gettext("Composer"));
composer.role = None;
}
});
let obj = self.obj().to_owned();
role_popover.connect_role_selected(move |_, role| {
if let Some(composer) = &mut *obj.imp().composer.borrow_mut() {
obj.imp().role_label.set_label(&role.to_string());
composer.role = role;
composer.role = Some(role);
}
});
@ -126,7 +135,7 @@ mod imp {
move |_, role| {
if let Some(composer) = &mut *obj.imp().composer.borrow_mut() {
obj.imp().role_label.set_label(&role.to_string());
composer.role = role;
composer.role = Some(role);
};
}
));
@ -184,7 +193,13 @@ impl WorkEditorComposerRow {
fn set_composer(&self, composer: Composer) {
self.set_title(&composer.person.to_string());
self.imp().role_label.set_label(&composer.role.to_string());
self.imp().role_label.set_label(
&composer
.role
.as_ref()
.map(ToString::to_string)
.unwrap_or_else(|| gettext("Composer")),
);
self.imp().composer.replace(Some(composer));
}

View file

@ -933,22 +933,6 @@ impl Library {
Ok(ensembles)
}
pub fn composer_default_role(&self) -> Result<Role> {
let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap();
Ok(roles::table
.filter(roles::role_id.eq("380d7e09eb2f49c1a90db2ba4acb6ffd"))
.first::<Role>(connection)?)
}
pub fn performer_default_role(&self) -> Result<Role> {
let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap();
Ok(roles::table
.filter(roles::role_id.eq("28ff0aeb11c041a6916d93e9b4884eef"))
.first::<Role>(connection)?)
}
pub fn create_person(&self, name: TranslatedString) -> Result<Person> {
let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap();
@ -1171,7 +1155,7 @@ impl Library {
let composer_data = tables::WorkPerson {
work_id: work_id.clone(),
person_id: composer.person.person_id,
role_id: composer.role.role_id,
role_id: composer.role.map(|r| r.role_id),
sequence_number: index as i32,
};
@ -1295,7 +1279,7 @@ impl Library {
let composer_data = tables::WorkPerson {
work_id: work_id.to_string(),
person_id: composer.person.person_id,
role_id: composer.role.role_id,
role_id: composer.role.map(|r| r.role_id),
sequence_number: index as i32,
};
@ -1425,7 +1409,7 @@ impl Library {
let recording_person_data = tables::RecordingPerson {
recording_id: recording_id.clone(),
person_id: performer.person.person_id,
role_id: performer.role.role_id,
role_id: performer.role.map(|r| r.role_id),
instrument_id: performer.instrument.map(|i| i.instrument_id),
sequence_number: index as i32,
};
@ -1439,7 +1423,7 @@ impl Library {
let recording_ensemble_data = tables::RecordingEnsemble {
recording_id: recording_id.clone(),
ensemble_id: ensemble.ensemble.ensemble_id,
role_id: ensemble.role.role_id,
role_id: ensemble.role.map(|r| r.role_id),
sequence_number: index as i32,
};
@ -1485,7 +1469,7 @@ impl Library {
let recording_person_data = tables::RecordingPerson {
recording_id: recording_id.to_string(),
person_id: performer.person.person_id,
role_id: performer.role.role_id,
role_id: performer.role.map(|r| r.role_id),
instrument_id: performer.instrument.map(|i| i.instrument_id),
sequence_number: index as i32,
};
@ -1503,7 +1487,7 @@ impl Library {
let recording_ensemble_data = tables::RecordingEnsemble {
recording_id: recording_id.to_string(),
ensemble_id: ensemble.ensemble.ensemble_id,
role_id: ensemble.role.role_id,
role_id: ensemble.role.map(|r| r.role_id),
sequence_number: index as i32,
};

View file

@ -29,9 +29,7 @@ mod imp {
pub instruments: RefCell<Vec<Instrument>>,
#[template_child]
pub stack: TemplateChild<gtk::Stack>,
#[template_child]
pub role_view: TemplateChild<adw::ToolbarView>,
pub stack: TemplateChild<adw::ViewStack>,
#[template_child]
pub role_search_entry: TemplateChild<gtk::SearchEntry>,
#[template_child]
@ -39,8 +37,6 @@ mod imp {
#[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>,
@ -71,21 +67,35 @@ mod imp {
self.obj().connect_visible_notify(|obj| {
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);
obj.imp().instrument_search_entry.set_text("");
obj.imp()
.instrument_scrolled_window
.vadjustment()
.set_value(0.0);
if obj.imp().stack.visible_child_name().as_deref() == Some("role") {
obj.imp().role_search_entry.grab_focus();
} else {
obj.imp().instrument_search_entry.grab_focus();
}
}
});
self.obj().search_roles("");
self.obj().search_instruments("");
}
fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
vec![
Signal::builder("selected")
.param_types([Role::static_type(), Instrument::static_type()])
Signal::builder("reset").build(),
Signal::builder("role-selected")
.param_types([Role::static_type()])
.build(),
Signal::builder("instrument-selected")
.param_types([Instrument::static_type()])
.build(),
Signal::builder("create-role").build(),
Signal::builder("create-instrument").build(),
@ -100,7 +110,7 @@ mod imp {
// 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()) {
if self.stack.visible_child_name().as_deref() == Some("role") {
self.role_list.child_focus(direction_type)
} else {
self.instrument_list.child_focus(direction_type)
@ -125,15 +135,34 @@ impl PerformerRoleSelectorPopover {
glib::Object::builder().property("library", library).build()
}
pub fn connect_selected<F: Fn(&Self, Role, Option<Instrument>) + 'static>(
pub fn connect_reset<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
self.connect_local("reset", true, move |values| {
let obj = values[0].get::<Self>().unwrap();
f(&obj);
None
})
}
pub fn connect_role_selected<F: Fn(&Self, Role) + 'static>(
&self,
f: F,
) -> glib::SignalHandlerId {
self.connect_local("selected", true, move |values| {
self.connect_local("role-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);
f(&obj, role);
None
})
}
pub fn connect_instrument_selected<F: Fn(&Self, Instrument) + 'static>(
&self,
f: F,
) -> glib::SignalHandlerId {
self.connect_local("instrument-selected", true, move |values| {
let obj = values[0].get::<Self>().unwrap();
let role = values[1].get::<Instrument>().unwrap();
f(&obj, role);
None
})
}
@ -154,6 +183,12 @@ impl PerformerRoleSelectorPopover {
})
}
#[template_callback]
fn reset_button_clicked(&self) {
self.emit_by_name::<()>("reset", &[]);
self.popdown();
}
#[template_callback]
fn role_search_changed(&self, entry: &gtk::SearchEntry) {
self.search_roles(&entry.text());
@ -168,12 +203,6 @@ impl PerformerRoleSelectorPopover {
}
}
#[template_callback]
fn back_button_clicked(&self) {
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: &gtk::SearchEntry) {
self.search_instruments(&entry.text());
@ -293,27 +322,12 @@ impl PerformerRoleSelectorPopover {
}
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();
}
self.emit_by_name::<()>("role-selected", &[&role]);
self.popdown();
}
fn select_instrument(&self, instrument: Instrument) {
let role = self.library().performer_default_role().unwrap();
self.emit_by_name::<()>("selected", &[&role, &instrument]);
self.emit_by_name::<()>("instrument-selected", &[&instrument]);
self.popdown();
}

View file

@ -65,6 +65,7 @@ mod imp {
fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
vec![
Signal::builder("reset").build(),
Signal::builder("role-selected")
.param_types([Role::static_type()])
.build(),
@ -101,6 +102,14 @@ impl RoleSelectorPopover {
glib::Object::builder().property("library", library).build()
}
pub fn connect_reset<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
self.connect_local("reset", true, move |values| {
let obj = values[0].get::<Self>().unwrap();
f(&obj);
None
})
}
pub fn connect_role_selected<F: Fn(&Self, Role) + 'static>(
&self,
f: F,
@ -140,6 +149,12 @@ impl RoleSelectorPopover {
self.popdown();
}
#[template_callback]
fn reset_button_clicked(&self) {
self.emit_by_name::<()>("reset", &[]);
self.popdown();
}
fn search(&self, search: &str) {
let imp = self.imp();