diff --git a/data/res/style.css b/data/res/style.css index b9c4662..4563c49 100644 --- a/data/res/style.css +++ b/data/res/style.css @@ -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); } \ No newline at end of file diff --git a/data/ui/person_editor.blp b/data/ui/person_editor.blp index 343444f..0bb8078 100644 --- a/data/ui/person_editor.blp +++ b/data/ui/person_editor.blp @@ -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" + ] + } } } } diff --git a/src/editor/instrument_selector_popover.rs b/src/editor/instrument_selector_popover.rs index 29b11cc..4196610 100644 --- a/src/editor/instrument_selector_popover.rs +++ b/src/editor/instrument_selector_popover.rs @@ -67,9 +67,12 @@ mod imp { fn signals() -> &'static [Signal] { static SIGNALS: Lazy> = 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(&self, f: F) -> glib::SignalHandlerId { + self.connect_local("create", true, move |values| { + let obj = values[0].get::().unwrap(); + f(&obj); + None + }) + } + #[template_callback] fn search_changed(&self, entry: >k::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(); } } diff --git a/src/editor/person_editor.rs b/src/editor/person_editor.rs index e31a1b9..f127145 100644 --- a/src/editor/person_editor.rs +++ b/src/editor/person_editor.rs @@ -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, + pub library: OnceCell, + pub person_id: OnceCell, + #[template_child] pub name_editor: TemplateChild, + #[template_child] + pub save_button: TemplateChild, } #[glib::object_subclass] @@ -30,7 +44,18 @@ mod imp { } } - impl ObjectImpl for MusicusPersonEditor {} + impl ObjectImpl for MusicusPersonEditor { + fn signals() -> &'static [Signal] { + static SIGNALS: Lazy> = 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(&self, f: F) -> glib::SignalHandlerId { + self.connect_local("created", true, move |values| { + let obj = values[0].get::().unwrap(); + let person = values[1].get::().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(); + } } diff --git a/src/editor/person_selector_popover.rs b/src/editor/person_selector_popover.rs index debe598..a05f165 100644 --- a/src/editor/person_selector_popover.rs +++ b/src/editor/person_selector_popover.rs @@ -67,9 +67,12 @@ mod imp { fn signals() -> &'static [Signal] { static SIGNALS: Lazy> = 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(&self, f: F) -> glib::SignalHandlerId { + self.connect_local("create", true, move |values| { + let obj = values[0].get::().unwrap(); + f(&obj); + None + }) + } + #[template_callback] fn search_changed(&self, entry: >k::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(); } } diff --git a/src/editor/role_selector_popover.rs b/src/editor/role_selector_popover.rs index 852eadd..8a91a51 100644 --- a/src/editor/role_selector_popover.rs +++ b/src/editor/role_selector_popover.rs @@ -67,9 +67,12 @@ mod imp { fn signals() -> &'static [Signal] { static SIGNALS: Lazy> = 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(&self, f: F) -> glib::SignalHandlerId { + self.connect_local("create", true, move |values| { + let obj = values[0].get::().unwrap(); + f(&obj); + None + }) + } + #[template_callback] fn search_changed(&self, entry: >k::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(); } } diff --git a/src/editor/translation_editor.rs b/src/editor/translation_editor.rs index f2afc58..aa2577b 100644 --- a/src/editor/translation_editor.rs +++ b/src/editor/translation_editor.rs @@ -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::::new(); diff --git a/src/editor/work_editor.rs b/src/editor/work_editor.rs index 07a3e44..6085bab 100644 --- a/src/editor/work_editor.rs +++ b/src/editor/work_editor.rs @@ -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); + } } diff --git a/src/home_page.rs b/src/home_page.rs index 6c0ce29..46f90a4 100644 --- a/src/home_page.rs +++ b/src/home_page.rs @@ -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( diff --git a/src/library.rs b/src/library.rs index 9b270ad..0027bf4 100644 --- a/src/library.rs +++ b/src/library.rs @@ -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::(connection)?) } + + pub fn create_person(&self, name: TranslatedString) -> Result { + 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)]