mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 11:47:25 +01:00
Compare commits
4 commits
48cfdd354a
...
8d9690dad6
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d9690dad6 | |||
| 66af523d5a | |||
| 45338c1bf9 | |||
| c50ef90e9b |
29 changed files with 161 additions and 251 deletions
|
|
@ -14,17 +14,6 @@
|
|||
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 {
|
||||
min-height: 50px;
|
||||
min-width: 200px;
|
||||
|
|
|
|||
|
|
@ -50,23 +50,32 @@ template $MusicusEmptyPage: Adw.NavigationPage {
|
|||
}
|
||||
|
||||
menu primary_menu {
|
||||
item {
|
||||
label: _("_Import music");
|
||||
action: "win.import";
|
||||
section {
|
||||
item {
|
||||
label: _("_Import music");
|
||||
action: "win.import";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("_Create album");
|
||||
action: "win.create-album";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("_Library manager");
|
||||
action: "win.library";
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("_Library manager");
|
||||
action: "win.library";
|
||||
}
|
||||
section {
|
||||
item {
|
||||
label: _("_Preferences");
|
||||
action: "win.preferences";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("_Preferences");
|
||||
action: "win.preferences";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("_About Musicus");
|
||||
action: "app.about";
|
||||
item {
|
||||
label: _("_About Musicus");
|
||||
action: "app.about";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ template $MusicusSearchPage: Adw.NavigationPage {
|
|||
activate => $select() swapped;
|
||||
|
||||
styles [
|
||||
"rounded-entry"
|
||||
"rounded-entry",
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -264,24 +264,33 @@ template $MusicusSearchPage: Adw.NavigationPage {
|
|||
}
|
||||
|
||||
menu primary_menu {
|
||||
item {
|
||||
label: _("_Import music");
|
||||
action: "win.import";
|
||||
section {
|
||||
item {
|
||||
label: _("_Import music");
|
||||
action: "win.import";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("_Create album");
|
||||
action: "win.create-album";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("_Library manager");
|
||||
action: "win.library";
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("_Library manager");
|
||||
action: "win.library";
|
||||
}
|
||||
section {
|
||||
item {
|
||||
label: _("_Preferences");
|
||||
action: "win.preferences";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("_Preferences");
|
||||
action: "win.preferences";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("_About Musicus");
|
||||
action: "app.about";
|
||||
item {
|
||||
label: _("_About Musicus");
|
||||
action: "app.about";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -72,8 +72,7 @@ mod imp {
|
|||
.unwrap()
|
||||
.recordings
|
||||
.iter()
|
||||
.map(|r| obj.player().recording_to_playlist(r))
|
||||
.flatten()
|
||||
.flat_map(|r| obj.player().recording_to_playlist(r))
|
||||
.collect::<Vec<PlaylistItem>>();
|
||||
|
||||
if let Err(err) = obj.player().append(playlist) {
|
||||
|
|
@ -165,8 +164,7 @@ impl AlbumPage {
|
|||
.unwrap()
|
||||
.recordings
|
||||
.iter()
|
||||
.map(|r| self.player().recording_to_playlist(r))
|
||||
.flatten()
|
||||
.flat_map(|r| self.player().recording_to_playlist(r))
|
||||
.collect::<Vec<PlaylistItem>>();
|
||||
|
||||
self.player().append_and_play(playlist);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ impl AlbumTile {
|
|||
pub fn new(album: &Album) -> Self {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ mod imp {
|
|||
self.parent_constructed();
|
||||
|
||||
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"))
|
||||
.build();
|
||||
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ impl RecordingEditor {
|
|||
}
|
||||
|
||||
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(
|
||||
&work
|
||||
.composers_string()
|
||||
|
|
|
|||
|
|
@ -245,8 +245,7 @@ impl TracksEditor {
|
|||
.track_rows
|
||||
.borrow()
|
||||
.iter()
|
||||
.map(|t| t.track_data().parts.clone())
|
||||
.flatten()
|
||||
.flat_map(|t| t.track_data().parts.clone())
|
||||
.collect::<Vec<Work>>()
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -390,17 +390,15 @@ impl WorkEditor {
|
|||
};
|
||||
|
||||
self.emit_by_name::<()>("created", &[&part]);
|
||||
} else if let Some(work_id) = self.imp().work_id.get() {
|
||||
library
|
||||
.update_work(work_id, name, parts, composers, instruments, enable_updates)
|
||||
.unwrap();
|
||||
} else {
|
||||
if let Some(work_id) = self.imp().work_id.get() {
|
||||
library
|
||||
.update_work(work_id, name, parts, composers, instruments, enable_updates)
|
||||
.unwrap();
|
||||
} else {
|
||||
let work = library
|
||||
.create_work(name, parts, composers, instruments, enable_updates)
|
||||
.unwrap();
|
||||
self.emit_by_name::<()>("created", &[&work]);
|
||||
}
|
||||
let work = library
|
||||
.create_work(name, parts, composers, instruments, enable_updates)
|
||||
.unwrap();
|
||||
self.emit_by_name::<()>("created", &[&work]);
|
||||
}
|
||||
|
||||
self.imp().navigation.get().unwrap().pop();
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ impl WorkEditorPartRow {
|
|||
}
|
||||
|
||||
fn set_part(&self, part: Work) {
|
||||
self.set_title(&part.name.get());
|
||||
self.set_title(part.name.get());
|
||||
|
||||
if !part.parts.is_empty() {
|
||||
self.set_subtitle(
|
||||
|
|
|
|||
|
|
@ -92,8 +92,8 @@ impl EmptyPage {
|
|||
#[template_callback]
|
||||
async fn download_library(&self) {
|
||||
let dialog = adw::AlertDialog::builder()
|
||||
.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."))
|
||||
.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."))
|
||||
.build();
|
||||
|
||||
dialog.add_response("continue", &gettext("Continue"));
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ impl Library {
|
|||
) -> Result<Work> {
|
||||
let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap();
|
||||
|
||||
let work = self.create_work_priv(
|
||||
let work = Self::create_work_priv(
|
||||
connection,
|
||||
name,
|
||||
parts,
|
||||
|
|
@ -226,7 +226,6 @@ impl Library {
|
|||
}
|
||||
|
||||
fn create_work_priv(
|
||||
&self,
|
||||
connection: &mut SqliteConnection,
|
||||
name: TranslatedString,
|
||||
parts: Vec<Work>,
|
||||
|
|
@ -242,7 +241,7 @@ impl Library {
|
|||
let work_data = tables::Work {
|
||||
work_id: work_id.clone(),
|
||||
parent_work_id: parent_work_id.map(|w| w.to_string()),
|
||||
sequence_number: sequence_number,
|
||||
sequence_number,
|
||||
name,
|
||||
created_at: now,
|
||||
edited_at: now,
|
||||
|
|
@ -256,7 +255,7 @@ impl Library {
|
|||
.execute(connection)?;
|
||||
|
||||
for (index, part) in parts.into_iter().enumerate() {
|
||||
self.create_work_priv(
|
||||
Self::create_work_priv(
|
||||
connection,
|
||||
part.name,
|
||||
part.parts,
|
||||
|
|
@ -309,7 +308,7 @@ impl Library {
|
|||
) -> Result<()> {
|
||||
let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap();
|
||||
|
||||
self.update_work_priv(
|
||||
Self::update_work_priv(
|
||||
connection,
|
||||
work_id,
|
||||
name,
|
||||
|
|
@ -327,7 +326,6 @@ impl Library {
|
|||
}
|
||||
|
||||
fn update_work_priv(
|
||||
&self,
|
||||
connection: &mut SqliteConnection,
|
||||
work_id: &str,
|
||||
name: TranslatedString,
|
||||
|
|
@ -367,7 +365,7 @@ impl Library {
|
|||
.optional()?
|
||||
.is_some()
|
||||
{
|
||||
self.update_work_priv(
|
||||
Self::update_work_priv(
|
||||
connection,
|
||||
&part.work_id,
|
||||
part.name,
|
||||
|
|
@ -381,7 +379,7 @@ impl Library {
|
|||
} else {
|
||||
// Note: The previously used ID is discarded. This should be OK, because
|
||||
// at this point, the part ID should not have been used anywhere.
|
||||
self.create_work_priv(
|
||||
Self::create_work_priv(
|
||||
connection,
|
||||
part.name,
|
||||
part.parts,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,10 @@ impl Library {
|
|||
&self,
|
||||
path: impl AsRef<Path>,
|
||||
) -> Result<async_channel::Receiver<ProcessMsg>> {
|
||||
log::info!("Importing library from ZIP at {}", path.as_ref().to_string_lossy());
|
||||
log::info!(
|
||||
"Importing library from ZIP at {}",
|
||||
path.as_ref().to_string_lossy()
|
||||
);
|
||||
let path = path.as_ref().to_owned();
|
||||
let library_folder = PathBuf::from(&self.folder());
|
||||
let this_connection = self.imp().connection.get().unwrap().clone();
|
||||
|
|
@ -52,7 +55,10 @@ impl Library {
|
|||
&self,
|
||||
path: impl AsRef<Path>,
|
||||
) -> Result<async_channel::Receiver<ProcessMsg>> {
|
||||
log::info!("Exporting library to ZIP at {}", path.as_ref().to_string_lossy());
|
||||
log::info!(
|
||||
"Exporting library to ZIP at {}",
|
||||
path.as_ref().to_string_lossy()
|
||||
);
|
||||
let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap();
|
||||
|
||||
let path = path.as_ref().to_owned();
|
||||
|
|
@ -223,22 +229,18 @@ fn import_metadata_from_url_priv(
|
|||
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) => {
|
||||
let _ = sender.send_blocking(ProcessMsg::Message(
|
||||
formatx!(gettext("Importing downloaded library"), &url).unwrap(),
|
||||
));
|
||||
|
||||
let _ = sender.send_blocking(ProcessMsg::Result(
|
||||
import_metadata_from_file(db_file.path(), this_connection, true).and_then(
|
||||
|tracks| {
|
||||
if !tracks.is_empty() {
|
||||
log::warn!("The metadata file at {url} contains tracks.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
),
|
||||
import_metadata_from_file(db_file.path(), this_connection, true).map(|tracks| {
|
||||
if !tracks.is_empty() {
|
||||
log::warn!("The metadata file at {url} contains tracks.");
|
||||
}
|
||||
}),
|
||||
));
|
||||
}
|
||||
Err(err) => {
|
||||
|
|
@ -263,7 +265,7 @@ fn import_library_from_url_priv(
|
|||
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 {
|
||||
Ok(archive_file) => {
|
||||
|
|
@ -275,7 +277,7 @@ fn import_library_from_url_priv(
|
|||
archive_file.path(),
|
||||
library_folder,
|
||||
this_connection,
|
||||
&sender,
|
||||
sender,
|
||||
)));
|
||||
}
|
||||
Err(err) => {
|
||||
|
|
|
|||
|
|
@ -414,7 +414,6 @@ impl Library {
|
|||
works,
|
||||
recordings,
|
||||
albums,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
LibraryQuery {
|
||||
|
|
|
|||
|
|
@ -128,7 +128,13 @@ impl LibraryManager {
|
|||
}
|
||||
Ok(path) => {
|
||||
if let Some(path) = path.path() {
|
||||
match self.imp().library.get().unwrap().import_library_from_zip(&path) {
|
||||
match self
|
||||
.imp()
|
||||
.library
|
||||
.get()
|
||||
.unwrap()
|
||||
.import_library_from_zip(&path)
|
||||
{
|
||||
Ok(receiver) => {
|
||||
let process = Process::new(
|
||||
&formatx!(
|
||||
|
|
@ -186,7 +192,13 @@ impl LibraryManager {
|
|||
}
|
||||
Ok(path) => {
|
||||
if let Some(path) = path.path() {
|
||||
match self.imp().library.get().unwrap().export_library_to_zip(&path) {
|
||||
match self
|
||||
.imp()
|
||||
.library
|
||||
.get()
|
||||
.unwrap()
|
||||
.export_library_to_zip(&path)
|
||||
{
|
||||
Ok(receiver) => {
|
||||
let process = Process::new(
|
||||
&formatx!(
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ mod program;
|
|||
mod program_tile;
|
||||
mod recording_tile;
|
||||
mod search_page;
|
||||
mod search_tag;
|
||||
mod selector;
|
||||
mod slider_row;
|
||||
mod tag_tile;
|
||||
|
|
@ -47,7 +46,7 @@ fn main() -> glib::ExitCode {
|
|||
gettextrs::textdomain(config::PKGNAME).unwrap();
|
||||
|
||||
gio::resources_register(
|
||||
&gio::Resource::load(&format!(
|
||||
&gio::Resource::load(format!(
|
||||
"{}/{}/{}.gresource",
|
||||
config::DATADIR,
|
||||
config::PKGNAME,
|
||||
|
|
|
|||
|
|
@ -221,14 +221,14 @@ impl Player {
|
|||
items.push(PlaylistItem::new(
|
||||
true,
|
||||
recording.work.composers_string(),
|
||||
&recording.work.name.get(),
|
||||
recording.work.name.get(),
|
||||
Some(&performances),
|
||||
None,
|
||||
&self.library_path_to_file_path(&tracks[0].path),
|
||||
self.library_path_to_file_path(&tracks[0].path),
|
||||
&tracks[0].track_id,
|
||||
));
|
||||
} else {
|
||||
let mut tracks = tracks.into_iter();
|
||||
let mut tracks = tracks.iter();
|
||||
let first_track = tracks.next().unwrap();
|
||||
|
||||
let track_title = |track: &Track, number: usize| -> String {
|
||||
|
|
@ -249,10 +249,10 @@ impl Player {
|
|||
items.push(PlaylistItem::new(
|
||||
true,
|
||||
recording.work.composers_string(),
|
||||
&recording.work.name.get(),
|
||||
recording.work.name.get(),
|
||||
Some(&performances),
|
||||
Some(&track_title(&first_track, 1)),
|
||||
&self.library_path_to_file_path(&first_track.path),
|
||||
Some(&track_title(first_track, 1)),
|
||||
self.library_path_to_file_path(&first_track.path),
|
||||
&first_track.track_id,
|
||||
));
|
||||
|
||||
|
|
@ -260,11 +260,11 @@ impl Player {
|
|||
items.push(PlaylistItem::new(
|
||||
false,
|
||||
recording.work.composers_string(),
|
||||
&recording.work.name.get(),
|
||||
recording.work.name.get(),
|
||||
Some(&performances),
|
||||
// track number = track index + 1 (first track) + 1 (zero based)
|
||||
Some(&track_title(&track, index + 2)),
|
||||
&self.library_path_to_file_path(&track.path),
|
||||
Some(&track_title(track, index + 2)),
|
||||
self.library_path_to_file_path(&track.path),
|
||||
&track.track_id,
|
||||
));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ impl Program {
|
|||
}
|
||||
|
||||
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()
|
||||
.property(
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ mod imp {
|
|||
self.set_program_from_settings(&settings);
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ mod imp {
|
|||
.push(&RecordingEditor::new(
|
||||
obj.imp().navigation.get().unwrap(),
|
||||
obj.imp().library.get().unwrap(),
|
||||
Some(&obj.imp().recording.get().unwrap()),
|
||||
Some(obj.imp().recording.get().unwrap()),
|
||||
));
|
||||
})
|
||||
.build();
|
||||
|
|
@ -90,8 +90,8 @@ mod imp {
|
|||
let delete_action = gio::ActionEntry::builder("delete")
|
||||
.activate(move |_, _, _| {
|
||||
let dialog = adw::AlertDialog::builder()
|
||||
.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."))
|
||||
.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."))
|
||||
.build();
|
||||
|
||||
dialog.add_response("delete", &gettext("Delete"));
|
||||
|
|
@ -142,7 +142,7 @@ impl RecordingTile {
|
|||
let obj: Self = glib::Object::new();
|
||||
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(
|
||||
&recording
|
||||
.work
|
||||
|
|
|
|||
|
|
@ -22,8 +22,7 @@ use crate::{
|
|||
program::Program,
|
||||
program_tile::ProgramTile,
|
||||
recording_tile::RecordingTile,
|
||||
search_tag::Tag,
|
||||
tag_tile::TagTile,
|
||||
tag_tile::{Tag, TagTile},
|
||||
util,
|
||||
};
|
||||
|
||||
|
|
@ -392,7 +391,7 @@ impl SearchPage {
|
|||
imp.header_box.set_visible(!query.is_empty());
|
||||
|
||||
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() {
|
||||
imp.subtitle_label.set_text(&composers);
|
||||
imp.subtitle_label.set_visible(true);
|
||||
|
|
@ -401,15 +400,15 @@ impl SearchPage {
|
|||
}
|
||||
Some(Tag::Work(work.to_owned()))
|
||||
} 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);
|
||||
Some(Tag::Composer(person.to_owned()))
|
||||
} 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);
|
||||
Some(Tag::Performer(person.to_owned()))
|
||||
} 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);
|
||||
Some(Tag::Ensemble(ensemble.to_owned()))
|
||||
} else if let Some(instrument) = &query.instrument {
|
||||
|
|
|
|||
|
|
@ -1,98 +0,0 @@
|
|||
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),
|
||||
}
|
||||
|
|
@ -294,7 +294,7 @@ impl RecordingSelectorPopover {
|
|||
.build(),
|
||||
);
|
||||
|
||||
row.set_tooltip_text(Some(&work.name.get()));
|
||||
row.set_tooltip_text(Some(work.name.get()));
|
||||
|
||||
let work = work.clone();
|
||||
let obj = self.clone();
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ impl WorkSelectorPopover {
|
|||
.build(),
|
||||
);
|
||||
|
||||
row.set_tooltip_text(Some(&work.name.get()));
|
||||
row.set_tooltip_text(Some(work.name.get()));
|
||||
|
||||
let work = work.clone();
|
||||
let obj = self.clone();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::cell::OnceCell;
|
|||
|
||||
use gtk::{glib, prelude::*, subclass::prelude::*};
|
||||
|
||||
use crate::search_tag::Tag;
|
||||
use crate::db::models::{Ensemble, Instrument, Person, Work};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
|
@ -78,3 +78,12 @@ impl TagTile {
|
|||
self.imp().tag.get().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Tag {
|
||||
Composer(Person),
|
||||
Performer(Person),
|
||||
Ensemble(Ensemble),
|
||||
Instrument(Instrument),
|
||||
Work(Work),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ pub fn error_toast(msgid: &str, err: anyhow::Error, toast_overlay: &adw::ToastOv
|
|||
log::error!("{msgid}: {err:?}");
|
||||
|
||||
let toast = adw::Toast::builder()
|
||||
.title(&gettext(msgid))
|
||||
.title(gettext(msgid))
|
||||
.button_label("Details")
|
||||
.build();
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ glib::wrapper! {
|
|||
impl ErrorDialog {
|
||||
pub fn present(err: &anyhow::Error, parent: &impl IsA<gtk::Widget>) {
|
||||
let obj: Self = glib::Object::builder()
|
||||
.property("error-text", &format!("{err:?}"))
|
||||
.property("error-text", format!("{err:?}"))
|
||||
.build();
|
||||
|
||||
obj.present(Some(parent));
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ use gettextrs::gettext;
|
|||
use gtk::{gio, glib, glib::clone};
|
||||
|
||||
use crate::{
|
||||
album_page::AlbumPage,
|
||||
config,
|
||||
editor::tracks::TracksEditor,
|
||||
editor::{album::AlbumEditor, tracks::TracksEditor},
|
||||
empty_page::EmptyPage,
|
||||
library::{Library, LibraryQuery},
|
||||
library_manager::LibraryManager,
|
||||
|
|
@ -87,6 +88,16 @@ mod imp {
|
|||
})
|
||||
.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 library_action = gio::ActionEntry::builder("library")
|
||||
.activate(move |_, _, _| {
|
||||
|
|
@ -108,8 +119,12 @@ mod imp {
|
|||
})
|
||||
.build();
|
||||
|
||||
self.obj()
|
||||
.add_action_entries([import_action, library_action, preferences_action]);
|
||||
self.obj().add_action_entries([
|
||||
import_action,
|
||||
create_album_action,
|
||||
library_action,
|
||||
preferences_action,
|
||||
]);
|
||||
|
||||
let player_bar = PlayerBar::new(&self.player);
|
||||
self.player_bar_revealer.set_child(Some(&player_bar));
|
||||
|
|
@ -187,8 +202,8 @@ mod imp {
|
|||
fn close_request(&self) -> glib::signal::Propagation {
|
||||
if self.process_manager.any_ongoing() {
|
||||
let dialog = adw::AlertDialog::builder()
|
||||
.heading(&gettext("Close window?"))
|
||||
.body(&gettext(
|
||||
.heading(gettext("Close window?"))
|
||||
.body(gettext(
|
||||
"There are ongoing processes that will be canceled.",
|
||||
))
|
||||
.build();
|
||||
|
|
@ -362,17 +377,12 @@ impl Window {
|
|||
fn reset_view(&self) {
|
||||
let navigation = self.imp().navigation_view.get();
|
||||
|
||||
// Get all pages that are not instances of SearchPage.
|
||||
// Get all pages that are not instances of SearchPage or AlbumPage.
|
||||
let mut navigation_stack = navigation
|
||||
.navigation_stack()
|
||||
.iter::<adw::NavigationPage>()
|
||||
.filter_map(|page| match page {
|
||||
Ok(page) => match page.downcast_ref::<SearchPage>() {
|
||||
Some(_) => None,
|
||||
None => Some(page),
|
||||
},
|
||||
Err(_) => None,
|
||||
})
|
||||
.filter_map(|page| page.ok())
|
||||
.filter(|page| !page.is::<SearchPage>() && !page.is::<AlbumPage>())
|
||||
.collect::<Vec<adw::NavigationPage>>();
|
||||
|
||||
navigation_stack.insert(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue