diff --git a/res/resources.xml b/res/resources.xml index d866eb3..97a0e37 100644 --- a/res/resources.xml +++ b/res/resources.xml @@ -4,6 +4,7 @@ ui/ensemble_editor.ui ui/instrument_editor.ui ui/person_editor.ui + ui/person_selector.ui ui/window.ui ui/work_editor.ui diff --git a/res/ui/person_selector.ui b/res/ui/person_selector.ui new file mode 100644 index 0000000..06eb55e --- /dev/null +++ b/res/ui/person_selector.ui @@ -0,0 +1,91 @@ + + + + + + False + True + 350 + 300 + True + dialog + + + True + False + vertical + + + True + True + 6 + 6 + 6 + 6 + edit-find-symbolic + False + False + + + False + True + 0 + + + + + True + True + in + + + True + False + + + True + False + + + True + False + No persons found. + + + + + + + + + True + True + 1 + + + + + + + True + False + Select person + True + + + True + True + True + + + True + False + list-add-symbolic + + + + + + + + diff --git a/src/database/tables.rs b/src/database/tables.rs index a7dc43b..342a724 100644 --- a/src/database/tables.rs +++ b/src/database/tables.rs @@ -8,6 +8,16 @@ pub struct Person { pub last_name: String, } +impl Person { + pub fn name_fl(&self) -> String { + format!("{} {}", self.first_name, self.last_name) + } + + pub fn name_lf(&self) -> String { + format!("{}, {}", self.last_name, self.first_name) + } +} + #[derive(Insertable, Queryable, Debug, Clone)] pub struct Instrument { pub id: i64, diff --git a/src/dialogs/mod.rs b/src/dialogs/mod.rs index ca9e250..9a6cefb 100644 --- a/src/dialogs/mod.rs +++ b/src/dialogs/mod.rs @@ -7,5 +7,10 @@ pub use instrument_editor::*; pub mod person_editor; pub use person_editor::*; +pub mod person_selector; +pub use person_selector::*; + +pub mod selector_row; + pub mod work_editor; -pub use work_editor::*; \ No newline at end of file +pub use work_editor::*; diff --git a/src/dialogs/person_selector.rs b/src/dialogs/person_selector.rs new file mode 100644 index 0000000..9fecb33 --- /dev/null +++ b/src/dialogs/person_selector.rs @@ -0,0 +1,75 @@ +use super::selector_row::SelectorRow; +use super::PersonEditor; +use crate::database::*; +use gio::prelude::*; +use glib::clone; +use gtk::prelude::*; +use gtk_macros::get_widget; +use std::cell::RefCell; +use std::convert::TryInto; +use std::rc::Rc; + +pub struct PersonSelector +where + F: Fn(Person) -> () + 'static, +{ + db: Rc, + window: gtk::Window, + callback: F, + persons: RefCell>, +} + +impl PersonSelector +where + F: Fn(Person) -> () + 'static, +{ + pub fn new>(db: Rc, parent: &P, callback: F) -> Rc { + let builder = + gtk::Builder::from_resource("/de/johrpan/musicus_editor/ui/person_selector.ui"); + + get_widget!(builder, gtk::Window, window); + get_widget!(builder, gtk::Button, add_button); + get_widget!(builder, gtk::Entry, search_entry); + get_widget!(builder, gtk::ListBox, list); + + let persons = db.get_persons(); + + for (index, person) in persons.iter().enumerate() { + let label = gtk::Label::new(Some(&person.name_lf())); + label.set_halign(gtk::Align::Start); + let row = SelectorRow::new(index.try_into().unwrap(), &label); + row.show_all(); + list.insert(&row, -1); + } + + let result = Rc::new(PersonSelector { + db: db, + window: window, + callback: callback, + persons: RefCell::new(persons), + }); + + list.connect_row_activated(clone!(@strong result => move |_, row| { + result.window.close(); + let row = row.get_child().unwrap().downcast::().unwrap(); + let index: usize = row.get_index().try_into().unwrap(); + (result.callback)(result.persons.borrow()[index].clone()); + })); + + add_button.connect_clicked(clone!(@strong result => move |_| { + let editor = PersonEditor::new(result.db.clone(), &result.window, None, clone!(@strong result => move |person| { + result.window.close(); + (result.callback)(person); + })); + editor.show(); + })); + + result.window.set_transient_for(Some(parent)); + + result + } + + pub fn show(&self) { + self.window.show(); + } +} diff --git a/src/dialogs/selector_row.rs b/src/dialogs/selector_row.rs new file mode 100644 index 0000000..41d3b39 --- /dev/null +++ b/src/dialogs/selector_row.rs @@ -0,0 +1,150 @@ +use glib::prelude::*; +use glib::subclass; +use glib::subclass::prelude::*; +use glib::translate::*; +use glib::{glib_object_impl, glib_object_subclass, glib_wrapper}; +use gtk::prelude::*; +use gtk::subclass::prelude::*; +use std::cell::{Cell, RefCell}; + +glib_wrapper! { + pub struct SelectorRow( + Object, + subclass::simple::ClassStruct, + SelectorRowClass> + ) @extends gtk::Bin, gtk::Container, gtk::Widget; + + match fn { + get_type => || SelectorRowPriv::get_type().to_glib(), + } +} + +impl SelectorRow { + pub fn new>(index: u64, child: &T) -> Self { + glib::Object::new( + Self::static_type(), + &[("index", &index), ("child", child.upcast_ref())], + ) + .expect("Failed to create SelectorRow GObject!") + .downcast() + .expect("SelectorRow GObject is of the wrong type!") + } + + pub fn get_index(&self) -> u64 { + self.get_property("index").unwrap().get().unwrap().unwrap() + } +} + +pub struct SelectorRowPriv { + index: Cell, + child: RefCell>, +} + +static PROPERTIES: [subclass::Property; 2] = [ + subclass::Property("index", |name| { + glib::ParamSpec::uint64( + name, + "Index", + "Index", + 0, + u64::MAX, + 0, + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("child", |name| { + glib::ParamSpec::object( + name, + "Child", + "Child", + gtk::Widget::static_type(), + glib::ParamFlags::READWRITE, + ) + }), +]; + +impl ObjectSubclass for SelectorRowPriv { + const NAME: &'static str = "SelectorRow"; + type ParentType = gtk::Bin; + type Instance = subclass::simple::InstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn class_init(klass: &mut Self::Class) { + klass.install_properties(&PROPERTIES); + } + + fn new() -> Self { + Self { + index: Cell::new(0), + child: RefCell::new(None), + } + } +} + +impl ObjectImpl for SelectorRowPriv { + glib_object_impl!(); + + fn constructed(&self, object: &glib::Object) { + self.parent_constructed(object); + + let row = object.downcast_ref::().unwrap(); + row.set_border_width(6); + + let child = self.child.borrow(); + match child.as_ref() { + Some(child) => row.add(child), + None => (), + } + } + + fn set_property(&self, object: &glib::Object, id: usize, value: &glib::Value) { + let prop = &PROPERTIES[id]; + + match *prop { + subclass::Property("index", ..) => { + let index = value + .get_some() + .expect("Wrong type for SelectorRow GObject index property!"); + self.index.set(index); + } + subclass::Property("child", ..) => { + let child = value + .get() + .expect("Wrong type for SelectorRow GObject child property!"); + + let row = object.downcast_ref::().unwrap(); + + { + let old = self.child.borrow(); + match old.as_ref() { + Some(old) => row.remove(old), + None => (), + } + } + + self.child.replace(child.clone()); + match child { + Some(child) => row.add(&child), + None => (), + } + } + _ => unimplemented!(), + } + } + + fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { + let prop = &PROPERTIES[id]; + + match *prop { + subclass::Property("index", ..) => Ok(self.index.get().to_value()), + subclass::Property("child", ..) => Ok(self.child.borrow().to_value()), + _ => unimplemented!(), + } + } +} + +impl WidgetImpl for SelectorRowPriv {} +impl ContainerImpl for SelectorRowPriv {} +impl BinImpl for SelectorRowPriv {} diff --git a/src/dialogs/work_editor.rs b/src/dialogs/work_editor.rs index 055aaf8..9ed57a8 100644 --- a/src/dialogs/work_editor.rs +++ b/src/dialogs/work_editor.rs @@ -1,3 +1,4 @@ +use super::person_selector::PersonSelector; use crate::database::*; use glib::clone; use gtk::prelude::*; @@ -40,11 +41,13 @@ impl PartOrSection { } pub struct WorkEditor { + db: Rc, window: gtk::Window, save_button: gtk::Button, id: i64, title_entry: gtk::Entry, composer: RefCell>, + composer_label: gtk::Label, instruments: RefCell>, structure: RefCell>, } @@ -120,11 +123,13 @@ impl WorkEditor { }); let result = Rc::new(WorkEditor { + db: db, window: window, save_button: save_button, id: id, title_entry: title_entry, composer: composer, + composer_label: composer_label, instruments: instruments, structure: structure, }); @@ -161,10 +166,18 @@ impl WorkEditor { sections: sections, }; - db.update_work(work.clone().into()); + result.db.update_work(work.clone().into()); callback(work); })); + composer_button.connect_clicked(clone!(@strong result => move |_| { + PersonSelector::new(result.db.clone(), &result.window, clone!(@strong result => move |person| { + result.composer.replace(Some(person.clone())); + result.composer_label.set_text(&person.name_fl()); + result.save_button.set_sensitive(true); + })).show(); + })); + result.window.set_transient_for(Some(parent)); result