mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 11:47:25 +01:00
Tidy up tracks editor and add track editing and deletion
This commit is contained in:
parent
5002eee67a
commit
3e34658b25
13 changed files with 503 additions and 374 deletions
38
po/de.po
38
po/de.po
|
|
@ -7,8 +7,8 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-11-08 00:09+0100\n"
|
"POT-Creation-Date: 2020-11-08 02:37+0100\n"
|
||||||
"PO-Revision-Date: 2020-11-08 00:12+0100\n"
|
"PO-Revision-Date: 2020-11-08 02:37+0100\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"Language: de\n"
|
"Language: de\n"
|
||||||
|
|
@ -53,8 +53,8 @@ msgid "Recordings"
|
||||||
msgstr "Aufnahmen"
|
msgstr "Aufnahmen"
|
||||||
|
|
||||||
#: res/ui/ensemble_screen.ui:188 res/ui/work_screen.ui:188
|
#: res/ui/ensemble_screen.ui:188 res/ui/work_screen.ui:188
|
||||||
#: src/dialogs/recording_selector.rs:227 src/screens/ensemble_screen.rs:77
|
#: src/dialogs/recording_selector.rs:205 src/screens/ensemble_screen.rs:53
|
||||||
#: src/screens/person_screen.rs:99 src/screens/work_screen.rs:78
|
#: src/screens/person_screen.rs:77 src/screens/work_screen.rs:54
|
||||||
msgid "No recordings found."
|
msgid "No recordings found."
|
||||||
msgstr "Keine Aufnahmen gefunden."
|
msgstr "Keine Aufnahmen gefunden."
|
||||||
|
|
||||||
|
|
@ -234,7 +234,7 @@ msgid "Select a composer on the left."
|
||||||
msgstr "Wählen Sie einen Komponisten aus."
|
msgstr "Wählen Sie einen Komponisten aus."
|
||||||
|
|
||||||
#: res/ui/recording_selector.ui:71 res/ui/recording_selector.ui:186
|
#: res/ui/recording_selector.ui:71 res/ui/recording_selector.ui:186
|
||||||
#: src/dialogs/recording_selector.rs:120 src/screens/person_screen.rs:72
|
#: src/dialogs/recording_selector.rs:109 src/screens/person_screen.rs:57
|
||||||
msgid "No works found."
|
msgid "No works found."
|
||||||
msgstr "Keine Werke gefunden."
|
msgstr "Keine Werke gefunden."
|
||||||
|
|
||||||
|
|
@ -299,7 +299,7 @@ msgstr "Keine Werkabschnitte hinzugefügt."
|
||||||
msgid "Structure"
|
msgid "Structure"
|
||||||
msgstr "Struktur"
|
msgstr "Struktur"
|
||||||
|
|
||||||
#: res/ui/work_selector.ui:113 src/widgets/person_list.rs:41
|
#: res/ui/work_selector.ui:113 src/widgets/person_list.rs:26
|
||||||
msgid "No persons found."
|
msgid "No persons found."
|
||||||
msgstr "Keine Personen gefunden."
|
msgstr "Keine Personen gefunden."
|
||||||
|
|
||||||
|
|
@ -323,19 +323,19 @@ msgstr "Weitere Informationen und Quellcode"
|
||||||
msgid "Select music library folder"
|
msgid "Select music library folder"
|
||||||
msgstr "Ordner der Musikbibliothek auswählen"
|
msgstr "Ordner der Musikbibliothek auswählen"
|
||||||
|
|
||||||
#: src/dialogs/tracks_editor.rs:59 src/screens/player_screen.rs:232
|
#: src/dialogs/tracks_editor.rs:60
|
||||||
#: src/screens/recording_screen.rs:63
|
|
||||||
msgid "Unknown"
|
|
||||||
msgstr "Unbekannt"
|
|
||||||
|
|
||||||
#: src/dialogs/tracks_editor.rs:81
|
|
||||||
msgid "Add some tracks."
|
msgid "Add some tracks."
|
||||||
msgstr "Fügen Sie Tracks hinzu."
|
msgstr "Fügen Sie Tracks hinzu."
|
||||||
|
|
||||||
#: src/dialogs/tracks_editor.rs:148
|
#: src/dialogs/tracks_editor.rs:118
|
||||||
msgid "Select audio files"
|
msgid "Select audio files"
|
||||||
msgstr "Audiodateien auswählen"
|
msgstr "Audiodateien auswählen"
|
||||||
|
|
||||||
|
#: src/dialogs/tracks_editor.rs:236 src/screens/player_screen.rs:230
|
||||||
|
#: src/screens/recording_screen.rs:79
|
||||||
|
msgid "Unknown"
|
||||||
|
msgstr "Unbekannt"
|
||||||
|
|
||||||
#: src/screens/ensemble_screen.rs:35
|
#: src/screens/ensemble_screen.rs:35
|
||||||
msgid "Edit ensemble"
|
msgid "Edit ensemble"
|
||||||
msgstr "Ensemble bearbeiten"
|
msgstr "Ensemble bearbeiten"
|
||||||
|
|
@ -360,7 +360,15 @@ msgstr "Aufnahme bearbeiten"
|
||||||
msgid "Delete recording"
|
msgid "Delete recording"
|
||||||
msgstr "Aufnahme löschen"
|
msgstr "Aufnahme löschen"
|
||||||
|
|
||||||
#: src/screens/recording_screen.rs:85
|
#: src/screens/recording_screen.rs:48
|
||||||
|
msgid "Edit tracks"
|
||||||
|
msgstr "Tracks bearbeiten"
|
||||||
|
|
||||||
|
#: src/screens/recording_screen.rs:54
|
||||||
|
msgid "Delete tracks"
|
||||||
|
msgstr "Tracks löschen"
|
||||||
|
|
||||||
|
#: src/screens/recording_screen.rs:69
|
||||||
msgid "No tracks found."
|
msgid "No tracks found."
|
||||||
msgstr "Keine Tracks gefunden."
|
msgstr "Keine Tracks gefunden."
|
||||||
|
|
||||||
|
|
@ -372,6 +380,6 @@ msgstr "Werk bearbeiten"
|
||||||
msgid "Delete work"
|
msgid "Delete work"
|
||||||
msgstr "Werk löschen"
|
msgstr "Werk löschen"
|
||||||
|
|
||||||
#: src/widgets/poe_list.rs:56
|
#: src/widgets/poe_list.rs:41
|
||||||
msgid "No persons or ensembles found."
|
msgid "No persons or ensembles found."
|
||||||
msgstr "Keine Personen oder Ensembles gefunden."
|
msgstr "Keine Personen oder Ensembles gefunden."
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: musicus\n"
|
"Project-Id-Version: musicus\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-11-08 00:09+0100\n"
|
"POT-Creation-Date: 2020-11-08 02:37+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
|
@ -52,8 +52,8 @@ msgid "Recordings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: res/ui/ensemble_screen.ui:188 res/ui/work_screen.ui:188
|
#: res/ui/ensemble_screen.ui:188 res/ui/work_screen.ui:188
|
||||||
#: src/dialogs/recording_selector.rs:227 src/screens/ensemble_screen.rs:77
|
#: src/dialogs/recording_selector.rs:205 src/screens/ensemble_screen.rs:53
|
||||||
#: src/screens/person_screen.rs:99 src/screens/work_screen.rs:78
|
#: src/screens/person_screen.rs:77 src/screens/work_screen.rs:54
|
||||||
msgid "No recordings found."
|
msgid "No recordings found."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -233,7 +233,7 @@ msgid "Select a composer on the left."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: res/ui/recording_selector.ui:71 res/ui/recording_selector.ui:186
|
#: res/ui/recording_selector.ui:71 res/ui/recording_selector.ui:186
|
||||||
#: src/dialogs/recording_selector.rs:120 src/screens/person_screen.rs:72
|
#: src/dialogs/recording_selector.rs:109 src/screens/person_screen.rs:57
|
||||||
msgid "No works found."
|
msgid "No works found."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -293,7 +293,7 @@ msgstr ""
|
||||||
msgid "Structure"
|
msgid "Structure"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: res/ui/work_selector.ui:113 src/widgets/person_list.rs:41
|
#: res/ui/work_selector.ui:113 src/widgets/person_list.rs:26
|
||||||
msgid "No persons found."
|
msgid "No persons found."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -317,19 +317,19 @@ msgstr ""
|
||||||
msgid "Select music library folder"
|
msgid "Select music library folder"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/dialogs/tracks_editor.rs:59 src/screens/player_screen.rs:232
|
#: src/dialogs/tracks_editor.rs:60
|
||||||
#: src/screens/recording_screen.rs:63
|
|
||||||
msgid "Unknown"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/dialogs/tracks_editor.rs:81
|
|
||||||
msgid "Add some tracks."
|
msgid "Add some tracks."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/dialogs/tracks_editor.rs:148
|
#: src/dialogs/tracks_editor.rs:118
|
||||||
msgid "Select audio files"
|
msgid "Select audio files"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/dialogs/tracks_editor.rs:236 src/screens/player_screen.rs:230
|
||||||
|
#: src/screens/recording_screen.rs:79
|
||||||
|
msgid "Unknown"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/ensemble_screen.rs:35
|
#: src/screens/ensemble_screen.rs:35
|
||||||
msgid "Edit ensemble"
|
msgid "Edit ensemble"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -354,7 +354,15 @@ msgstr ""
|
||||||
msgid "Delete recording"
|
msgid "Delete recording"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/recording_screen.rs:85
|
#: src/screens/recording_screen.rs:48
|
||||||
|
msgid "Edit tracks"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/screens/recording_screen.rs:54
|
||||||
|
msgid "Delete tracks"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/screens/recording_screen.rs:69
|
||||||
msgid "No tracks found."
|
msgid "No tracks found."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -366,6 +374,6 @@ msgstr ""
|
||||||
msgid "Delete work"
|
msgid "Delete work"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/widgets/poe_list.rs:56
|
#: src/widgets/poe_list.rs:41
|
||||||
msgid "No persons or ensembles found."
|
msgid "No persons or ensembles found."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
|
||||||
|
|
@ -106,19 +106,17 @@ impl RecordingSelectorPersonScreen {
|
||||||
|
|
||||||
header.set_title(Some(&person.name_fl()));
|
header.set_title(Some(&person.name_fl()));
|
||||||
|
|
||||||
let work_list = List::new(
|
let work_list = List::new(&gettext("No works found."));
|
||||||
|work: &WorkDescription| {
|
|
||||||
let label = gtk::Label::new(Some(&work.title));
|
work_list.set_make_widget(|work: &WorkDescription| {
|
||||||
label.set_halign(gtk::Align::Start);
|
let label = gtk::Label::new(Some(&work.title));
|
||||||
label.set_margin_start(6);
|
label.set_halign(gtk::Align::Start);
|
||||||
label.set_margin_end(6);
|
label.set_margin_start(6);
|
||||||
label.set_margin_top(6);
|
label.set_margin_end(6);
|
||||||
label.set_margin_bottom(6);
|
label.set_margin_top(6);
|
||||||
label.upcast()
|
label.set_margin_bottom(6);
|
||||||
},
|
label.upcast()
|
||||||
|_| true,
|
});
|
||||||
&gettext("No works found."),
|
|
||||||
);
|
|
||||||
|
|
||||||
stack.add_named(&work_list.widget, "content");
|
stack.add_named(&work_list.widget, "content");
|
||||||
|
|
||||||
|
|
@ -204,28 +202,26 @@ impl RecordingSelectorWorkScreen {
|
||||||
header.set_title(Some(&work.title));
|
header.set_title(Some(&work.title));
|
||||||
header.set_subtitle(Some(&work.composer.name_fl()));
|
header.set_subtitle(Some(&work.composer.name_fl()));
|
||||||
|
|
||||||
let recording_list = List::new(
|
let recording_list = List::new(&gettext("No recordings found."));
|
||||||
|recording: &RecordingDescription| {
|
|
||||||
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
|
||||||
|
|
||||||
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
recording_list.set_make_widget(|recording: &RecordingDescription| {
|
||||||
work_label.set_halign(gtk::Align::Start);
|
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
||||||
|
|
||||||
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||||
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
work_label.set_halign(gtk::Align::Start);
|
||||||
performers_label.set_opacity(0.5);
|
|
||||||
performers_label.set_halign(gtk::Align::Start);
|
|
||||||
|
|
||||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
||||||
vbox.set_border_width(6);
|
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||||
vbox.add(&work_label);
|
performers_label.set_opacity(0.5);
|
||||||
vbox.add(&performers_label);
|
performers_label.set_halign(gtk::Align::Start);
|
||||||
|
|
||||||
vbox.upcast()
|
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||||
},
|
vbox.set_border_width(6);
|
||||||
|_| true,
|
vbox.add(&work_label);
|
||||||
&gettext("No recordings found."),
|
vbox.add(&performers_label);
|
||||||
);
|
|
||||||
|
vbox.upcast()
|
||||||
|
});
|
||||||
|
|
||||||
stack.add_named(&recording_list.widget, "content");
|
stack.add_named(&recording_list.widget, "content");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,18 +9,32 @@ use gtk_macros::get_widget;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
/// A dialog for editing a set of tracks.
|
||||||
|
// TODO: Disable buttons if no track is selected.
|
||||||
pub struct TracksEditor {
|
pub struct TracksEditor {
|
||||||
|
backend: Rc<Backend>,
|
||||||
window: libhandy::Window,
|
window: libhandy::Window,
|
||||||
|
save_button: gtk::Button,
|
||||||
|
recording_stack: gtk::Stack,
|
||||||
|
work_label: gtk::Label,
|
||||||
|
performers_label: gtk::Label,
|
||||||
|
track_list: Rc<List<TrackDescription>>,
|
||||||
|
recording: RefCell<Option<RecordingDescription>>,
|
||||||
|
tracks: RefCell<Vec<TrackDescription>>,
|
||||||
|
callback: RefCell<Option<Box<dyn Fn() -> ()>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TracksEditor {
|
impl TracksEditor {
|
||||||
pub fn new<F: Fn() -> () + 'static, P: IsA<gtk::Window>>(
|
/// Create a new track editor an optionally initialize it with a recording and a list of
|
||||||
|
/// tracks.
|
||||||
|
pub fn new<P: IsA<gtk::Window>>(
|
||||||
backend: Rc<Backend>,
|
backend: Rc<Backend>,
|
||||||
parent: &P,
|
parent: &P,
|
||||||
recording: Option<RecordingDescription>,
|
recording: Option<RecordingDescription>,
|
||||||
tracks: Vec<TrackDescription>,
|
tracks: Vec<TrackDescription>,
|
||||||
callback: F,
|
) -> Rc<Self> {
|
||||||
) -> Self {
|
// UI setup
|
||||||
|
|
||||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/tracks_editor.ui");
|
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/tracks_editor.ui");
|
||||||
|
|
||||||
get_widget!(builder, libhandy::Window, window);
|
get_widget!(builder, libhandy::Window, window);
|
||||||
|
|
@ -43,120 +57,82 @@ impl TracksEditor {
|
||||||
window.close();
|
window.close();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let recording = Rc::new(RefCell::new(recording));
|
let track_list = List::new(&gettext("Add some tracks."));
|
||||||
let tracks = Rc::new(RefCell::new(tracks));
|
scroll.add(&track_list.widget);
|
||||||
|
|
||||||
let track_list = List::new(
|
let this = Rc::new(Self {
|
||||||
clone!(@strong recording => move |track: &TrackDescription| {
|
backend,
|
||||||
let mut title_parts = Vec::<String>::new();
|
window,
|
||||||
for part in &track.work_parts {
|
save_button,
|
||||||
if let Some(recording) = &*recording.borrow() {
|
recording_stack,
|
||||||
title_parts.push(recording.work.parts[*part].title.clone());
|
work_label,
|
||||||
|
performers_label,
|
||||||
|
track_list,
|
||||||
|
recording: RefCell::new(recording),
|
||||||
|
tracks: RefCell::new(tracks),
|
||||||
|
callback: RefCell::new(None),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Signals and callbacks
|
||||||
|
|
||||||
|
this.save_button
|
||||||
|
.connect_clicked(clone!(@strong this => move |_| {
|
||||||
|
let context = glib::MainContext::default();
|
||||||
|
let this = this.clone();
|
||||||
|
context.spawn_local(async move {
|
||||||
|
this.backend.update_tracks(
|
||||||
|
this.recording.borrow().as_ref().unwrap().id,
|
||||||
|
this.tracks.borrow().clone(),
|
||||||
|
).await.unwrap();
|
||||||
|
|
||||||
|
if let Some(callback) = &*this.callback.borrow() {
|
||||||
|
callback();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let title = if title_parts.is_empty() {
|
this.window.close();
|
||||||
gettext("Unknown")
|
});
|
||||||
} else {
|
|
||||||
title_parts.join(", ")
|
|
||||||
};
|
|
||||||
|
|
||||||
let title_label = gtk::Label::new(Some(&title));
|
}));
|
||||||
title_label.set_ellipsize(pango::EllipsizeMode::End);
|
|
||||||
title_label.set_halign(gtk::Align::Start);
|
|
||||||
|
|
||||||
let file_name_label = gtk::Label::new(Some(&track.file_name));
|
recording_button.connect_clicked(clone!(@strong this => move |_| {
|
||||||
file_name_label.set_ellipsize(pango::EllipsizeMode::End);
|
|
||||||
file_name_label.set_opacity(0.5);
|
|
||||||
file_name_label.set_halign(gtk::Align::Start);
|
|
||||||
|
|
||||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
|
||||||
vbox.set_border_width(6);
|
|
||||||
vbox.add(&title_label);
|
|
||||||
vbox.add(&file_name_label);
|
|
||||||
|
|
||||||
vbox.upcast()
|
|
||||||
}),
|
|
||||||
|_| true,
|
|
||||||
&gettext("Add some tracks."),
|
|
||||||
);
|
|
||||||
|
|
||||||
let autofill_parts = Rc::new(clone!(@strong recording, @strong tracks, @strong track_list => move || {
|
|
||||||
if let Some(recording) = &*recording.borrow() {
|
|
||||||
let mut tracks = tracks.borrow_mut();
|
|
||||||
for (index, _) in recording.work.parts.iter().enumerate() {
|
|
||||||
if let Some(mut track) = tracks.get_mut(index) {
|
|
||||||
track.work_parts = vec!(index);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
track_list.show_items(tracks.clone());
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
recording_button.connect_clicked(clone!(
|
|
||||||
@strong backend,
|
|
||||||
@strong window,
|
|
||||||
@strong save_button,
|
|
||||||
@strong work_label,
|
|
||||||
@strong performers_label,
|
|
||||||
@strong recording_stack,
|
|
||||||
@strong recording,
|
|
||||||
@strong autofill_parts => move |_| {
|
|
||||||
RecordingSelector::new(
|
RecordingSelector::new(
|
||||||
backend.clone(),
|
this.backend.clone(),
|
||||||
&window,
|
&this.window,
|
||||||
clone!(
|
clone!(@strong this => move |recording| {
|
||||||
@strong save_button,
|
this.recording_selected(&recording);
|
||||||
@strong work_label,
|
this.recording.replace(Some(recording));
|
||||||
@strong performers_label,
|
}),
|
||||||
@strong recording_stack,
|
).show();
|
||||||
@strong recording,
|
|
||||||
@strong autofill_parts => move |r| {
|
|
||||||
work_label.set_text(&r.work.get_title());
|
|
||||||
performers_label.set_text(&r.get_performers());
|
|
||||||
recording_stack.set_visible_child_name("selected");
|
|
||||||
recording.replace(Some(r));
|
|
||||||
save_button.set_sensitive(true);
|
|
||||||
autofill_parts();
|
|
||||||
}
|
|
||||||
)).show();
|
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
let callback = Rc::new(callback);
|
this.track_list
|
||||||
save_button.connect_clicked(clone!(@strong window, @strong backend, @strong recording, @strong tracks, @strong callback => move |_| {
|
.set_make_widget(clone!(@strong this => move |track| {
|
||||||
let context = glib::MainContext::default();
|
this.build_track_row(track)
|
||||||
let window = window.clone();
|
}));
|
||||||
let backend = backend.clone();
|
|
||||||
let recording = recording.clone();
|
|
||||||
let tracks = tracks.clone();
|
|
||||||
let callback = callback.clone();
|
|
||||||
context.spawn_local(async move {
|
|
||||||
backend.update_tracks(recording.borrow().as_ref().unwrap().id, tracks.borrow().clone()).await.unwrap();
|
|
||||||
callback();
|
|
||||||
window.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
}));
|
add_track_button.connect_clicked(clone!(@strong this => move |_| {
|
||||||
|
let music_library_path = this.backend.get_music_library_path().unwrap();
|
||||||
|
|
||||||
add_track_button.connect_clicked(clone!(@strong window, @strong tracks, @strong track_list, @strong autofill_parts => move |_| {
|
let dialog = gtk::FileChooserNative::new(
|
||||||
let music_library_path = backend.get_music_library_path().unwrap();
|
Some(&gettext("Select audio files")),
|
||||||
|
Some(&this.window),
|
||||||
|
gtk::FileChooserAction::Open,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
let dialog = gtk::FileChooserNative::new(Some(&gettext("Select audio files")), Some(&window), gtk::FileChooserAction::Open, None, None);
|
|
||||||
dialog.set_select_multiple(true);
|
dialog.set_select_multiple(true);
|
||||||
dialog.set_current_folder(&music_library_path);
|
dialog.set_current_folder(&music_library_path);
|
||||||
|
|
||||||
if let gtk::ResponseType::Accept = dialog.run() {
|
if let gtk::ResponseType::Accept = dialog.run() {
|
||||||
let mut index = match track_list.get_selected_index() {
|
let mut index = match this.track_list.get_selected_index() {
|
||||||
Some(index) => index + 1,
|
Some(index) => index + 1,
|
||||||
None => tracks.borrow().len(),
|
None => this.tracks.borrow().len(),
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut tracks = tracks.borrow_mut();
|
let mut tracks = this.tracks.borrow_mut();
|
||||||
for file_name in dialog.get_filenames() {
|
for file_name in dialog.get_filenames() {
|
||||||
let file_name = file_name.strip_prefix(&music_library_path).unwrap();
|
let file_name = file_name.strip_prefix(&music_library_path).unwrap();
|
||||||
tracks.insert(index, TrackDescription {
|
tracks.insert(index, TrackDescription {
|
||||||
|
|
@ -166,75 +142,143 @@ impl TracksEditor {
|
||||||
index += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
track_list.show_items(tracks.borrow().clone());
|
this.track_list.show_items(this.tracks.borrow().clone());
|
||||||
autofill_parts();
|
this.autofill_parts();
|
||||||
track_list.select_index(index);
|
this.track_list.select_index(index);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
remove_track_button.connect_clicked(
|
remove_track_button.connect_clicked(clone!(@strong this => move |_| {
|
||||||
clone!(@strong tracks, @strong track_list => move |_| {
|
match this.track_list.get_selected_index() {
|
||||||
match track_list.get_selected_index() {
|
Some(index) => {
|
||||||
Some(index) => {
|
let mut tracks = this.tracks.borrow_mut();
|
||||||
tracks.borrow_mut().remove(index);
|
tracks.remove(index);
|
||||||
track_list.show_items(tracks.borrow().clone());
|
this.track_list.show_items(tracks.clone());
|
||||||
track_list.select_index(index);
|
this.track_list.select_index(index);
|
||||||
}
|
|
||||||
None => (),
|
|
||||||
}
|
}
|
||||||
}),
|
None => (),
|
||||||
);
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
move_track_up_button.connect_clicked(
|
move_track_up_button.connect_clicked(clone!(@strong this => move |_| {
|
||||||
clone!(@strong tracks, @strong track_list => move |_| {
|
match this.track_list.get_selected_index() {
|
||||||
match track_list.get_selected_index() {
|
Some(index) => {
|
||||||
Some(index) => {
|
if index > 0 {
|
||||||
if index > 0 {
|
let mut tracks = this.tracks.borrow_mut();
|
||||||
tracks.borrow_mut().swap(index - 1, index);
|
tracks.swap(index - 1, index);
|
||||||
track_list.show_items(tracks.borrow().clone());
|
this.track_list.show_items(tracks.clone());
|
||||||
track_list.select_index(index - 1);
|
this.track_list.select_index(index - 1);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => (),
|
|
||||||
}
|
}
|
||||||
}),
|
None => (),
|
||||||
);
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
move_track_down_button.connect_clicked(
|
move_track_down_button.connect_clicked(clone!(@strong this => move |_| {
|
||||||
clone!(@strong tracks, @strong track_list => move |_| {
|
match this.track_list.get_selected_index() {
|
||||||
match track_list.get_selected_index() {
|
Some(index) => {
|
||||||
Some(index) => {
|
let mut tracks = this.tracks.borrow_mut();
|
||||||
if index < tracks.borrow().len() - 1 {
|
if index < tracks.len() - 1 {
|
||||||
tracks.borrow_mut().swap(index, index + 1);
|
tracks.swap(index, index + 1);
|
||||||
track_list.show_items(tracks.borrow().clone());
|
this.track_list.show_items(tracks.clone());
|
||||||
track_list.select_index(index + 1);
|
this.track_list.select_index(index + 1);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => (),
|
|
||||||
}
|
}
|
||||||
}),
|
None => (),
|
||||||
);
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
edit_track_button.connect_clicked(clone!(@strong window, @strong tracks, @strong track_list, @strong recording => move |_| {
|
edit_track_button.connect_clicked(clone!(@strong this => move |_| {
|
||||||
if let Some(index) = track_list.get_selected_index() {
|
if let Some(index) = this.track_list.get_selected_index() {
|
||||||
if let Some(recording) = &*recording.borrow() {
|
if let Some(recording) = &*this.recording.borrow() {
|
||||||
TrackEditor::new(&window, tracks.borrow()[index].clone(), recording.work.clone(), clone!(@strong tracks, @strong track_list => move |track| {
|
TrackEditor::new(&this.window, this.tracks.borrow()[index].clone(), recording.work.clone(), clone!(@strong this => move |track| {
|
||||||
let mut tracks = tracks.borrow_mut();
|
let mut tracks = this.tracks.borrow_mut();
|
||||||
tracks[index] = track;
|
tracks[index] = track;
|
||||||
track_list.show_items(tracks.clone());
|
this.track_list.show_items(tracks.clone());
|
||||||
track_list.select_index(index);
|
this.track_list.select_index(index);
|
||||||
})).show();
|
})).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
scroll.add(&track_list.widget);
|
// Initialization
|
||||||
|
|
||||||
Self { window }
|
if let Some(recording) = &*this.recording.borrow() {
|
||||||
|
this.recording_selected(recording);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.track_list.show_items(this.tracks.borrow().clone());
|
||||||
|
|
||||||
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a callback to be called when the tracks are saved.
|
||||||
|
pub fn set_callback<F: Fn() -> () + 'static>(&self, cb: F) {
|
||||||
|
self.callback.replace(Some(Box::new(cb)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open the track editor.
|
||||||
pub fn show(&self) {
|
pub fn show(&self) {
|
||||||
self.window.show();
|
self.window.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a widget representing a track.
|
||||||
|
fn build_track_row(&self, track: &TrackDescription) -> gtk::Widget {
|
||||||
|
let mut title_parts = Vec::<String>::new();
|
||||||
|
for part in &track.work_parts {
|
||||||
|
if let Some(recording) = &*self.recording.borrow() {
|
||||||
|
title_parts.push(recording.work.parts[*part].title.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let title = if title_parts.is_empty() {
|
||||||
|
gettext("Unknown")
|
||||||
|
} else {
|
||||||
|
title_parts.join(", ")
|
||||||
|
};
|
||||||
|
|
||||||
|
let title_label = gtk::Label::new(Some(&title));
|
||||||
|
title_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||||
|
title_label.set_halign(gtk::Align::Start);
|
||||||
|
|
||||||
|
let file_name_label = gtk::Label::new(Some(&track.file_name));
|
||||||
|
file_name_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||||
|
file_name_label.set_opacity(0.5);
|
||||||
|
file_name_label.set_halign(gtk::Align::Start);
|
||||||
|
|
||||||
|
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||||
|
vbox.set_border_width(6);
|
||||||
|
vbox.add(&title_label);
|
||||||
|
vbox.add(&file_name_label);
|
||||||
|
|
||||||
|
vbox.upcast()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set everything up after selecting a recording.
|
||||||
|
fn recording_selected(&self, recording: &RecordingDescription) {
|
||||||
|
self.work_label.set_text(&recording.work.get_title());
|
||||||
|
self.performers_label.set_text(&recording.get_performers());
|
||||||
|
self.recording_stack.set_visible_child_name("selected");
|
||||||
|
self.save_button.set_sensitive(true);
|
||||||
|
self.autofill_parts();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Automatically try to put work part information from the selected recording into the
|
||||||
|
/// selected tracks.
|
||||||
|
fn autofill_parts(&self) {
|
||||||
|
if let Some(recording) = &*self.recording.borrow() {
|
||||||
|
let mut tracks = self.tracks.borrow_mut();
|
||||||
|
|
||||||
|
for (index, _) in recording.work.parts.iter().enumerate() {
|
||||||
|
if let Some(mut track) = tracks.get_mut(index) {
|
||||||
|
track.work_parts = vec![index];
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.track_list.show_items(tracks.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,31 +50,33 @@ impl EnsembleScreen {
|
||||||
|
|
||||||
menu_button.set_menu_model(Some(&menu));
|
menu_button.set_menu_model(Some(&menu));
|
||||||
|
|
||||||
let recording_list = List::new(
|
let recording_list = List::new(&gettext("No recordings found."));
|
||||||
|recording: &RecordingDescription| {
|
|
||||||
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
|
||||||
|
|
||||||
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
recording_list.set_make_widget(|recording: &RecordingDescription| {
|
||||||
work_label.set_halign(gtk::Align::Start);
|
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
||||||
|
|
||||||
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||||
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
work_label.set_halign(gtk::Align::Start);
|
||||||
performers_label.set_opacity(0.5);
|
|
||||||
performers_label.set_halign(gtk::Align::Start);
|
|
||||||
|
|
||||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
||||||
vbox.set_border_width(6);
|
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||||
vbox.add(&work_label);
|
performers_label.set_opacity(0.5);
|
||||||
vbox.add(&performers_label);
|
performers_label.set_halign(gtk::Align::Start);
|
||||||
|
|
||||||
vbox.upcast()
|
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||||
},
|
vbox.set_border_width(6);
|
||||||
|
vbox.add(&work_label);
|
||||||
|
vbox.add(&performers_label);
|
||||||
|
|
||||||
|
vbox.upcast()
|
||||||
|
});
|
||||||
|
|
||||||
|
recording_list.set_filter(
|
||||||
clone!(@strong search_entry => move |recording: &RecordingDescription| {
|
clone!(@strong search_entry => move |recording: &RecordingDescription| {
|
||||||
let search = search_entry.get_text().to_string().to_lowercase();
|
let search = search_entry.get_text().to_string().to_lowercase();
|
||||||
let text = recording.work.get_title() + &recording.get_performers();
|
let text = recording.work.get_title() + &recording.get_performers();
|
||||||
search.is_empty() || text.contains(&search)
|
search.is_empty() || text.contains(&search)
|
||||||
}),
|
}),
|
||||||
&gettext("No recordings found."),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
recording_frame.add(&recording_list.widget.clone());
|
recording_frame.add(&recording_list.widget.clone());
|
||||||
|
|
|
||||||
|
|
@ -54,49 +54,53 @@ impl PersonScreen {
|
||||||
|
|
||||||
menu_button.set_menu_model(Some(&menu));
|
menu_button.set_menu_model(Some(&menu));
|
||||||
|
|
||||||
let work_list = List::new(
|
let work_list = List::new(&gettext("No works found."));
|
||||||
|work: &WorkDescription| {
|
|
||||||
let label = gtk::Label::new(Some(&work.title));
|
work_list.set_make_widget(|work: &WorkDescription| {
|
||||||
label.set_halign(gtk::Align::Start);
|
let label = gtk::Label::new(Some(&work.title));
|
||||||
label.set_margin_start(6);
|
label.set_halign(gtk::Align::Start);
|
||||||
label.set_margin_end(6);
|
label.set_margin_start(6);
|
||||||
label.set_margin_top(6);
|
label.set_margin_end(6);
|
||||||
label.set_margin_bottom(6);
|
label.set_margin_top(6);
|
||||||
label.upcast()
|
label.set_margin_bottom(6);
|
||||||
},
|
label.upcast()
|
||||||
|
});
|
||||||
|
|
||||||
|
work_list.set_filter(
|
||||||
clone!(@strong search_entry => move |work: &WorkDescription| {
|
clone!(@strong search_entry => move |work: &WorkDescription| {
|
||||||
let search = search_entry.get_text().to_string().to_lowercase();
|
let search = search_entry.get_text().to_string().to_lowercase();
|
||||||
let title = work.title.to_lowercase();
|
let title = work.title.to_lowercase();
|
||||||
search.is_empty() || title.contains(&search)
|
search.is_empty() || title.contains(&search)
|
||||||
}),
|
}),
|
||||||
&gettext("No works found."),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let recording_list = List::new(
|
let recording_list = List::new(&gettext("No recordings found."));
|
||||||
|recording: &RecordingDescription| {
|
|
||||||
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
|
||||||
|
|
||||||
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
recording_list.set_make_widget(|recording: &RecordingDescription| {
|
||||||
work_label.set_halign(gtk::Align::Start);
|
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
||||||
|
|
||||||
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||||
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
work_label.set_halign(gtk::Align::Start);
|
||||||
performers_label.set_opacity(0.5);
|
|
||||||
performers_label.set_halign(gtk::Align::Start);
|
|
||||||
|
|
||||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
||||||
vbox.set_border_width(6);
|
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||||
vbox.add(&work_label);
|
performers_label.set_opacity(0.5);
|
||||||
vbox.add(&performers_label);
|
performers_label.set_halign(gtk::Align::Start);
|
||||||
|
|
||||||
vbox.upcast()
|
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||||
},
|
vbox.set_border_width(6);
|
||||||
|
vbox.add(&work_label);
|
||||||
|
vbox.add(&performers_label);
|
||||||
|
|
||||||
|
vbox.upcast()
|
||||||
|
});
|
||||||
|
|
||||||
|
recording_list.set_filter(
|
||||||
clone!(@strong search_entry => move |recording: &RecordingDescription| {
|
clone!(@strong search_entry => move |recording: &RecordingDescription| {
|
||||||
let search = search_entry.get_text().to_string().to_lowercase();
|
let search = search_entry.get_text().to_string().to_lowercase();
|
||||||
let text = recording.work.get_title() + &recording.get_performers();
|
let text = recording.work.get_title() + &recording.get_performers();
|
||||||
search.is_empty() || text.contains(&search)
|
search.is_empty() || text.contains(&search)
|
||||||
}),
|
}),
|
||||||
&gettext("No recordings found."),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
work_frame.add(&work_list.widget);
|
work_frame.add(&work_list.widget);
|
||||||
|
|
|
||||||
|
|
@ -123,48 +123,46 @@ impl PlayerScreen {
|
||||||
|
|
||||||
let current_item = Rc::new(Cell::<usize>::new(0));
|
let current_item = Rc::new(Cell::<usize>::new(0));
|
||||||
let current_track = Rc::new(Cell::<usize>::new(0));
|
let current_track = Rc::new(Cell::<usize>::new(0));
|
||||||
let list = List::new(
|
let list = List::new("");
|
||||||
clone!(
|
|
||||||
@strong current_item,
|
|
||||||
@strong current_track
|
|
||||||
=> move |element: &PlaylistElement| {
|
|
||||||
let title_label = gtk::Label::new(Some(&element.title));
|
|
||||||
title_label.set_ellipsize(pango::EllipsizeMode::End);
|
|
||||||
title_label.set_halign(gtk::Align::Start);
|
|
||||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
|
||||||
vbox.add(&title_label);
|
|
||||||
if let Some(subtitle) = &element.subtitle {
|
|
||||||
let subtitle_label = gtk::Label::new(Some(&subtitle));
|
|
||||||
subtitle_label.set_ellipsize(pango::EllipsizeMode::End);
|
|
||||||
subtitle_label.set_halign(gtk::Align::Start);
|
|
||||||
subtitle_label.set_opacity(0.5);
|
|
||||||
vbox.add(&subtitle_label);
|
|
||||||
}
|
|
||||||
|
|
||||||
let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 6);
|
list.set_make_widget(clone!(
|
||||||
hbox.set_border_width(6);
|
@strong current_item,
|
||||||
|
@strong current_track
|
||||||
if element.playable {
|
=> move |element: &PlaylistElement| {
|
||||||
let image = gtk::Image::new();
|
let title_label = gtk::Label::new(Some(&element.title));
|
||||||
|
title_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||||
if element.item == current_item.get() && element.track == current_track.get() {
|
title_label.set_halign(gtk::Align::Start);
|
||||||
image.set_from_icon_name(
|
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||||
Some("media-playback-start-symbolic"),
|
vbox.add(&title_label);
|
||||||
gtk::IconSize::Button,
|
if let Some(subtitle) = &element.subtitle {
|
||||||
);
|
let subtitle_label = gtk::Label::new(Some(&subtitle));
|
||||||
}
|
subtitle_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||||
|
subtitle_label.set_halign(gtk::Align::Start);
|
||||||
hbox.add(&image);
|
subtitle_label.set_opacity(0.5);
|
||||||
} else if element.item > 0 {
|
vbox.add(&subtitle_label);
|
||||||
hbox.set_margin_top(18);
|
|
||||||
}
|
|
||||||
hbox.add(&vbox);
|
|
||||||
hbox.upcast()
|
|
||||||
}
|
}
|
||||||
),
|
|
||||||
|_| true,
|
let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 6);
|
||||||
"",
|
hbox.set_border_width(6);
|
||||||
);
|
|
||||||
|
if element.playable {
|
||||||
|
let image = gtk::Image::new();
|
||||||
|
|
||||||
|
if element.item == current_item.get() && element.track == current_track.get() {
|
||||||
|
image.set_from_icon_name(
|
||||||
|
Some("media-playback-start-symbolic"),
|
||||||
|
gtk::IconSize::Button,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
hbox.add(&image);
|
||||||
|
} else if element.item > 0 {
|
||||||
|
hbox.set_margin_top(18);
|
||||||
|
}
|
||||||
|
hbox.add(&vbox);
|
||||||
|
hbox.upcast()
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
list.set_selected(clone!(@strong player => move |element| {
|
list.set_selected(clone!(@strong player => move |element| {
|
||||||
if let Some(player) = &*player.borrow() {
|
if let Some(player) = &*player.borrow() {
|
||||||
|
|
|
||||||
|
|
@ -45,14 +45,30 @@ impl RecordingScreen {
|
||||||
Some(&glib::Variant::from(recording.id)),
|
Some(&glib::Variant::from(recording.id)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let edit_tracks_menu_item = gio::MenuItem::new(Some(&gettext("Edit tracks")), None);
|
||||||
|
edit_tracks_menu_item.set_action_and_target_value(
|
||||||
|
Some("win.edit-tracks"),
|
||||||
|
Some(&glib::Variant::from(recording.id)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let delete_tracks_menu_item = gio::MenuItem::new(Some(&gettext("Delete tracks")), None);
|
||||||
|
delete_tracks_menu_item.set_action_and_target_value(
|
||||||
|
Some("win.delete-tracks"),
|
||||||
|
Some(&glib::Variant::from(recording.id)),
|
||||||
|
);
|
||||||
|
|
||||||
let menu = gio::Menu::new();
|
let menu = gio::Menu::new();
|
||||||
menu.append_item(&edit_menu_item);
|
menu.append_item(&edit_menu_item);
|
||||||
menu.append_item(&delete_menu_item);
|
menu.append_item(&delete_menu_item);
|
||||||
|
menu.append_item(&edit_tracks_menu_item);
|
||||||
|
menu.append_item(&delete_tracks_menu_item);
|
||||||
|
|
||||||
menu_button.set_menu_model(Some(&menu));
|
menu_button.set_menu_model(Some(&menu));
|
||||||
|
|
||||||
let recording = Rc::new(recording);
|
let recording = Rc::new(recording);
|
||||||
let list = List::new(
|
let list = List::new(&gettext("No tracks found."));
|
||||||
|
|
||||||
|
list.set_make_widget(
|
||||||
clone!(@strong recording => move |track: &TrackDescription| {
|
clone!(@strong recording => move |track: &TrackDescription| {
|
||||||
let mut title_parts = Vec::<String>::new();
|
let mut title_parts = Vec::<String>::new();
|
||||||
for part in &track.work_parts {
|
for part in &track.work_parts {
|
||||||
|
|
@ -81,8 +97,6 @@ impl RecordingScreen {
|
||||||
|
|
||||||
vbox.upcast()
|
vbox.upcast()
|
||||||
}),
|
}),
|
||||||
|_| true,
|
|
||||||
&gettext("No tracks found."),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
frame.add(&list.widget);
|
frame.add(&list.widget);
|
||||||
|
|
|
||||||
|
|
@ -51,32 +51,32 @@ impl WorkScreen {
|
||||||
|
|
||||||
menu_button.set_menu_model(Some(&menu));
|
menu_button.set_menu_model(Some(&menu));
|
||||||
|
|
||||||
let recording_list = List::new(
|
let recording_list = List::new(&gettext("No recordings found."));
|
||||||
|recording: &RecordingDescription| {
|
|
||||||
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
|
||||||
|
|
||||||
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
recording_list.set_make_widget(|recording: &RecordingDescription| {
|
||||||
work_label.set_halign(gtk::Align::Start);
|
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
||||||
|
|
||||||
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||||
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
work_label.set_halign(gtk::Align::Start);
|
||||||
performers_label.set_opacity(0.5);
|
|
||||||
performers_label.set_halign(gtk::Align::Start);
|
|
||||||
|
|
||||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
||||||
vbox.set_border_width(6);
|
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||||
vbox.add(&work_label);
|
performers_label.set_opacity(0.5);
|
||||||
vbox.add(&performers_label);
|
performers_label.set_halign(gtk::Align::Start);
|
||||||
|
|
||||||
vbox.upcast()
|
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||||
},
|
vbox.set_border_width(6);
|
||||||
clone!(@strong search_entry => move |recording: &RecordingDescription| {
|
vbox.add(&work_label);
|
||||||
let search = search_entry.get_text().to_string().to_lowercase();
|
vbox.add(&performers_label);
|
||||||
let text = recording.work.get_title().to_lowercase() + &recording.get_performers().to_lowercase();
|
|
||||||
search.is_empty() || text.contains(&search)
|
vbox.upcast()
|
||||||
}),
|
});
|
||||||
&gettext("No recordings found."),
|
|
||||||
);
|
recording_list.set_filter(clone!(@strong search_entry => move |recording: &RecordingDescription| {
|
||||||
|
let search = search_entry.get_text().to_string().to_lowercase();
|
||||||
|
let text = recording.work.get_title().to_lowercase() + &recording.get_performers().to_lowercase();
|
||||||
|
search.is_empty() || text.contains(&search)
|
||||||
|
}),);
|
||||||
|
|
||||||
recording_frame.add(&recording_list.widget);
|
recording_frame.add(&recording_list.widget);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,16 @@ where
|
||||||
{
|
{
|
||||||
pub widget: gtk::ListBox,
|
pub widget: gtk::ListBox,
|
||||||
items: RefCell<Vec<T>>,
|
items: RefCell<Vec<T>>,
|
||||||
make_widget: Box<dyn Fn(&T) -> gtk::Widget + 'static>,
|
make_widget: RefCell<Option<Box<dyn Fn(&T) -> gtk::Widget>>>,
|
||||||
selected: RefCell<Option<Box<dyn Fn(&T) -> () + 'static>>>,
|
filter: RefCell<Option<Box<dyn Fn(&T) -> bool>>>,
|
||||||
|
selected: RefCell<Option<Box<dyn Fn(&T) -> ()>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> List<T>
|
impl<T> List<T>
|
||||||
where
|
where
|
||||||
T: 'static,
|
T: 'static,
|
||||||
{
|
{
|
||||||
pub fn new<M, F>(make_widget: M, filter: F, placeholder_text: &str) -> Rc<Self>
|
pub fn new(placeholder_text: &str) -> Rc<Self> {
|
||||||
where
|
|
||||||
M: Fn(&T) -> gtk::Widget + 'static,
|
|
||||||
F: Fn(&T) -> bool + 'static,
|
|
||||||
{
|
|
||||||
let placeholder_label = gtk::Label::new(Some(placeholder_text));
|
let placeholder_label = gtk::Label::new(Some(placeholder_text));
|
||||||
placeholder_label.set_margin_top(6);
|
placeholder_label.set_margin_top(6);
|
||||||
placeholder_label.set_margin_bottom(6);
|
placeholder_label.set_margin_bottom(6);
|
||||||
|
|
@ -35,38 +32,46 @@ where
|
||||||
widget.set_placeholder(Some(&placeholder_label));
|
widget.set_placeholder(Some(&placeholder_label));
|
||||||
widget.show();
|
widget.show();
|
||||||
|
|
||||||
let result = Rc::new(Self {
|
let this = Rc::new(Self {
|
||||||
widget,
|
widget,
|
||||||
items: RefCell::new(Vec::new()),
|
items: RefCell::new(Vec::new()),
|
||||||
make_widget: Box::new(make_widget),
|
make_widget: RefCell::new(None),
|
||||||
|
filter: RefCell::new(None),
|
||||||
selected: RefCell::new(None),
|
selected: RefCell::new(None),
|
||||||
});
|
});
|
||||||
|
|
||||||
result
|
this.widget
|
||||||
.widget
|
.connect_row_activated(clone!(@strong this => move |_, row| {
|
||||||
.connect_row_activated(clone!(@strong result => move |_, row| {
|
if let Some(selected) = &*this.selected.borrow() {
|
||||||
if let Some(selected) = &*result.selected.borrow() {
|
|
||||||
let row = row.get_child().unwrap().downcast::<SelectorRow>().unwrap();
|
let row = row.get_child().unwrap().downcast::<SelectorRow>().unwrap();
|
||||||
let index: usize = row.get_index().try_into().unwrap();
|
let index: usize = row.get_index().try_into().unwrap();
|
||||||
selected(&result.items.borrow()[index]);
|
selected(&this.items.borrow()[index]);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
result
|
this.widget
|
||||||
.widget
|
.set_filter_func(Some(Box::new(clone!(@strong this => move |row| {
|
||||||
.set_filter_func(Some(Box::new(clone!(@strong result => move |row| {
|
if let Some(filter) = &*this.filter.borrow() {
|
||||||
let row = row.get_child().unwrap().downcast::<SelectorRow>().unwrap();
|
let row = row.get_child().unwrap().downcast::<SelectorRow>().unwrap();
|
||||||
let index: usize = row.get_index().try_into().unwrap();
|
let index: usize = row.get_index().try_into().unwrap();
|
||||||
filter(&result.items.borrow()[index])
|
filter(&this.items.borrow()[index])
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
}))));
|
}))));
|
||||||
|
|
||||||
result
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_selected<S>(&self, selected: S)
|
pub fn set_make_widget<F: Fn(&T) -> gtk::Widget + 'static>(&self, make_widget: F) {
|
||||||
where
|
self.make_widget.replace(Some(Box::new(make_widget)));
|
||||||
S: Fn(&T) -> () + 'static,
|
}
|
||||||
{
|
|
||||||
|
pub fn set_filter<F: Fn(&T) -> bool + 'static>(&self, filter: F) {
|
||||||
|
self.filter.replace(Some(Box::new(filter)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_selected<S: Fn(&T) -> () + 'static>(&self, selected: S) {
|
||||||
self.selected.replace(Some(Box::new(selected)));
|
self.selected.replace(Some(Box::new(selected)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,10 +114,12 @@ where
|
||||||
self.widget.remove(&child);
|
self.widget.remove(&child);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (index, item) in self.items.borrow().iter().enumerate() {
|
if let Some(make_widget) = &*self.make_widget.borrow() {
|
||||||
let row = SelectorRow::new(index.try_into().unwrap(), &(self.make_widget)(item));
|
for (index, item) in self.items.borrow().iter().enumerate() {
|
||||||
row.show_all();
|
let row = SelectorRow::new(index.try_into().unwrap(), &make_widget(item));
|
||||||
self.widget.insert(&row, -1);
|
row.show_all();
|
||||||
|
self.widget.insert(&row, -1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,23 +23,23 @@ impl PersonList {
|
||||||
get_widget!(builder, gtk::Stack, stack);
|
get_widget!(builder, gtk::Stack, stack);
|
||||||
get_widget!(builder, gtk::ScrolledWindow, scrolled_window);
|
get_widget!(builder, gtk::ScrolledWindow, scrolled_window);
|
||||||
|
|
||||||
let list = List::new(
|
let list = List::new(&gettext("No persons found."));
|
||||||
|person: &Person| {
|
|
||||||
let label = gtk::Label::new(Some(&person.name_lf()));
|
list.set_make_widget(|person: &Person| {
|
||||||
label.set_halign(gtk::Align::Start);
|
let label = gtk::Label::new(Some(&person.name_lf()));
|
||||||
label.set_margin_start(6);
|
label.set_halign(gtk::Align::Start);
|
||||||
label.set_margin_end(6);
|
label.set_margin_start(6);
|
||||||
label.set_margin_top(6);
|
label.set_margin_end(6);
|
||||||
label.set_margin_bottom(6);
|
label.set_margin_top(6);
|
||||||
label.upcast()
|
label.set_margin_bottom(6);
|
||||||
},
|
label.upcast()
|
||||||
clone!(@strong search_entry => move |person: &Person| {
|
});
|
||||||
let search = search_entry.get_text().to_string().to_lowercase();
|
|
||||||
let name = person.name_fl().to_lowercase();
|
list.set_filter(clone!(@strong search_entry => move |person: &Person| {
|
||||||
search.is_empty() || name.contains(&search)
|
let search = search_entry.get_text().to_string().to_lowercase();
|
||||||
}),
|
let name = person.name_fl().to_lowercase();
|
||||||
&gettext("No persons found."),
|
search.is_empty() || name.contains(&search)
|
||||||
);
|
}));
|
||||||
|
|
||||||
scrolled_window.add(&list.widget);
|
scrolled_window.add(&list.widget);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,22 +38,24 @@ impl PoeList {
|
||||||
get_widget!(builder, gtk::Stack, stack);
|
get_widget!(builder, gtk::Stack, stack);
|
||||||
get_widget!(builder, gtk::ScrolledWindow, scrolled_window);
|
get_widget!(builder, gtk::ScrolledWindow, scrolled_window);
|
||||||
|
|
||||||
let list = List::new(
|
let list = List::new(&gettext("No persons or ensembles found."));
|
||||||
|poe: &PersonOrEnsemble| {
|
|
||||||
let label = gtk::Label::new(Some(&poe.get_title()));
|
list.set_make_widget(|poe: &PersonOrEnsemble| {
|
||||||
label.set_halign(gtk::Align::Start);
|
let label = gtk::Label::new(Some(&poe.get_title()));
|
||||||
label.set_margin_start(6);
|
label.set_halign(gtk::Align::Start);
|
||||||
label.set_margin_end(6);
|
label.set_margin_start(6);
|
||||||
label.set_margin_top(6);
|
label.set_margin_end(6);
|
||||||
label.set_margin_bottom(6);
|
label.set_margin_top(6);
|
||||||
label.upcast()
|
label.set_margin_bottom(6);
|
||||||
},
|
label.upcast()
|
||||||
|
});
|
||||||
|
|
||||||
|
list.set_filter(
|
||||||
clone!(@strong search_entry => move |poe: &PersonOrEnsemble| {
|
clone!(@strong search_entry => move |poe: &PersonOrEnsemble| {
|
||||||
let search = search_entry.get_text().to_string().to_lowercase();
|
let search = search_entry.get_text().to_string().to_lowercase();
|
||||||
let title = poe.get_title().to_lowercase();
|
let title = poe.get_title().to_lowercase();
|
||||||
search.is_empty() || title.contains(&search)
|
search.is_empty() || title.contains(&search)
|
||||||
}),
|
}),
|
||||||
&gettext("No persons or ensembles found."),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
scrolled_window.add(&list.widget);
|
scrolled_window.add(&list.widget);
|
||||||
|
|
|
||||||
|
|
@ -85,9 +85,13 @@ impl Window {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
add_button.connect_clicked(clone!(@strong result => move |_| {
|
add_button.connect_clicked(clone!(@strong result => move |_| {
|
||||||
TracksEditor::new(result.backend.clone(), &result.window, None, Vec::new(), clone!(@strong result => move || {
|
let editor = TracksEditor::new(result.backend.clone(), &result.window, None, Vec::new());
|
||||||
|
|
||||||
|
editor.set_callback(clone!(@strong result => move || {
|
||||||
result.reload();
|
result.reload();
|
||||||
})).show();
|
}));
|
||||||
|
|
||||||
|
editor.show();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
result
|
result
|
||||||
|
|
@ -172,9 +176,13 @@ impl Window {
|
||||||
result.window,
|
result.window,
|
||||||
"add-tracks",
|
"add-tracks",
|
||||||
clone!(@strong result => move |_, _| {
|
clone!(@strong result => move |_, _| {
|
||||||
TracksEditor::new(result.backend.clone(), &result.window, None, Vec::new(), clone!(@strong result => move || {
|
let editor = TracksEditor::new(result.backend.clone(), &result.window, None, Vec::new());
|
||||||
|
|
||||||
|
editor.set_callback(clone!(@strong result => move || {
|
||||||
result.reload();
|
result.reload();
|
||||||
})).show();
|
}));
|
||||||
|
|
||||||
|
editor.show();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -306,6 +314,44 @@ impl Window {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
action!(
|
||||||
|
result.window,
|
||||||
|
"edit-tracks",
|
||||||
|
Some(glib::VariantTy::new("x").unwrap()),
|
||||||
|
clone!(@strong result => move |_, id| {
|
||||||
|
let id = id.unwrap().get().unwrap();
|
||||||
|
let result = result.clone();
|
||||||
|
let c = glib::MainContext::default();
|
||||||
|
c.spawn_local(async move {
|
||||||
|
let recording = result.backend.get_recording_description(id).await.unwrap();
|
||||||
|
let tracks = result.backend.get_tracks(id).await.unwrap();
|
||||||
|
|
||||||
|
let editor = TracksEditor::new(result.backend.clone(), &result.window, Some(recording), tracks);
|
||||||
|
|
||||||
|
editor.set_callback(clone!(@strong result => move || {
|
||||||
|
result.reload();
|
||||||
|
}));
|
||||||
|
|
||||||
|
editor.show();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
action!(
|
||||||
|
result.window,
|
||||||
|
"delete-tracks",
|
||||||
|
Some(glib::VariantTy::new("x").unwrap()),
|
||||||
|
clone!(@strong result => move |_, id| {
|
||||||
|
let id = id.unwrap().get().unwrap();
|
||||||
|
let result = result.clone();
|
||||||
|
let c = glib::MainContext::default();
|
||||||
|
c.spawn_local(async move {
|
||||||
|
result.backend.delete_tracks(id).await.unwrap();
|
||||||
|
result.reload();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
let context = glib::MainContext::default();
|
let context = glib::MainContext::default();
|
||||||
let clone = result.clone();
|
let clone = result.clone();
|
||||||
context.spawn_local(async move {
|
context.spawn_local(async move {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue