mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-27 04:07:25 +01:00
Move desktop app to subdirectory
This commit is contained in:
parent
ea3bd35ffd
commit
775f3ffe90
109 changed files with 64 additions and 53 deletions
|
|
@ -1,145 +0,0 @@
|
|||
use super::*;
|
||||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use crate::widgets::*;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libhandy::HeaderBarExt;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct EnsembleScreen {
|
||||
backend: Rc<Backend>,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
recording_list: Rc<List<RecordingDescription>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
impl EnsembleScreen {
|
||||
pub fn new(backend: Rc<Backend>, ensemble: Ensemble) -> Rc<Self> {
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/ensemble_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, libhandy::HeaderBar, header);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::MenuButton, menu_button);
|
||||
get_widget!(builder, gtk::SearchEntry, search_entry);
|
||||
get_widget!(builder, gtk::Stack, stack);
|
||||
get_widget!(builder, gtk::Frame, recording_frame);
|
||||
|
||||
header.set_title(Some(&ensemble.name));
|
||||
|
||||
let edit_menu_item = gio::MenuItem::new(Some(&gettext("Edit ensemble")), None);
|
||||
edit_menu_item.set_action_and_target_value(
|
||||
Some("win.edit-ensemble"),
|
||||
Some(&glib::Variant::from(ensemble.id)),
|
||||
);
|
||||
|
||||
let delete_menu_item = gio::MenuItem::new(Some(&gettext("Delete ensemble")), None);
|
||||
delete_menu_item.set_action_and_target_value(
|
||||
Some("win.delete-ensemble"),
|
||||
Some(&glib::Variant::from(ensemble.id)),
|
||||
);
|
||||
|
||||
let menu = gio::Menu::new();
|
||||
menu.append_item(&edit_menu_item);
|
||||
menu.append_item(&delete_menu_item);
|
||||
|
||||
menu_button.set_menu_model(Some(&menu));
|
||||
|
||||
let recording_list = List::new(&gettext("No recordings found."));
|
||||
|
||||
recording_list.set_make_widget(|recording: &RecordingDescription| {
|
||||
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
||||
|
||||
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
work_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
||||
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
performers_label.set_opacity(0.5);
|
||||
performers_label.set_halign(gtk::Align::Start);
|
||||
|
||||
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| {
|
||||
let search = search_entry.get_text().to_string().to_lowercase();
|
||||
let text = recording.work.get_title() + &recording.get_performers();
|
||||
search.is_empty() || text.contains(&search)
|
||||
}),
|
||||
);
|
||||
|
||||
recording_frame.add(&recording_list.widget.clone());
|
||||
|
||||
let result = Rc::new(Self {
|
||||
backend,
|
||||
widget,
|
||||
stack,
|
||||
recording_list,
|
||||
navigator: RefCell::new(None),
|
||||
});
|
||||
|
||||
search_entry.connect_search_changed(clone!(@strong result => move |_| {
|
||||
result.recording_list.invalidate_filter();
|
||||
}));
|
||||
|
||||
back_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.pop();
|
||||
}
|
||||
}));
|
||||
|
||||
result
|
||||
.recording_list
|
||||
.set_selected(clone!(@strong result => move |recording| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.push(RecordingScreen::new(result.backend.clone(), recording.clone()));
|
||||
}
|
||||
}));
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
context.spawn_local(async move {
|
||||
let recordings = clone
|
||||
.backend
|
||||
.get_recordings_for_ensemble(ensemble.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if recordings.is_empty() {
|
||||
clone.stack.set_visible_child_name("nothing");
|
||||
} else {
|
||||
clone.recording_list.show_items(recordings);
|
||||
clone.stack.set_visible_child_name("content");
|
||||
}
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl NavigatorScreen for EnsembleScreen {
|
||||
fn attach_navigator(&self, navigator: Rc<Navigator>) {
|
||||
self.navigator.replace(Some(navigator));
|
||||
}
|
||||
|
||||
fn get_widget(&self) -> gtk::Widget {
|
||||
self.widget.clone().upcast()
|
||||
}
|
||||
|
||||
fn detach_navigator(&self) {
|
||||
self.navigator.replace(None);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
pub mod ensemble_screen;
|
||||
pub use ensemble_screen::*;
|
||||
|
||||
pub mod person_screen;
|
||||
pub use person_screen::*;
|
||||
|
||||
pub mod player_screen;
|
||||
pub use player_screen::*;
|
||||
|
||||
pub mod work_screen;
|
||||
pub use work_screen::*;
|
||||
|
||||
pub mod recording_screen;
|
||||
pub use recording_screen::*;
|
||||
|
|
@ -1,199 +0,0 @@
|
|||
use super::*;
|
||||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use crate::widgets::*;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libhandy::HeaderBarExt;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct PersonScreen {
|
||||
backend: Rc<Backend>,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
work_list: Rc<List<WorkDescription>>,
|
||||
recording_list: Rc<List<RecordingDescription>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
impl PersonScreen {
|
||||
pub fn new(backend: Rc<Backend>, person: Person) -> Rc<Self> {
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/person_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, libhandy::HeaderBar, header);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::MenuButton, menu_button);
|
||||
get_widget!(builder, gtk::SearchEntry, search_entry);
|
||||
get_widget!(builder, gtk::Stack, stack);
|
||||
get_widget!(builder, gtk::Box, work_box);
|
||||
get_widget!(builder, gtk::Frame, work_frame);
|
||||
get_widget!(builder, gtk::Box, recording_box);
|
||||
get_widget!(builder, gtk::Frame, recording_frame);
|
||||
|
||||
header.set_title(Some(&person.name_fl()));
|
||||
|
||||
let edit_menu_item = gio::MenuItem::new(Some(&gettext("Edit person")), None);
|
||||
edit_menu_item.set_action_and_target_value(
|
||||
Some("win.edit-person"),
|
||||
Some(&glib::Variant::from(person.id)),
|
||||
);
|
||||
|
||||
let delete_menu_item = gio::MenuItem::new(Some(&gettext("Delete person")), None);
|
||||
delete_menu_item.set_action_and_target_value(
|
||||
Some("win.delete-person"),
|
||||
Some(&glib::Variant::from(person.id)),
|
||||
);
|
||||
|
||||
let menu = gio::Menu::new();
|
||||
menu.append_item(&edit_menu_item);
|
||||
menu.append_item(&delete_menu_item);
|
||||
|
||||
menu_button.set_menu_model(Some(&menu));
|
||||
|
||||
let work_list = List::new(&gettext("No works found."));
|
||||
|
||||
work_list.set_make_widget(|work: &WorkDescription| {
|
||||
let label = gtk::Label::new(Some(&work.title));
|
||||
label.set_halign(gtk::Align::Start);
|
||||
label.set_margin_start(6);
|
||||
label.set_margin_end(6);
|
||||
label.set_margin_top(6);
|
||||
label.set_margin_bottom(6);
|
||||
label.upcast()
|
||||
});
|
||||
|
||||
work_list.set_filter(
|
||||
clone!(@strong search_entry => move |work: &WorkDescription| {
|
||||
let search = search_entry.get_text().to_string().to_lowercase();
|
||||
let title = work.title.to_lowercase();
|
||||
search.is_empty() || title.contains(&search)
|
||||
}),
|
||||
);
|
||||
|
||||
let recording_list = List::new(&gettext("No recordings found."));
|
||||
|
||||
recording_list.set_make_widget(|recording: &RecordingDescription| {
|
||||
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
||||
|
||||
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
work_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
||||
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
performers_label.set_opacity(0.5);
|
||||
performers_label.set_halign(gtk::Align::Start);
|
||||
|
||||
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| {
|
||||
let search = search_entry.get_text().to_string().to_lowercase();
|
||||
let text = recording.work.get_title() + &recording.get_performers();
|
||||
search.is_empty() || text.contains(&search)
|
||||
}),
|
||||
);
|
||||
|
||||
work_frame.add(&work_list.widget);
|
||||
recording_frame.add(&recording_list.widget);
|
||||
|
||||
let result = Rc::new(Self {
|
||||
backend,
|
||||
widget,
|
||||
stack,
|
||||
work_list,
|
||||
recording_list,
|
||||
navigator: RefCell::new(None),
|
||||
});
|
||||
|
||||
search_entry.connect_search_changed(clone!(@strong result => move |_| {
|
||||
result.work_list.invalidate_filter();
|
||||
result.recording_list.invalidate_filter();
|
||||
}));
|
||||
|
||||
back_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.clone().pop();
|
||||
}
|
||||
}));
|
||||
|
||||
result
|
||||
.work_list
|
||||
.set_selected(clone!(@strong result => move |work| {
|
||||
result.recording_list.clear_selection();
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.push(WorkScreen::new(result.backend.clone(), work.clone()));
|
||||
}
|
||||
}));
|
||||
|
||||
result
|
||||
.recording_list
|
||||
.set_selected(clone!(@strong result => move |recording| {
|
||||
result.work_list.clear_selection();
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.push(RecordingScreen::new(result.backend.clone(), recording.clone()));
|
||||
}
|
||||
}));
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
context.spawn_local(async move {
|
||||
let works = clone
|
||||
.backend
|
||||
.get_work_descriptions(person.id)
|
||||
.await
|
||||
.unwrap();
|
||||
let recordings = clone
|
||||
.backend
|
||||
.get_recordings_for_person(person.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if works.is_empty() && recordings.is_empty() {
|
||||
clone.stack.set_visible_child_name("nothing");
|
||||
} else {
|
||||
if works.is_empty() {
|
||||
work_box.hide();
|
||||
} else {
|
||||
clone.work_list.show_items(works);
|
||||
}
|
||||
|
||||
if recordings.is_empty() {
|
||||
recording_box.hide();
|
||||
} else {
|
||||
clone.recording_list.show_items(recordings);
|
||||
}
|
||||
|
||||
clone.stack.set_visible_child_name("content");
|
||||
}
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl NavigatorScreen for PersonScreen {
|
||||
fn attach_navigator(&self, navigator: Rc<Navigator>) {
|
||||
self.navigator.replace(Some(navigator));
|
||||
}
|
||||
|
||||
fn get_widget(&self) -> gtk::Widget {
|
||||
self.widget.clone().upcast()
|
||||
}
|
||||
|
||||
fn detach_navigator(&self) {
|
||||
self.navigator.replace(None);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,334 +0,0 @@
|
|||
use crate::player::*;
|
||||
use crate::widgets::*;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
struct PlaylistElement {
|
||||
pub item: usize,
|
||||
pub track: usize,
|
||||
pub title: String,
|
||||
pub subtitle: Option<String>,
|
||||
pub playable: bool,
|
||||
}
|
||||
|
||||
pub struct PlayerScreen {
|
||||
pub widget: gtk::Box,
|
||||
title_label: gtk::Label,
|
||||
subtitle_label: gtk::Label,
|
||||
previous_button: gtk::Button,
|
||||
play_button: gtk::Button,
|
||||
next_button: gtk::Button,
|
||||
position_label: gtk::Label,
|
||||
position: gtk::Adjustment,
|
||||
duration_label: gtk::Label,
|
||||
play_image: gtk::Image,
|
||||
pause_image: gtk::Image,
|
||||
list: Rc<List<PlaylistElement>>,
|
||||
player: Rc<RefCell<Option<Rc<Player>>>>,
|
||||
seeking: Rc<Cell<bool>>,
|
||||
current_item: Rc<Cell<usize>>,
|
||||
current_track: Rc<Cell<usize>>,
|
||||
back_cb: Rc<RefCell<Option<Box<dyn Fn() -> ()>>>>,
|
||||
}
|
||||
|
||||
impl PlayerScreen {
|
||||
pub fn new() -> Self {
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/player_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::Label, title_label);
|
||||
get_widget!(builder, gtk::Label, subtitle_label);
|
||||
get_widget!(builder, gtk::Button, previous_button);
|
||||
get_widget!(builder, gtk::Button, play_button);
|
||||
get_widget!(builder, gtk::Button, next_button);
|
||||
get_widget!(builder, gtk::Button, stop_button);
|
||||
get_widget!(builder, gtk::Label, position_label);
|
||||
get_widget!(builder, gtk::Scale, position_scale);
|
||||
get_widget!(builder, gtk::Adjustment, position);
|
||||
get_widget!(builder, gtk::Label, duration_label);
|
||||
get_widget!(builder, gtk::Image, play_image);
|
||||
get_widget!(builder, gtk::Image, pause_image);
|
||||
get_widget!(builder, gtk::Frame, frame);
|
||||
|
||||
let back_cb = Rc::new(RefCell::new(None::<Box<dyn Fn() -> ()>>));
|
||||
|
||||
back_button.connect_clicked(clone!(@strong back_cb => move |_| {
|
||||
if let Some(cb) = &*back_cb.borrow() {
|
||||
cb();
|
||||
}
|
||||
}));
|
||||
|
||||
let player = Rc::new(RefCell::new(None::<Rc<Player>>));
|
||||
let seeking = Rc::new(Cell::new(false));
|
||||
|
||||
previous_button.connect_clicked(clone!(@strong player => move |_| {
|
||||
if let Some(player) = &*player.borrow() {
|
||||
player.previous().unwrap();
|
||||
}
|
||||
}));
|
||||
|
||||
play_button.connect_clicked(clone!(@strong player => move |_| {
|
||||
if let Some(player) = &*player.borrow() {
|
||||
player.play_pause();
|
||||
}
|
||||
}));
|
||||
|
||||
next_button.connect_clicked(clone!(@strong player => move |_| {
|
||||
if let Some(player) = &*player.borrow() {
|
||||
player.next().unwrap();
|
||||
}
|
||||
}));
|
||||
|
||||
stop_button.connect_clicked(clone!(@strong player, @strong back_cb => move |_| {
|
||||
if let Some(player) = &*player.borrow() {
|
||||
if let Some(cb) = &*back_cb.borrow() {
|
||||
cb();
|
||||
}
|
||||
|
||||
player.clear();
|
||||
}
|
||||
}));
|
||||
|
||||
position_scale.connect_button_press_event(clone!(@strong seeking => move |_, _| {
|
||||
seeking.replace(true);
|
||||
Inhibit(false)
|
||||
}));
|
||||
|
||||
position_scale.connect_button_release_event(
|
||||
clone!(@strong seeking, @strong position, @strong player => move |_, _| {
|
||||
if let Some(player) = &*player.borrow() {
|
||||
player.seek(position.get_value() as u64);
|
||||
}
|
||||
|
||||
seeking.replace(false);
|
||||
Inhibit(false)
|
||||
}),
|
||||
);
|
||||
|
||||
position_scale.connect_value_changed(
|
||||
clone!(@strong seeking, @strong position, @strong position_label => move |_| {
|
||||
if seeking.get() {
|
||||
let ms = position.get_value() as u64;
|
||||
let min = ms / 60000;
|
||||
let sec = (ms % 60000) / 1000;
|
||||
position_label.set_text(&format!("{}:{:02}", min, sec));
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let current_item = Rc::new(Cell::<usize>::new(0));
|
||||
let current_track = Rc::new(Cell::<usize>::new(0));
|
||||
let list = List::new("");
|
||||
|
||||
list.set_make_widget(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);
|
||||
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| {
|
||||
if let Some(player) = &*player.borrow() {
|
||||
player.set_track(element.item, element.track).unwrap();
|
||||
}
|
||||
}));
|
||||
|
||||
frame.add(&list.widget);
|
||||
|
||||
Self {
|
||||
widget,
|
||||
title_label,
|
||||
subtitle_label,
|
||||
previous_button,
|
||||
play_button,
|
||||
next_button,
|
||||
position_label,
|
||||
position,
|
||||
duration_label,
|
||||
play_image,
|
||||
pause_image,
|
||||
list,
|
||||
player,
|
||||
seeking,
|
||||
current_item,
|
||||
current_track,
|
||||
back_cb,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_player(&self, player: Option<Rc<Player>>) {
|
||||
self.player.replace(player.clone());
|
||||
|
||||
if let Some(player) = player {
|
||||
let playlist = Rc::new(RefCell::new(Vec::<PlaylistItem>::new()));
|
||||
|
||||
player.add_playlist_cb(clone!(
|
||||
@strong player,
|
||||
@strong self.previous_button as previous_button,
|
||||
@strong self.next_button as next_button,
|
||||
@strong self.list as list,
|
||||
@strong playlist
|
||||
=> move |new_playlist| {
|
||||
playlist.replace(new_playlist);
|
||||
previous_button.set_sensitive(player.has_previous());
|
||||
next_button.set_sensitive(player.has_next());
|
||||
|
||||
let mut elements = Vec::new();
|
||||
for (item_index, item) in playlist.borrow().iter().enumerate() {
|
||||
elements.push(PlaylistElement {
|
||||
item: item_index,
|
||||
track: 0,
|
||||
title: item.recording.work.get_title(),
|
||||
subtitle: Some(item.recording.get_performers()),
|
||||
playable: false,
|
||||
});
|
||||
|
||||
for (track_index, track) in item.tracks.iter().enumerate() {
|
||||
let mut parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
parts.push(item.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
|
||||
let title = if parts.is_empty() {
|
||||
gettext("Unknown")
|
||||
} else {
|
||||
parts.join(", ")
|
||||
};
|
||||
|
||||
elements.push(PlaylistElement {
|
||||
item: item_index,
|
||||
track: track_index,
|
||||
title: title,
|
||||
subtitle: None,
|
||||
playable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
list.show_items(elements);
|
||||
}
|
||||
));
|
||||
|
||||
player.add_track_cb(clone!(
|
||||
@strong player,
|
||||
@strong playlist,
|
||||
@strong self.previous_button as previous_button,
|
||||
@strong self.next_button as next_button,
|
||||
@strong self.title_label as title_label,
|
||||
@strong self.subtitle_label as subtitle_label,
|
||||
@strong self.position_label as position_label,
|
||||
@strong self.current_item as self_item,
|
||||
@strong self.current_track as self_track,
|
||||
@strong self.list as list
|
||||
=> move |current_item, current_track| {
|
||||
previous_button.set_sensitive(player.has_previous());
|
||||
next_button.set_sensitive(player.has_next());
|
||||
|
||||
let item = &playlist.borrow()[current_item];
|
||||
let track = &item.tracks[current_track];
|
||||
|
||||
let mut parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
parts.push(item.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
|
||||
let mut title = item.recording.work.get_title();
|
||||
if !parts.is_empty() {
|
||||
title = format!("{}: {}", title, parts.join(", "));
|
||||
}
|
||||
|
||||
title_label.set_text(&title);
|
||||
subtitle_label.set_text(&item.recording.get_performers());
|
||||
position_label.set_text("0:00");
|
||||
|
||||
self_item.replace(current_item);
|
||||
self_track.replace(current_track);
|
||||
list.update();
|
||||
}
|
||||
));
|
||||
|
||||
player.add_duration_cb(clone!(
|
||||
@strong self.duration_label as duration_label,
|
||||
@strong self.position as position
|
||||
=> move |ms| {
|
||||
let min = ms / 60000;
|
||||
let sec = (ms % 60000) / 1000;
|
||||
duration_label.set_text(&format!("{}:{:02}", min, sec));
|
||||
position.set_upper(ms as f64);
|
||||
}
|
||||
));
|
||||
|
||||
player.add_playing_cb(clone!(
|
||||
@strong self.play_button as play_button,
|
||||
@strong self.play_image as play_image,
|
||||
@strong self.pause_image as pause_image
|
||||
=> move |playing| {
|
||||
if let Some(child) = play_button.get_child() {
|
||||
play_button.remove( &child);
|
||||
}
|
||||
|
||||
play_button.add(if playing {
|
||||
&pause_image
|
||||
} else {
|
||||
&play_image
|
||||
});
|
||||
}
|
||||
));
|
||||
|
||||
player.add_position_cb(clone!(
|
||||
@strong self.position_label as position_label,
|
||||
@strong self.position as position,
|
||||
@strong self.seeking as seeking
|
||||
=> move |ms| {
|
||||
if !seeking.get() {
|
||||
let min = ms / 60000;
|
||||
let sec = (ms % 60000) / 1000;
|
||||
position_label.set_text(&format!("{}:{:02}", min, sec));
|
||||
position.set_value(ms as f64);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_back_cb<F: Fn() -> () + 'static>(&self, cb: F) {
|
||||
self.back_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use crate::player::*;
|
||||
use crate::widgets::*;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libhandy::HeaderBarExt;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct RecordingScreen {
|
||||
backend: Rc<Backend>,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
tracks: RefCell<Vec<TrackDescription>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
impl RecordingScreen {
|
||||
pub fn new(backend: Rc<Backend>, recording: RecordingDescription) -> Rc<Self> {
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, libhandy::HeaderBar, header);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::MenuButton, menu_button);
|
||||
get_widget!(builder, gtk::Stack, stack);
|
||||
get_widget!(builder, gtk::Frame, frame);
|
||||
get_widget!(builder, gtk::Button, add_to_playlist_button);
|
||||
|
||||
header.set_title(Some(&recording.work.get_title()));
|
||||
header.set_subtitle(Some(&recording.get_performers()));
|
||||
|
||||
let edit_menu_item = gio::MenuItem::new(Some(&gettext("Edit recording")), None);
|
||||
edit_menu_item.set_action_and_target_value(
|
||||
Some("win.edit-recording"),
|
||||
Some(&glib::Variant::from(recording.id)),
|
||||
);
|
||||
|
||||
let delete_menu_item = gio::MenuItem::new(Some(&gettext("Delete recording")), None);
|
||||
delete_menu_item.set_action_and_target_value(
|
||||
Some("win.delete-recording"),
|
||||
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();
|
||||
menu.append_item(&edit_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));
|
||||
|
||||
let recording = Rc::new(recording);
|
||||
let list = List::new(&gettext("No tracks found."));
|
||||
|
||||
list.set_make_widget(
|
||||
clone!(@strong recording => move |track: &TrackDescription| {
|
||||
let mut title_parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
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()
|
||||
}),
|
||||
);
|
||||
|
||||
frame.add(&list.widget);
|
||||
|
||||
let result = Rc::new(Self {
|
||||
backend,
|
||||
widget,
|
||||
stack,
|
||||
tracks: RefCell::new(Vec::new()),
|
||||
navigator: RefCell::new(None),
|
||||
});
|
||||
|
||||
back_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.clone().pop();
|
||||
}
|
||||
}));
|
||||
|
||||
add_to_playlist_button.connect_clicked(
|
||||
clone!(@strong result, @strong recording => move |_| {
|
||||
if let Some(player) = result.backend.get_player() {
|
||||
player.add_item(PlaylistItem {
|
||||
recording: (*recording).clone(),
|
||||
tracks: result.tracks.borrow().clone(),
|
||||
}).unwrap();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
let id = recording.id;
|
||||
context.spawn_local(async move {
|
||||
let tracks = clone.backend.get_tracks(id).await.unwrap();
|
||||
list.show_items(tracks.clone());
|
||||
clone.stack.set_visible_child_name("content");
|
||||
clone.tracks.replace(tracks);
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl NavigatorScreen for RecordingScreen {
|
||||
fn attach_navigator(&self, navigator: Rc<Navigator>) {
|
||||
self.navigator.replace(Some(navigator));
|
||||
}
|
||||
|
||||
fn get_widget(&self) -> gtk::Widget {
|
||||
self.widget.clone().upcast()
|
||||
}
|
||||
|
||||
fn detach_navigator(&self) {
|
||||
self.navigator.replace(None);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
use super::*;
|
||||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use crate::widgets::*;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libhandy::HeaderBarExt;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct WorkScreen {
|
||||
backend: Rc<Backend>,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
recording_list: Rc<List<RecordingDescription>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
impl WorkScreen {
|
||||
pub fn new(backend: Rc<Backend>, work: WorkDescription) -> Rc<Self> {
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, libhandy::HeaderBar, header);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::MenuButton, menu_button);
|
||||
get_widget!(builder, gtk::SearchEntry, search_entry);
|
||||
get_widget!(builder, gtk::Stack, stack);
|
||||
get_widget!(builder, gtk::Frame, recording_frame);
|
||||
|
||||
header.set_title(Some(&work.title));
|
||||
header.set_subtitle(Some(&work.composer.name_fl()));
|
||||
|
||||
let edit_menu_item = gio::MenuItem::new(Some(&gettext("Edit work")), None);
|
||||
edit_menu_item.set_action_and_target_value(
|
||||
Some("win.edit-work"),
|
||||
Some(&glib::Variant::from(work.id)),
|
||||
);
|
||||
|
||||
let delete_menu_item = gio::MenuItem::new(Some(&gettext("Delete work")), None);
|
||||
delete_menu_item.set_action_and_target_value(
|
||||
Some("win.delete-work"),
|
||||
Some(&glib::Variant::from(work.id)),
|
||||
);
|
||||
|
||||
let menu = gio::Menu::new();
|
||||
menu.append_item(&edit_menu_item);
|
||||
menu.append_item(&delete_menu_item);
|
||||
|
||||
menu_button.set_menu_model(Some(&menu));
|
||||
|
||||
let recording_list = List::new(&gettext("No recordings found."));
|
||||
|
||||
recording_list.set_make_widget(|recording: &RecordingDescription| {
|
||||
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
||||
|
||||
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
work_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
||||
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
performers_label.set_opacity(0.5);
|
||||
performers_label.set_halign(gtk::Align::Start);
|
||||
|
||||
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| {
|
||||
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);
|
||||
|
||||
let result = Rc::new(Self {
|
||||
backend,
|
||||
widget,
|
||||
stack,
|
||||
recording_list,
|
||||
navigator: RefCell::new(None),
|
||||
});
|
||||
|
||||
search_entry.connect_search_changed(clone!(@strong result => move |_| {
|
||||
result.recording_list.invalidate_filter();
|
||||
}));
|
||||
|
||||
back_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.clone().pop();
|
||||
}
|
||||
}));
|
||||
|
||||
result
|
||||
.recording_list
|
||||
.set_selected(clone!(@strong result => move |recording| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.push(RecordingScreen::new(result.backend.clone(), recording.clone()));
|
||||
}
|
||||
}));
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
context.spawn_local(async move {
|
||||
let recordings = clone
|
||||
.backend
|
||||
.get_recordings_for_work(work.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if recordings.is_empty() {
|
||||
clone.stack.set_visible_child_name("nothing");
|
||||
} else {
|
||||
clone.recording_list.show_items(recordings);
|
||||
clone.stack.set_visible_child_name("content");
|
||||
}
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl NavigatorScreen for WorkScreen {
|
||||
fn attach_navigator(&self, navigator: Rc<Navigator>) {
|
||||
self.navigator.replace(Some(navigator));
|
||||
}
|
||||
|
||||
fn get_widget(&self) -> gtk::Widget {
|
||||
self.widget.clone().upcast()
|
||||
}
|
||||
|
||||
fn detach_navigator(&self) {
|
||||
self.navigator.replace(None);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue