Fully functional person editor

This commit is contained in:
Elias Projahn 2024-06-06 15:17:56 +02:00
parent f49f23a501
commit 3dc601e0f0
10 changed files with 214 additions and 43 deletions

View file

@ -11,7 +11,7 @@
border-radius: 100px;
}
.searchbar .searchtag > button {
.searchbar .searchtag>button {
min-width: 24px;
min-height: 24px;
margin: 0px;
@ -22,7 +22,7 @@
min-width: 200px;
}
.tile > box {
.tile>box {
margin: 6px 12px;
}
@ -48,7 +48,7 @@
background-color: rgba(0, 0, 0, 0);
}
.playlist > row {
.playlist>row {
border-radius: 12px;
}
@ -64,7 +64,7 @@
font-size: smaller;
}
.selector > contents {
.selector>contents {
padding: 0;
}
@ -74,7 +74,14 @@
padding-bottom: 8px;
}
.selector-list > row {
.selector-list>row {
padding: 6px;
border-radius: 6px;
}
button.save {
min-height: 48px;
font-weight: 700;
font-size: 11pt;
color: var(--accent-color);
}

View file

@ -25,6 +25,17 @@ template $MusicusPersonEditor: Adw.NavigationPage {
$MusicusTranslationEditor name_editor {
margin-top: 12;
}
Gtk.Button save_button {
margin-top: 24;
label: _("Create person");
clicked => $save() swapped;
styles [
"card",
"save"
]
}
}
}
}

View file

@ -67,9 +67,12 @@ mod imp {
fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
vec![Signal::builder("instrument-selected")
.param_types([Instrument::static_type()])
.build()]
vec![
Signal::builder("instrument-selected")
.param_types([Instrument::static_type()])
.build(),
Signal::builder("create").build(),
]
});
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]
fn search_changed(&self, entry: &gtk::SearchEntry) {
self.search(&entry.text());
@ -135,7 +146,12 @@ impl MusicusInstrumentSelectorPopover {
fn search(&self, search: &str) {
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();
@ -182,7 +198,7 @@ impl MusicusInstrumentSelectorPopover {
}
fn create(&self) {
log::info!("Create instrument!");
self.emit_by_name::<()>("create", &[]);
self.popdown();
}
}

View file

@ -1,16 +1,30 @@
use adw::{prelude::*, subclass::prelude::*};
use gtk::glib;
use std::cell::OnceCell;
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 {
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate)]
#[template(file = "data/ui/person_editor.blp")]
pub struct MusicusPersonEditor {
pub navigation: OnceCell<adw::NavigationView>,
pub library: OnceCell<MusicusLibrary>,
pub person_id: OnceCell<String>,
#[template_child]
pub name_editor: TemplateChild<MusicusTranslationEditor>,
#[template_child]
pub save_button: TemplateChild<gtk::Button>,
}
#[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 NavigationPageImpl for MusicusPersonEditor {}
}
@ -42,13 +67,46 @@ glib::wrapper! {
#[gtk::template_callbacks]
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();
obj.imp().navigation.set(navigation.to_owned()).unwrap();
obj.imp().library.set(library.to_owned()).unwrap();
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
}
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, _: &gtk::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();
}
}

View file

@ -67,9 +67,12 @@ mod imp {
fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
vec![Signal::builder("person-selected")
.param_types([Person::static_type()])
.build()]
vec![
Signal::builder("person-selected")
.param_types([Person::static_type()])
.build(),
Signal::builder("create").build(),
]
});
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]
fn search_changed(&self, entry: &gtk::SearchEntry) {
self.search(&entry.text());
@ -182,7 +193,7 @@ impl MusicusPersonSelectorPopover {
}
fn create(&self) {
log::info!("Create person!");
self.emit_by_name::<()>("create", &[]);
self.popdown();
}
}

View file

@ -67,9 +67,12 @@ mod imp {
fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
vec![Signal::builder("role-selected")
.param_types([Role::static_type()])
.build()]
vec![
Signal::builder("role-selected")
.param_types([Role::static_type()])
.build(),
Signal::builder("create").build(),
]
});
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]
fn search_changed(&self, entry: &gtk::SearchEntry) {
self.search(&entry.text());
@ -182,7 +193,7 @@ impl MusicusRoleSelectorPopover {
}
fn create(&self) {
log::info!("Create role!");
self.emit_by_name::<()>("create", &[]);
self.popdown();
}
}

View file

@ -76,7 +76,7 @@ impl MusicusTranslationEditor {
self.add_entry(&util::LANG, &self.imp().entry_row.text());
}
fn translation(&self) -> TranslatedString {
pub fn translation(&self) -> TranslatedString {
let imp = self.imp();
let mut translation = HashMap::<String, String>::new();

View file

@ -5,7 +5,7 @@ use crate::{
},
editor::{
instrument_selector_popover::MusicusInstrumentSelectorPopover,
person_selector_popover::MusicusPersonSelectorPopover,
person_editor::MusicusPersonEditor, person_selector_popover::MusicusPersonSelectorPopover,
translation_editor::MusicusTranslationEditor,
work_editor_composer_row::MusicusWorkEditorComposerRow,
},
@ -80,24 +80,20 @@ mod imp {
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);
persons_popover.connect_person_selected(move |_, person| {
obj.add_composer(person);
});
row.connect_remove(clone!(@weak obj => move |row| {
obj.imp().composer_list.remove(row);
obj.imp().composer_rows.borrow_mut().retain(|c| c != row);
}));
let obj = self.obj().clone();
persons_popover.connect_create(move |_| {
let editor = MusicusPersonEditor::new(&obj.navigation(), &obj.library(), None);
obj.imp()
.composer_list
.insert(&row, obj.imp().composer_rows.borrow().len() as i32);
editor.connect_created(clone!(@weak obj => move |_, person| {
obj.add_composer(person);
}));
obj.imp().composer_rows.borrow_mut().push(row);
},
);
obj.navigation().push(&editor);
});
self.select_person_box.append(&persons_popover);
self.persons_popover.set(persons_popover).unwrap();
@ -210,4 +206,21 @@ impl MusicusWorkEditor {
fn add_instrument(&self, _: &adw::ActionRow) {
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);
}
}

View file

@ -141,8 +141,11 @@ impl MusicusHomePage {
if let Some(tag) = self.imp().search_entry.tags().first() {
match tag {
Tag::Composer(person) | Tag::Performer(person) => {
self.navigation()
.push(&MusicusPersonEditor::new(Some(person)));
self.navigation().push(&MusicusPersonEditor::new(
&self.navigation(),
&self.library(),
Some(person),
));
}
Tag::Ensemble(_) => todo!(),
Tag::Work(work) => self.navigation().push(&MusicusWorkEditor::new(

View file

@ -4,10 +4,11 @@ use std::{
};
use anyhow::Result;
use chrono::prelude::*;
use diesel::{dsl::exists, prelude::*, QueryDsl, SqliteConnection};
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! {
/// Represents the SQL RANDOM() function.
@ -444,6 +445,46 @@ impl MusicusLibrary {
.filter(roles::role_id.eq("380d7e09eb2f49c1a90db2ba4acb6ffd"))
.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)]