mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 11:47: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
609
Cargo.lock
generated
609
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
42
data/ui/ensemble_editor.blp
Normal file
42
data/ui/ensemble_editor.blp
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $MusicusEnsembleEditor: Adw.NavigationPage {
|
||||
title: _("Ensemble");
|
||||
|
||||
Adw.ToolbarView {
|
||||
[top]
|
||||
Adw.HeaderBar header_bar {}
|
||||
|
||||
Adw.Clamp {
|
||||
Gtk.Box {
|
||||
orientation: vertical;
|
||||
|
||||
Gtk.Label {
|
||||
label: _("Name");
|
||||
xalign: 0;
|
||||
margin-top: 24;
|
||||
|
||||
styles [
|
||||
"heading"
|
||||
]
|
||||
}
|
||||
|
||||
$MusicusTranslationEditor name_editor {
|
||||
margin-top: 12;
|
||||
}
|
||||
|
||||
Gtk.Button save_button {
|
||||
margin-top: 24;
|
||||
label: _("Create ensemble");
|
||||
clicked => $save() swapped;
|
||||
|
||||
styles [
|
||||
"card",
|
||||
"save"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
data/ui/ensemble_selector_popover.blp
Normal file
35
data/ui/ensemble_selector_popover.blp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $MusicusEnsembleSelectorPopover: Gtk.Popover {
|
||||
styles [
|
||||
"selector"
|
||||
]
|
||||
|
||||
Adw.ToolbarView {
|
||||
[top]
|
||||
Gtk.SearchEntry search_entry {
|
||||
placeholder-text: _("Search ensembles…");
|
||||
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.ScrolledWindow scrolled_window {
|
||||
height-request: 200;
|
||||
|
||||
Gtk.ListBox list_box {
|
||||
styles [
|
||||
"selector-list"
|
||||
]
|
||||
|
||||
selection-mode: none;
|
||||
activate-on-single-click: true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
data/ui/instrument_editor.blp
Normal file
42
data/ui/instrument_editor.blp
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $MusicusInstrumentEditor: Adw.NavigationPage {
|
||||
title: _("Instrument");
|
||||
|
||||
Adw.ToolbarView {
|
||||
[top]
|
||||
Adw.HeaderBar header_bar {}
|
||||
|
||||
Adw.Clamp {
|
||||
Gtk.Box {
|
||||
orientation: vertical;
|
||||
|
||||
Gtk.Label {
|
||||
label: _("Name");
|
||||
xalign: 0;
|
||||
margin-top: 24;
|
||||
|
||||
styles [
|
||||
"heading"
|
||||
]
|
||||
}
|
||||
|
||||
$MusicusTranslationEditor name_editor {
|
||||
margin-top: 12;
|
||||
}
|
||||
|
||||
Gtk.Button save_button {
|
||||
margin-top: 24;
|
||||
label: _("Create instrument");
|
||||
clicked => $save() swapped;
|
||||
|
||||
styles [
|
||||
"card",
|
||||
"save"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,5 +8,50 @@ template $MusicusLibraryManager : Adw.NavigationPage {
|
|||
Adw.ToolbarView {
|
||||
[top]
|
||||
Adw.HeaderBar {}
|
||||
|
||||
Gtk.Box {
|
||||
orientation: vertical;
|
||||
spacing: 12;
|
||||
|
||||
Gtk.Button {
|
||||
label: _("Add person");
|
||||
clicked => $add_person() swapped;
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
label: _("Add role");
|
||||
clicked => $add_role() swapped;
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
label: _("Add instrument");
|
||||
clicked => $add_instrument() swapped;
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
label: _("Add work");
|
||||
clicked => $add_work() swapped;
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
label: _("Add ensemble");
|
||||
clicked => $add_ensemble() swapped;
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
label: _("Add recording");
|
||||
clicked => $add_recording() swapped;
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
label: _("Add album");
|
||||
clicked => $add_album() swapped;
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
label: _("Add medium");
|
||||
clicked => $add_medium() swapped;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
94
data/ui/performer_role_selector_popover.blp
Normal file
94
data/ui/performer_role_selector_popover.blp
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $MusicusPerformerRoleSelectorPopover: Gtk.Popover {
|
||||
styles [
|
||||
"selector"
|
||||
]
|
||||
|
||||
Gtk.Stack stack {
|
||||
transition-type: slide_left_right;
|
||||
|
||||
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.ScrolledWindow role_scrolled_window {
|
||||
height-request: 200;
|
||||
|
||||
Gtk.ListBox role_list {
|
||||
styles [
|
||||
"selector-list"
|
||||
]
|
||||
|
||||
selection-mode: none;
|
||||
activate-on-single-click: true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ToolbarView instrument_view {
|
||||
[top]
|
||||
Gtk.Box {
|
||||
margin-start: 8;
|
||||
margin-end: 8;
|
||||
margin-top: 8;
|
||||
margin-bottom: 6;
|
||||
orientation: vertical;
|
||||
|
||||
Gtk.CenterBox {
|
||||
[start]
|
||||
Gtk.Button {
|
||||
styles [
|
||||
"flat"
|
||||
]
|
||||
|
||||
icon-name: "go-previous-symbolic";
|
||||
clicked => $back_button_clicked() swapped;
|
||||
}
|
||||
|
||||
[center]
|
||||
Gtk.Label {
|
||||
styles [
|
||||
"heading"
|
||||
]
|
||||
|
||||
label: _("Performer");
|
||||
ellipsize: end;
|
||||
margin-start: 6;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
Gtk.ListBox instrument_list {
|
||||
styles [
|
||||
"selector-list"
|
||||
]
|
||||
|
||||
selection-mode: none;
|
||||
activate-on-single-click: true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
141
data/ui/recording_editor.blp
Normal file
141
data/ui/recording_editor.blp
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $MusicusRecordingEditor: Adw.NavigationPage {
|
||||
title: _("Recording");
|
||||
|
||||
Adw.ToolbarView {
|
||||
[top]
|
||||
Adw.HeaderBar header_bar {}
|
||||
|
||||
Gtk.ScrolledWindow {
|
||||
Adw.Clamp {
|
||||
Gtk.Box {
|
||||
orientation: vertical;
|
||||
margin-bottom: 24;
|
||||
margin-start: 12;
|
||||
margin-end: 12;
|
||||
|
||||
Gtk.Label {
|
||||
label: _("Recording");
|
||||
xalign: 0;
|
||||
margin-top: 24;
|
||||
|
||||
styles [
|
||||
"heading"
|
||||
]
|
||||
}
|
||||
|
||||
Gtk.ListBox {
|
||||
selection-mode: none;
|
||||
margin-top: 12;
|
||||
|
||||
styles [
|
||||
"boxed-list"
|
||||
]
|
||||
|
||||
Adw.ActionRow work_row {
|
||||
title: _("Select work");
|
||||
activatable: true;
|
||||
activated => $select_work() swapped;
|
||||
|
||||
[prefix]
|
||||
Gtk.Box select_work_box {
|
||||
Gtk.Image {
|
||||
icon-name: "document-edit-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.SpinRow year_row {
|
||||
title: _("Year");
|
||||
|
||||
adjustment: Gtk.Adjustment {
|
||||
lower: 0;
|
||||
upper: 3000;
|
||||
value: 2000;
|
||||
step-increment: 1;
|
||||
page-increment: 10;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.Label {
|
||||
label: _("Performers");
|
||||
xalign: 0;
|
||||
margin-top: 24;
|
||||
|
||||
styles [
|
||||
"heading"
|
||||
]
|
||||
}
|
||||
|
||||
Gtk.ListBox performer_list {
|
||||
selection-mode: none;
|
||||
margin-top: 12;
|
||||
|
||||
styles [
|
||||
"boxed-list"
|
||||
]
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Add performer");
|
||||
activatable: true;
|
||||
activated => $select_person() swapped;
|
||||
|
||||
[prefix]
|
||||
Gtk.Box select_person_box {
|
||||
Gtk.Image {
|
||||
icon-name: "list-add-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.Label {
|
||||
label: _("Ensembles");
|
||||
xalign: 0;
|
||||
margin-top: 24;
|
||||
|
||||
styles [
|
||||
"heading"
|
||||
]
|
||||
}
|
||||
|
||||
Gtk.ListBox ensemble_list {
|
||||
selection-mode: none;
|
||||
margin-top: 12;
|
||||
|
||||
styles [
|
||||
"boxed-list"
|
||||
]
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Add ensemble");
|
||||
activatable: true;
|
||||
activated => $select_ensemble() swapped;
|
||||
|
||||
[prefix]
|
||||
Gtk.Box select_ensemble_box {
|
||||
Gtk.Image {
|
||||
icon-name: "list-add-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.Button save_button {
|
||||
margin-top: 24;
|
||||
label: _("Create recording");
|
||||
clicked => $save() swapped;
|
||||
|
||||
styles [
|
||||
"card",
|
||||
"save"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
data/ui/recording_editor_ensemble_row.blp
Normal file
33
data/ui/recording_editor_ensemble_row.blp
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $MusicusRecordingEditorEnsembleRow: Adw.ActionRow {
|
||||
Gtk.Button {
|
||||
icon-name: "user-trash-symbolic";
|
||||
valign: center;
|
||||
clicked => $remove() swapped;
|
||||
|
||||
styles [
|
||||
"flat"
|
||||
]
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
valign: center;
|
||||
clicked => $open_role_popover() swapped;
|
||||
|
||||
styles [
|
||||
"flat"
|
||||
]
|
||||
|
||||
Gtk.Box role_box {
|
||||
spacing: 6;
|
||||
|
||||
Gtk.Label role_label {}
|
||||
|
||||
Gtk.Image {
|
||||
icon-name: "pan-down-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
data/ui/recording_editor_performer_row.blp
Normal file
33
data/ui/recording_editor_performer_row.blp
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $MusicusRecordingEditorPerformerRow: Adw.ActionRow {
|
||||
Gtk.Button {
|
||||
icon-name: "user-trash-symbolic";
|
||||
valign: center;
|
||||
clicked => $remove() swapped;
|
||||
|
||||
styles [
|
||||
"flat"
|
||||
]
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
valign: center;
|
||||
clicked => $open_role_popover() swapped;
|
||||
|
||||
styles [
|
||||
"flat"
|
||||
]
|
||||
|
||||
Gtk.Box role_box {
|
||||
spacing: 6;
|
||||
|
||||
Gtk.Label role_label {}
|
||||
|
||||
Gtk.Image {
|
||||
icon-name: "pan-down-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $MusicusRecordingTile : Gtk.FlowBoxChild {
|
||||
styles ["card", "activatable", "tile"]
|
||||
template $MusicusRecordingTile: Gtk.FlowBoxChild {
|
||||
styles [
|
||||
"card",
|
||||
"activatable",
|
||||
"tile"
|
||||
]
|
||||
|
||||
Gtk.Box {
|
||||
spacing: 12;
|
||||
|
|
@ -18,29 +21,50 @@ template $MusicusRecordingTile : Gtk.FlowBoxChild {
|
|||
hexpand: true;
|
||||
|
||||
Gtk.Label work_label {
|
||||
styles ["work"]
|
||||
styles [
|
||||
"work"
|
||||
]
|
||||
|
||||
halign: start;
|
||||
wrap: true;
|
||||
}
|
||||
|
||||
Gtk.Label composer_label {
|
||||
styles ["composer"]
|
||||
styles [
|
||||
"composer"
|
||||
]
|
||||
|
||||
halign: start;
|
||||
wrap: true;
|
||||
}
|
||||
|
||||
Gtk.Label performances_label {
|
||||
styles ["performances", "dim-label"]
|
||||
styles [
|
||||
"performances",
|
||||
"dim-label"
|
||||
]
|
||||
|
||||
halign: start;
|
||||
wrap: true;
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
styles ["flat"]
|
||||
Gtk.MenuButton {
|
||||
styles [
|
||||
"flat"
|
||||
]
|
||||
|
||||
valign: start;
|
||||
margin-top: 12;
|
||||
icon-name: "view-more-symbolic";
|
||||
|
||||
popover: Gtk.PopoverMenu {
|
||||
menu-model: edit_menu;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
menu edit_menu {
|
||||
item (_("Edit recording"), "recording.edit")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,6 +124,17 @@ template $MusicusWorkEditor: Adw.NavigationPage {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.Button save_button {
|
||||
margin-top: 24;
|
||||
label: _("Create work");
|
||||
clicked => $save() swapped;
|
||||
|
||||
styles [
|
||||
"card",
|
||||
"save"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
93
data/ui/work_selector_popover.blp
Normal file
93
data/ui/work_selector_popover.blp
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $MusicusWorkSelectorPopover: Gtk.Popover {
|
||||
styles [
|
||||
"selector"
|
||||
]
|
||||
|
||||
Gtk.Stack stack {
|
||||
transition-type: slide_left_right;
|
||||
|
||||
Adw.ToolbarView composer_view {
|
||||
[top]
|
||||
Gtk.SearchEntry composer_search_entry {
|
||||
placeholder-text: _("Search composers…");
|
||||
margin-start: 8;
|
||||
margin-end: 8;
|
||||
margin-top: 8;
|
||||
margin-bottom: 6;
|
||||
search-changed => $composer_search_changed() swapped;
|
||||
activate => $composer_activate() swapped;
|
||||
stop-search => $stop_search() swapped;
|
||||
}
|
||||
|
||||
Gtk.ScrolledWindow composer_scrolled_window {
|
||||
height-request: 200;
|
||||
|
||||
Gtk.ListBox composer_list {
|
||||
styles [
|
||||
"selector-list"
|
||||
]
|
||||
|
||||
selection-mode: none;
|
||||
activate-on-single-click: true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ToolbarView work_view {
|
||||
[top]
|
||||
Gtk.Box {
|
||||
margin-start: 8;
|
||||
margin-end: 8;
|
||||
margin-top: 8;
|
||||
margin-bottom: 6;
|
||||
orientation: vertical;
|
||||
|
||||
Gtk.CenterBox {
|
||||
[start]
|
||||
Gtk.Button {
|
||||
styles [
|
||||
"flat"
|
||||
]
|
||||
|
||||
icon-name: "go-previous-symbolic";
|
||||
clicked => $back_button_clicked() swapped;
|
||||
}
|
||||
|
||||
[center]
|
||||
Gtk.Label composer_label {
|
||||
styles [
|
||||
"heading"
|
||||
]
|
||||
|
||||
ellipsize: end;
|
||||
margin-start: 6;
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.SearchEntry work_search_entry {
|
||||
placeholder-text: _("Search works…");
|
||||
margin-top: 6;
|
||||
search-changed => $work_search_changed() swapped;
|
||||
activate => $work_activate() swapped;
|
||||
stop-search => $stop_search() swapped;
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.ScrolledWindow work_scrolled_window {
|
||||
height-request: 200;
|
||||
|
||||
Gtk.ListBox work_list {
|
||||
styles [
|
||||
"selector-list"
|
||||
]
|
||||
|
||||
selection-mode: none;
|
||||
activate-on-single-click: true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
//! This module contains higher-level models combining information from
|
||||
//! multiple database tables.
|
||||
|
||||
use std::{fmt::Display, path::Path};
|
||||
use std::fmt::Display;
|
||||
|
||||
use anyhow::Result;
|
||||
use diesel::prelude::*;
|
||||
use gtk::glib::{self, Boxed};
|
||||
|
||||
use super::{schema::*, tables, TranslatedString};
|
||||
|
||||
// Re-exports for tables that don't need additional information.
|
||||
pub use tables::{Album, Instrument, Person, Role};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Boxed, Clone, Debug)]
|
||||
#[boxed_type(name = "MusicusWork")]
|
||||
pub struct Work {
|
||||
pub work_id: String,
|
||||
pub name: TranslatedString,
|
||||
|
|
@ -36,21 +38,22 @@ pub struct Composer {
|
|||
pub role: Role,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Boxed, Clone, Debug)]
|
||||
#[boxed_type(name = "MusicusEnsemble")]
|
||||
pub struct Ensemble {
|
||||
pub ensemble_id: String,
|
||||
pub name: TranslatedString,
|
||||
pub persons: Vec<(Person, Instrument)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Boxed, Clone, Debug)]
|
||||
#[boxed_type(name = "MusicusRecording")]
|
||||
pub struct Recording {
|
||||
pub recording_id: String,
|
||||
pub work: Work,
|
||||
pub year: Option<i32>,
|
||||
pub persons: Vec<Performer>,
|
||||
pub ensembles: Vec<Ensemble>,
|
||||
pub tracks: Vec<Track>,
|
||||
pub ensembles: Vec<EnsemblePerformer>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -60,6 +63,12 @@ pub struct Performer {
|
|||
pub instrument: Option<Instrument>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EnsemblePerformer {
|
||||
pub ensemble: Ensemble,
|
||||
pub role: Role,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Track {
|
||||
pub track_id: String,
|
||||
|
|
@ -229,11 +238,7 @@ impl Display for Ensemble {
|
|||
}
|
||||
|
||||
impl Recording {
|
||||
pub fn from_table(
|
||||
data: tables::Recording,
|
||||
library_path: &str,
|
||||
connection: &mut SqliteConnection,
|
||||
) -> Result<Self> {
|
||||
pub fn from_table(data: tables::Recording, connection: &mut SqliteConnection) -> Result<Self> {
|
||||
let work = Work::from_table(
|
||||
works::table
|
||||
.filter(works::work_id.eq(&data.work_id))
|
||||
|
|
@ -249,24 +254,15 @@ impl Recording {
|
|||
.map(|r| Performer::from_table(r, connection))
|
||||
.collect::<Result<Vec<Performer>>>()?;
|
||||
|
||||
let ensembles: Vec<Ensemble> = ensembles::table
|
||||
let ensembles = ensembles::table
|
||||
.inner_join(recording_ensembles::table)
|
||||
.order(recording_ensembles::sequence_number)
|
||||
.filter(recording_ensembles::recording_id.eq(&data.recording_id))
|
||||
.select(tables::Ensemble::as_select())
|
||||
.load::<tables::Ensemble>(connection)?
|
||||
.select(tables::RecordingEnsemble::as_select())
|
||||
.load::<tables::RecordingEnsemble>(connection)?
|
||||
.into_iter()
|
||||
.map(|e| Ensemble::from_table(e, connection))
|
||||
.collect::<Result<Vec<Ensemble>>>()?;
|
||||
|
||||
let tracks: Vec<Track> = tracks::table
|
||||
.order(tracks::recording_index)
|
||||
.filter(tracks::recording_id.eq(&data.recording_id))
|
||||
.select(tables::Track::as_select())
|
||||
.load::<tables::Track>(connection)?
|
||||
.into_iter()
|
||||
.map(|t| Track::from_table(t, library_path, connection))
|
||||
.collect::<Result<Vec<Track>>>()?;
|
||||
.map(|e| EnsemblePerformer::from_table(e, connection))
|
||||
.collect::<Result<Vec<EnsemblePerformer>>>()?;
|
||||
|
||||
Ok(Self {
|
||||
recording_id: data.recording_id,
|
||||
|
|
@ -274,7 +270,6 @@ impl Recording {
|
|||
year: data.year,
|
||||
persons,
|
||||
ensembles,
|
||||
tracks,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -289,7 +284,7 @@ impl Recording {
|
|||
&mut self
|
||||
.ensembles
|
||||
.iter()
|
||||
.map(|e| e.name.get().to_string())
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>(),
|
||||
);
|
||||
|
||||
|
|
@ -344,12 +339,33 @@ impl Display for Performer {
|
|||
}
|
||||
}
|
||||
|
||||
impl Track {
|
||||
impl EnsemblePerformer {
|
||||
pub fn from_table(
|
||||
data: tables::Track,
|
||||
library_path: &str,
|
||||
data: tables::RecordingEnsemble,
|
||||
connection: &mut SqliteConnection,
|
||||
) -> Result<Self> {
|
||||
let ensemble_data = ensembles::table
|
||||
.filter(ensembles::ensemble_id.eq(&data.ensemble_id))
|
||||
.first::<tables::Ensemble>(connection)?;
|
||||
|
||||
let ensemble = Ensemble::from_table(ensemble_data, connection)?;
|
||||
|
||||
let role: Role = roles::table
|
||||
.filter(roles::role_id.eq(&data.role_id))
|
||||
.first(connection)?;
|
||||
|
||||
Ok(Self { ensemble, role })
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EnsemblePerformer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.ensemble.name.get().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Track {
|
||||
pub fn from_table(data: tables::Track, connection: &mut SqliteConnection) -> Result<Self> {
|
||||
let works: Vec<Work> = works::table
|
||||
.inner_join(track_works::table)
|
||||
.order(track_works::sequence_number)
|
||||
|
|
@ -362,11 +378,7 @@ impl Track {
|
|||
|
||||
Ok(Self {
|
||||
track_id: data.track_id,
|
||||
path: Path::new(library_path)
|
||||
.join(&data.path)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
path: data.path,
|
||||
works,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ pub struct Role {
|
|||
}
|
||||
|
||||
#[derive(Boxed, Insertable, Queryable, Selectable, Clone, Debug)]
|
||||
#[boxed_type(name = "MusicusInstrument")]
|
||||
#[boxed_type(name = "MusicusInstrument", nullable)]
|
||||
#[diesel(check_for_backend(Sqlite))]
|
||||
pub struct Instrument {
|
||||
pub instrument_id: String,
|
||||
|
|
|
|||
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_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!(
|
||||
editor.connect_created(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
#[weak]
|
||||
row,
|
||||
#[strong]
|
||||
instrument,
|
||||
move |_| {
|
||||
obj.imp().instrument_list.remove(&row);
|
||||
obj.imp()
|
||||
.instruments
|
||||
.borrow_mut()
|
||||
.retain(|i| *i != instrument);
|
||||
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();
|
||||
}
|
||||
}
|
||||
317
src/library.rs
317
src/library.rs
|
|
@ -48,7 +48,7 @@ mod imp {
|
|||
|
||||
let db_path = PathBuf::from(&self.folder.get().unwrap()).join("musicus.db");
|
||||
let connection = db::connect(db_path.to_str().unwrap()).unwrap();
|
||||
self.connection.set(Some(connection));
|
||||
self.connection.replace(Some(connection));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -232,7 +232,7 @@ impl MusicusLibrary {
|
|||
.distinct()
|
||||
.load::<tables::Recording>(connection)?
|
||||
.into_iter()
|
||||
.map(|r| Recording::from_table(r, &&self.folder(), connection))
|
||||
.map(|r| Recording::from_table(r, connection))
|
||||
.collect::<Result<Vec<Recording>>>()?;
|
||||
|
||||
let albums = albums::table
|
||||
|
|
@ -293,7 +293,7 @@ impl MusicusLibrary {
|
|||
.distinct()
|
||||
.load::<tables::Recording>(connection)?
|
||||
.into_iter()
|
||||
.map(|r| Recording::from_table(r, &self.folder(), connection))
|
||||
.map(|r| Recording::from_table(r, connection))
|
||||
.collect::<Result<Vec<Recording>>>()?;
|
||||
|
||||
let albums = albums::table
|
||||
|
|
@ -336,7 +336,7 @@ impl MusicusLibrary {
|
|||
.distinct()
|
||||
.load::<tables::Recording>(connection)?
|
||||
.into_iter()
|
||||
.map(|r| Recording::from_table(r, &self.folder(), connection))
|
||||
.map(|r| Recording::from_table(r, connection))
|
||||
.collect::<Result<Vec<Recording>>>()?;
|
||||
|
||||
LibraryResults {
|
||||
|
|
@ -363,7 +363,7 @@ impl MusicusLibrary {
|
|||
.distinct()
|
||||
.load::<tables::Recording>(connection)?
|
||||
.into_iter()
|
||||
.map(|r| Recording::from_table(r, &self.folder(), connection))
|
||||
.map(|r| Recording::from_table(r, connection))
|
||||
.collect::<Result<Vec<Recording>>>()?;
|
||||
|
||||
LibraryResults {
|
||||
|
|
@ -378,7 +378,7 @@ impl MusicusLibrary {
|
|||
.filter(recordings::work_id.eq(&work.work_id))
|
||||
.load::<tables::Recording>(connection)?
|
||||
.into_iter()
|
||||
.map(|r| Recording::from_table(r, &self.folder(), connection))
|
||||
.map(|r| Recording::from_table(r, connection))
|
||||
.collect::<Result<Vec<Recording>>>()?;
|
||||
|
||||
LibraryResults {
|
||||
|
|
@ -479,7 +479,23 @@ impl MusicusLibrary {
|
|||
.select(tables::Recording::as_select())
|
||||
.first::<tables::Recording>(connection)?;
|
||||
|
||||
Recording::from_table(row, &self.folder(), connection)
|
||||
Recording::from_table(row, connection)
|
||||
}
|
||||
|
||||
pub fn tracks_for_recording(&self, recording_id: &str) -> Result<Vec<Track>> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let tracks = tracks::table
|
||||
.order(tracks::recording_index)
|
||||
.filter(tracks::recording_id.eq(&recording_id))
|
||||
.select(tables::Track::as_select())
|
||||
.load::<tables::Track>(connection)?
|
||||
.into_iter()
|
||||
.map(|t| Track::from_table(t, connection))
|
||||
.collect::<Result<Vec<Track>>>()?;
|
||||
|
||||
Ok(tracks)
|
||||
}
|
||||
|
||||
pub fn track_played(&self, track_id: &str) -> Result<()> {
|
||||
|
|
@ -572,6 +588,29 @@ impl MusicusLibrary {
|
|||
Ok(works)
|
||||
}
|
||||
|
||||
pub fn search_ensembles(&self, search: &str) -> Result<Vec<Ensemble>> {
|
||||
let search = format!("%{}%", search);
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let ensembles = ensembles::table
|
||||
.order(ensembles::last_used_at.desc())
|
||||
.left_join(ensemble_persons::table.inner_join(persons::table))
|
||||
.filter(
|
||||
ensembles::name
|
||||
.like(&search)
|
||||
.or(persons::name.like(&search)),
|
||||
)
|
||||
.limit(20)
|
||||
.select(ensembles::all_columns)
|
||||
.load::<tables::Ensemble>(connection)?
|
||||
.into_iter()
|
||||
.map(|e| Ensemble::from_table(e, connection))
|
||||
.collect::<Result<Vec<Ensemble>>>()?;
|
||||
|
||||
Ok(ensembles)
|
||||
}
|
||||
|
||||
pub fn composer_default_role(&self) -> Result<Role> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
|
@ -581,6 +620,15 @@ impl MusicusLibrary {
|
|||
.first::<Role>(connection)?)
|
||||
}
|
||||
|
||||
pub fn performer_default_role(&self) -> Result<Role> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
Ok(roles::table
|
||||
.filter(roles::role_id.eq("28ff0aeb11c041a6916d93e9b4884eef"))
|
||||
.first::<Role>(connection)?)
|
||||
}
|
||||
|
||||
pub fn create_person(&self, name: TranslatedString) -> Result<Person> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
|
@ -621,6 +669,46 @@ impl MusicusLibrary {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_instrument(&self, name: TranslatedString) -> Result<Instrument> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let now = Local::now().naive_local();
|
||||
|
||||
let instrument = Instrument {
|
||||
instrument_id: db::generate_id(),
|
||||
name,
|
||||
created_at: now,
|
||||
edited_at: now,
|
||||
last_used_at: now,
|
||||
last_played_at: None,
|
||||
};
|
||||
|
||||
diesel::insert_into(instruments::table)
|
||||
.values(&instrument)
|
||||
.execute(connection)?;
|
||||
|
||||
Ok(instrument)
|
||||
}
|
||||
|
||||
pub fn update_instrument(&self, id: &str, name: TranslatedString) -> Result<()> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let now = Local::now().naive_local();
|
||||
|
||||
diesel::update(instruments::table)
|
||||
.filter(instruments::instrument_id.eq(id))
|
||||
.set((
|
||||
instruments::name.eq(name),
|
||||
instruments::edited_at.eq(now),
|
||||
instruments::last_used_at.eq(now),
|
||||
))
|
||||
.execute(connection)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_role(&self, name: TranslatedString) -> Result<Role> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
|
@ -659,6 +747,221 @@ impl MusicusLibrary {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_work(
|
||||
&self,
|
||||
name: TranslatedString,
|
||||
parts: Vec<WorkPart>,
|
||||
persons: Vec<Composer>,
|
||||
instruments: Vec<Instrument>,
|
||||
) -> Result<Work> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let work_id = db::generate_id();
|
||||
let now = Local::now().naive_local();
|
||||
|
||||
let work_data = tables::Work {
|
||||
work_id: work_id.clone(),
|
||||
parent_work_id: None,
|
||||
sequence_number: None,
|
||||
name,
|
||||
created_at: now,
|
||||
edited_at: now,
|
||||
last_used_at: now,
|
||||
last_played_at: None,
|
||||
};
|
||||
|
||||
diesel::insert_into(works::table)
|
||||
.values(&work_data)
|
||||
.execute(connection)?;
|
||||
|
||||
for (index, part) in parts.into_iter().enumerate() {
|
||||
let part_data = tables::Work {
|
||||
work_id: part.work_id,
|
||||
parent_work_id: Some(work_id.clone()),
|
||||
sequence_number: Some(index as i32),
|
||||
name: part.name,
|
||||
created_at: now,
|
||||
edited_at: now,
|
||||
last_used_at: now,
|
||||
last_played_at: None,
|
||||
};
|
||||
|
||||
diesel::insert_into(works::table)
|
||||
.values(&part_data)
|
||||
.execute(connection)?;
|
||||
}
|
||||
|
||||
for (index, composer) in persons.into_iter().enumerate() {
|
||||
let composer_data = tables::WorkPerson {
|
||||
work_id: work_id.clone(),
|
||||
person_id: composer.person.person_id,
|
||||
role_id: composer.role.role_id,
|
||||
sequence_number: index as i32,
|
||||
};
|
||||
|
||||
diesel::insert_into(work_persons::table)
|
||||
.values(composer_data)
|
||||
.execute(connection)?;
|
||||
}
|
||||
|
||||
for (index, instrument) in instruments.into_iter().enumerate() {
|
||||
let instrument_data = tables::WorkInstrument {
|
||||
work_id: work_id.clone(),
|
||||
instrument_id: instrument.instrument_id,
|
||||
sequence_number: index as i32,
|
||||
};
|
||||
|
||||
diesel::insert_into(work_instruments::table)
|
||||
.values(instrument_data)
|
||||
.execute(connection)?;
|
||||
}
|
||||
|
||||
let work = Work::from_table(work_data, connection)?;
|
||||
|
||||
Ok(work)
|
||||
}
|
||||
|
||||
pub fn update_work(
|
||||
&self,
|
||||
id: &str,
|
||||
name: TranslatedString,
|
||||
parts: Vec<WorkPart>,
|
||||
persons: Vec<Composer>,
|
||||
instruments: Vec<Instrument>,
|
||||
) -> Result<()> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let now = Local::now().naive_local();
|
||||
|
||||
// TODO: Update work, check which work parts etc exist, update them,
|
||||
// create new work parts, delete and readd composers and instruments.
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn create_ensemble(&self, name: TranslatedString) -> Result<Ensemble> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let now = Local::now().naive_local();
|
||||
|
||||
let ensemble_data = tables::Ensemble {
|
||||
ensemble_id: db::generate_id(),
|
||||
name,
|
||||
created_at: now,
|
||||
edited_at: now,
|
||||
last_used_at: now,
|
||||
last_played_at: None,
|
||||
};
|
||||
|
||||
// TODO: Add persons.
|
||||
|
||||
diesel::insert_into(ensembles::table)
|
||||
.values(&ensemble_data)
|
||||
.execute(connection)?;
|
||||
|
||||
let ensemble = Ensemble::from_table(ensemble_data, connection)?;
|
||||
|
||||
Ok(ensemble)
|
||||
}
|
||||
|
||||
pub fn update_ensemble(&self, id: &str, name: TranslatedString) -> Result<()> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let now = Local::now().naive_local();
|
||||
|
||||
diesel::update(ensembles::table)
|
||||
.filter(ensembles::ensemble_id.eq(id))
|
||||
.set((
|
||||
ensembles::name.eq(name),
|
||||
ensembles::edited_at.eq(now),
|
||||
ensembles::last_used_at.eq(now),
|
||||
))
|
||||
.execute(connection)?;
|
||||
|
||||
// TODO: Support updating persons.
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_recording(
|
||||
&self,
|
||||
work: Work,
|
||||
year: Option<i32>,
|
||||
performers: Vec<Performer>,
|
||||
ensembles: Vec<EnsemblePerformer>,
|
||||
) -> Result<Recording> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let recording_id = db::generate_id();
|
||||
let now = Local::now().naive_local();
|
||||
|
||||
let recording_data = tables::Recording {
|
||||
recording_id: recording_id.clone(),
|
||||
work_id: work.work_id.clone(),
|
||||
year,
|
||||
created_at: now,
|
||||
edited_at: now,
|
||||
last_used_at: now,
|
||||
last_played_at: None,
|
||||
};
|
||||
|
||||
diesel::insert_into(recordings::table)
|
||||
.values(&recording_data)
|
||||
.execute(connection)?;
|
||||
|
||||
for (index, performer) in performers.into_iter().enumerate() {
|
||||
let recording_person_data = tables::RecordingPerson {
|
||||
recording_id: recording_id.clone(),
|
||||
person_id: performer.person.person_id,
|
||||
role_id: performer.role.role_id,
|
||||
instrument_id: performer.instrument.map(|i| i.instrument_id),
|
||||
sequence_number: index as i32,
|
||||
};
|
||||
|
||||
diesel::insert_into(recording_persons::table)
|
||||
.values(&recording_person_data)
|
||||
.execute(connection)?;
|
||||
}
|
||||
|
||||
for (index, ensemble) in ensembles.into_iter().enumerate() {
|
||||
let recording_ensemble_data = tables::RecordingEnsemble {
|
||||
recording_id: recording_id.clone(),
|
||||
ensemble_id: ensemble.ensemble.ensemble_id,
|
||||
role_id: ensemble.role.role_id,
|
||||
sequence_number: index as i32,
|
||||
};
|
||||
|
||||
diesel::insert_into(recording_ensembles::table)
|
||||
.values(&recording_ensemble_data)
|
||||
.execute(connection)?;
|
||||
}
|
||||
|
||||
let recording = Recording::from_table(recording_data, connection)?;
|
||||
|
||||
Ok(recording)
|
||||
}
|
||||
|
||||
pub fn update_recording(
|
||||
&self,
|
||||
id: &str,
|
||||
work: Work,
|
||||
year: Option<i32>,
|
||||
performers: Vec<Performer>,
|
||||
ensembles: Vec<EnsemblePerformer>,
|
||||
) -> Result<()> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let now = Local::now().naive_local();
|
||||
|
||||
// TODO: Update recording.
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
|
|
|
|||
|
|
@ -1,20 +1,23 @@
|
|||
use adw::{
|
||||
prelude::*,
|
||||
subclass::{navigation_page::NavigationPageImpl, prelude::*},
|
||||
};
|
||||
use gtk::glib::{self, Properties};
|
||||
use adw::subclass::prelude::*;
|
||||
use gtk::glib;
|
||||
use std::cell::OnceCell;
|
||||
|
||||
use crate::library::MusicusLibrary;
|
||||
use crate::{
|
||||
editor::{
|
||||
ensemble_editor::MusicusEnsembleEditor, instrument_editor::MusicusInstrumentEditor,
|
||||
person_editor::MusicusPersonEditor, recording_editor::MusicusRecordingEditor,
|
||||
role_editor::MusicusRoleEditor, work_editor::MusicusWorkEditor,
|
||||
},
|
||||
library::MusicusLibrary,
|
||||
};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
#[derive(Properties, Debug, Default, gtk::CompositeTemplate)]
|
||||
#[properties(wrapper_type = super::LibraryManager)]
|
||||
#[derive(Debug, Default, gtk::CompositeTemplate)]
|
||||
#[template(file = "data/ui/library_manager.blp")]
|
||||
pub struct LibraryManager {
|
||||
#[property(get, construct_only)]
|
||||
pub navigation: OnceCell<adw::NavigationView>,
|
||||
pub library: OnceCell<MusicusLibrary>,
|
||||
}
|
||||
|
||||
|
|
@ -34,9 +37,7 @@ mod imp {
|
|||
}
|
||||
}
|
||||
|
||||
#[glib::derived_properties]
|
||||
impl ObjectImpl for LibraryManager {}
|
||||
|
||||
impl WidgetImpl for LibraryManager {}
|
||||
impl NavigationPageImpl for LibraryManager {}
|
||||
}
|
||||
|
|
@ -48,7 +49,110 @@ glib::wrapper! {
|
|||
|
||||
#[gtk::template_callbacks]
|
||||
impl LibraryManager {
|
||||
pub fn new(library: &MusicusLibrary) -> Self {
|
||||
glib::Object::builder().property("library", library).build()
|
||||
pub fn new(navigation: &adw::NavigationView, library: &MusicusLibrary) -> Self {
|
||||
let obj: Self = glib::Object::new();
|
||||
let imp = obj.imp();
|
||||
|
||||
imp.navigation.set(navigation.to_owned()).unwrap();
|
||||
imp.library.set(library.to_owned()).unwrap();
|
||||
|
||||
obj
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn add_person(&self, _: >k::Button) {
|
||||
self.imp()
|
||||
.navigation
|
||||
.get()
|
||||
.unwrap()
|
||||
.push(&MusicusPersonEditor::new(
|
||||
&self.imp().navigation.get().unwrap(),
|
||||
&self.imp().library.get().unwrap(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn add_role(&self, _: >k::Button) {
|
||||
self.imp()
|
||||
.navigation
|
||||
.get()
|
||||
.unwrap()
|
||||
.push(&MusicusRoleEditor::new(
|
||||
&self.imp().navigation.get().unwrap(),
|
||||
&self.imp().library.get().unwrap(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn add_instrument(&self, _: >k::Button) {
|
||||
self.imp()
|
||||
.navigation
|
||||
.get()
|
||||
.unwrap()
|
||||
.push(&MusicusInstrumentEditor::new(
|
||||
&self.imp().navigation.get().unwrap(),
|
||||
&self.imp().library.get().unwrap(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn add_work(&self, _: >k::Button) {
|
||||
self.imp()
|
||||
.navigation
|
||||
.get()
|
||||
.unwrap()
|
||||
.push(&MusicusWorkEditor::new(
|
||||
&self.imp().navigation.get().unwrap(),
|
||||
&self.imp().library.get().unwrap(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn add_ensemble(&self, _: >k::Button) {
|
||||
self.imp()
|
||||
.navigation
|
||||
.get()
|
||||
.unwrap()
|
||||
.push(&MusicusEnsembleEditor::new(
|
||||
&self.imp().navigation.get().unwrap(),
|
||||
&self.imp().library.get().unwrap(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn add_recording(&self, _: >k::Button) {
|
||||
self.imp()
|
||||
.navigation
|
||||
.get()
|
||||
.unwrap()
|
||||
.push(&MusicusRecordingEditor::new(
|
||||
&self.imp().navigation.get().unwrap(),
|
||||
&self.imp().library.get().unwrap(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn add_medium(&self, _: >k::Button) {
|
||||
todo!("Medium import");
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn add_album(&self, _: >k::Button) {
|
||||
todo!("Album editor");
|
||||
// self.imp()
|
||||
// .navigation
|
||||
// .get()
|
||||
// .unwrap()
|
||||
// .push(&MusicusAlbumEditor::new(
|
||||
// &self.imp().navigation.get().unwrap(),
|
||||
// &self.imp().library.get().unwrap(),
|
||||
// None,
|
||||
// ));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::{
|
||||
cell::{Cell, OnceCell, RefCell},
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
|
|
@ -236,7 +237,7 @@ impl MusicusPlayer {
|
|||
}
|
||||
|
||||
pub fn play_recording(&self, recording: &Recording) {
|
||||
let tracks = &recording.tracks;
|
||||
let tracks = &self.library().unwrap().tracks_for_recording(&recording.recording_id).unwrap();
|
||||
|
||||
if tracks.is_empty() {
|
||||
log::warn!("Ignoring recording without tracks being added to the playlist.");
|
||||
|
|
@ -254,7 +255,7 @@ impl MusicusPlayer {
|
|||
&recording.work.name.get(),
|
||||
Some(&performances),
|
||||
None,
|
||||
&tracks[0].path,
|
||||
&self.library_path_to_file_path(&tracks[0].path),
|
||||
&tracks[0].track_id,
|
||||
));
|
||||
} else {
|
||||
|
|
@ -282,7 +283,7 @@ impl MusicusPlayer {
|
|||
&recording.work.name.get(),
|
||||
Some(&performances),
|
||||
Some(&track_title(&first_track, 1)),
|
||||
&first_track.path,
|
||||
&self.library_path_to_file_path(&first_track.path),
|
||||
&first_track.track_id,
|
||||
));
|
||||
|
||||
|
|
@ -294,7 +295,7 @@ impl MusicusPlayer {
|
|||
Some(&performances),
|
||||
// track number = track index + 1 (first track) + 1 (zero based)
|
||||
Some(&track_title(&track, index + 2)),
|
||||
&track.path,
|
||||
&self.library_path_to_file_path(&track.path),
|
||||
&track.track_id,
|
||||
));
|
||||
}
|
||||
|
|
@ -384,6 +385,14 @@ impl MusicusPlayer {
|
|||
self.play_recording(&recording);
|
||||
}
|
||||
}
|
||||
|
||||
fn library_path_to_file_path(&self, path: &str) -> String {
|
||||
PathBuf::from(self.library().unwrap().folder())
|
||||
.join(path)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MusicusPlayer {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
use gtk::{glib, subclass::prelude::*};
|
||||
use gtk::{gio, glib, prelude::*, subclass::prelude::*};
|
||||
use std::cell::OnceCell;
|
||||
|
||||
use crate::db::models::Recording;
|
||||
use crate::{
|
||||
db::models::Recording, editor::recording_editor::MusicusRecordingEditor,
|
||||
library::MusicusLibrary,
|
||||
};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
|
@ -16,6 +19,8 @@ mod imp {
|
|||
#[template_child]
|
||||
pub performances_label: TemplateChild<gtk::Label>,
|
||||
|
||||
pub navigation: OnceCell<adw::NavigationView>,
|
||||
pub library: OnceCell<MusicusLibrary>,
|
||||
pub recording: OnceCell<Recording>,
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +39,31 @@ mod imp {
|
|||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for MusicusRecordingTile {}
|
||||
impl ObjectImpl for MusicusRecordingTile {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
let obj = self.obj().to_owned();
|
||||
let edit_action = gio::ActionEntry::builder("edit")
|
||||
.activate(move |_, _, _| {
|
||||
obj.imp()
|
||||
.navigation
|
||||
.get()
|
||||
.unwrap()
|
||||
.push(&MusicusRecordingEditor::new(
|
||||
obj.imp().navigation.get().unwrap(),
|
||||
obj.imp().library.get().unwrap(),
|
||||
Some(&obj.imp().recording.get().unwrap()),
|
||||
));
|
||||
})
|
||||
.build();
|
||||
|
||||
let actions = gio::SimpleActionGroup::new();
|
||||
actions.add_action_entries([edit_action]);
|
||||
self.obj().insert_action_group("recording", Some(&actions));
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for MusicusRecordingTile {}
|
||||
impl FlowBoxChildImpl for MusicusRecordingTile {}
|
||||
}
|
||||
|
|
@ -45,15 +74,23 @@ glib::wrapper! {
|
|||
}
|
||||
|
||||
impl MusicusRecordingTile {
|
||||
pub fn new(recording: &Recording) -> Self {
|
||||
pub fn new(
|
||||
navigation: &adw::NavigationView,
|
||||
library: &MusicusLibrary,
|
||||
recording: &Recording,
|
||||
) -> Self {
|
||||
let obj: Self = glib::Object::new();
|
||||
let imp = obj.imp();
|
||||
|
||||
imp.work_label.set_label(&recording.work.name.get());
|
||||
imp.composer_label.set_label(&recording.work.composers_string());
|
||||
imp.performances_label.set_label(&recording.performers_string());
|
||||
imp.composer_label
|
||||
.set_label(&recording.work.composers_string());
|
||||
imp.performances_label
|
||||
.set_label(&recording.performers_string());
|
||||
|
||||
imp.recording.set(recording.clone()).unwrap();
|
||||
imp.navigation.set(navigation.to_owned()).unwrap();
|
||||
imp.library.set(library.to_owned()).unwrap();
|
||||
imp.recording.set(recording.to_owned()).unwrap();
|
||||
|
||||
obj
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,6 +175,6 @@ impl MusicusWindow {
|
|||
let navigation = self.imp().navigation_view.get();
|
||||
navigation
|
||||
.replace(&[MusicusHomePage::new(&navigation, &library, &self.imp().player).into()]);
|
||||
navigation.add(&LibraryManager::new(&library));
|
||||
navigation.add(&LibraryManager::new(&navigation, &library));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue