diff --git a/data/ui/library_manager.blp b/data/ui/library_manager.blp index 00c73e8..5ff9a54 100644 --- a/data/ui/library_manager.blp +++ b/data/ui/library_manager.blp @@ -23,7 +23,7 @@ template $MusicusLibraryManager: Adw.NavigationPage { margin-top: 24; styles [ - "heading" + "heading", ] } @@ -32,7 +32,7 @@ template $MusicusLibraryManager: Adw.NavigationPage { margin-top: 12; styles [ - "boxed-list-separate" + "boxed-list-separate", ] Adw.ActionRow library_path_row { @@ -41,7 +41,7 @@ template $MusicusLibraryManager: Adw.NavigationPage { activated => $open_library() swapped; styles [ - "property" + "property", ] [suffix] @@ -62,232 +62,6 @@ template $MusicusLibraryManager: Adw.NavigationPage { activated => $export_archive() swapped; } } - - Gtk.Label { - label: _("Contents"); - xalign: 0; - margin-top: 24; - - styles [ - "heading" - ] - } - - Gtk.ListBox { - selection-mode: none; - margin-top: 12; - - styles [ - "boxed-list" - ] - - Adw.ActionRow { - title: _("Persons"); - activatable: true; - activated => $show_persons() swapped; - - [suffix] - Gtk.Box { - spacing: 6; - - Gtk.Label n_persons_label { - label: "0"; - - styles [ - "numeric" - ] - } - - Gtk.Image { - icon-name: "go-next-symbolic"; - } - } - } - - Adw.ActionRow { - title: _("Roles"); - activatable: true; - activated => $show_roles() swapped; - - [suffix] - Gtk.Box { - spacing: 6; - - Gtk.Label n_roles_label { - label: "0"; - - styles [ - "numeric" - ] - } - - Gtk.Image { - icon-name: "go-next-symbolic"; - } - } - } - - Adw.ActionRow { - title: _("Instruments"); - activatable: true; - activated => $show_instruments() swapped; - - [suffix] - Gtk.Box { - spacing: 6; - - Gtk.Label n_instruments_label { - label: "0"; - - styles [ - "numeric" - ] - } - - Gtk.Image { - icon-name: "go-next-symbolic"; - } - } - } - - Adw.ActionRow { - title: _("Works"); - activatable: true; - activated => $show_works() swapped; - - [suffix] - Gtk.Box { - spacing: 6; - - Gtk.Label n_works_label { - label: "0"; - - styles [ - "numeric" - ] - } - - Gtk.Image { - icon-name: "go-next-symbolic"; - } - } - } - - Adw.ActionRow { - title: _("Ensembles"); - activatable: true; - activated => $show_ensembles() swapped; - - [suffix] - Gtk.Box { - spacing: 6; - - Gtk.Label n_ensembles_label { - label: "0"; - - styles [ - "numeric" - ] - } - - Gtk.Image { - icon-name: "go-next-symbolic"; - } - } - } - - Adw.ActionRow { - title: _("Recordings"); - activatable: true; - activated => $show_recordings() swapped; - - [suffix] - Gtk.Box { - spacing: 6; - - Gtk.Label n_recordings_label { - label: "0"; - - styles [ - "numeric" - ] - } - - Gtk.Image { - icon-name: "go-next-symbolic"; - } - } - } - - Adw.ActionRow { - title: _("Tracks"); - activatable: true; - activated => $show_tracks() swapped; - - [suffix] - Gtk.Box { - spacing: 6; - - Gtk.Label n_tracks_label { - label: "0"; - - styles [ - "numeric" - ] - } - - Gtk.Image { - icon-name: "go-next-symbolic"; - } - } - } - - Adw.ActionRow { - title: _("Mediums"); - activatable: true; - activated => $show_mediums() swapped; - - [suffix] - Gtk.Box { - spacing: 6; - - Gtk.Label n_mediums_label { - label: "0"; - - styles [ - "numeric" - ] - } - - Gtk.Image { - icon-name: "go-next-symbolic"; - } - } - } - - Adw.ActionRow { - title: _("Albums"); - activatable: true; - activated => $show_albums() swapped; - - [suffix] - Gtk.Box { - spacing: 6; - - Gtk.Label n_albums_label { - label: "0"; - - styles [ - "numeric" - ] - } - - Gtk.Image { - icon-name: "go-next-symbolic"; - } - } - } - } } } } diff --git a/data/ui/library_manager_albums_page.blp b/data/ui/library_manager_albums_page.blp deleted file mode 100644 index 0ea2610..0000000 --- a/data/ui/library_manager_albums_page.blp +++ /dev/null @@ -1,42 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $MusicusLibraryManagerAlbumsPage: Adw.NavigationPage { - title: _("Albums"); - - Adw.ToolbarView { - [top] - Gtk.Box { - orientation: vertical; - - Adw.HeaderBar { - [end] - Gtk.Button { - icon-name: "list-add-symbolic"; - clicked => $create() swapped; - } - } - - Adw.Clamp { - Gtk.SearchEntry search_entry { - placeholder-text: _("Search albums…"); - search-changed => $search_changed() swapped; - } - } - } - - Gtk.ScrolledWindow { - Adw.Clamp { - Gtk.ListBox list { - selection-mode: none; - margin-top: 12; - valign: start; - - styles [ - "boxed-list" - ] - } - } - } - } -} diff --git a/src/library_manager.rs b/src/library_manager.rs new file mode 100644 index 0000000..6d3c1df --- /dev/null +++ b/src/library_manager.rs @@ -0,0 +1,92 @@ +use std::{cell::OnceCell, ffi::OsStr, path::Path}; + +use adw::{prelude::*, subclass::prelude::*}; +use gettextrs::gettext; +use gtk::glib; + +use crate::{library::Library, window::Window}; + +mod imp { + use super::*; + + #[derive(Debug, Default, gtk::CompositeTemplate)] + #[template(file = "data/ui/library_manager.blp")] + pub struct LibraryManager { + pub navigation: OnceCell, + pub library: OnceCell, + + #[template_child] + pub library_path_row: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for LibraryManager { + const NAME: &'static str = "MusicusLibraryManager"; + type Type = super::LibraryManager; + type ParentType = adw::NavigationPage; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + klass.bind_template_instance_callbacks(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for LibraryManager {} + impl WidgetImpl for LibraryManager {} + impl NavigationPageImpl for LibraryManager {} +} + +glib::wrapper! { + pub struct LibraryManager(ObjectSubclass) + @extends gtk::Widget, adw::NavigationPage; +} + +#[gtk::template_callbacks] +impl LibraryManager { + pub fn new(navigation: &adw::NavigationView, library: &Library) -> Self { + let obj: Self = glib::Object::new(); + + obj.imp().navigation.set(navigation.to_owned()).unwrap(); + obj.imp().library.set(library.to_owned()).unwrap(); + + if let Some(Some(filename)) = Path::new(&library.folder()).file_name().map(OsStr::to_str) { + obj.imp().library_path_row.set_subtitle(filename); + } + + obj + } + + #[template_callback] + async fn open_library(&self) { + let dialog = gtk::FileDialog::builder() + .title(gettext("Select music library folder")) + .modal(true) + .build(); + + let root = self.root(); + let window = root + .as_ref() + .and_then(|r| r.downcast_ref::()) + .and_then(|w| w.downcast_ref::()) + .unwrap(); + + match dialog.select_folder_future(Some(window)).await { + Err(err) => { + if !err.matches(gtk::DialogError::Dismissed) { + log::error!("Folder selection failed: {err}"); + } + } + Ok(folder) => window.set_library_folder(&folder), + } + } + + #[template_callback] + fn import_archive(&self) {} + + #[template_callback] + fn export_archive(&self) {} +} diff --git a/src/library_manager/albums_page.rs b/src/library_manager/albums_page.rs deleted file mode 100644 index 2bb1bd1..0000000 --- a/src/library_manager/albums_page.rs +++ /dev/null @@ -1,145 +0,0 @@ -use std::cell::{OnceCell, RefCell}; - -use adw::{prelude::*, subclass::prelude::*}; -use gettextrs::gettext; -use gtk::glib::{self, clone}; - -use crate::{db::models::Album, editor::album::AlbumEditor, library::Library}; - -mod imp { - use super::*; - - #[derive(Debug, Default, gtk::CompositeTemplate)] - #[template(file = "data/ui/library_manager_albums_page.blp")] - pub struct AlbumsPage { - pub navigation: OnceCell, - pub library: OnceCell, - pub albums: RefCell>, - pub albums_filtered: RefCell>, - - #[template_child] - pub search_entry: TemplateChild, - #[template_child] - pub list: TemplateChild, - } - - #[glib::object_subclass] - impl ObjectSubclass for AlbumsPage { - const NAME: &'static str = "MusicusLibraryManagerAlbumsPage"; - type Type = super::AlbumsPage; - type ParentType = adw::NavigationPage; - - fn class_init(klass: &mut Self::Class) { - klass.bind_template(); - klass.bind_template_instance_callbacks(); - } - - fn instance_init(obj: &glib::subclass::InitializingObject) { - obj.init_template(); - } - } - - impl ObjectImpl for AlbumsPage {} - impl WidgetImpl for AlbumsPage {} - - impl NavigationPageImpl for AlbumsPage { - fn showing(&self) { - self.parent_showing(); - self.obj().update(); - } - } -} - -glib::wrapper! { - pub struct AlbumsPage(ObjectSubclass) - @extends gtk::Widget, adw::NavigationPage; -} - -#[gtk::template_callbacks] -impl AlbumsPage { - pub fn new(navigation: &adw::NavigationView, library: &Library) -> Self { - let obj: Self = glib::Object::new(); - let imp = obj.imp(); - - imp.navigation.set(navigation.to_owned()).unwrap(); - imp.library.set(library.to_owned()).unwrap(); - - obj - } - - fn update(&self) { - let albums = self.imp().library.get().unwrap().all_albums().unwrap(); - self.imp().albums.replace(albums); - self.search_changed(); - } - - #[template_callback] - fn search_changed(&self) { - let albums_filtered = self - .imp() - .albums - .borrow() - .iter() - .filter(|a| { - a.name - .get() - .contains(&self.imp().search_entry.text().to_string()) - }) - .cloned() - .collect::>(); - - self.imp().list.remove_all(); - - for album in albums_filtered { - let row = adw::ActionRow::builder() - .title(album.name.get()) - .activatable(true) - .build(); - - row.connect_activated(clone!( - #[weak(rename_to = obj)] - self, - #[strong] - album, - move |_| { - obj.imp().navigation.get().unwrap().push(&AlbumEditor::new( - &obj.imp().navigation.get().unwrap(), - &obj.imp().library.get().unwrap(), - Some(&album), - )); - } - )); - - let delete_button = gtk::Button::builder() - .icon_name("user-trash-symbolic") - .tooltip_text(gettext("Delete album")) - .valign(gtk::Align::Center) - .css_classes(["flat"]) - .build(); - - // TODO: - // delete_button.connect_clicked(clone!( - // #[weak(rename_to = obj)] - // self, - // #[strong] - // album, - // move |_| { - // obj.imp().library.delete_album(&album.album_id).unwrap(); - // } - // )); - - row.add_suffix(&delete_button); - - self.imp().list.append(&row); - } - } - - #[template_callback] - fn create(&self) { - self.imp().navigation.get().unwrap().push(&AlbumEditor::new( - &self.imp().navigation.get().unwrap(), - &self.imp().library.get().unwrap(), - None, - )); - } -} diff --git a/src/library_manager/mod.rs b/src/library_manager/mod.rs deleted file mode 100644 index 5ec5c04..0000000 --- a/src/library_manager/mod.rs +++ /dev/null @@ -1,227 +0,0 @@ -pub mod albums_page; - -use std::{ - cell::{OnceCell, RefCell}, - ffi::OsStr, - path::Path, -}; - -use adw::{prelude::*, subclass::prelude::*}; -use albums_page::AlbumsPage; -use gettextrs::gettext; -use gtk::glib; - -use crate::{ - db::{ - models::{Album, Ensemble, Instrument, Person, Recording, Role, Track, Work}, - tables::Medium, - }, - library::Library, - window::Window, -}; - -mod imp { - use super::*; - - #[derive(Debug, Default, gtk::CompositeTemplate)] - #[template(file = "data/ui/library_manager.blp")] - pub struct LibraryManager { - pub navigation: OnceCell, - pub library: OnceCell, - - pub persons: RefCell>, - pub roles: RefCell>, - pub instruments: RefCell>, - pub works: RefCell>, - pub ensembles: RefCell>, - pub recordings: RefCell>, - pub tracks: RefCell>, - pub mediums: RefCell>, - pub albums: RefCell>, - - #[template_child] - pub library_path_row: TemplateChild, - #[template_child] - pub n_persons_label: TemplateChild, - #[template_child] - pub n_roles_label: TemplateChild, - #[template_child] - pub n_instruments_label: TemplateChild, - #[template_child] - pub n_works_label: TemplateChild, - #[template_child] - pub n_ensembles_label: TemplateChild, - #[template_child] - pub n_recordings_label: TemplateChild, - #[template_child] - pub n_tracks_label: TemplateChild, - #[template_child] - pub n_mediums_label: TemplateChild, - #[template_child] - pub n_albums_label: TemplateChild, - } - - #[glib::object_subclass] - impl ObjectSubclass for LibraryManager { - const NAME: &'static str = "MusicusLibraryManager"; - type Type = super::LibraryManager; - type ParentType = adw::NavigationPage; - - fn class_init(klass: &mut Self::Class) { - klass.bind_template(); - klass.bind_template_instance_callbacks(); - } - - fn instance_init(obj: &glib::subclass::InitializingObject) { - obj.init_template(); - } - } - - impl ObjectImpl for LibraryManager {} - impl WidgetImpl for LibraryManager {} - - impl NavigationPageImpl for LibraryManager { - fn showing(&self) { - self.parent_showing(); - self.obj().update(); - } - } -} - -glib::wrapper! { - pub struct LibraryManager(ObjectSubclass) - @extends gtk::Widget, adw::NavigationPage; -} - -#[gtk::template_callbacks] -impl LibraryManager { - pub fn new(navigation: &adw::NavigationView, library: &Library) -> Self { - let obj: Self = glib::Object::new(); - let imp = obj.imp(); - - imp.navigation.set(navigation.to_owned()).unwrap(); - imp.library.set(library.to_owned()).unwrap(); - - obj - } - - #[template_callback] - async fn open_library(&self) { - let dialog = gtk::FileDialog::builder() - .title(gettext("Select music library folder")) - .modal(true) - .build(); - - let root = self.root(); - let window = root - .as_ref() - .and_then(|r| r.downcast_ref::()) - .and_then(|w| w.downcast_ref::()) - .unwrap(); - - match dialog.select_folder_future(Some(window)).await { - Err(err) => { - if !err.matches(gtk::DialogError::Dismissed) { - log::error!("Folder selection failed: {err}"); - } - } - Ok(folder) => window.set_library_folder(&folder), - } - } - - #[template_callback] - fn import_archive(&self) {} - - #[template_callback] - fn export_archive(&self) {} - - #[template_callback] - fn show_persons(&self) {} - - #[template_callback] - fn show_roles(&self) {} - - #[template_callback] - fn show_instruments(&self) {} - - #[template_callback] - fn show_works(&self) {} - - #[template_callback] - fn show_ensembles(&self) {} - - #[template_callback] - fn show_recordings(&self) {} - - #[template_callback] - fn show_tracks(&self) {} - - #[template_callback] - fn show_mediums(&self) {} - - #[template_callback] - fn show_albums(&self) { - let navigation = self.imp().navigation.get().unwrap(); - let library = self.imp().library.get().unwrap(); - navigation.push(&AlbumsPage::new(navigation, library)); - } - - // TODO: Make this async. - fn update(&self) { - let library = self.imp().library.get().unwrap(); - - if let Some(Some(filename)) = Path::new(&library.folder()).file_name().map(OsStr::to_str) { - self.imp().library_path_row.set_subtitle(filename); - } - - let persons = library.all_persons().unwrap(); - self.imp() - .n_persons_label - .set_label(&persons.len().to_string()); - self.imp().persons.replace(persons); - - let roles = library.all_roles().unwrap(); - self.imp().n_roles_label.set_label(&roles.len().to_string()); - self.imp().roles.replace(roles); - - let instruments = library.all_instruments().unwrap(); - self.imp() - .n_instruments_label - .set_label(&instruments.len().to_string()); - self.imp().instruments.replace(instruments); - - let works = library.all_works().unwrap(); - self.imp().n_works_label.set_label(&works.len().to_string()); - self.imp().works.replace(works); - - let ensembles = library.all_ensembles().unwrap(); - self.imp() - .n_ensembles_label - .set_label(&ensembles.len().to_string()); - self.imp().ensembles.replace(ensembles); - - let recordings = library.all_recordings().unwrap(); - self.imp() - .n_recordings_label - .set_label(&recordings.len().to_string()); - self.imp().recordings.replace(recordings); - - let tracks = library.all_tracks().unwrap(); - self.imp() - .n_tracks_label - .set_label(&tracks.len().to_string()); - self.imp().tracks.replace(tracks); - - let mediums = library.all_mediums().unwrap(); - self.imp() - .n_mediums_label - .set_label(&mediums.len().to_string()); - self.imp().mediums.replace(mediums); - - let albums = library.all_albums().unwrap(); - self.imp() - .n_albums_label - .set_label(&albums.len().to_string()); - self.imp().albums.replace(albums); - } -}