mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 11:47:25 +01:00
Add functional query tags
This commit is contained in:
parent
9d4f37f601
commit
3c83452573
4 changed files with 217 additions and 29 deletions
|
|
@ -1,7 +1,8 @@
|
|||
use crate::{
|
||||
library::{LibraryQuery, MusicusLibrary},
|
||||
library::{Ensemble, LibraryQuery, MusicusLibrary, Person, Recording, Work},
|
||||
player::MusicusPlayer,
|
||||
search_entry::MusicusSearchEntry,
|
||||
search_tag::Tag,
|
||||
tile::MusicusTile,
|
||||
};
|
||||
use adw::subclass::{navigation_page::NavigationPageImpl, prelude::*};
|
||||
|
|
@ -9,7 +10,7 @@ use gtk::{
|
|||
glib::{self, clone, Properties},
|
||||
prelude::*,
|
||||
};
|
||||
use std::cell::OnceCell;
|
||||
use std::cell::{OnceCell, RefCell};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
|
@ -24,6 +25,11 @@ mod imp {
|
|||
#[property(get, construct_only)]
|
||||
pub player: OnceCell<MusicusPlayer>,
|
||||
|
||||
pub persons: RefCell<Vec<Person>>,
|
||||
pub ensembles: RefCell<Vec<Ensemble>>,
|
||||
pub works: RefCell<Vec<Work>>,
|
||||
pub recordings: RefCell<Vec<Recording>>,
|
||||
|
||||
#[template_child]
|
||||
pub search_entry: TemplateChild<MusicusSearchEntry>,
|
||||
#[template_child]
|
||||
|
|
@ -106,7 +112,15 @@ impl MusicusHomePage {
|
|||
|
||||
#[template_callback]
|
||||
fn select(&self, search_entry: &MusicusSearchEntry) {
|
||||
search_entry.add_tag("Tag");
|
||||
let imp = self.imp();
|
||||
|
||||
if let Some(person) = imp.persons.borrow().first() {
|
||||
search_entry.add_tag(Tag::Person(person.clone()));
|
||||
} else if let Some(ensemble) = imp.ensembles.borrow().first() {
|
||||
search_entry.add_tag(Tag::Ensemble(ensemble.clone()));
|
||||
} else if let Some(work) = imp.works.borrow().first() {
|
||||
search_entry.add_tag(Tag::Work(work.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
fn query(&self, query: &LibraryQuery) {
|
||||
|
|
@ -137,25 +151,34 @@ impl MusicusHomePage {
|
|||
imp.recordings_flow_box
|
||||
.set_visible(!results.recordings.is_empty());
|
||||
|
||||
for person in results.persons {
|
||||
for person in &results.persons {
|
||||
imp.persons_flow_box
|
||||
.append(&MusicusTile::with_title(&person.name_fl()));
|
||||
}
|
||||
|
||||
for ensemble in results.ensembles {
|
||||
for ensemble in &results.ensembles {
|
||||
imp.ensembles_flow_box
|
||||
.append(&MusicusTile::with_title(&ensemble.name));
|
||||
}
|
||||
|
||||
for work in results.works {
|
||||
imp.works_flow_box
|
||||
.append(&MusicusTile::with_subtitle(&work.title, &work.composer.name_fl()));
|
||||
for work in &results.works {
|
||||
imp.works_flow_box.append(&MusicusTile::with_subtitle(
|
||||
&work.title,
|
||||
&work.composer.name_fl(),
|
||||
));
|
||||
}
|
||||
|
||||
for _recording in results.recordings {
|
||||
imp.recordings_flow_box
|
||||
.append(&MusicusTile::with_title("TODO"));
|
||||
for recording in &results.recordings {
|
||||
imp.recordings_flow_box.append(&MusicusTile::with_subtitle(
|
||||
&recording.work.title,
|
||||
&recording.work.composer.name_fl(),
|
||||
));
|
||||
}
|
||||
|
||||
imp.persons.replace(results.persons);
|
||||
imp.ensembles.replace(results.ensembles);
|
||||
imp.works.replace(results.works);
|
||||
imp.recordings.replace(results.recordings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
141
src/library.rs
141
src/library.rs
|
|
@ -56,18 +56,86 @@ impl MusicusLibrary {
|
|||
..
|
||||
} => {
|
||||
let persons = self.con()
|
||||
.prepare("SELECT first_name, last_name FROM persons WHERE first_name LIKE ?1 OR last_name LIKE ?1 LIMIT 9")
|
||||
.prepare("SELECT id, first_name, last_name FROM persons WHERE first_name LIKE ?1 OR last_name LIKE ?1 LIMIT 9")
|
||||
.unwrap()
|
||||
.query_map([&search], Person::from_row)
|
||||
.unwrap()
|
||||
.collect::<rusqlite::Result<Vec<Person>>>()
|
||||
.unwrap();
|
||||
|
||||
let ensembles = self
|
||||
.con()
|
||||
.prepare("SELECT id, name FROM ensembles WHERE name LIKE ?1 LIMIT 9")
|
||||
.unwrap()
|
||||
.query_map([&search], Ensemble::from_row)
|
||||
.unwrap()
|
||||
.collect::<rusqlite::Result<Vec<Ensemble>>>()
|
||||
.unwrap();
|
||||
|
||||
let works = self
|
||||
.con()
|
||||
.prepare("SELECT works.id, works.title, persons.id, persons.first_name, persons.last_name FROM works INNER JOIN persons ON works.composer = persons.id WHERE title LIKE ?1 LIMIT 9")
|
||||
.unwrap()
|
||||
.query_map([&search], Work::from_row)
|
||||
.unwrap()
|
||||
.collect::<rusqlite::Result<Vec<Work>>>()
|
||||
.unwrap();
|
||||
|
||||
LibraryResults {
|
||||
persons,
|
||||
ensembles,
|
||||
works,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
LibraryQuery {
|
||||
person: Some(person),
|
||||
ensemble: None,
|
||||
work: None,
|
||||
..
|
||||
} => {
|
||||
let persons = self.con()
|
||||
.prepare("SELECT DISTINCT persons.id, persons.first_name, persons.last_name FROM persons INNER JOIN performances ON performances.person = persons.id INNER JOIN recordings ON recordings.id = performances.recording INNER JOIN works ON works.id = recordings.work WHERE works.composer IS ?1 AND (persons.first_name LIKE ?2 OR persons.last_name LIKE ?2) LIMIT 9")
|
||||
.unwrap()
|
||||
.query_map([&person.id, &search], Person::from_row)
|
||||
.unwrap()
|
||||
.collect::<rusqlite::Result<Vec<Person>>>()
|
||||
.unwrap();
|
||||
|
||||
let ensembles = self
|
||||
.con()
|
||||
.prepare("SELECT DISTINCT ensembles.id, ensembles.name FROM ensembles INNER JOIN performances ON performances.ensemble = ensembles.id INNER JOIN recordings ON recordings.id = performances.recording INNER JOIN works ON works.id = recordings.work WHERE works.composer IS ?1 AND ensembles.name LIKE ?2 LIMIT 9")
|
||||
.unwrap()
|
||||
.query_map([&person.id, &search], Ensemble::from_row)
|
||||
.unwrap()
|
||||
.collect::<rusqlite::Result<Vec<Ensemble>>>()
|
||||
.unwrap();
|
||||
|
||||
let works = self
|
||||
.con()
|
||||
.prepare("SELECT DISTINCT works.id, works.title, persons.id, persons.first_name, persons.last_name FROM works INNER JOIN persons ON works.composer = persons.id WHERE works.composer = ?1 AND title LIKE ?2 LIMIT 9")
|
||||
.unwrap()
|
||||
.query_map([&person.id, &search], Work::from_row)
|
||||
.unwrap()
|
||||
.collect::<rusqlite::Result<Vec<Work>>>()
|
||||
.unwrap();
|
||||
|
||||
let recordings = self
|
||||
.con()
|
||||
.prepare("SELECT DISTINCT recordings.id, works.id, works.title, persons.id, persons.first_name, persons.last_name FROM recordings INNER JOIN works ON recordings.work = works.id INNER JOIN persons ON works.composer = persons.id INNER JOIN performances ON recordings.id = performances.recording WHERE performances.person IS ?1 AND (works.title LIKE ?2 OR persons.first_name LIKE ?2 OR persons.last_name LIKE ?2) LIMIT 9")
|
||||
.unwrap()
|
||||
.query_map([&person.id, &search], Recording::from_row)
|
||||
.unwrap()
|
||||
.collect::<rusqlite::Result<Vec<Recording>>>()
|
||||
.unwrap();
|
||||
|
||||
LibraryResults {
|
||||
persons,
|
||||
ensembles,
|
||||
works,
|
||||
recordings,
|
||||
}
|
||||
}
|
||||
_ => LibraryResults::default(),
|
||||
}
|
||||
}
|
||||
|
|
@ -77,7 +145,7 @@ impl MusicusLibrary {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Debug)]
|
||||
pub struct LibraryQuery {
|
||||
pub person: Option<Person>,
|
||||
pub ensemble: Option<Ensemble>,
|
||||
|
|
@ -85,7 +153,7 @@ pub struct LibraryQuery {
|
|||
pub search: String,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Debug)]
|
||||
pub struct LibraryResults {
|
||||
pub persons: Vec<Person>,
|
||||
pub ensembles: Vec<Ensemble>,
|
||||
|
|
@ -95,11 +163,16 @@ pub struct LibraryResults {
|
|||
|
||||
impl LibraryResults {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.persons.is_empty() && self.ensembles.is_empty() && self.works.is_empty()
|
||||
self.persons.is_empty()
|
||||
&& self.ensembles.is_empty()
|
||||
&& self.works.is_empty()
|
||||
&& self.recordings.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Person {
|
||||
pub id: String,
|
||||
pub first_name: String,
|
||||
pub last_name: String,
|
||||
}
|
||||
|
|
@ -107,8 +180,9 @@ pub struct Person {
|
|||
impl Person {
|
||||
pub fn from_row(row: &Row) -> rusqlite::Result<Self> {
|
||||
Ok(Self {
|
||||
first_name: row.get(0)?,
|
||||
last_name: row.get(1)?,
|
||||
id: row.get(0)?,
|
||||
first_name: row.get(1)?,
|
||||
last_name: row.get(2)?,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -117,12 +191,61 @@ impl Person {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Ensemble {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
pub struct Work {
|
||||
pub title: String,
|
||||
impl Ensemble {
|
||||
pub fn from_row(row: &Row) -> rusqlite::Result<Self> {
|
||||
Ok(Self {
|
||||
id: row.get(0)?,
|
||||
name: row.get(1)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Recording {}
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Work {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub composer: Person,
|
||||
}
|
||||
|
||||
impl Work {
|
||||
pub fn from_row(row: &Row) -> rusqlite::Result<Self> {
|
||||
Ok(Self {
|
||||
id: row.get(0)?,
|
||||
title: row.get(1)?,
|
||||
composer: Person {
|
||||
id: row.get(2)?,
|
||||
first_name: row.get(3)?,
|
||||
last_name: row.get(4)?,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Recording {
|
||||
pub id: String,
|
||||
pub work: Work,
|
||||
}
|
||||
|
||||
impl Recording {
|
||||
pub fn from_row(row: &Row) -> rusqlite::Result<Self> {
|
||||
Ok(Self {
|
||||
id: row.get(0)?,
|
||||
work: Work {
|
||||
id: row.get(1)?,
|
||||
title: row.get(2)?,
|
||||
composer: Person {
|
||||
id: row.get(3)?,
|
||||
first_name: row.get(4)?,
|
||||
last_name: row.get(5)?,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
use crate::{library::LibraryQuery, search_tag::MusicusSearchTag};
|
||||
use crate::{
|
||||
library::LibraryQuery,
|
||||
search_tag::{MusicusSearchTag, Tag},
|
||||
};
|
||||
use adw::{gdk, gio, glib, glib::clone, glib::subclass::Signal, prelude::*, subclass::prelude::*};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{cell::RefCell, time::Duration};
|
||||
|
|
@ -140,20 +143,30 @@ impl MusicusSearchEntry {
|
|||
self.imp().text.set_text("");
|
||||
}
|
||||
|
||||
pub fn add_tag(&self, name: &str) {
|
||||
pub fn add_tag(&self, tag: Tag) {
|
||||
self.imp().text.set_text("");
|
||||
let tag = MusicusSearchTag::new(name);
|
||||
let tag = MusicusSearchTag::new(tag);
|
||||
self.imp().tags_box.append(&tag);
|
||||
self.imp().tags.borrow_mut().push(tag);
|
||||
}
|
||||
|
||||
pub fn query(&self) -> LibraryQuery {
|
||||
LibraryQuery {
|
||||
let mut query = LibraryQuery {
|
||||
search: self.imp().text.text().to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for tag in &*self.imp().tags.borrow() {
|
||||
match tag.tag().clone() {
|
||||
Tag::Person(person) => query.person = Some(person),
|
||||
Tag::Ensemble(ensemble) => query.ensemble = Some(ensemble),
|
||||
Tag::Work(work) => query.work = Some(work),
|
||||
}
|
||||
}
|
||||
|
||||
query
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn activate(&self, _: >k::Text) {
|
||||
self.emit_by_name::<()>("activate", &[]);
|
||||
|
|
@ -162,8 +175,15 @@ impl MusicusSearchEntry {
|
|||
#[template_callback]
|
||||
fn backspace(&self, text: >k::Text) {
|
||||
if text.cursor_position() == 0 {
|
||||
if let Some(tag) = self.imp().tags.borrow_mut().pop() {
|
||||
let changed = if let Some(tag) = self.imp().tags.borrow_mut().pop() {
|
||||
self.imp().tags_box.remove(&tag);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if changed {
|
||||
self.emit_by_name::<()>("query-changed", &[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use crate::library::{Ensemble, Person, Work};
|
||||
use adw::{glib, glib::subclass::Signal, prelude::*, subclass::prelude::*};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::cell::OnceCell;
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
|
@ -9,6 +11,7 @@ mod imp {
|
|||
pub struct MusicusSearchTag {
|
||||
#[template_child]
|
||||
pub label: TemplateChild<gtk::Label>,
|
||||
pub tag: OnceCell<Tag>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
|
@ -47,10 +50,22 @@ glib::wrapper! {
|
|||
|
||||
#[gtk::template_callbacks]
|
||||
impl MusicusSearchTag {
|
||||
pub fn new(label: &str) -> Self {
|
||||
let tag: MusicusSearchTag = glib::Object::new();
|
||||
tag.imp().label.set_label(label);
|
||||
tag
|
||||
pub fn new(tag: Tag) -> Self {
|
||||
let obj: MusicusSearchTag = glib::Object::new();
|
||||
|
||||
obj.imp().label.set_label(&match &tag {
|
||||
Tag::Person(person) => person.name_fl(),
|
||||
Tag::Ensemble(ensemble) => ensemble.name.clone(),
|
||||
Tag::Work(work) => format!("{}: {}", &work.composer.name_fl(), &work.title),
|
||||
});
|
||||
|
||||
obj.imp().tag.set(tag).unwrap();
|
||||
|
||||
obj
|
||||
}
|
||||
|
||||
pub fn tag(&self) -> &Tag {
|
||||
self.imp().tag.get().unwrap()
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
|
|
@ -58,3 +73,10 @@ impl MusicusSearchTag {
|
|||
self.emit_by_name::<()>("remove", &[]);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Tag {
|
||||
Person(Person),
|
||||
Ensemble(Ensemble),
|
||||
Work(Work),
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue