musicus/src/selector/instrument.rs

204 lines
5.9 KiB
Rust
Raw Normal View History

2025-03-01 09:57:01 +01:00
use std::cell::{OnceCell, RefCell};
2024-05-31 13:39:27 +02:00
use gettextrs::gettext;
use gtk::{
glib::{self, subclass::Signal, Properties},
prelude::*,
subclass::prelude::*,
};
use once_cell::sync::Lazy;
2025-03-01 09:57:01 +01:00
use crate::{activatable_row::ActivatableRow, db::models::Instrument, library::Library};
2024-05-31 13:39:27 +02:00
mod imp {
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate, Properties)]
2025-03-01 09:57:01 +01:00
#[properties(wrapper_type = super::InstrumentSelectorPopover)]
#[template(file = "data/ui/selector/instrument.blp")]
pub struct InstrumentSelectorPopover {
2024-05-31 13:39:27 +02:00
#[property(get, construct_only)]
2025-03-01 09:57:01 +01:00
pub library: OnceCell<Library>,
2024-05-31 13:39:27 +02:00
pub instruments: RefCell<Vec<Instrument>>,
#[template_child]
pub search_entry: TemplateChild<gtk::SearchEntry>,
#[template_child]
pub scrolled_window: TemplateChild<gtk::ScrolledWindow>,
#[template_child]
pub list_box: TemplateChild<gtk::ListBox>,
}
#[glib::object_subclass]
2025-03-01 09:57:01 +01:00
impl ObjectSubclass for InstrumentSelectorPopover {
2024-05-31 13:39:27 +02:00
const NAME: &'static str = "MusicusInstrumentSelectorPopover";
2025-03-01 09:57:01 +01:00
type Type = super::InstrumentSelectorPopover;
2024-05-31 13:39:27 +02:00
type ParentType = gtk::Popover;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
klass.bind_template_instance_callbacks();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
#[glib::derived_properties]
2025-03-01 09:57:01 +01:00
impl ObjectImpl for InstrumentSelectorPopover {
2024-05-31 13:39:27 +02:00
fn constructed(&self) {
self.parent_constructed();
2025-03-01 09:57:01 +01:00
self.obj().connect_visible_notify(|obj| {
if obj.is_visible() {
obj.imp().search_entry.set_text("");
obj.imp().search_entry.grab_focus();
obj.imp().scrolled_window.vadjustment().set_value(0.0);
}
});
2024-05-31 13:39:27 +02:00
self.obj().search("");
}
fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
2024-06-06 15:17:56 +02:00
vec![
Signal::builder("instrument-selected")
.param_types([Instrument::static_type()])
.build(),
Signal::builder("create").build(),
]
2024-05-31 13:39:27 +02:00
});
SIGNALS.as_ref()
}
}
2025-03-01 09:57:01 +01:00
impl WidgetImpl for InstrumentSelectorPopover {
2024-05-31 13:39:27 +02:00
// TODO: Fix focus.
fn focus(&self, direction_type: gtk::DirectionType) -> bool {
if direction_type == gtk::DirectionType::Down {
self.list_box.child_focus(direction_type)
} else {
self.parent_focus(direction_type)
}
}
}
2025-03-01 09:57:01 +01:00
impl PopoverImpl for InstrumentSelectorPopover {}
2024-05-31 13:39:27 +02:00
}
glib::wrapper! {
2025-03-01 09:57:01 +01:00
pub struct InstrumentSelectorPopover(ObjectSubclass<imp::InstrumentSelectorPopover>)
2024-05-31 13:39:27 +02:00
@extends gtk::Widget, gtk::Popover;
}
#[gtk::template_callbacks]
2025-03-01 09:57:01 +01:00
impl InstrumentSelectorPopover {
pub fn new(library: &Library) -> Self {
2024-05-31 13:39:27 +02:00
glib::Object::builder().property("library", library).build()
}
pub fn connect_instrument_selected<F: Fn(&Self, Instrument) + 'static>(
&self,
f: F,
) -> glib::SignalHandlerId {
self.connect_local("instrument-selected", true, move |values| {
let obj = values[0].get::<Self>().unwrap();
let instrument = values[1].get::<Instrument>().unwrap();
f(&obj, instrument);
None
})
}
2024-06-06 15:17:56 +02:00
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
})
}
2024-05-31 13:39:27 +02:00
#[template_callback]
fn search_changed(&self, entry: &gtk::SearchEntry) {
self.search(&entry.text());
}
#[template_callback]
fn activate(&self, _: &gtk::SearchEntry) {
if let Some(instrument) = self.imp().instruments.borrow().first() {
self.select(instrument.clone());
} else {
self.create();
}
}
#[template_callback]
fn stop_search(&self, _: &gtk::SearchEntry) {
self.popdown();
}
fn search(&self, search: &str) {
let imp = self.imp();
2024-06-06 15:17:56 +02:00
let instruments = imp
.library
.get()
.unwrap()
.search_instruments(search)
.unwrap();
2024-05-31 13:39:27 +02:00
imp.list_box.remove_all();
for instrument in &instruments {
2025-03-01 09:57:01 +01:00
let row = ActivatableRow::new(
2024-05-31 13:39:27 +02:00
&gtk::Label::builder()
.label(instrument.to_string())
.halign(gtk::Align::Start)
.build(),
);
2025-02-09 08:14:46 +01:00
row.set_tooltip_text(Some(&instrument.to_string()));
2024-05-31 13:39:27 +02:00
let instrument = instrument.clone();
let obj = self.clone();
2025-03-01 09:57:01 +01:00
row.connect_activated(move |_: &ActivatableRow| {
2024-05-31 13:39:27 +02:00
obj.select(instrument.clone());
});
imp.list_box.append(&row);
}
let create_box = gtk::Box::builder().spacing(12).build();
create_box.append(&gtk::Image::builder().icon_name("list-add-symbolic").build());
create_box.append(
&gtk::Label::builder()
.label(gettext("Create new instrument"))
.halign(gtk::Align::Start)
.build(),
);
2025-03-01 09:57:01 +01:00
let create_row = ActivatableRow::new(&create_box);
2024-05-31 13:39:27 +02:00
let obj = self.clone();
2025-03-01 09:57:01 +01:00
create_row.connect_activated(move |_: &ActivatableRow| {
2024-05-31 13:39:27 +02:00
obj.create();
});
imp.list_box.append(&create_row);
imp.instruments.replace(instruments);
}
fn select(&self, instrument: Instrument) {
self.emit_by_name::<()>("instrument-selected", &[&instrument]);
self.popdown();
}
fn create(&self) {
2024-06-06 15:17:56 +02:00
self.emit_by_name::<()>("create", &[]);
2024-05-31 13:39:27 +02:00
self.popdown();
}
}