mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 19:57:25 +01:00
Add basic person selector
This commit is contained in:
parent
ed225f61ad
commit
ea2dcbb4db
7 changed files with 347 additions and 2 deletions
|
|
@ -4,6 +4,7 @@
|
||||||
<file preprocess="xml-stripblanks">ui/ensemble_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/ensemble_editor.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/instrument_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/instrument_editor.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/person_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/person_editor.ui</file>
|
||||||
|
<file preprocess="xml-stripblanks">ui/person_selector.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/window.ui</file>
|
<file preprocess="xml-stripblanks">ui/window.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/work_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/work_editor.ui</file>
|
||||||
</gresource>
|
</gresource>
|
||||||
|
|
|
||||||
91
res/ui/person_selector.ui
Normal file
91
res/ui/person_selector.ui
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.36.0 -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.22"/>
|
||||||
|
<object class="GtkWindow" id="window">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="modal">True</property>
|
||||||
|
<property name="default_width">350</property>
|
||||||
|
<property name="default_height">300</property>
|
||||||
|
<property name="destroy_with_parent">True</property>
|
||||||
|
<property name="type_hint">dialog</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSearchEntry" id="search_entry">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="margin_start">6</property>
|
||||||
|
<property name="margin_end">6</property>
|
||||||
|
<property name="margin_top">6</property>
|
||||||
|
<property name="margin_bottom">6</property>
|
||||||
|
<property name="primary_icon_name">edit-find-symbolic</property>
|
||||||
|
<property name="primary_icon_activatable">False</property>
|
||||||
|
<property name="primary_icon_sensitive">False</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="shadow_type">in</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkViewport">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBox" id="list">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child type="placeholder">
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label" translatable="yes">No persons found.</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child type="titlebar">
|
||||||
|
<object class="GtkHeaderBar">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="title" translatable="yes">Select person</property>
|
||||||
|
<property name="show_close_button">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="add_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="icon_name">list-add-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</interface>
|
||||||
|
|
@ -8,6 +8,16 @@ pub struct Person {
|
||||||
pub last_name: String,
|
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)]
|
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||||
pub struct Instrument {
|
pub struct Instrument {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,10 @@ pub use instrument_editor::*;
|
||||||
pub mod person_editor;
|
pub mod person_editor;
|
||||||
pub use person_editor::*;
|
pub use person_editor::*;
|
||||||
|
|
||||||
|
pub mod person_selector;
|
||||||
|
pub use person_selector::*;
|
||||||
|
|
||||||
|
pub mod selector_row;
|
||||||
|
|
||||||
pub mod work_editor;
|
pub mod work_editor;
|
||||||
pub use work_editor::*;
|
pub use work_editor::*;
|
||||||
|
|
|
||||||
75
src/dialogs/person_selector.rs
Normal file
75
src/dialogs/person_selector.rs
Normal file
|
|
@ -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<F>
|
||||||
|
where
|
||||||
|
F: Fn(Person) -> () + 'static,
|
||||||
|
{
|
||||||
|
db: Rc<Database>,
|
||||||
|
window: gtk::Window,
|
||||||
|
callback: F,
|
||||||
|
persons: RefCell<Vec<Person>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> PersonSelector<F>
|
||||||
|
where
|
||||||
|
F: Fn(Person) -> () + 'static,
|
||||||
|
{
|
||||||
|
pub fn new<P: IsA<gtk::Window>>(db: Rc<Database>, parent: &P, callback: F) -> Rc<Self> {
|
||||||
|
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::<SelectorRow>().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();
|
||||||
|
}
|
||||||
|
}
|
||||||
150
src/dialogs/selector_row.rs
Normal file
150
src/dialogs/selector_row.rs
Normal file
|
|
@ -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::InstanceStruct<SelectorRowPriv>,
|
||||||
|
subclass::simple::ClassStruct<SelectorRowPriv>,
|
||||||
|
SelectorRowClass>
|
||||||
|
) @extends gtk::Bin, gtk::Container, gtk::Widget;
|
||||||
|
|
||||||
|
match fn {
|
||||||
|
get_type => || SelectorRowPriv::get_type().to_glib(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelectorRow {
|
||||||
|
pub fn new<T: IsA<gtk::Widget>>(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<u64>,
|
||||||
|
child: RefCell<Option<gtk::Widget>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Self>;
|
||||||
|
type Class = subclass::simple::ClassStruct<Self>;
|
||||||
|
|
||||||
|
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::<SelectorRow>().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::<SelectorRow>().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<glib::Value, ()> {
|
||||||
|
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 {}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use super::person_selector::PersonSelector;
|
||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
|
|
@ -40,11 +41,13 @@ impl PartOrSection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WorkEditor {
|
pub struct WorkEditor {
|
||||||
|
db: Rc<Database>,
|
||||||
window: gtk::Window,
|
window: gtk::Window,
|
||||||
save_button: gtk::Button,
|
save_button: gtk::Button,
|
||||||
id: i64,
|
id: i64,
|
||||||
title_entry: gtk::Entry,
|
title_entry: gtk::Entry,
|
||||||
composer: RefCell<Option<Person>>,
|
composer: RefCell<Option<Person>>,
|
||||||
|
composer_label: gtk::Label,
|
||||||
instruments: RefCell<Vec<Instrument>>,
|
instruments: RefCell<Vec<Instrument>>,
|
||||||
structure: RefCell<Vec<PartOrSection>>,
|
structure: RefCell<Vec<PartOrSection>>,
|
||||||
}
|
}
|
||||||
|
|
@ -120,11 +123,13 @@ impl WorkEditor {
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = Rc::new(WorkEditor {
|
let result = Rc::new(WorkEditor {
|
||||||
|
db: db,
|
||||||
window: window,
|
window: window,
|
||||||
save_button: save_button,
|
save_button: save_button,
|
||||||
id: id,
|
id: id,
|
||||||
title_entry: title_entry,
|
title_entry: title_entry,
|
||||||
composer: composer,
|
composer: composer,
|
||||||
|
composer_label: composer_label,
|
||||||
instruments: instruments,
|
instruments: instruments,
|
||||||
structure: structure,
|
structure: structure,
|
||||||
});
|
});
|
||||||
|
|
@ -161,10 +166,18 @@ impl WorkEditor {
|
||||||
sections: sections,
|
sections: sections,
|
||||||
};
|
};
|
||||||
|
|
||||||
db.update_work(work.clone().into());
|
result.db.update_work(work.clone().into());
|
||||||
callback(work);
|
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.window.set_transient_for(Some(parent));
|
||||||
|
|
||||||
result
|
result
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue