From 3c834525730e1b08940d7b98e99b737828a0fa5f Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Sun, 8 Oct 2023 00:16:41 +0200 Subject: [PATCH] Add functional query tags --- src/home_page.rs | 45 ++++++++++---- src/library.rs | 141 +++++++++++++++++++++++++++++++++++++++++--- src/search_entry.rs | 30 ++++++++-- src/search_tag.rs | 30 ++++++++-- 4 files changed, 217 insertions(+), 29 deletions(-) diff --git a/src/home_page.rs b/src/home_page.rs index 02d05d2..4371f1b 100644 --- a/src/home_page.rs +++ b/src/home_page.rs @@ -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, + pub persons: RefCell>, + pub ensembles: RefCell>, + pub works: RefCell>, + pub recordings: RefCell>, + #[template_child] pub search_entry: TemplateChild, #[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); } } } diff --git a/src/library.rs b/src/library.rs index 7d245b9..d3e0bf4 100644 --- a/src/library.rs +++ b/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::>>() .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::>>() + .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::>>() + .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::>>() + .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::>>() + .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::>>() + .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::>>() + .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, pub ensemble: Option, @@ -85,7 +153,7 @@ pub struct LibraryQuery { pub search: String, } -#[derive(Default)] +#[derive(Default, Debug)] pub struct LibraryResults { pub persons: Vec, pub ensembles: Vec, @@ -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 { 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 { + 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 { + 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 { + 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)?, + }, + }, + }) + } +} diff --git a/src/search_entry.rs b/src/search_entry.rs index ab3ee8a..c5a5709 100644 --- a/src/search_entry.rs +++ b/src/search_entry.rs @@ -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,18 +143,28 @@ 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] @@ -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", &[]); } } } diff --git a/src/search_tag.rs b/src/search_tag.rs index 5d716d6..ab9becf 100644 --- a/src/search_tag.rs +++ b/src/search_tag.rs @@ -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, + pub tag: OnceCell, } #[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), +}