mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 19:57:25 +01:00
Fully functional person editor
This commit is contained in:
parent
f49f23a501
commit
3dc601e0f0
10 changed files with 214 additions and 43 deletions
|
|
@ -78,3 +78,10 @@
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.save {
|
||||||
|
min-height: 48px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 11pt;
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,17 @@ template $MusicusPersonEditor: Adw.NavigationPage {
|
||||||
$MusicusTranslationEditor name_editor {
|
$MusicusTranslationEditor name_editor {
|
||||||
margin-top: 12;
|
margin-top: 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Gtk.Button save_button {
|
||||||
|
margin-top: 24;
|
||||||
|
label: _("Create person");
|
||||||
|
clicked => $save() swapped;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"card",
|
||||||
|
"save"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,12 @@ mod imp {
|
||||||
|
|
||||||
fn signals() -> &'static [Signal] {
|
fn signals() -> &'static [Signal] {
|
||||||
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
||||||
vec![Signal::builder("instrument-selected")
|
vec![
|
||||||
|
Signal::builder("instrument-selected")
|
||||||
.param_types([Instrument::static_type()])
|
.param_types([Instrument::static_type()])
|
||||||
.build()]
|
.build(),
|
||||||
|
Signal::builder("create").build(),
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
SIGNALS.as_ref()
|
SIGNALS.as_ref()
|
||||||
|
|
@ -113,6 +116,14 @@ impl MusicusInstrumentSelectorPopover {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]
|
#[template_callback]
|
||||||
fn search_changed(&self, entry: >k::SearchEntry) {
|
fn search_changed(&self, entry: >k::SearchEntry) {
|
||||||
self.search(&entry.text());
|
self.search(&entry.text());
|
||||||
|
|
@ -135,7 +146,12 @@ impl MusicusInstrumentSelectorPopover {
|
||||||
fn search(&self, search: &str) {
|
fn search(&self, search: &str) {
|
||||||
let imp = self.imp();
|
let imp = self.imp();
|
||||||
|
|
||||||
let instruments = imp.library.get().unwrap().search_instruments(search).unwrap();
|
let instruments = imp
|
||||||
|
.library
|
||||||
|
.get()
|
||||||
|
.unwrap()
|
||||||
|
.search_instruments(search)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
imp.list_box.remove_all();
|
imp.list_box.remove_all();
|
||||||
|
|
||||||
|
|
@ -182,7 +198,7 @@ impl MusicusInstrumentSelectorPopover {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create(&self) {
|
fn create(&self) {
|
||||||
log::info!("Create instrument!");
|
self.emit_by_name::<()>("create", &[]);
|
||||||
self.popdown();
|
self.popdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,30 @@
|
||||||
use adw::{prelude::*, subclass::prelude::*};
|
use std::cell::OnceCell;
|
||||||
use gtk::glib;
|
|
||||||
|
|
||||||
use crate::{db::models::Person, editor::translation_editor::MusicusTranslationEditor};
|
use adw::{prelude::*, subclass::prelude::*};
|
||||||
|
use gettextrs::gettext;
|
||||||
|
use gtk::glib::{self, subclass::Signal};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
db::models::Person, editor::translation_editor::MusicusTranslationEditor,
|
||||||
|
library::MusicusLibrary,
|
||||||
|
};
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Debug, Default, gtk::CompositeTemplate)]
|
#[derive(Debug, Default, gtk::CompositeTemplate)]
|
||||||
#[template(file = "data/ui/person_editor.blp")]
|
#[template(file = "data/ui/person_editor.blp")]
|
||||||
pub struct MusicusPersonEditor {
|
pub struct MusicusPersonEditor {
|
||||||
|
pub navigation: OnceCell<adw::NavigationView>,
|
||||||
|
pub library: OnceCell<MusicusLibrary>,
|
||||||
|
pub person_id: OnceCell<String>,
|
||||||
|
|
||||||
#[template_child]
|
#[template_child]
|
||||||
pub name_editor: TemplateChild<MusicusTranslationEditor>,
|
pub name_editor: TemplateChild<MusicusTranslationEditor>,
|
||||||
|
#[template_child]
|
||||||
|
pub save_button: TemplateChild<gtk::Button>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
|
|
@ -30,7 +44,18 @@ mod imp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectImpl for MusicusPersonEditor {}
|
impl ObjectImpl for MusicusPersonEditor {
|
||||||
|
fn signals() -> &'static [Signal] {
|
||||||
|
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
||||||
|
vec![Signal::builder("created")
|
||||||
|
.param_types([Person::static_type()])
|
||||||
|
.build()]
|
||||||
|
});
|
||||||
|
|
||||||
|
SIGNALS.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl WidgetImpl for MusicusPersonEditor {}
|
impl WidgetImpl for MusicusPersonEditor {}
|
||||||
impl NavigationPageImpl for MusicusPersonEditor {}
|
impl NavigationPageImpl for MusicusPersonEditor {}
|
||||||
}
|
}
|
||||||
|
|
@ -42,13 +67,46 @@ glib::wrapper! {
|
||||||
|
|
||||||
#[gtk::template_callbacks]
|
#[gtk::template_callbacks]
|
||||||
impl MusicusPersonEditor {
|
impl MusicusPersonEditor {
|
||||||
pub fn new(person: Option<&Person>) -> Self {
|
pub fn new(
|
||||||
|
navigation: &adw::NavigationView,
|
||||||
|
library: &MusicusLibrary,
|
||||||
|
person: Option<&Person>,
|
||||||
|
) -> Self {
|
||||||
let obj: Self = glib::Object::new();
|
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(person) = person {
|
if let Some(person) = person {
|
||||||
|
obj.imp().save_button.set_label(&gettext("Save changes"));
|
||||||
|
obj.imp().person_id.set(person.person_id.clone()).unwrap();
|
||||||
obj.imp().name_editor.set_translation(&person.name);
|
obj.imp().name_editor.set_translation(&person.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
obj
|
obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn connect_created<F: Fn(&Self, Person) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||||
|
self.connect_local("created", true, move |values| {
|
||||||
|
let obj = values[0].get::<Self>().unwrap();
|
||||||
|
let person = values[1].get::<Person>().unwrap();
|
||||||
|
f(&obj, person);
|
||||||
|
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(person_id) = self.imp().person_id.get() {
|
||||||
|
library.update_person(person_id, name).unwrap();
|
||||||
|
} else {
|
||||||
|
let person = library.create_person(name).unwrap();
|
||||||
|
self.emit_by_name::<()>("created", &[&person]);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.imp().navigation.get().unwrap().pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,12 @@ mod imp {
|
||||||
|
|
||||||
fn signals() -> &'static [Signal] {
|
fn signals() -> &'static [Signal] {
|
||||||
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
||||||
vec![Signal::builder("person-selected")
|
vec![
|
||||||
|
Signal::builder("person-selected")
|
||||||
.param_types([Person::static_type()])
|
.param_types([Person::static_type()])
|
||||||
.build()]
|
.build(),
|
||||||
|
Signal::builder("create").build(),
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
SIGNALS.as_ref()
|
SIGNALS.as_ref()
|
||||||
|
|
@ -113,6 +116,14 @@ impl MusicusPersonSelectorPopover {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]
|
#[template_callback]
|
||||||
fn search_changed(&self, entry: >k::SearchEntry) {
|
fn search_changed(&self, entry: >k::SearchEntry) {
|
||||||
self.search(&entry.text());
|
self.search(&entry.text());
|
||||||
|
|
@ -182,7 +193,7 @@ impl MusicusPersonSelectorPopover {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create(&self) {
|
fn create(&self) {
|
||||||
log::info!("Create person!");
|
self.emit_by_name::<()>("create", &[]);
|
||||||
self.popdown();
|
self.popdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,12 @@ mod imp {
|
||||||
|
|
||||||
fn signals() -> &'static [Signal] {
|
fn signals() -> &'static [Signal] {
|
||||||
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
||||||
vec![Signal::builder("role-selected")
|
vec![
|
||||||
|
Signal::builder("role-selected")
|
||||||
.param_types([Role::static_type()])
|
.param_types([Role::static_type()])
|
||||||
.build()]
|
.build(),
|
||||||
|
Signal::builder("create").build(),
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
SIGNALS.as_ref()
|
SIGNALS.as_ref()
|
||||||
|
|
@ -113,6 +116,14 @@ impl MusicusRoleSelectorPopover {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]
|
#[template_callback]
|
||||||
fn search_changed(&self, entry: >k::SearchEntry) {
|
fn search_changed(&self, entry: >k::SearchEntry) {
|
||||||
self.search(&entry.text());
|
self.search(&entry.text());
|
||||||
|
|
@ -182,7 +193,7 @@ impl MusicusRoleSelectorPopover {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create(&self) {
|
fn create(&self) {
|
||||||
log::info!("Create role!");
|
self.emit_by_name::<()>("create", &[]);
|
||||||
self.popdown();
|
self.popdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ impl MusicusTranslationEditor {
|
||||||
self.add_entry(&util::LANG, &self.imp().entry_row.text());
|
self.add_entry(&util::LANG, &self.imp().entry_row.text());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn translation(&self) -> TranslatedString {
|
pub fn translation(&self) -> TranslatedString {
|
||||||
let imp = self.imp();
|
let imp = self.imp();
|
||||||
let mut translation = HashMap::<String, String>::new();
|
let mut translation = HashMap::<String, String>::new();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
editor::{
|
editor::{
|
||||||
instrument_selector_popover::MusicusInstrumentSelectorPopover,
|
instrument_selector_popover::MusicusInstrumentSelectorPopover,
|
||||||
person_selector_popover::MusicusPersonSelectorPopover,
|
person_editor::MusicusPersonEditor, person_selector_popover::MusicusPersonSelectorPopover,
|
||||||
translation_editor::MusicusTranslationEditor,
|
translation_editor::MusicusTranslationEditor,
|
||||||
work_editor_composer_row::MusicusWorkEditorComposerRow,
|
work_editor_composer_row::MusicusWorkEditorComposerRow,
|
||||||
},
|
},
|
||||||
|
|
@ -80,24 +80,20 @@ mod imp {
|
||||||
let persons_popover = MusicusPersonSelectorPopover::new(self.library.get().unwrap());
|
let persons_popover = MusicusPersonSelectorPopover::new(self.library.get().unwrap());
|
||||||
|
|
||||||
let obj = self.obj().clone();
|
let obj = self.obj().clone();
|
||||||
persons_popover.connect_person_selected(
|
persons_popover.connect_person_selected(move |_, person| {
|
||||||
move |_: &MusicusPersonSelectorPopover, person: Person| {
|
obj.add_composer(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| {
|
let obj = self.obj().clone();
|
||||||
obj.imp().composer_list.remove(row);
|
persons_popover.connect_create(move |_| {
|
||||||
obj.imp().composer_rows.borrow_mut().retain(|c| c != row);
|
let editor = MusicusPersonEditor::new(&obj.navigation(), &obj.library(), None);
|
||||||
|
|
||||||
|
editor.connect_created(clone!(@weak obj => move |_, person| {
|
||||||
|
obj.add_composer(person);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
obj.imp()
|
obj.navigation().push(&editor);
|
||||||
.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.select_person_box.append(&persons_popover);
|
||||||
self.persons_popover.set(persons_popover).unwrap();
|
self.persons_popover.set(persons_popover).unwrap();
|
||||||
|
|
@ -210,4 +206,21 @@ impl MusicusWorkEditor {
|
||||||
fn add_instrument(&self, _: &adw::ActionRow) {
|
fn add_instrument(&self, _: &adw::ActionRow) {
|
||||||
self.imp().instruments_popover.get().unwrap().popup();
|
self.imp().instruments_popover.get().unwrap().popup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_composer(&self, person: Person) {
|
||||||
|
let role = self.library().composer_default_role().unwrap();
|
||||||
|
let composer = Composer { person, role };
|
||||||
|
let row = MusicusWorkEditorComposerRow::new(&self.library(), composer);
|
||||||
|
|
||||||
|
row.connect_remove(clone!(@weak self as obj => move |row| {
|
||||||
|
obj.imp().composer_list.remove(row);
|
||||||
|
obj.imp().composer_rows.borrow_mut().retain(|c| c != row);
|
||||||
|
}));
|
||||||
|
|
||||||
|
self.imp()
|
||||||
|
.composer_list
|
||||||
|
.insert(&row, self.imp().composer_rows.borrow().len() as i32);
|
||||||
|
|
||||||
|
self.imp().composer_rows.borrow_mut().push(row);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -141,8 +141,11 @@ impl MusicusHomePage {
|
||||||
if let Some(tag) = self.imp().search_entry.tags().first() {
|
if let Some(tag) = self.imp().search_entry.tags().first() {
|
||||||
match tag {
|
match tag {
|
||||||
Tag::Composer(person) | Tag::Performer(person) => {
|
Tag::Composer(person) | Tag::Performer(person) => {
|
||||||
self.navigation()
|
self.navigation().push(&MusicusPersonEditor::new(
|
||||||
.push(&MusicusPersonEditor::new(Some(person)));
|
&self.navigation(),
|
||||||
|
&self.library(),
|
||||||
|
Some(person),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Tag::Ensemble(_) => todo!(),
|
Tag::Ensemble(_) => todo!(),
|
||||||
Tag::Work(work) => self.navigation().push(&MusicusWorkEditor::new(
|
Tag::Work(work) => self.navigation().push(&MusicusWorkEditor::new(
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,11 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use chrono::prelude::*;
|
||||||
use diesel::{dsl::exists, prelude::*, QueryDsl, SqliteConnection};
|
use diesel::{dsl::exists, prelude::*, QueryDsl, SqliteConnection};
|
||||||
use gtk::{glib, glib::Properties, prelude::*, subclass::prelude::*};
|
use gtk::{glib, glib::Properties, prelude::*, subclass::prelude::*};
|
||||||
|
|
||||||
use crate::db::{self, models::*, schema::*, tables};
|
use crate::db::{self, models::*, schema::*, tables, TranslatedString};
|
||||||
|
|
||||||
diesel::sql_function! {
|
diesel::sql_function! {
|
||||||
/// Represents the SQL RANDOM() function.
|
/// Represents the SQL RANDOM() function.
|
||||||
|
|
@ -444,6 +445,46 @@ impl MusicusLibrary {
|
||||||
.filter(roles::role_id.eq("380d7e09eb2f49c1a90db2ba4acb6ffd"))
|
.filter(roles::role_id.eq("380d7e09eb2f49c1a90db2ba4acb6ffd"))
|
||||||
.first::<Role>(connection)?)
|
.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();
|
||||||
|
|
||||||
|
let now = Local::now().naive_local();
|
||||||
|
|
||||||
|
let person = Person {
|
||||||
|
person_id: db::generate_id(),
|
||||||
|
name,
|
||||||
|
created_at: now,
|
||||||
|
edited_at: now,
|
||||||
|
last_used_at: now,
|
||||||
|
last_played_at: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
diesel::insert_into(persons::table)
|
||||||
|
.values(&person)
|
||||||
|
.execute(connection)?;
|
||||||
|
|
||||||
|
Ok(person)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_person(&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(persons::table)
|
||||||
|
.filter(persons::person_id.eq(id))
|
||||||
|
.set((
|
||||||
|
persons::name.eq(name),
|
||||||
|
persons::edited_at.eq(now),
|
||||||
|
persons::last_used_at.eq(now),
|
||||||
|
))
|
||||||
|
.execute(connection)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue