mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 11:47:25 +01:00
editor: First changes for work editor
This commit is contained in:
parent
15ba043050
commit
55b344605b
19 changed files with 1291 additions and 19 deletions
|
|
@ -62,4 +62,19 @@
|
||||||
|
|
||||||
.playlisttile .parttitle {
|
.playlisttile .parttitle {
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector > contents {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector-list {
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector-list > row {
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
35
data/ui/instrument_selector_popover.blp
Normal file
35
data/ui/instrument_selector_popover.blp
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
template $MusicusInstrumentSelectorPopover: Gtk.Popover {
|
||||||
|
styles [
|
||||||
|
"selector"
|
||||||
|
]
|
||||||
|
|
||||||
|
Adw.ToolbarView {
|
||||||
|
[top]
|
||||||
|
Gtk.SearchEntry search_entry {
|
||||||
|
placeholder-text: _("Search instruments…");
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
data/ui/person_selector_popover.blp
Normal file
35
data/ui/person_selector_popover.blp
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
template $MusicusPersonSelectorPopover: Gtk.Popover {
|
||||||
|
styles [
|
||||||
|
"selector"
|
||||||
|
]
|
||||||
|
|
||||||
|
Adw.ToolbarView {
|
||||||
|
[top]
|
||||||
|
Gtk.SearchEntry search_entry {
|
||||||
|
placeholder-text: _("Search persons…");
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
data/ui/role_selector_popover.blp
Normal file
35
data/ui/role_selector_popover.blp
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
template $MusicusRoleSelectorPopover: Gtk.Popover {
|
||||||
|
styles [
|
||||||
|
"selector"
|
||||||
|
]
|
||||||
|
|
||||||
|
Adw.ToolbarView {
|
||||||
|
[top]
|
||||||
|
Gtk.SearchEntry search_entry {
|
||||||
|
placeholder-text: _("Search roles…");
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +1,27 @@
|
||||||
using Gtk 4.0;
|
using Gtk 4.0;
|
||||||
using Adw 1;
|
using Adw 1;
|
||||||
|
|
||||||
template $MusicusTranslationEntry : Adw.EntryRow {
|
template $MusicusTranslationEntry: Adw.EntryRow {
|
||||||
title: _("Translated name");
|
title: _("Translated name");
|
||||||
|
|
||||||
Gtk.Button {
|
Gtk.Button {
|
||||||
icon-name: "edit-delete-symbolic";
|
icon-name: "user-trash-symbolic";
|
||||||
valign: center;
|
valign: center;
|
||||||
clicked => $remove() swapped;
|
clicked => $remove() swapped;
|
||||||
styles ["flat"]
|
|
||||||
|
styles [
|
||||||
|
"flat"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk.Button {
|
Gtk.Button {
|
||||||
valign: center;
|
valign: center;
|
||||||
clicked => $open_lang_popover() swapped;
|
clicked => $open_lang_popover() swapped;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"flat"
|
||||||
|
]
|
||||||
|
|
||||||
Gtk.Box {
|
Gtk.Box {
|
||||||
spacing: 6;
|
spacing: 6;
|
||||||
|
|
||||||
|
|
@ -38,7 +45,10 @@ template $MusicusTranslationEntry : Adw.EntryRow {
|
||||||
Gtk.Label {
|
Gtk.Label {
|
||||||
label: _("Language code");
|
label: _("Language code");
|
||||||
halign: start;
|
halign: start;
|
||||||
styles ["heading"]
|
|
||||||
|
styles [
|
||||||
|
"heading"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk.Label {
|
Gtk.Label {
|
||||||
|
|
@ -48,7 +58,10 @@ template $MusicusTranslationEntry : Adw.EntryRow {
|
||||||
wrap: true;
|
wrap: true;
|
||||||
max-width-chars: 40;
|
max-width-chars: 40;
|
||||||
halign: start;
|
halign: start;
|
||||||
styles ["dim-label"]
|
|
||||||
|
styles [
|
||||||
|
"dim-label"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk.Entry lang_entry {}
|
Gtk.Entry lang_entry {}
|
||||||
|
|
@ -56,4 +69,4 @@ template $MusicusTranslationEntry : Adw.EntryRow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
119
data/ui/work_editor.blp
Normal file
119
data/ui/work_editor.blp
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
template $MusicusWorkEditor: Adw.NavigationPage {
|
||||||
|
title: _("Work");
|
||||||
|
|
||||||
|
Adw.ToolbarView {
|
||||||
|
[top]
|
||||||
|
Adw.HeaderBar header_bar {}
|
||||||
|
|
||||||
|
Gtk.ScrolledWindow {
|
||||||
|
Adw.Clamp {
|
||||||
|
Gtk.Box {
|
||||||
|
orientation: vertical;
|
||||||
|
margin-bottom: 24;
|
||||||
|
margin-start: 12;
|
||||||
|
margin-end: 12;
|
||||||
|
|
||||||
|
$MusicusTranslationSection name_section {}
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
label: _("Composers");
|
||||||
|
xalign: 0;
|
||||||
|
margin-top: 24;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"heading"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.ListBox composer_list {
|
||||||
|
selection-mode: none;
|
||||||
|
margin-top: 12;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"boxed-list"
|
||||||
|
]
|
||||||
|
|
||||||
|
Adw.ActionRow {
|
||||||
|
title: _("Add composer");
|
||||||
|
activatable: true;
|
||||||
|
activated => $add_person() swapped;
|
||||||
|
|
||||||
|
[prefix]
|
||||||
|
Gtk.Box select_person_box {
|
||||||
|
Gtk.Image {
|
||||||
|
icon-name: "list-add-symbolic";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
label: _("Structure");
|
||||||
|
xalign: 0;
|
||||||
|
margin-top: 24;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"heading"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.ListBox part_list {
|
||||||
|
selection-mode: none;
|
||||||
|
margin-top: 12;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"boxed-list"
|
||||||
|
]
|
||||||
|
|
||||||
|
Adw.ActionRow {
|
||||||
|
title: _("Add part");
|
||||||
|
activatable: true;
|
||||||
|
activated => $add_part() swapped;
|
||||||
|
|
||||||
|
[prefix]
|
||||||
|
Gtk.Image {
|
||||||
|
icon-name: "list-add-symbolic";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
label: _("Instruments");
|
||||||
|
xalign: 0;
|
||||||
|
margin-top: 24;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"heading"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.ListBox instrument_list {
|
||||||
|
selection-mode: none;
|
||||||
|
margin-top: 12;
|
||||||
|
margin-bottom: 24;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"boxed-list"
|
||||||
|
]
|
||||||
|
|
||||||
|
Adw.ActionRow {
|
||||||
|
title: _("Add instrument");
|
||||||
|
activatable: true;
|
||||||
|
activated => $add_instrument() swapped;
|
||||||
|
|
||||||
|
[prefix]
|
||||||
|
Gtk.Box select_instrument_box {
|
||||||
|
Gtk.Image {
|
||||||
|
icon-name: "list-add-symbolic";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
data/ui/work_editor_composer_row.blp
Normal file
33
data/ui/work_editor_composer_row.blp
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
template $MusicusWorkEditorComposerRow: 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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ pub mod models;
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
pub mod tables;
|
pub mod tables;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, fmt::Display};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use diesel::{
|
use diesel::{
|
||||||
|
|
@ -63,6 +63,12 @@ impl TranslatedString {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for TranslatedString {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.get())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<DB: Backend> FromSql<Text, DB> for TranslatedString
|
impl<DB: Backend> FromSql<Text, DB> for TranslatedString
|
||||||
where
|
where
|
||||||
String: FromSql<Text, DB>,
|
String: FromSql<Text, DB>,
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ pub struct Work {
|
||||||
pub work_id: String,
|
pub work_id: String,
|
||||||
pub name: TranslatedString,
|
pub name: TranslatedString,
|
||||||
pub parts: Vec<WorkPart>,
|
pub parts: Vec<WorkPart>,
|
||||||
pub persons: Vec<Person>,
|
pub persons: Vec<Composer>,
|
||||||
pub instruments: Vec<Instrument>,
|
pub instruments: Vec<Instrument>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,6 +27,14 @@ pub struct WorkPart {
|
||||||
pub name: TranslatedString,
|
pub name: TranslatedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Selectable, Clone, Debug)]
|
||||||
|
pub struct Composer {
|
||||||
|
#[diesel(embed)]
|
||||||
|
pub person: Person,
|
||||||
|
#[diesel(embed)]
|
||||||
|
pub role: Role,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Ensemble {
|
pub struct Ensemble {
|
||||||
pub ensemble_id: String,
|
pub ensemble_id: String,
|
||||||
|
|
@ -65,6 +73,45 @@ impl PartialEq for Person {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Person {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Instrument {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Instrument {}
|
||||||
|
impl PartialEq for Instrument {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.instrument_id == other.instrument_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Role {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Role {}
|
||||||
|
impl PartialEq for Role {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.role_id == other.role_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Composer {}
|
||||||
|
impl PartialEq for Composer {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.person == other.person && self.role == other.role
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Work {
|
impl Work {
|
||||||
pub fn from_table(data: tables::Work, connection: &mut SqliteConnection) -> Result<Self> {
|
pub fn from_table(data: tables::Work, connection: &mut SqliteConnection) -> Result<Self> {
|
||||||
fn visit_children(
|
fn visit_children(
|
||||||
|
|
@ -95,11 +142,11 @@ impl Work {
|
||||||
|
|
||||||
let parts = visit_children(&data.work_id, 0, connection)?;
|
let parts = visit_children(&data.work_id, 0, connection)?;
|
||||||
|
|
||||||
let persons: Vec<Person> = persons::table
|
let persons: Vec<Composer> = persons::table
|
||||||
.inner_join(work_persons::table)
|
.inner_join(work_persons::table.inner_join(roles::table))
|
||||||
.order(work_persons::sequence_number)
|
.order(work_persons::sequence_number)
|
||||||
.filter(work_persons::work_id.eq(&data.work_id))
|
.filter(work_persons::work_id.eq(&data.work_id))
|
||||||
.select(tables::Person::as_select())
|
.select(Composer::as_select())
|
||||||
.load(connection)?;
|
.load(connection)?;
|
||||||
|
|
||||||
let instruments: Vec<Instrument> = instruments::table
|
let instruments: Vec<Instrument> = instruments::table
|
||||||
|
|
@ -119,9 +166,10 @@ impl Work {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn composers_string(&self) -> String {
|
pub fn composers_string(&self) -> String {
|
||||||
|
// TODO: Include roles except default composer.
|
||||||
self.persons
|
self.persons
|
||||||
.iter()
|
.iter()
|
||||||
.map(|p| p.name.get().to_string())
|
.map(|p| p.person.name.get().to_string())
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join(", ")
|
.join(", ")
|
||||||
}
|
}
|
||||||
|
|
@ -134,6 +182,12 @@ impl PartialEq for Work {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Work {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}: {}", self.composers_string(), self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Ensemble {
|
impl Ensemble {
|
||||||
pub fn from_table(data: tables::Ensemble, connection: &mut SqliteConnection) -> Result<Self> {
|
pub fn from_table(data: tables::Ensemble, connection: &mut SqliteConnection) -> Result<Self> {
|
||||||
let persons: Vec<(Person, Instrument)> = persons::table
|
let persons: Vec<(Person, Instrument)> = persons::table
|
||||||
|
|
@ -158,6 +212,12 @@ impl PartialEq for Ensemble {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Ensemble {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Recording {
|
impl Recording {
|
||||||
pub fn from_table(
|
pub fn from_table(
|
||||||
data: tables::Recording,
|
data: tables::Recording,
|
||||||
|
|
@ -227,6 +287,12 @@ impl Recording {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Recording {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}; {}", self.work, self.performers_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Performer {
|
impl Performer {
|
||||||
pub fn from_table(
|
pub fn from_table(
|
||||||
data: tables::RecordingPerson,
|
data: tables::RecordingPerson,
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,12 @@
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::sqlite::Sqlite;
|
use diesel::sqlite::Sqlite;
|
||||||
|
use gtk::glib::{self, Boxed};
|
||||||
|
|
||||||
use super::{schema::*, TranslatedString};
|
use super::{schema::*, TranslatedString};
|
||||||
|
|
||||||
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
|
#[derive(Boxed, Insertable, Queryable, Selectable, Clone, Debug)]
|
||||||
|
#[boxed_type(name = "MusicusPerson")]
|
||||||
#[diesel(check_for_backend(Sqlite))]
|
#[diesel(check_for_backend(Sqlite))]
|
||||||
pub struct Person {
|
pub struct Person {
|
||||||
pub person_id: String,
|
pub person_id: String,
|
||||||
|
|
@ -18,7 +20,8 @@ pub struct Person {
|
||||||
pub last_played_at: Option<NaiveDateTime>,
|
pub last_played_at: Option<NaiveDateTime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
|
#[derive(Boxed, Insertable, Queryable, Selectable, Clone, Debug)]
|
||||||
|
#[boxed_type(name = "MusicusRole")]
|
||||||
#[diesel(check_for_backend(Sqlite))]
|
#[diesel(check_for_backend(Sqlite))]
|
||||||
pub struct Role {
|
pub struct Role {
|
||||||
pub role_id: String,
|
pub role_id: String,
|
||||||
|
|
@ -28,7 +31,8 @@ pub struct Role {
|
||||||
pub last_used_at: NaiveDateTime,
|
pub last_used_at: NaiveDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
|
#[derive(Boxed, Insertable, Queryable, Selectable, Clone, Debug)]
|
||||||
|
#[boxed_type(name = "MusicusInstrument")]
|
||||||
#[diesel(check_for_backend(Sqlite))]
|
#[diesel(check_for_backend(Sqlite))]
|
||||||
pub struct Instrument {
|
pub struct Instrument {
|
||||||
pub instrument_id: String,
|
pub instrument_id: String,
|
||||||
|
|
|
||||||
188
src/editor/instrument_selector_popover.rs
Normal file
188
src/editor/instrument_selector_popover.rs
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
use crate::{db::models::Instrument, 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::MusicusInstrumentSelectorPopover)]
|
||||||
|
#[template(file = "data/ui/instrument_selector_popover.blp")]
|
||||||
|
pub struct MusicusInstrumentSelectorPopover {
|
||||||
|
#[property(get, construct_only)]
|
||||||
|
pub library: OnceCell<MusicusLibrary>,
|
||||||
|
|
||||||
|
pub instruments: RefCell<Vec<Instrument>>,
|
||||||
|
|
||||||
|
#[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 MusicusInstrumentSelectorPopover {
|
||||||
|
const NAME: &'static str = "MusicusInstrumentSelectorPopover";
|
||||||
|
type Type = super::MusicusInstrumentSelectorPopover;
|
||||||
|
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 MusicusInstrumentSelectorPopover {
|
||||||
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
|
||||||
|
self.obj()
|
||||||
|
.connect_visible_notify(|obj: &super::MusicusInstrumentSelectorPopover| {
|
||||||
|
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("instrument-selected")
|
||||||
|
.param_types([Instrument::static_type()])
|
||||||
|
.build()]
|
||||||
|
});
|
||||||
|
|
||||||
|
SIGNALS.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetImpl for MusicusInstrumentSelectorPopover {
|
||||||
|
// 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 MusicusInstrumentSelectorPopover {}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct MusicusInstrumentSelectorPopover(ObjectSubclass<imp::MusicusInstrumentSelectorPopover>)
|
||||||
|
@extends gtk::Widget, gtk::Popover;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gtk::template_callbacks]
|
||||||
|
impl MusicusInstrumentSelectorPopover {
|
||||||
|
pub fn new(library: &MusicusLibrary) -> Self {
|
||||||
|
glib::Object::builder().property("library", library).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect_instrument_selected<F: Fn(&Self, Instrument) + 'static>(
|
||||||
|
&self,
|
||||||
|
f: F,
|
||||||
|
) -> glib::SignalHandlerId {
|
||||||
|
self.connect_local("instrument-selected", true, move |values| {
|
||||||
|
let obj = values[0].get::<Self>().unwrap();
|
||||||
|
let instrument = values[1].get::<Instrument>().unwrap();
|
||||||
|
f(&obj, instrument);
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn search_changed(&self, entry: >k::SearchEntry) {
|
||||||
|
self.search(&entry.text());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn activate(&self, _: >k::SearchEntry) {
|
||||||
|
if let Some(instrument) = self.imp().instruments.borrow().first() {
|
||||||
|
self.select(instrument.clone());
|
||||||
|
} else {
|
||||||
|
self.create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn stop_search(&self, _: >k::SearchEntry) {
|
||||||
|
self.popdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search(&self, search: &str) {
|
||||||
|
let imp = self.imp();
|
||||||
|
|
||||||
|
let instruments = imp.library.get().unwrap().search_instruments(search).unwrap();
|
||||||
|
|
||||||
|
imp.list_box.remove_all();
|
||||||
|
|
||||||
|
for instrument in &instruments {
|
||||||
|
let row = MusicusActivatableRow::new(
|
||||||
|
>k::Label::builder()
|
||||||
|
.label(instrument.to_string())
|
||||||
|
.halign(gtk::Align::Start)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let instrument = instrument.clone();
|
||||||
|
let obj = self.clone();
|
||||||
|
row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||||
|
obj.select(instrument.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 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();
|
||||||
|
});
|
||||||
|
|
||||||
|
imp.list_box.append(&create_row);
|
||||||
|
|
||||||
|
imp.instruments.replace(instruments);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select(&self, instrument: Instrument) {
|
||||||
|
self.emit_by_name::<()>("instrument-selected", &[&instrument]);
|
||||||
|
self.popdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(&self) {
|
||||||
|
log::info!("Create instrument!");
|
||||||
|
self.popdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
pub mod activatable_row;
|
pub mod activatable_row;
|
||||||
|
pub mod instrument_selector_popover;
|
||||||
pub mod person_editor;
|
pub mod person_editor;
|
||||||
|
pub mod person_selector_popover;
|
||||||
|
pub mod role_selector_popover;
|
||||||
pub mod translation_entry;
|
pub mod translation_entry;
|
||||||
pub mod translation_section;
|
pub mod translation_section;
|
||||||
|
pub mod work_editor;
|
||||||
|
pub mod work_editor_composer_row;
|
||||||
188
src/editor/person_selector_popover.rs
Normal file
188
src/editor/person_selector_popover.rs
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
use crate::{db::models::Person, 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::MusicusPersonSelectorPopover)]
|
||||||
|
#[template(file = "data/ui/person_selector_popover.blp")]
|
||||||
|
pub struct MusicusPersonSelectorPopover {
|
||||||
|
#[property(get, construct_only)]
|
||||||
|
pub library: OnceCell<MusicusLibrary>,
|
||||||
|
|
||||||
|
pub persons: RefCell<Vec<Person>>,
|
||||||
|
|
||||||
|
#[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 MusicusPersonSelectorPopover {
|
||||||
|
const NAME: &'static str = "MusicusPersonSelectorPopover";
|
||||||
|
type Type = super::MusicusPersonSelectorPopover;
|
||||||
|
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 MusicusPersonSelectorPopover {
|
||||||
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
|
||||||
|
self.obj()
|
||||||
|
.connect_visible_notify(|obj: &super::MusicusPersonSelectorPopover| {
|
||||||
|
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("person-selected")
|
||||||
|
.param_types([Person::static_type()])
|
||||||
|
.build()]
|
||||||
|
});
|
||||||
|
|
||||||
|
SIGNALS.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetImpl for MusicusPersonSelectorPopover {
|
||||||
|
// 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 MusicusPersonSelectorPopover {}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct MusicusPersonSelectorPopover(ObjectSubclass<imp::MusicusPersonSelectorPopover>)
|
||||||
|
@extends gtk::Widget, gtk::Popover;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gtk::template_callbacks]
|
||||||
|
impl MusicusPersonSelectorPopover {
|
||||||
|
pub fn new(library: &MusicusLibrary) -> Self {
|
||||||
|
glib::Object::builder().property("library", library).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect_person_selected<F: Fn(&Self, Person) + 'static>(
|
||||||
|
&self,
|
||||||
|
f: F,
|
||||||
|
) -> glib::SignalHandlerId {
|
||||||
|
self.connect_local("person-selected", true, move |values| {
|
||||||
|
let obj = values[0].get::<Self>().unwrap();
|
||||||
|
let person = values[1].get::<Person>().unwrap();
|
||||||
|
f(&obj, person);
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn search_changed(&self, entry: >k::SearchEntry) {
|
||||||
|
self.search(&entry.text());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn activate(&self, _: >k::SearchEntry) {
|
||||||
|
if let Some(person) = self.imp().persons.borrow().first() {
|
||||||
|
self.select(person.clone());
|
||||||
|
} else {
|
||||||
|
self.create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn stop_search(&self, _: >k::SearchEntry) {
|
||||||
|
self.popdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search(&self, search: &str) {
|
||||||
|
let imp = self.imp();
|
||||||
|
|
||||||
|
let persons = imp.library.get().unwrap().search_persons(search).unwrap();
|
||||||
|
|
||||||
|
imp.list_box.remove_all();
|
||||||
|
|
||||||
|
for person in &persons {
|
||||||
|
let row = MusicusActivatableRow::new(
|
||||||
|
>k::Label::builder()
|
||||||
|
.label(person.to_string())
|
||||||
|
.halign(gtk::Align::Start)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let person = person.clone();
|
||||||
|
let obj = self.clone();
|
||||||
|
row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||||
|
obj.select(person.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 person"))
|
||||||
|
.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.persons.replace(persons);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select(&self, person: Person) {
|
||||||
|
self.emit_by_name::<()>("person-selected", &[&person]);
|
||||||
|
self.popdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(&self) {
|
||||||
|
log::info!("Create person!");
|
||||||
|
self.popdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
188
src/editor/role_selector_popover.rs
Normal file
188
src/editor/role_selector_popover.rs
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
use crate::{db::models::Role, 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::MusicusRoleSelectorPopover)]
|
||||||
|
#[template(file = "data/ui/role_selector_popover.blp")]
|
||||||
|
pub struct MusicusRoleSelectorPopover {
|
||||||
|
#[property(get, construct_only)]
|
||||||
|
pub library: OnceCell<MusicusLibrary>,
|
||||||
|
|
||||||
|
pub roles: RefCell<Vec<Role>>,
|
||||||
|
|
||||||
|
#[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 MusicusRoleSelectorPopover {
|
||||||
|
const NAME: &'static str = "MusicusRoleSelectorPopover";
|
||||||
|
type Type = super::MusicusRoleSelectorPopover;
|
||||||
|
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 MusicusRoleSelectorPopover {
|
||||||
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
|
||||||
|
self.obj()
|
||||||
|
.connect_visible_notify(|obj: &super::MusicusRoleSelectorPopover| {
|
||||||
|
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("role-selected")
|
||||||
|
.param_types([Role::static_type()])
|
||||||
|
.build()]
|
||||||
|
});
|
||||||
|
|
||||||
|
SIGNALS.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetImpl for MusicusRoleSelectorPopover {
|
||||||
|
// 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 MusicusRoleSelectorPopover {}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct MusicusRoleSelectorPopover(ObjectSubclass<imp::MusicusRoleSelectorPopover>)
|
||||||
|
@extends gtk::Widget, gtk::Popover;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gtk::template_callbacks]
|
||||||
|
impl MusicusRoleSelectorPopover {
|
||||||
|
pub fn new(library: &MusicusLibrary) -> Self {
|
||||||
|
glib::Object::builder().property("library", library).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect_role_selected<F: Fn(&Self, Role) + 'static>(
|
||||||
|
&self,
|
||||||
|
f: F,
|
||||||
|
) -> glib::SignalHandlerId {
|
||||||
|
self.connect_local("role-selected", true, move |values| {
|
||||||
|
let obj = values[0].get::<Self>().unwrap();
|
||||||
|
let role = values[1].get::<Role>().unwrap();
|
||||||
|
f(&obj, role);
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn search_changed(&self, entry: >k::SearchEntry) {
|
||||||
|
self.search(&entry.text());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn activate(&self, _: >k::SearchEntry) {
|
||||||
|
if let Some(role) = self.imp().roles.borrow().first() {
|
||||||
|
self.select(role.clone());
|
||||||
|
} else {
|
||||||
|
self.create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn stop_search(&self, _: >k::SearchEntry) {
|
||||||
|
self.popdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search(&self, search: &str) {
|
||||||
|
let imp = self.imp();
|
||||||
|
|
||||||
|
let roles = imp.library.get().unwrap().search_roles(search).unwrap();
|
||||||
|
|
||||||
|
imp.list_box.remove_all();
|
||||||
|
|
||||||
|
for role in &roles {
|
||||||
|
let row = MusicusActivatableRow::new(
|
||||||
|
>k::Label::builder()
|
||||||
|
.label(role.to_string())
|
||||||
|
.halign(gtk::Align::Start)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let role = role.clone();
|
||||||
|
let obj = self.clone();
|
||||||
|
row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||||
|
obj.select(role.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 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();
|
||||||
|
});
|
||||||
|
|
||||||
|
imp.list_box.append(&create_row);
|
||||||
|
|
||||||
|
imp.roles.replace(roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select(&self, role: Role) {
|
||||||
|
self.emit_by_name::<()>("role-selected", &[&role]);
|
||||||
|
self.popdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(&self) {
|
||||||
|
log::info!("Create role!");
|
||||||
|
self.popdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
162
src/editor/work_editor.rs
Normal file
162
src/editor/work_editor.rs
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
use crate::{
|
||||||
|
db::models::{Composer, Instrument, Person},
|
||||||
|
editor::{
|
||||||
|
instrument_selector_popover::MusicusInstrumentSelectorPopover,
|
||||||
|
person_selector_popover::MusicusPersonSelectorPopover,
|
||||||
|
translation_section::MusicusTranslationSection,
|
||||||
|
work_editor_composer_row::MusicusWorkEditorComposerRow,
|
||||||
|
},
|
||||||
|
library::MusicusLibrary,
|
||||||
|
};
|
||||||
|
|
||||||
|
use adw::{prelude::*, subclass::prelude::*};
|
||||||
|
use gtk::glib::{self, clone, Properties};
|
||||||
|
|
||||||
|
use std::cell::{OnceCell, RefCell};
|
||||||
|
|
||||||
|
mod imp {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, gtk::CompositeTemplate, Properties)]
|
||||||
|
#[properties(wrapper_type = super::MusicusWorkEditor)]
|
||||||
|
#[template(file = "data/ui/work_editor.blp")]
|
||||||
|
pub struct MusicusWorkEditor {
|
||||||
|
#[property(get, construct_only)]
|
||||||
|
pub library: OnceCell<MusicusLibrary>,
|
||||||
|
|
||||||
|
// 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>>,
|
||||||
|
pub instruments: RefCell<Vec<Instrument>>,
|
||||||
|
|
||||||
|
pub persons_popover: OnceCell<MusicusPersonSelectorPopover>,
|
||||||
|
pub instruments_popover: OnceCell<MusicusInstrumentSelectorPopover>,
|
||||||
|
|
||||||
|
#[template_child]
|
||||||
|
pub composer_list: TemplateChild<gtk::ListBox>,
|
||||||
|
#[template_child]
|
||||||
|
pub select_person_box: TemplateChild<gtk::Box>,
|
||||||
|
#[template_child]
|
||||||
|
pub instrument_list: TemplateChild<gtk::ListBox>,
|
||||||
|
#[template_child]
|
||||||
|
pub select_instrument_box: TemplateChild<gtk::Box>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for MusicusWorkEditor {
|
||||||
|
const NAME: &'static str = "MusicusWorkEditor";
|
||||||
|
type Type = super::MusicusWorkEditor;
|
||||||
|
type ParentType = adw::NavigationPage;
|
||||||
|
|
||||||
|
fn class_init(klass: &mut Self::Class) {
|
||||||
|
MusicusTranslationSection::static_type();
|
||||||
|
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 MusicusWorkEditor {
|
||||||
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
|
||||||
|
let persons_popover = MusicusPersonSelectorPopover::new(self.library.get().unwrap());
|
||||||
|
|
||||||
|
let obj = self.obj().clone();
|
||||||
|
persons_popover.connect_person_selected(
|
||||||
|
move |_: &MusicusPersonSelectorPopover, person: Person| {
|
||||||
|
let role = obj.library().composer_default_role().unwrap();
|
||||||
|
let composer = Composer { person, role };
|
||||||
|
let row = MusicusWorkEditorComposerRow::new(&obj.library(), composer);
|
||||||
|
|
||||||
|
row.connect_remove(clone!(@weak obj => move |row| {
|
||||||
|
obj.imp().composer_list.remove(row);
|
||||||
|
obj.imp().composer_rows.borrow_mut().retain(|c| c != row);
|
||||||
|
}));
|
||||||
|
|
||||||
|
obj.imp()
|
||||||
|
.composer_list
|
||||||
|
.insert(&row, obj.imp().composer_rows.borrow().len() as i32);
|
||||||
|
|
||||||
|
obj.imp().composer_rows.borrow_mut().push(row);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
self.select_person_box.append(&persons_popover);
|
||||||
|
self.persons_popover.set(persons_popover).unwrap();
|
||||||
|
|
||||||
|
let instruments_popover =
|
||||||
|
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();
|
||||||
|
|
||||||
|
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 obj, @weak row, @strong instrument => move |_| {
|
||||||
|
obj.imp().instrument_list.remove(&row);
|
||||||
|
let mut instruments = obj.imp().instruments.borrow_mut();
|
||||||
|
let index = instruments.iter().position(|i| *i == instrument).unwrap();
|
||||||
|
instruments.remove(index);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
self.select_instrument_box.append(&instruments_popover);
|
||||||
|
self.instruments_popover.set(instruments_popover).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetImpl for MusicusWorkEditor {}
|
||||||
|
impl NavigationPageImpl for MusicusWorkEditor {}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct MusicusWorkEditor(ObjectSubclass<imp::MusicusWorkEditor>)
|
||||||
|
@extends gtk::Widget, adw::NavigationPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gtk::template_callbacks]
|
||||||
|
impl MusicusWorkEditor {
|
||||||
|
pub fn new(library: &MusicusLibrary) -> Self {
|
||||||
|
glib::Object::builder().property("library", library).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn add_person(&self, _: &adw::ActionRow) {
|
||||||
|
self.imp().persons_popover.get().unwrap().popup();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn add_part(&self, _: &adw::ActionRow) {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn add_instrument(&self, _: &adw::ActionRow) {
|
||||||
|
self.imp().instruments_popover.get().unwrap().popup();
|
||||||
|
}
|
||||||
|
}
|
||||||
129
src/editor/work_editor_composer_row.rs
Normal file
129
src/editor/work_editor_composer_row.rs
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
use crate::{
|
||||||
|
db::models::{Composer, Role},
|
||||||
|
editor::role_selector_popover::MusicusRoleSelectorPopover,
|
||||||
|
library::MusicusLibrary,
|
||||||
|
};
|
||||||
|
|
||||||
|
use adw::{prelude::*, subclass::prelude::*};
|
||||||
|
use gtk::glib::{self, subclass::Signal, Properties};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
use std::cell::{OnceCell, RefCell};
|
||||||
|
|
||||||
|
mod imp {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Properties, Debug, Default, gtk::CompositeTemplate)]
|
||||||
|
#[properties(wrapper_type = super::MusicusWorkEditorComposerRow)]
|
||||||
|
#[template(file = "data/ui/work_editor_composer_row.blp")]
|
||||||
|
pub struct MusicusWorkEditorComposerRow {
|
||||||
|
#[property(get, construct_only)]
|
||||||
|
pub library: OnceCell<MusicusLibrary>,
|
||||||
|
|
||||||
|
pub composer: RefCell<Option<Composer>>,
|
||||||
|
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 MusicusWorkEditorComposerRow {
|
||||||
|
const NAME: &'static str = "MusicusWorkEditorComposerRow";
|
||||||
|
type Type = super::MusicusWorkEditorComposerRow;
|
||||||
|
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 MusicusWorkEditorComposerRow {
|
||||||
|
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(composer) = &mut *obj.imp().composer.borrow_mut() {
|
||||||
|
obj.imp().role_label.set_label(&role.to_string());
|
||||||
|
composer.role = role;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.role_box.append(&role_popover);
|
||||||
|
self.role_popover.set(role_popover).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetImpl for MusicusWorkEditorComposerRow {}
|
||||||
|
impl ListBoxRowImpl for MusicusWorkEditorComposerRow {}
|
||||||
|
impl PreferencesRowImpl for MusicusWorkEditorComposerRow {}
|
||||||
|
impl ActionRowImpl for MusicusWorkEditorComposerRow {}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct MusicusWorkEditorComposerRow(ObjectSubclass<imp::MusicusWorkEditorComposerRow>)
|
||||||
|
@extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow, adw::ActionRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gtk::template_callbacks]
|
||||||
|
impl MusicusWorkEditorComposerRow {
|
||||||
|
pub fn new(library: &MusicusLibrary, composer: Composer) -> Self {
|
||||||
|
let obj: Self = glib::Object::builder().property("library", library).build();
|
||||||
|
obj.set_composer(composer);
|
||||||
|
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 composer(&self) -> Composer {
|
||||||
|
self.imp().composer.borrow().to_owned().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_composer(&self, composer: Composer) {
|
||||||
|
self.set_title(&composer.person.to_string());
|
||||||
|
self.imp().role_label.set_label(&composer.role.to_string());
|
||||||
|
self.imp().composer.replace(Some(composer));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn open_role_popover(&self, _: >k::Button) {
|
||||||
|
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", &[]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -357,6 +357,57 @@ impl MusicusLibrary {
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn search_persons(&self, search: &str) -> Result<Vec<Person>> {
|
||||||
|
let search = format!("%{}%", search);
|
||||||
|
let mut binding = self.imp().connection.borrow_mut();
|
||||||
|
let connection = &mut *binding.as_mut().unwrap();
|
||||||
|
|
||||||
|
let persons = persons::table
|
||||||
|
.order(persons::last_used_at.desc())
|
||||||
|
.filter(persons::name.like(&search))
|
||||||
|
.limit(20)
|
||||||
|
.load(connection)?;
|
||||||
|
|
||||||
|
Ok(persons)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn search_roles(&self, search: &str) -> Result<Vec<Role>> {
|
||||||
|
let search = format!("%{}%", search);
|
||||||
|
let mut binding = self.imp().connection.borrow_mut();
|
||||||
|
let connection = &mut *binding.as_mut().unwrap();
|
||||||
|
|
||||||
|
let roles = roles::table
|
||||||
|
.order(roles::last_used_at.desc())
|
||||||
|
.filter(roles::name.like(&search))
|
||||||
|
.limit(20)
|
||||||
|
.load(connection)?;
|
||||||
|
|
||||||
|
Ok(roles)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn search_instruments(&self, search: &str) -> Result<Vec<Instrument>> {
|
||||||
|
let search = format!("%{}%", search);
|
||||||
|
let mut binding = self.imp().connection.borrow_mut();
|
||||||
|
let connection = &mut *binding.as_mut().unwrap();
|
||||||
|
|
||||||
|
let instruments = instruments::table
|
||||||
|
.order(instruments::last_used_at.desc())
|
||||||
|
.filter(instruments::name.like(&search))
|
||||||
|
.limit(20)
|
||||||
|
.load(connection)?;
|
||||||
|
|
||||||
|
Ok(instruments)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn composer_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("380d7e09eb2f49c1a90db2ba4acb6ffd"))
|
||||||
|
.first::<Role>(connection)?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use adw::{
|
||||||
use gtk::glib::{self, Properties};
|
use gtk::glib::{self, Properties};
|
||||||
use std::cell::OnceCell;
|
use std::cell::OnceCell;
|
||||||
|
|
||||||
use crate::editor::person_editor::MusicusPersonEditor;
|
use crate::editor::work_editor::MusicusWorkEditor;
|
||||||
use crate::library::MusicusLibrary;
|
use crate::library::MusicusLibrary;
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
|
|
@ -39,7 +39,7 @@ mod imp {
|
||||||
impl ObjectImpl for LibraryManager {
|
impl ObjectImpl for LibraryManager {
|
||||||
fn constructed(&self) {
|
fn constructed(&self) {
|
||||||
self.parent_constructed();
|
self.parent_constructed();
|
||||||
self.obj().set_child(Some(&MusicusPersonEditor::new()));
|
self.obj().set_child(Some(&MusicusWorkEditor::new(self.library.get().unwrap())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ lazy_static! {
|
||||||
},
|
},
|
||||||
None => "generic".to_string(),
|
None => "generic".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
log::info!("Intialized user language to '{lang}'.");
|
log::info!("Intialized user language to '{lang}'.");
|
||||||
lang
|
lang
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue