Compare commits

..

No commits in common. "8d9690dad65e554e772fb63a8e821afffca0c06f" and "48cfdd354aeccf8ab14a2349345c9c2cfaa740c7" have entirely different histories.

29 changed files with 251 additions and 161 deletions

View file

@ -14,6 +14,17 @@
padding-bottom: 3px; padding-bottom: 3px;
} }
.searchbar .searchtag {
background-color: alpha(currentColor, 0.1);
border-radius: 100px;
}
.searchbar .searchtag>button {
min-width: 24px;
min-height: 24px;
margin: 0px;
}
.tile { .tile {
min-height: 50px; min-height: 50px;
min-width: 200px; min-width: 200px;

View file

@ -50,24 +50,16 @@ template $MusicusEmptyPage: Adw.NavigationPage {
} }
menu primary_menu { menu primary_menu {
section {
item { item {
label: _("_Import music"); label: _("_Import music");
action: "win.import"; action: "win.import";
} }
item {
label: _("_Create album");
action: "win.create-album";
}
item { item {
label: _("_Library manager"); label: _("_Library manager");
action: "win.library"; action: "win.library";
} }
}
section {
item { item {
label: _("_Preferences"); label: _("_Preferences");
action: "win.preferences"; action: "win.preferences";
@ -77,5 +69,4 @@ menu primary_menu {
label: _("_About Musicus"); label: _("_About Musicus");
action: "app.about"; action: "app.about";
} }
}
} }

View file

@ -80,7 +80,7 @@ template $MusicusSearchPage: Adw.NavigationPage {
activate => $select() swapped; activate => $select() swapped;
styles [ styles [
"rounded-entry", "rounded-entry"
] ]
} }
@ -264,24 +264,16 @@ template $MusicusSearchPage: Adw.NavigationPage {
} }
menu primary_menu { menu primary_menu {
section {
item { item {
label: _("_Import music"); label: _("_Import music");
action: "win.import"; action: "win.import";
} }
item {
label: _("_Create album");
action: "win.create-album";
}
item { item {
label: _("_Library manager"); label: _("_Library manager");
action: "win.library"; action: "win.library";
} }
}
section {
item { item {
label: _("_Preferences"); label: _("_Preferences");
action: "win.preferences"; action: "win.preferences";
@ -291,7 +283,6 @@ menu primary_menu {
label: _("_About Musicus"); label: _("_About Musicus");
action: "app.about"; action: "app.about";
} }
}
} }
menu item_menu { menu item_menu {

22
data/ui/search_tag.blp Normal file
View file

@ -0,0 +1,22 @@
using Gtk 4.0;
template $MusicusSearchTag : Gtk.Box {
styles ["searchtag"]
margin-start: 6;
margin-end: 6;
Gtk.Label label {
styles ["caption-heading"]
margin-start: 12;
margin-end: 6;
max-width-chars: 15;
ellipsize: end;
}
Gtk.Button button {
styles ["flat", "circular"]
icon-name: "window-close-symbolic";
clicked => $remove() swapped;
}
}

View file

@ -72,7 +72,8 @@ mod imp {
.unwrap() .unwrap()
.recordings .recordings
.iter() .iter()
.flat_map(|r| obj.player().recording_to_playlist(r)) .map(|r| obj.player().recording_to_playlist(r))
.flatten()
.collect::<Vec<PlaylistItem>>(); .collect::<Vec<PlaylistItem>>();
if let Err(err) = obj.player().append(playlist) { if let Err(err) = obj.player().append(playlist) {
@ -164,7 +165,8 @@ impl AlbumPage {
.unwrap() .unwrap()
.recordings .recordings
.iter() .iter()
.flat_map(|r| self.player().recording_to_playlist(r)) .map(|r| self.player().recording_to_playlist(r))
.flatten()
.collect::<Vec<PlaylistItem>>(); .collect::<Vec<PlaylistItem>>();
self.player().append_and_play(playlist); self.player().append_and_play(playlist);

View file

@ -45,7 +45,7 @@ impl AlbumTile {
pub fn new(album: &Album) -> Self { pub fn new(album: &Album) -> Self {
let obj: Self = glib::Object::new(); let obj: Self = glib::Object::new();
obj.imp().title_label.set_label(album.name.get()); obj.imp().title_label.set_label(&album.name.get());
obj.imp().album.set(album.clone()).unwrap(); obj.imp().album.set(album.clone()).unwrap();
obj obj

View file

@ -69,7 +69,7 @@ mod imp {
self.parent_constructed(); self.parent_constructed();
let set_design_action = gio::ActionEntry::builder("set-design") let set_design_action = gio::ActionEntry::builder("set-design")
.parameter_type(Some(glib::VariantTy::STRING)) .parameter_type(Some(&glib::VariantTy::STRING))
.state(glib::Variant::from("program-1")) .state(glib::Variant::from("program-1"))
.build(); .build();

View file

@ -246,7 +246,7 @@ impl RecordingEditor {
} }
fn set_work(&self, work: Work) { fn set_work(&self, work: Work) {
self.imp().work_row.set_title(work.name.get()); self.imp().work_row.set_title(&work.name.get());
self.imp().work_row.set_subtitle( self.imp().work_row.set_subtitle(
&work &work
.composers_string() .composers_string()

View file

@ -245,7 +245,8 @@ impl TracksEditor {
.track_rows .track_rows
.borrow() .borrow()
.iter() .iter()
.flat_map(|t| t.track_data().parts.clone()) .map(|t| t.track_data().parts.clone())
.flatten()
.collect::<Vec<Work>>() .collect::<Vec<Work>>()
}; };

View file

@ -390,7 +390,8 @@ impl WorkEditor {
}; };
self.emit_by_name::<()>("created", &[&part]); self.emit_by_name::<()>("created", &[&part]);
} else if let Some(work_id) = self.imp().work_id.get() { } else {
if let Some(work_id) = self.imp().work_id.get() {
library library
.update_work(work_id, name, parts, composers, instruments, enable_updates) .update_work(work_id, name, parts, composers, instruments, enable_updates)
.unwrap(); .unwrap();
@ -400,6 +401,7 @@ impl WorkEditor {
.unwrap(); .unwrap();
self.emit_by_name::<()>("created", &[&work]); self.emit_by_name::<()>("created", &[&work]);
} }
}
self.imp().navigation.get().unwrap().pop(); self.imp().navigation.get().unwrap().pop();
} }

View file

@ -145,7 +145,7 @@ impl WorkEditorPartRow {
} }
fn set_part(&self, part: Work) { fn set_part(&self, part: Work) {
self.set_title(part.name.get()); self.set_title(&part.name.get());
if !part.parts.is_empty() { if !part.parts.is_empty() {
self.set_subtitle( self.set_subtitle(

View file

@ -92,8 +92,8 @@ impl EmptyPage {
#[template_callback] #[template_callback]
async fn download_library(&self) { async fn download_library(&self) {
let dialog = adw::AlertDialog::builder() let dialog = adw::AlertDialog::builder()
.heading(gettext("Disclaimer")) .heading(&gettext("Disclaimer"))
.body(gettext("You are about to download a library of audio files. These are from recordings that are in the public domain under EU law and are hosted on a server within the EU. Please ensure that you comply with the copyright laws of you country.")) .body(&gettext("You are about to download a library of audio files. These are from recordings that are in the public domain under EU law and are hosted on a server within the EU. Please ensure that you comply with the copyright laws of you country."))
.build(); .build();
dialog.add_response("continue", &gettext("Continue")); dialog.add_response("continue", &gettext("Continue"));

View file

@ -209,7 +209,7 @@ impl Library {
) -> Result<Work> { ) -> Result<Work> {
let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap(); let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap();
let work = Self::create_work_priv( let work = self.create_work_priv(
connection, connection,
name, name,
parts, parts,
@ -226,6 +226,7 @@ impl Library {
} }
fn create_work_priv( fn create_work_priv(
&self,
connection: &mut SqliteConnection, connection: &mut SqliteConnection,
name: TranslatedString, name: TranslatedString,
parts: Vec<Work>, parts: Vec<Work>,
@ -241,7 +242,7 @@ impl Library {
let work_data = tables::Work { let work_data = tables::Work {
work_id: work_id.clone(), work_id: work_id.clone(),
parent_work_id: parent_work_id.map(|w| w.to_string()), parent_work_id: parent_work_id.map(|w| w.to_string()),
sequence_number, sequence_number: sequence_number,
name, name,
created_at: now, created_at: now,
edited_at: now, edited_at: now,
@ -255,7 +256,7 @@ impl Library {
.execute(connection)?; .execute(connection)?;
for (index, part) in parts.into_iter().enumerate() { for (index, part) in parts.into_iter().enumerate() {
Self::create_work_priv( self.create_work_priv(
connection, connection,
part.name, part.name,
part.parts, part.parts,
@ -308,7 +309,7 @@ impl Library {
) -> Result<()> { ) -> Result<()> {
let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap(); let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap();
Self::update_work_priv( self.update_work_priv(
connection, connection,
work_id, work_id,
name, name,
@ -326,6 +327,7 @@ impl Library {
} }
fn update_work_priv( fn update_work_priv(
&self,
connection: &mut SqliteConnection, connection: &mut SqliteConnection,
work_id: &str, work_id: &str,
name: TranslatedString, name: TranslatedString,
@ -365,7 +367,7 @@ impl Library {
.optional()? .optional()?
.is_some() .is_some()
{ {
Self::update_work_priv( self.update_work_priv(
connection, connection,
&part.work_id, &part.work_id,
part.name, part.name,
@ -379,7 +381,7 @@ impl Library {
} else { } else {
// Note: The previously used ID is discarded. This should be OK, because // Note: The previously used ID is discarded. This should be OK, because
// at this point, the part ID should not have been used anywhere. // at this point, the part ID should not have been used anywhere.
Self::create_work_priv( self.create_work_priv(
connection, connection,
part.name, part.name,
part.parts, part.parts,

View file

@ -29,10 +29,7 @@ impl Library {
&self, &self,
path: impl AsRef<Path>, path: impl AsRef<Path>,
) -> Result<async_channel::Receiver<ProcessMsg>> { ) -> Result<async_channel::Receiver<ProcessMsg>> {
log::info!( log::info!("Importing library from ZIP at {}", path.as_ref().to_string_lossy());
"Importing library from ZIP at {}",
path.as_ref().to_string_lossy()
);
let path = path.as_ref().to_owned(); let path = path.as_ref().to_owned();
let library_folder = PathBuf::from(&self.folder()); let library_folder = PathBuf::from(&self.folder());
let this_connection = self.imp().connection.get().unwrap().clone(); let this_connection = self.imp().connection.get().unwrap().clone();
@ -55,10 +52,7 @@ impl Library {
&self, &self,
path: impl AsRef<Path>, path: impl AsRef<Path>,
) -> Result<async_channel::Receiver<ProcessMsg>> { ) -> Result<async_channel::Receiver<ProcessMsg>> {
log::info!( log::info!("Exporting library to ZIP at {}", path.as_ref().to_string_lossy());
"Exporting library to ZIP at {}",
path.as_ref().to_string_lossy()
);
let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap(); let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap();
let path = path.as_ref().to_owned(); let path = path.as_ref().to_owned();
@ -229,18 +223,22 @@ fn import_metadata_from_url_priv(
formatx!(gettext("Downloading {}"), &url).unwrap(), formatx!(gettext("Downloading {}"), &url).unwrap(),
)); ));
match runtime.block_on(download_tmp_file(&url, sender)) { match runtime.block_on(download_tmp_file(&url, &sender)) {
Ok(db_file) => { Ok(db_file) => {
let _ = sender.send_blocking(ProcessMsg::Message( let _ = sender.send_blocking(ProcessMsg::Message(
formatx!(gettext("Importing downloaded library"), &url).unwrap(), formatx!(gettext("Importing downloaded library"), &url).unwrap(),
)); ));
let _ = sender.send_blocking(ProcessMsg::Result( let _ = sender.send_blocking(ProcessMsg::Result(
import_metadata_from_file(db_file.path(), this_connection, true).map(|tracks| { import_metadata_from_file(db_file.path(), this_connection, true).and_then(
|tracks| {
if !tracks.is_empty() { if !tracks.is_empty() {
log::warn!("The metadata file at {url} contains tracks."); log::warn!("The metadata file at {url} contains tracks.");
} }
}),
Ok(())
},
),
)); ));
} }
Err(err) => { Err(err) => {
@ -265,7 +263,7 @@ fn import_library_from_url_priv(
formatx!(gettext("Downloading {}"), &url).unwrap(), formatx!(gettext("Downloading {}"), &url).unwrap(),
)); ));
let archive_file = runtime.block_on(download_tmp_file(&url, sender)); let archive_file = runtime.block_on(download_tmp_file(&url, &sender));
match archive_file { match archive_file {
Ok(archive_file) => { Ok(archive_file) => {
@ -277,7 +275,7 @@ fn import_library_from_url_priv(
archive_file.path(), archive_file.path(),
library_folder, library_folder,
this_connection, this_connection,
sender, &sender,
))); )));
} }
Err(err) => { Err(err) => {

View file

@ -414,6 +414,7 @@ impl Library {
works, works,
recordings, recordings,
albums, albums,
..Default::default()
} }
} }
LibraryQuery { LibraryQuery {

View file

@ -128,13 +128,7 @@ impl LibraryManager {
} }
Ok(path) => { Ok(path) => {
if let Some(path) = path.path() { if let Some(path) = path.path() {
match self match self.imp().library.get().unwrap().import_library_from_zip(&path) {
.imp()
.library
.get()
.unwrap()
.import_library_from_zip(&path)
{
Ok(receiver) => { Ok(receiver) => {
let process = Process::new( let process = Process::new(
&formatx!( &formatx!(
@ -192,13 +186,7 @@ impl LibraryManager {
} }
Ok(path) => { Ok(path) => {
if let Some(path) = path.path() { if let Some(path) = path.path() {
match self match self.imp().library.get().unwrap().export_library_to_zip(&path) {
.imp()
.library
.get()
.unwrap()
.export_library_to_zip(&path)
{
Ok(receiver) => { Ok(receiver) => {
let process = Process::new( let process = Process::new(
&formatx!( &formatx!(

View file

@ -20,6 +20,7 @@ mod program;
mod program_tile; mod program_tile;
mod recording_tile; mod recording_tile;
mod search_page; mod search_page;
mod search_tag;
mod selector; mod selector;
mod slider_row; mod slider_row;
mod tag_tile; mod tag_tile;
@ -46,7 +47,7 @@ fn main() -> glib::ExitCode {
gettextrs::textdomain(config::PKGNAME).unwrap(); gettextrs::textdomain(config::PKGNAME).unwrap();
gio::resources_register( gio::resources_register(
&gio::Resource::load(format!( &gio::Resource::load(&format!(
"{}/{}/{}.gresource", "{}/{}/{}.gresource",
config::DATADIR, config::DATADIR,
config::PKGNAME, config::PKGNAME,

View file

@ -221,14 +221,14 @@ impl Player {
items.push(PlaylistItem::new( items.push(PlaylistItem::new(
true, true,
recording.work.composers_string(), recording.work.composers_string(),
recording.work.name.get(), &recording.work.name.get(),
Some(&performances), Some(&performances),
None, None,
self.library_path_to_file_path(&tracks[0].path), &self.library_path_to_file_path(&tracks[0].path),
&tracks[0].track_id, &tracks[0].track_id,
)); ));
} else { } else {
let mut tracks = tracks.iter(); let mut tracks = tracks.into_iter();
let first_track = tracks.next().unwrap(); let first_track = tracks.next().unwrap();
let track_title = |track: &Track, number: usize| -> String { let track_title = |track: &Track, number: usize| -> String {
@ -249,10 +249,10 @@ impl Player {
items.push(PlaylistItem::new( items.push(PlaylistItem::new(
true, true,
recording.work.composers_string(), recording.work.composers_string(),
recording.work.name.get(), &recording.work.name.get(),
Some(&performances), Some(&performances),
Some(&track_title(first_track, 1)), Some(&track_title(&first_track, 1)),
self.library_path_to_file_path(&first_track.path), &self.library_path_to_file_path(&first_track.path),
&first_track.track_id, &first_track.track_id,
)); ));
@ -260,11 +260,11 @@ impl Player {
items.push(PlaylistItem::new( items.push(PlaylistItem::new(
false, false,
recording.work.composers_string(), recording.work.composers_string(),
recording.work.name.get(), &recording.work.name.get(),
Some(&performances), Some(&performances),
// track number = track index + 1 (first track) + 1 (zero based) // track number = track index + 1 (first track) + 1 (zero based)
Some(&track_title(track, index + 2)), Some(&track_title(&track, index + 2)),
self.library_path_to_file_path(&track.path), &self.library_path_to_file_path(&track.path),
&track.track_id, &track.track_id,
)); ));
} }

View file

@ -83,7 +83,7 @@ impl Program {
} }
pub fn from_query(query: LibraryQuery) -> Self { pub fn from_query(query: LibraryQuery) -> Self {
let settings = gio::Settings::new(config::APP_ID); let settings = gio::Settings::new(&config::APP_ID);
glib::Object::builder() glib::Object::builder()
.property( .property(

View file

@ -56,7 +56,7 @@ mod imp {
self.set_program_from_settings(&settings); self.set_program_from_settings(&settings);
let obj = self.obj().to_owned(); let obj = self.obj().to_owned();
settings.connect_changed(Some(self.key.get().unwrap()), move |settings, _| { settings.connect_changed(Some(&self.key.get().unwrap()), move |settings, _| {
obj.imp().set_program_from_settings(settings); obj.imp().set_program_from_settings(settings);
}); });
} }

View file

@ -69,7 +69,7 @@ mod imp {
.push(&RecordingEditor::new( .push(&RecordingEditor::new(
obj.imp().navigation.get().unwrap(), obj.imp().navigation.get().unwrap(),
obj.imp().library.get().unwrap(), obj.imp().library.get().unwrap(),
Some(obj.imp().recording.get().unwrap()), Some(&obj.imp().recording.get().unwrap()),
)); ));
}) })
.build(); .build();
@ -90,8 +90,8 @@ mod imp {
let delete_action = gio::ActionEntry::builder("delete") let delete_action = gio::ActionEntry::builder("delete")
.activate(move |_, _, _| { .activate(move |_, _, _| {
let dialog = adw::AlertDialog::builder() let dialog = adw::AlertDialog::builder()
.heading(gettext("Delete recording?")) .heading(&gettext("Delete recording?"))
.body(gettext("The recording will be removed from your music library and the corresponding audio files will be deleted. This action cannot be undone.")) .body(&gettext("The recording will be removed from your music library and the corresponding audio files will be deleted. This action cannot be undone."))
.build(); .build();
dialog.add_response("delete", &gettext("Delete")); dialog.add_response("delete", &gettext("Delete"));
@ -142,7 +142,7 @@ impl RecordingTile {
let obj: Self = glib::Object::new(); let obj: Self = glib::Object::new();
let imp = obj.imp(); let imp = obj.imp();
imp.work_label.set_label(recording.work.name.get()); imp.work_label.set_label(&recording.work.name.get());
imp.composer_label.set_label( imp.composer_label.set_label(
&recording &recording
.work .work

View file

@ -22,7 +22,8 @@ use crate::{
program::Program, program::Program,
program_tile::ProgramTile, program_tile::ProgramTile,
recording_tile::RecordingTile, recording_tile::RecordingTile,
tag_tile::{Tag, TagTile}, search_tag::Tag,
tag_tile::TagTile,
util, util,
}; };
@ -391,7 +392,7 @@ impl SearchPage {
imp.header_box.set_visible(!query.is_empty()); imp.header_box.set_visible(!query.is_empty());
let highlight = if let Some(work) = &query.work { let highlight = if let Some(work) = &query.work {
imp.title_label.set_text(work.name.get()); imp.title_label.set_text(&work.name.get());
if let Some(composers) = work.composers_string() { if let Some(composers) = work.composers_string() {
imp.subtitle_label.set_text(&composers); imp.subtitle_label.set_text(&composers);
imp.subtitle_label.set_visible(true); imp.subtitle_label.set_visible(true);
@ -400,15 +401,15 @@ impl SearchPage {
} }
Some(Tag::Work(work.to_owned())) Some(Tag::Work(work.to_owned()))
} else if let Some(person) = &query.composer { } else if let Some(person) = &query.composer {
imp.title_label.set_text(person.name.get()); imp.title_label.set_text(&person.name.get());
imp.subtitle_label.set_visible(false); imp.subtitle_label.set_visible(false);
Some(Tag::Composer(person.to_owned())) Some(Tag::Composer(person.to_owned()))
} else if let Some(person) = &query.performer { } else if let Some(person) = &query.performer {
imp.title_label.set_text(person.name.get()); imp.title_label.set_text(&person.name.get());
imp.subtitle_label.set_visible(false); imp.subtitle_label.set_visible(false);
Some(Tag::Performer(person.to_owned())) Some(Tag::Performer(person.to_owned()))
} else if let Some(ensemble) = &query.ensemble { } else if let Some(ensemble) = &query.ensemble {
imp.title_label.set_text(ensemble.name.get()); imp.title_label.set_text(&ensemble.name.get());
imp.subtitle_label.set_visible(false); imp.subtitle_label.set_visible(false);
Some(Tag::Ensemble(ensemble.to_owned())) Some(Tag::Ensemble(ensemble.to_owned()))
} else if let Some(instrument) = &query.instrument { } else if let Some(instrument) = &query.instrument {

98
src/search_tag.rs Normal file
View file

@ -0,0 +1,98 @@
use std::cell::OnceCell;
use adw::{glib, glib::subclass::Signal, prelude::*, subclass::prelude::*};
use once_cell::sync::Lazy;
use crate::db::models::{Ensemble, Instrument, Person, Work};
mod imp {
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate)]
#[template(file = "data/ui/search_tag.blp")]
pub struct SearchTag {
#[template_child]
pub label: TemplateChild<gtk::Label>,
pub tag: OnceCell<Tag>,
}
#[glib::object_subclass]
impl ObjectSubclass for SearchTag {
const NAME: &'static str = "MusicusSearchTag";
type Type = super::SearchTag;
type ParentType = gtk::Box;
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();
}
}
impl ObjectImpl for SearchTag {
fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> =
Lazy::new(|| vec![Signal::builder("remove").build()]);
SIGNALS.as_ref()
}
}
impl WidgetImpl for SearchTag {}
impl BoxImpl for SearchTag {}
}
glib::wrapper! {
pub struct SearchTag(ObjectSubclass<imp::SearchTag>)
@extends gtk::Widget;
}
#[gtk::template_callbacks]
impl SearchTag {
pub fn new(tag: Tag) -> Self {
let obj: SearchTag = glib::Object::new();
let label = match &tag {
Tag::Composer(person) => person.name.get(),
Tag::Performer(person) => person.name.get(),
Tag::Ensemble(ensemble) => ensemble.name.get(),
Tag::Instrument(instrument) => instrument.name.get(),
Tag::Work(work) => work.name.get(),
};
obj.imp().label.set_label(label);
obj.set_tooltip_text(Some(label));
obj.imp().tag.set(tag).unwrap();
obj
}
pub fn connect_remove<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
self.connect_local("remove", true, move |values| {
let obj = values[0].get::<Self>().unwrap();
f(&obj);
None
})
}
pub fn tag(&self) -> &Tag {
self.imp().tag.get().unwrap()
}
#[template_callback]
fn remove(&self) {
self.emit_by_name::<()>("remove", &[]);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Tag {
Composer(Person),
Performer(Person),
Ensemble(Ensemble),
Instrument(Instrument),
Work(Work),
}

View file

@ -294,7 +294,7 @@ impl RecordingSelectorPopover {
.build(), .build(),
); );
row.set_tooltip_text(Some(work.name.get())); row.set_tooltip_text(Some(&work.name.get()));
let work = work.clone(); let work = work.clone();
let obj = self.clone(); let obj = self.clone();

View file

@ -256,7 +256,7 @@ impl WorkSelectorPopover {
.build(), .build(),
); );
row.set_tooltip_text(Some(work.name.get())); row.set_tooltip_text(Some(&work.name.get()));
let work = work.clone(); let work = work.clone();
let obj = self.clone(); let obj = self.clone();

View file

@ -2,7 +2,7 @@ use std::cell::OnceCell;
use gtk::{glib, prelude::*, subclass::prelude::*}; use gtk::{glib, prelude::*, subclass::prelude::*};
use crate::db::models::{Ensemble, Instrument, Person, Work}; use crate::search_tag::Tag;
mod imp { mod imp {
use super::*; use super::*;
@ -78,12 +78,3 @@ impl TagTile {
self.imp().tag.get().unwrap() self.imp().tag.get().unwrap()
} }
} }
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Tag {
Composer(Person),
Performer(Person),
Ensemble(Ensemble),
Instrument(Instrument),
Work(Work),
}

View file

@ -28,7 +28,7 @@ pub fn error_toast(msgid: &str, err: anyhow::Error, toast_overlay: &adw::ToastOv
log::error!("{msgid}: {err:?}"); log::error!("{msgid}: {err:?}");
let toast = adw::Toast::builder() let toast = adw::Toast::builder()
.title(gettext(msgid)) .title(&gettext(msgid))
.button_label("Details") .button_label("Details")
.build(); .build();

View file

@ -61,7 +61,7 @@ glib::wrapper! {
impl ErrorDialog { impl ErrorDialog {
pub fn present(err: &anyhow::Error, parent: &impl IsA<gtk::Widget>) { pub fn present(err: &anyhow::Error, parent: &impl IsA<gtk::Widget>) {
let obj: Self = glib::Object::builder() let obj: Self = glib::Object::builder()
.property("error-text", format!("{err:?}")) .property("error-text", &format!("{err:?}"))
.build(); .build();
obj.present(Some(parent)); obj.present(Some(parent));

View file

@ -9,9 +9,8 @@ use gettextrs::gettext;
use gtk::{gio, glib, glib::clone}; use gtk::{gio, glib, glib::clone};
use crate::{ use crate::{
album_page::AlbumPage,
config, config,
editor::{album::AlbumEditor, tracks::TracksEditor}, editor::tracks::TracksEditor,
empty_page::EmptyPage, empty_page::EmptyPage,
library::{Library, LibraryQuery}, library::{Library, LibraryQuery},
library_manager::LibraryManager, library_manager::LibraryManager,
@ -88,16 +87,6 @@ mod imp {
}) })
.build(); .build();
let obj = self.obj().to_owned();
let create_album_action = gio::ActionEntry::builder("create-album")
.activate(move |_, _, _| {
if let Some(library) = &*obj.imp().library.borrow() {
let editor = AlbumEditor::new(&obj.imp().navigation_view, library, None);
obj.imp().navigation_view.push(&editor);
}
})
.build();
let obj = self.obj().to_owned(); let obj = self.obj().to_owned();
let library_action = gio::ActionEntry::builder("library") let library_action = gio::ActionEntry::builder("library")
.activate(move |_, _, _| { .activate(move |_, _, _| {
@ -119,12 +108,8 @@ mod imp {
}) })
.build(); .build();
self.obj().add_action_entries([ self.obj()
import_action, .add_action_entries([import_action, library_action, preferences_action]);
create_album_action,
library_action,
preferences_action,
]);
let player_bar = PlayerBar::new(&self.player); let player_bar = PlayerBar::new(&self.player);
self.player_bar_revealer.set_child(Some(&player_bar)); self.player_bar_revealer.set_child(Some(&player_bar));
@ -202,8 +187,8 @@ mod imp {
fn close_request(&self) -> glib::signal::Propagation { fn close_request(&self) -> glib::signal::Propagation {
if self.process_manager.any_ongoing() { if self.process_manager.any_ongoing() {
let dialog = adw::AlertDialog::builder() let dialog = adw::AlertDialog::builder()
.heading(gettext("Close window?")) .heading(&gettext("Close window?"))
.body(gettext( .body(&gettext(
"There are ongoing processes that will be canceled.", "There are ongoing processes that will be canceled.",
)) ))
.build(); .build();
@ -377,12 +362,17 @@ impl Window {
fn reset_view(&self) { fn reset_view(&self) {
let navigation = self.imp().navigation_view.get(); let navigation = self.imp().navigation_view.get();
// Get all pages that are not instances of SearchPage or AlbumPage. // Get all pages that are not instances of SearchPage.
let mut navigation_stack = navigation let mut navigation_stack = navigation
.navigation_stack() .navigation_stack()
.iter::<adw::NavigationPage>() .iter::<adw::NavigationPage>()
.filter_map(|page| page.ok()) .filter_map(|page| match page {
.filter(|page| !page.is::<SearchPage>() && !page.is::<AlbumPage>()) Ok(page) => match page.downcast_ref::<SearchPage>() {
Some(_) => None,
None => Some(page),
},
Err(_) => None,
})
.collect::<Vec<adw::NavigationPage>>(); .collect::<Vec<adw::NavigationPage>>();
navigation_stack.insert( navigation_stack.insert(