mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 11:47:25 +01:00
Update gtk-rs crates
This commit is contained in:
parent
df6e2e86c7
commit
7d7343ea8c
63 changed files with 3499 additions and 908 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -4,4 +4,3 @@
|
||||||
/builddir
|
/builddir
|
||||||
/flatpak
|
/flatpak
|
||||||
/target
|
/target
|
||||||
Cargo.lock
|
|
||||||
|
|
|
||||||
2477
Cargo.lock
generated
Normal file
2477
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -21,6 +21,4 @@ pub enum Error {
|
||||||
Other(String),
|
Other(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,15 @@ impl Backend {
|
||||||
|
|
||||||
/// Set the path to the music library folder and start a database thread in the background.
|
/// Set the path to the music library folder and start a database thread in the background.
|
||||||
pub async fn set_music_library_path(&self, path: PathBuf) -> Result<()> {
|
pub async fn set_music_library_path(&self, path: PathBuf) -> Result<()> {
|
||||||
if let Err(err) = self.settings.set_string("music-library-path", path.to_str().unwrap()) {
|
if let Err(err) = self
|
||||||
warn!("The music library path could not be saved using GSettings. It will most likely \
|
.settings
|
||||||
not be available at the next startup. Error message: {}", err);
|
.set_string("music-library-path", path.to_str().unwrap())
|
||||||
|
{
|
||||||
|
warn!(
|
||||||
|
"The music library path could not be saved using GSettings. It will most likely \
|
||||||
|
not be available at the next startup. Error message: {}",
|
||||||
|
err
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.set_music_library_path_priv(path).await
|
self.set_music_library_path_priv(path).await
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
use musicus_database::Track;
|
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
use gstreamer_player::prelude::*;
|
use gstreamer_player::prelude::*;
|
||||||
|
use musicus_database::Track;
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
@ -101,9 +101,11 @@ impl Player {
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
result.mpris.connect_play_pause(clone!(@weak result => move || {
|
result
|
||||||
result.play_pause();
|
.mpris
|
||||||
}));
|
.connect_play_pause(clone!(@weak result => move || {
|
||||||
|
result.play_pause();
|
||||||
|
}));
|
||||||
|
|
||||||
result.mpris.connect_play(clone!(@weak result => move || {
|
result.mpris.connect_play(clone!(@weak result => move || {
|
||||||
if !result.is_playing() {
|
if !result.is_playing() {
|
||||||
|
|
@ -117,9 +119,11 @@ impl Player {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
result.mpris.connect_previous(clone!(@weak result => move || {
|
result
|
||||||
let _ = result.previous();
|
.mpris
|
||||||
}));
|
.connect_previous(clone!(@weak result => move || {
|
||||||
|
let _ = result.previous();
|
||||||
|
}));
|
||||||
|
|
||||||
result.mpris.connect_next(clone!(@weak result => move || {
|
result.mpris.connect_next(clone!(@weak result => move || {
|
||||||
let _ = result.next();
|
let _ = result.next();
|
||||||
|
|
@ -246,10 +250,9 @@ impl Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn previous(&self) -> Result<()> {
|
pub fn previous(&self) -> Result<()> {
|
||||||
let mut current_track = self
|
let mut current_track = self.current_track.get().ok_or(Error::Other(String::from(
|
||||||
.current_track
|
"Player tried to access non existant current track.",
|
||||||
.get()
|
)))?;
|
||||||
.ok_or(Error::Other(String::from("Player tried to access non existant current track.")))?;
|
|
||||||
|
|
||||||
if current_track > 0 {
|
if current_track > 0 {
|
||||||
current_track -= 1;
|
current_track -= 1;
|
||||||
|
|
@ -270,10 +273,9 @@ impl Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next(&self) -> Result<()> {
|
pub fn next(&self) -> Result<()> {
|
||||||
let mut current_track = self
|
let mut current_track = self.current_track.get().ok_or(Error::Other(String::from(
|
||||||
.current_track
|
"Player tried to access non existant current track.",
|
||||||
.get()
|
)))?;
|
||||||
.ok_or(Error::Other(String::from("Player tried to access non existant current track.")))?;
|
|
||||||
|
|
||||||
let playlist = self.playlist.borrow();
|
let playlist = self.playlist.borrow();
|
||||||
|
|
||||||
|
|
@ -289,11 +291,17 @@ impl Player {
|
||||||
pub fn set_track(&self, current_track: usize) -> Result<()> {
|
pub fn set_track(&self, current_track: usize) -> Result<()> {
|
||||||
let track = &self.playlist.borrow()[current_track];
|
let track = &self.playlist.borrow()[current_track];
|
||||||
|
|
||||||
let path = self.music_library_path.join(track.path.clone())
|
let path = self
|
||||||
.into_os_string().into_string().unwrap();
|
.music_library_path
|
||||||
|
.join(track.path.clone())
|
||||||
|
.into_os_string()
|
||||||
|
.into_string()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let uri = glib::filename_to_uri(&path, None)
|
let uri = glib::filename_to_uri(&path, None).or(Err(Error::Other(format!(
|
||||||
.or(Err(Error::Other(format!("Failed to create URI from path: {}", path))))?;
|
"Failed to create URI from path: {}",
|
||||||
|
path
|
||||||
|
))))?;
|
||||||
|
|
||||||
self.player.set_uri(&uri);
|
self.player.set_uri(&uri);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{Backend, Error, Result};
|
use crate::{Backend, Error, Result};
|
||||||
use musicus_client::LoginData;
|
|
||||||
use futures_channel::oneshot;
|
use futures_channel::oneshot;
|
||||||
|
use musicus_client::LoginData;
|
||||||
use secret_service::{Collection, EncryptionType, SecretService};
|
use secret_service::{Collection, EncryptionType, SecretService};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
@ -35,14 +35,18 @@ impl Backend {
|
||||||
let items = collection.get_all_items()?;
|
let items = collection.get_all_items()?;
|
||||||
|
|
||||||
let key = "musicus-login-data";
|
let key = "musicus-login-data";
|
||||||
let item = items.iter().find(|item| item.get_label().unwrap_or_default() == key);
|
let item = items
|
||||||
|
.iter()
|
||||||
|
.find(|item| item.get_label().unwrap_or_default() == key);
|
||||||
|
|
||||||
Ok(match item {
|
Ok(match item {
|
||||||
Some(item) => {
|
Some(item) => {
|
||||||
let username = item
|
let username = item
|
||||||
.get_attributes()?
|
.get_attributes()?
|
||||||
.get("username")
|
.get("username")
|
||||||
.ok_or(Error::Other("Missing username in SecretService attributes."))?
|
.ok_or(Error::Other(
|
||||||
|
"Missing username in SecretService attributes.",
|
||||||
|
))?
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
let password = std::str::from_utf8(&item.get_secret()?)?.to_owned();
|
let password = std::str::from_utf8(&item.get_secret()?)?.to_owned();
|
||||||
|
|
@ -63,7 +67,13 @@ impl Backend {
|
||||||
|
|
||||||
let mut attributes = HashMap::new();
|
let mut attributes = HashMap::new();
|
||||||
attributes.insert("username", data.username.as_str());
|
attributes.insert("username", data.username.as_str());
|
||||||
collection.create_item(key, attributes, data.password.as_bytes(), true, "text/plain")?;
|
collection.create_item(
|
||||||
|
key,
|
||||||
|
attributes,
|
||||||
|
data.password.as_bytes(),
|
||||||
|
true,
|
||||||
|
"text/plain",
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,4 @@ pub enum Error {
|
||||||
Other(&'static str),
|
Other(&'static str),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@ impl Client {
|
||||||
/// Post a new instrument to the server.
|
/// Post a new instrument to the server.
|
||||||
pub async fn post_instrument(&self, data: &Instrument) -> Result<()> {
|
pub async fn post_instrument(&self, data: &Instrument) -> Result<()> {
|
||||||
info!("Post instrument {:?}", data);
|
info!("Post instrument {:?}", data);
|
||||||
self.post("instruments", serde_json::to_string(data)?).await?;
|
self.post("instruments", serde_json::to_string(data)?)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use isahc::{AsyncBody, Request, Response};
|
|
||||||
use isahc::http::StatusCode;
|
use isahc::http::StatusCode;
|
||||||
use isahc::prelude::*;
|
use isahc::prelude::*;
|
||||||
|
use isahc::{AsyncBody, Request, Response};
|
||||||
use log::info;
|
use log::info;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::time::Duration;
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
pub mod ensembles;
|
pub mod ensembles;
|
||||||
pub use ensembles::*;
|
pub use ensembles::*;
|
||||||
|
|
@ -164,16 +164,21 @@ impl Client {
|
||||||
|
|
||||||
/// Require the server URL to be set.
|
/// Require the server URL to be set.
|
||||||
fn server_url(&self) -> Result<String> {
|
fn server_url(&self) -> Result<String> {
|
||||||
self.get_server_url().ok_or(Error::Other("The server URL is not available!"))
|
self.get_server_url()
|
||||||
|
.ok_or(Error::Other("The server URL is not available!"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Require the login data to be set.
|
/// Require the login data to be set.
|
||||||
fn login_data(&self) -> Result<LoginData> {
|
fn login_data(&self) -> Result<LoginData> {
|
||||||
self.get_login_data().ok_or(Error::Other("The login data is unset!"))
|
self.get_login_data()
|
||||||
|
.ok_or(Error::Other("The login data is unset!"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Require a login token to be set.
|
/// Require a login token to be set.
|
||||||
fn token(&self) -> Result<String> {
|
fn token(&self) -> Result<String> {
|
||||||
self.token.borrow().clone().ok_or(Error::Other("No login token found!"))
|
self.token
|
||||||
|
.borrow()
|
||||||
|
.clone()
|
||||||
|
.ok_or(Error::Other("No login token found!"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ impl Client {
|
||||||
/// recording.
|
/// recording.
|
||||||
pub async fn get_mediums_for_recording(&self, recording_id: &str) -> Result<Vec<Medium>> {
|
pub async fn get_mediums_for_recording(&self, recording_id: &str) -> Result<Vec<Medium>> {
|
||||||
info!("Get mediums for recording {}", recording_id);
|
info!("Get mediums for recording {}", recording_id);
|
||||||
let body = self.get(&format!("recordings/{}/mediums", recording_id)).await?;
|
let body = self
|
||||||
|
.get(&format!("recordings/{}/mediums", recording_id))
|
||||||
|
.await?;
|
||||||
let mediums: Vec<Medium> = serde_json::from_str(&body)?;
|
let mediums: Vec<Medium> = serde_json::from_str(&body)?;
|
||||||
Ok(mediums)
|
Ok(mediums)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@ impl Client {
|
||||||
/// Post a new recording to the server.
|
/// Post a new recording to the server.
|
||||||
pub async fn post_recording(&self, data: &Recording) -> Result<()> {
|
pub async fn post_recording(&self, data: &Recording) -> Result<()> {
|
||||||
info!("Post recording {:?}", data);
|
info!("Post recording {:?}", data);
|
||||||
self.post("recordings", serde_json::to_string(data)?).await?;
|
self.post("recordings", serde_json::to_string(data)?)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{Client, Result};
|
use crate::{Client, Result};
|
||||||
use isahc::Request;
|
|
||||||
use isahc::http::StatusCode;
|
use isahc::http::StatusCode;
|
||||||
use isahc::prelude::*;
|
use isahc::prelude::*;
|
||||||
|
use isahc::Request;
|
||||||
use log::info;
|
use log::info;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use tokio::sync::oneshot::{self, Sender};
|
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
use tokio::sync::oneshot::{self, Sender};
|
||||||
|
|
||||||
/// An action the database thread can perform.
|
/// An action the database thread can perform.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -359,28 +359,32 @@ impl DbThread {
|
||||||
/// Get all mediums with the specified source ID.
|
/// Get all mediums with the specified source ID.
|
||||||
pub async fn get_mediums_by_source_id(&self, id: &str) -> Result<Vec<Medium>> {
|
pub async fn get_mediums_by_source_id(&self, id: &str) -> Result<Vec<Medium>> {
|
||||||
let (sender, receiver) = oneshot::channel();
|
let (sender, receiver) = oneshot::channel();
|
||||||
self.action_sender.send(GetMediumsBySourceId(id.to_owned(), sender))?;
|
self.action_sender
|
||||||
|
.send(GetMediumsBySourceId(id.to_owned(), sender))?;
|
||||||
receiver.await?
|
receiver.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all mediums on which a person performs.
|
/// Get all mediums on which a person performs.
|
||||||
pub async fn get_mediums_for_person(&self, id: &str) -> Result<Vec<Medium>> {
|
pub async fn get_mediums_for_person(&self, id: &str) -> Result<Vec<Medium>> {
|
||||||
let (sender, receiver) = oneshot::channel();
|
let (sender, receiver) = oneshot::channel();
|
||||||
self.action_sender.send(GetMediumsForPerson(id.to_owned(), sender))?;
|
self.action_sender
|
||||||
|
.send(GetMediumsForPerson(id.to_owned(), sender))?;
|
||||||
receiver.await?
|
receiver.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all mediums on which an ensemble performs.
|
/// Get all mediums on which an ensemble performs.
|
||||||
pub async fn get_mediums_for_ensemble(&self, id: &str) -> Result<Vec<Medium>> {
|
pub async fn get_mediums_for_ensemble(&self, id: &str) -> Result<Vec<Medium>> {
|
||||||
let (sender, receiver) = oneshot::channel();
|
let (sender, receiver) = oneshot::channel();
|
||||||
self.action_sender.send(GetMediumsForEnsemble(id.to_owned(), sender))?;
|
self.action_sender
|
||||||
|
.send(GetMediumsForEnsemble(id.to_owned(), sender))?;
|
||||||
receiver.await?
|
receiver.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all tracks for a recording.
|
/// Get all tracks for a recording.
|
||||||
pub async fn get_tracks(&self, recording_id: &str) -> Result<Vec<Track>> {
|
pub async fn get_tracks(&self, recording_id: &str) -> Result<Vec<Track>> {
|
||||||
let (sender, receiver) = oneshot::channel();
|
let (sender, receiver) = oneshot::channel();
|
||||||
self.action_sender.send(GetTracks(recording_id.to_owned(), sender))?;
|
self.action_sender
|
||||||
|
.send(GetTracks(recording_id.to_owned(), sender))?;
|
||||||
receiver.await?
|
receiver.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::session::{ImportSession, ImportTrack, State};
|
use crate::session::{ImportSession, ImportTrack, State};
|
||||||
use gstreamer::prelude::*;
|
use gstreamer::prelude::*;
|
||||||
use gstreamer::{ClockTime, ElementFactory, MessageType, MessageView, TocEntryType};
|
|
||||||
use gstreamer::tags::{Duration, TrackNumber};
|
use gstreamer::tags::{Duration, TrackNumber};
|
||||||
|
use gstreamer::{ClockTime, ElementFactory, MessageType, MessageView, TocEntryType};
|
||||||
use log::info;
|
use log::info;
|
||||||
use sha2::{Sha256, Digest};
|
use sha2::{Digest, Sha256};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
|
|
||||||
|
|
@ -28,23 +28,31 @@ pub(super) fn new() -> Result<ImportSession> {
|
||||||
pipeline.add_many(&[&cdparanoiasrc, &queue, &audioconvert, &flacenc, &fakesink])?;
|
pipeline.add_many(&[&cdparanoiasrc, &queue, &audioconvert, &flacenc, &fakesink])?;
|
||||||
gstreamer::Element::link_many(&[&cdparanoiasrc, &queue, &audioconvert, &flacenc, &fakesink])?;
|
gstreamer::Element::link_many(&[&cdparanoiasrc, &queue, &audioconvert, &flacenc, &fakesink])?;
|
||||||
|
|
||||||
let bus = pipeline.get_bus().ok_or(Error::u(String::from("Failed to get bus from pipeline.")))?;
|
let bus = pipeline
|
||||||
|
.get_bus()
|
||||||
|
.ok_or(Error::u(String::from("Failed to get bus from pipeline.")))?;
|
||||||
|
|
||||||
// Run the pipeline into the paused state and wait for the resulting TOC message on the bus.
|
// Run the pipeline into the paused state and wait for the resulting TOC message on the bus.
|
||||||
|
|
||||||
pipeline.set_state(gstreamer::State::Paused)?;
|
pipeline.set_state(gstreamer::State::Paused)?;
|
||||||
|
|
||||||
let msg = bus.timed_pop_filtered(ClockTime::from_seconds(5),
|
let msg = bus.timed_pop_filtered(
|
||||||
&vec![MessageType::Toc, MessageType::Error]);
|
ClockTime::from_seconds(5),
|
||||||
|
&vec![MessageType::Toc, MessageType::Error],
|
||||||
|
);
|
||||||
|
|
||||||
let toc = match msg {
|
let toc = match msg {
|
||||||
Some(msg) => match msg.view() {
|
Some(msg) => match msg.view() {
|
||||||
MessageView::Error(err) => Err(Error::os(err.get_error())),
|
MessageView::Error(err) => Err(Error::os(err.get_error())),
|
||||||
MessageView::Toc(toc) => Ok(toc.get_toc().0),
|
MessageView::Toc(toc) => Ok(toc.get_toc().0),
|
||||||
_ => Err(Error::u(format!("Unexpected message from GStreamer: {:?}", msg))),
|
_ => Err(Error::u(format!(
|
||||||
|
"Unexpected message from GStreamer: {:?}",
|
||||||
|
msg
|
||||||
|
))),
|
||||||
},
|
},
|
||||||
None => Err(Error::Timeout(
|
None => Err(Error::Timeout(format!(
|
||||||
format!("Timeout while waiting for first message from GStreamer."))),
|
"Timeout while waiting for first message from GStreamer."
|
||||||
|
))),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
pipeline.set_state(gstreamer::State::Ready)?;
|
pipeline.set_state(gstreamer::State::Ready)?;
|
||||||
|
|
@ -66,22 +74,31 @@ pub(super) fn new() -> Result<ImportSession> {
|
||||||
|
|
||||||
for entry in toc.get_entries() {
|
for entry in toc.get_entries() {
|
||||||
if entry.get_entry_type() == TocEntryType::Track {
|
if entry.get_entry_type() == TocEntryType::Track {
|
||||||
let duration = entry.get_tags()
|
let duration = entry
|
||||||
|
.get_tags()
|
||||||
.ok_or(Error::u(String::from("No tags in TOC entry.")))?
|
.ok_or(Error::u(String::from("No tags in TOC entry.")))?
|
||||||
.get::<Duration>()
|
.get::<Duration>()
|
||||||
.ok_or(Error::u(String::from("No duration tag found in TOC entry.")))?
|
.ok_or(Error::u(String::from(
|
||||||
|
"No duration tag found in TOC entry.",
|
||||||
|
)))?
|
||||||
.get()
|
.get()
|
||||||
.ok_or(Error::u(String::from("Failed to unwrap duration tag from TOC entry.")))?
|
.ok_or(Error::u(String::from(
|
||||||
|
"Failed to unwrap duration tag from TOC entry.",
|
||||||
|
)))?
|
||||||
.mseconds()
|
.mseconds()
|
||||||
.ok_or(Error::u(String::from("Failed to unwrap track duration.")))?;
|
.ok_or(Error::u(String::from("Failed to unwrap track duration.")))?;
|
||||||
|
|
||||||
let number = entry.get_tags()
|
let number = entry
|
||||||
|
.get_tags()
|
||||||
.ok_or(Error::u(String::from("No tags in TOC entry.")))?
|
.ok_or(Error::u(String::from("No tags in TOC entry.")))?
|
||||||
.get::<TrackNumber>()
|
.get::<TrackNumber>()
|
||||||
.ok_or(Error::u(String::from("No track number tag found in TOC entry.")))?
|
.ok_or(Error::u(String::from(
|
||||||
|
"No track number tag found in TOC entry.",
|
||||||
|
)))?
|
||||||
.get()
|
.get()
|
||||||
.ok_or(Error::u(
|
.ok_or(Error::u(String::from(
|
||||||
String::from("Failed to unwrap track number tag from TOC entry.")))?;
|
"Failed to unwrap track number tag from TOC entry.",
|
||||||
|
)))?;
|
||||||
|
|
||||||
hasher.update(duration.to_le_bytes());
|
hasher.update(duration.to_le_bytes());
|
||||||
|
|
||||||
|
|
@ -129,11 +146,11 @@ pub(super) fn new() -> Result<ImportSession> {
|
||||||
info!("Finished ripping track {}.", track.number);
|
info!("Finished ripping track {}.", track.number);
|
||||||
pipeline.set_state(gstreamer::State::Ready)?;
|
pipeline.set_state(gstreamer::State::Ready)?;
|
||||||
break;
|
break;
|
||||||
},
|
}
|
||||||
MessageView::Error(err) => {
|
MessageView::Error(err) => {
|
||||||
pipeline.set_state(gstreamer::State::Null)?;
|
pipeline.set_state(gstreamer::State::Null)?;
|
||||||
Err(Error::os(err.get_error()))?;
|
Err(Error::os(err.get_error()))?;
|
||||||
},
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -157,8 +174,9 @@ pub(super) fn new() -> Result<ImportSession> {
|
||||||
|
|
||||||
/// Create a new temporary directory and return its path.
|
/// Create a new temporary directory and return its path.
|
||||||
fn create_tmp_dir() -> Result<PathBuf> {
|
fn create_tmp_dir() -> Result<PathBuf> {
|
||||||
let mut tmp_dir = glib::get_tmp_dir().ok_or(Error::u(
|
let mut tmp_dir = glib::tmp_dir().ok_or(Error::u(String::from(
|
||||||
String::from("Failed to get temporary directory using glib::get_tmp_dir().")))?;
|
"Failed to get temporary directory using glib::get_tmp_dir().",
|
||||||
|
)))?;
|
||||||
|
|
||||||
let dir_name = format!("musicus-{}", rand::random::<u64>());
|
let dir_name = format!("musicus-{}", rand::random::<u64>());
|
||||||
tmp_dir.push(dir_name);
|
tmp_dir.push(dir_name);
|
||||||
|
|
@ -167,4 +185,3 @@ fn create_tmp_dir() -> Result<PathBuf> {
|
||||||
|
|
||||||
Ok(tmp_dir)
|
Ok(tmp_dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,7 @@ impl Error {
|
||||||
|
|
||||||
/// Create a new unexpected error without an explicit source.
|
/// Create a new unexpected error without an explicit source.
|
||||||
pub(super) fn u(msg: String) -> Self {
|
pub(super) fn u(msg: String) -> Self {
|
||||||
Self::Unexpected {
|
Self::Unexpected { msg, source: None }
|
||||||
msg,
|
|
||||||
source: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new unexpected error with an explicit source.
|
/// Create a new unexpected error with an explicit source.
|
||||||
|
|
@ -85,4 +82,3 @@ impl From<std::io::Error> for Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ use crate::error::{Error, Result};
|
||||||
use crate::session::{ImportSession, ImportTrack, State};
|
use crate::session::{ImportSession, ImportTrack, State};
|
||||||
use gstreamer::ClockTime;
|
use gstreamer::ClockTime;
|
||||||
use gstreamer_pbutils::Discoverer;
|
use gstreamer_pbutils::Discoverer;
|
||||||
use log::{warn, info};
|
use log::{info, warn};
|
||||||
use sha2::{Sha256, Digest};
|
use sha2::{Digest, Sha256};
|
||||||
use std::fs::DirEntry;
|
use std::fs::DirEntry;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
|
|
@ -17,26 +17,32 @@ pub(super) fn new(path: PathBuf) -> Result<ImportSession> {
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
let discoverer = Discoverer::new(ClockTime::from_seconds(1))?;
|
let discoverer = Discoverer::new(ClockTime::from_seconds(1))?;
|
||||||
|
|
||||||
let mut entries = std::fs::read_dir(path)?.collect::<std::result::Result<Vec<DirEntry>, std::io::Error>>()?;
|
let mut entries =
|
||||||
|
std::fs::read_dir(path)?.collect::<std::result::Result<Vec<DirEntry>, std::io::Error>>()?;
|
||||||
entries.sort_by(|entry1, entry2| entry1.file_name().cmp(&entry2.file_name()));
|
entries.sort_by(|entry1, entry2| entry1.file_name().cmp(&entry2.file_name()));
|
||||||
|
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
if entry.file_type()?.is_file() {
|
if entry.file_type()?.is_file() {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
|
|
||||||
let uri = glib::filename_to_uri(&path, None)
|
let uri = glib::filename_to_uri(&path, None).or(Err(Error::u(format!(
|
||||||
.or(Err(Error::u(format!("Failed to create URI from path: {:?}", path))))?;
|
"Failed to create URI from path: {:?}",
|
||||||
|
path
|
||||||
|
))))?;
|
||||||
|
|
||||||
let info = discoverer.discover_uri(&uri)?;
|
let info = discoverer.discover_uri(&uri)?;
|
||||||
|
|
||||||
if !info.get_audio_streams().is_empty() {
|
if !info.get_audio_streams().is_empty() {
|
||||||
let duration = info.get_duration().mseconds()
|
let duration = info
|
||||||
|
.get_duration()
|
||||||
|
.mseconds()
|
||||||
.ok_or(Error::u(format!("Failed to get duration for {}.", uri)))?;
|
.ok_or(Error::u(format!("Failed to get duration for {}.", uri)))?;
|
||||||
|
|
||||||
let file_name = entry.file_name();
|
let file_name = entry.file_name();
|
||||||
let name = file_name.into_string()
|
let name = file_name.into_string().or(Err(Error::u(format!(
|
||||||
.or(Err(Error::u(format!(
|
"Failed to convert OsString to String: {:?}",
|
||||||
"Failed to convert OsString to String: {:?}", entry.file_name()))))?;
|
entry.file_name()
|
||||||
|
))))?;
|
||||||
|
|
||||||
hasher.update(duration.to_le_bytes());
|
hasher.update(duration.to_le_bytes());
|
||||||
|
|
||||||
|
|
@ -50,7 +56,10 @@ pub(super) fn new(path: PathBuf) -> Result<ImportSession> {
|
||||||
tracks.push(track);
|
tracks.push(track);
|
||||||
number += 1;
|
number += 1;
|
||||||
} else {
|
} else {
|
||||||
warn!("File {} skipped, because it doesn't contain any audio streams.", uri);
|
warn!(
|
||||||
|
"File {} skipped, because it doesn't contain any audio streams.",
|
||||||
|
uri
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
pub use session::{ImportSession, ImportTrack, State};
|
|
||||||
pub use error::{Error, Result};
|
pub use error::{Error, Result};
|
||||||
|
pub use session::{ImportSession, ImportTrack, State};
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{disc, folder};
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
|
use crate::{disc, folder};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::thread;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::thread;
|
||||||
use tokio::sync::{oneshot, watch};
|
use tokio::sync::{oneshot, watch};
|
||||||
|
|
||||||
/// The current state of the import process.
|
/// The current state of the import process.
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ async-trait = "0.1.42"
|
||||||
futures-channel = "0.3.5"
|
futures-channel = "0.3.5"
|
||||||
gettext-rs = { version = "0.5.0", features = ["gettext-system"] }
|
gettext-rs = { version = "0.5.0", features = ["gettext-system"] }
|
||||||
gstreamer = "0.16.4"
|
gstreamer = "0.16.4"
|
||||||
gtk-macros = "0.2.0"
|
gtk-macros = "0.3.0"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
musicus_backend = { version = "0.1.0", path = "../backend" }
|
musicus_backend = { version = "0.1.0", path = "../backend" }
|
||||||
once_cell = "1.5.2"
|
once_cell = "1.5.2"
|
||||||
|
|
@ -33,7 +33,7 @@ git = "https://github.com/gtk-rs/gtk4-rs"
|
||||||
package = "gtk4"
|
package = "gtk4"
|
||||||
|
|
||||||
[dependencies.libadwaita]
|
[dependencies.libadwaita]
|
||||||
git = "https://gitlab.gnome.org/bilelmoussaoui/libadwaita-rs"
|
git = "https://gitlab.gnome.org/World/Rust/libadwaita-rs.git"
|
||||||
package = "libadwaita"
|
package = "libadwaita"
|
||||||
|
|
||||||
[dependencies.pango]
|
[dependencies.pango]
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use crate::navigator::{NavigationHandle, Screen};
|
use crate::navigator::{NavigationHandle, Screen};
|
||||||
use crate::selectors::{EnsembleSelector, InstrumentSelector, PersonSelector};
|
use crate::selectors::{EnsembleSelector, InstrumentSelector, PersonSelector};
|
||||||
use crate::widgets::{Editor, Section, ButtonRow, Widget};
|
use crate::widgets::{ButtonRow, Editor, Section, Widget};
|
||||||
use gettextrs::gettext;
|
use gettextrs::gettext;
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use libadwaita::prelude::*;
|
use libadwaita::prelude::*;
|
||||||
use musicus_backend::db::{Performance, Person, Ensemble, Instrument};
|
use musicus_backend::db::{Ensemble, Instrument, Performance, Person};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
|
@ -40,8 +40,9 @@ impl Screen<Option<Performance>, Performance> for PerformanceEditor {
|
||||||
performer_list.append(&ensemble_row.get_widget());
|
performer_list.append(&ensemble_row.get_widget());
|
||||||
|
|
||||||
let performer_section = Section::new(&gettext("Performer"), &performer_list);
|
let performer_section = Section::new(&gettext("Performer"), &performer_list);
|
||||||
performer_section.set_subtitle(
|
performer_section.set_subtitle(&gettext(
|
||||||
&gettext("Select either a person or an ensemble as a performer."));
|
"Select either a person or an ensemble as a performer.",
|
||||||
|
));
|
||||||
|
|
||||||
let role_list = gtk::ListBoxBuilder::new()
|
let role_list = gtk::ListBoxBuilder::new()
|
||||||
.selection_mode(gtk::SelectionMode::None)
|
.selection_mode(gtk::SelectionMode::None)
|
||||||
|
|
@ -59,8 +60,9 @@ impl Screen<Option<Performance>, Performance> for PerformanceEditor {
|
||||||
role_list.append(&role_row.get_widget());
|
role_list.append(&role_row.get_widget());
|
||||||
|
|
||||||
let role_section = Section::new(&gettext("Role"), &role_list);
|
let role_section = Section::new(&gettext("Role"), &role_list);
|
||||||
role_section.set_subtitle(
|
role_section.set_subtitle(&gettext(
|
||||||
&gettext("Optionally, choose a role to specify what the performer does."));
|
"Optionally, choose a role to specify what the performer does.",
|
||||||
|
));
|
||||||
|
|
||||||
editor.add_content(&performer_section);
|
editor.add_content(&performer_section);
|
||||||
editor.add_content(&role_section);
|
editor.add_content(&role_section);
|
||||||
|
|
@ -102,7 +104,7 @@ impl Screen<Option<Performance>, Performance> for PerformanceEditor {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.ensemble_row.set_cb(clone!(@weak this => move || {
|
this.ensemble_row.set_cb(clone!(@weak this => move || {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(ensemble) = push!(this.handle, EnsembleSelector).await {
|
if let Some(ensemble) = push!(this.handle, EnsembleSelector).await {
|
||||||
this.show_person(None);
|
this.show_person(None);
|
||||||
|
|
@ -113,7 +115,7 @@ impl Screen<Option<Performance>, Performance> for PerformanceEditor {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.role_row.set_cb(clone!(@weak this => move || {
|
this.role_row.set_cb(clone!(@weak this => move || {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(role) = push!(this.handle, InstrumentSelector).await {
|
if let Some(role) = push!(this.handle, InstrumentSelector).await {
|
||||||
this.show_role(Some(&role));
|
this.show_role(Some(&role));
|
||||||
|
|
@ -122,10 +124,11 @@ impl Screen<Option<Performance>, Performance> for PerformanceEditor {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.reset_role_button.connect_clicked(clone!(@weak this => move |_| {
|
this.reset_role_button
|
||||||
this.show_role(None);
|
.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.role.replace(None);
|
this.show_role(None);
|
||||||
}));
|
this.role.replace(None);
|
||||||
|
}));
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ impl Screen<Option<Person>, Person> for PersonEditor {
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
this.editor.set_back_cb(clone!(@weak this => move || {
|
this.editor.set_back_cb(clone!(@weak this => move || {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -84,11 +84,11 @@ impl Screen<Option<Person>, Person> for PersonEditor {
|
||||||
|
|
||||||
this.first_name
|
this.first_name
|
||||||
.entry
|
.entry
|
||||||
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
||||||
|
|
||||||
this.last_name
|
this.last_name
|
||||||
.entry
|
.entry
|
||||||
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
||||||
|
|
||||||
this.validate();
|
this.validate();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use super::performance::PerformanceEditor;
|
use super::performance::PerformanceEditor;
|
||||||
|
use crate::navigator::{NavigationHandle, Screen};
|
||||||
use crate::selectors::WorkSelector;
|
use crate::selectors::WorkSelector;
|
||||||
use crate::widgets::{List, Widget};
|
use crate::widgets::{List, Widget};
|
||||||
use crate::navigator::{NavigationHandle, Screen};
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gettextrs::gettext;
|
use gettextrs::gettext;
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
|
|
@ -74,26 +74,27 @@ impl Screen<Option<Recording>, Recording> for RecordingEditor {
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.save_button.connect_clicked(clone!(@weak this => move |_| {
|
this.save_button
|
||||||
spawn!(@clone this, async move {
|
.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.widget.set_visible_child_name("loading");
|
spawn!(@clone this, async move {
|
||||||
match this.save().await {
|
this.widget.set_visible_child_name("loading");
|
||||||
Ok(recording) => {
|
match this.save().await {
|
||||||
this.handle.pop(Some(recording));
|
Ok(recording) => {
|
||||||
|
this.handle.pop(Some(recording));
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
this.info_bar.set_revealed(true);
|
||||||
|
this.widget.set_visible_child_name("content");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
});
|
||||||
this.info_bar.set_revealed(true);
|
}));
|
||||||
this.widget.set_visible_child_name("content");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
work_button.connect_clicked(clone!(@weak this => move |_| {
|
work_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(work) = push!(this.handle, WorkSelector).await {
|
if let Some(work) = push!(this.handle, WorkSelector).await {
|
||||||
this.work_selected(&work);
|
this.work_selected(&work);
|
||||||
|
|
@ -102,13 +103,13 @@ impl Screen<Option<Recording>, Recording> for RecordingEditor {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.performance_list.set_make_widget_cb(clone!(@weak this => move |index| {
|
this.performance_list.set_make_widget_cb(clone!(@weak this => @default-panic, move |index| {
|
||||||
let performance = &this.performances.borrow()[index];
|
let performance = &this.performances.borrow()[index];
|
||||||
|
|
||||||
let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic"));
|
let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic"));
|
||||||
delete_button.set_valign(gtk::Align::Center);
|
delete_button.set_valign(gtk::Align::Center);
|
||||||
|
|
||||||
delete_button.connect_clicked(clone!(@weak this => move |_| {
|
delete_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
let length = {
|
let length = {
|
||||||
let mut performances = this.performances.borrow_mut();
|
let mut performances = this.performances.borrow_mut();
|
||||||
performances.remove(index);
|
performances.remove(index);
|
||||||
|
|
@ -121,7 +122,7 @@ impl Screen<Option<Recording>, Recording> for RecordingEditor {
|
||||||
let edit_button = gtk::Button::from_icon_name(Some("document-edit-symbolic"));
|
let edit_button = gtk::Button::from_icon_name(Some("document-edit-symbolic"));
|
||||||
edit_button.set_valign(gtk::Align::Center);
|
edit_button.set_valign(gtk::Align::Center);
|
||||||
|
|
||||||
edit_button.connect_clicked(clone!(@weak this => move |_| {
|
edit_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
let performance = &this.performances.borrow()[index];
|
let performance = &this.performances.borrow()[index];
|
||||||
if let Some(performance) = push!(this.handle, PerformanceEditor, Some(performance.to_owned())).await {
|
if let Some(performance) = push!(this.handle, PerformanceEditor, Some(performance.to_owned())).await {
|
||||||
|
|
@ -190,16 +191,17 @@ impl RecordingEditor {
|
||||||
.borrow()
|
.borrow()
|
||||||
.clone()
|
.clone()
|
||||||
.expect("Tried to create recording without work!"),
|
.expect("Tried to create recording without work!"),
|
||||||
comment: self.comment_entry.get_text().to_string(),
|
comment: self.comment_entry.text().to_string(),
|
||||||
performances: self.performances.borrow().clone(),
|
performances: self.performances.borrow().clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let upload = self.upload_switch.get_active();
|
let upload = self.upload_switch.state();
|
||||||
if upload {
|
if upload {
|
||||||
self.handle.backend.cl().post_recording(&recording).await?;
|
self.handle.backend.cl().post_recording(&recording).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.handle.backend
|
self.handle
|
||||||
|
.backend
|
||||||
.db()
|
.db()
|
||||||
.update_recording(recording.clone().into())
|
.update_recording(recording.clone().into())
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -117,12 +117,12 @@ impl Screen<Option<Work>, Work> for WorkEditor {
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.save_button
|
this.save_button
|
||||||
.connect_clicked(clone!(@weak this => move |_| {
|
.connect_clicked(clone!(@weak this => move |_| {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
this.widget.set_visible_child_name("loading");
|
this.widget.set_visible_child_name("loading");
|
||||||
match this.save().await {
|
match this.save().await {
|
||||||
|
|
@ -137,7 +137,7 @@ impl Screen<Option<Work>, Work> for WorkEditor {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
composer_button.connect_clicked(clone!(@weak this => move |_| {
|
composer_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(person) = push!(this.handle, PersonSelector).await {
|
if let Some(person) = push!(this.handle, PersonSelector).await {
|
||||||
this.show_composer(&person);
|
this.show_composer(&person);
|
||||||
|
|
@ -147,10 +147,10 @@ impl Screen<Option<Work>, Work> for WorkEditor {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.title_entry
|
this.title_entry
|
||||||
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
||||||
|
|
||||||
this.instrument_list
|
this.instrument_list.set_make_widget_cb(
|
||||||
.set_make_widget_cb(clone!(@weak this => move |index| {
|
clone!(@weak this => @default-panic, move |index| {
|
||||||
let instrument = &this.instruments.borrow()[index];
|
let instrument = &this.instruments.borrow()[index];
|
||||||
|
|
||||||
let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic"));
|
let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic"));
|
||||||
|
|
@ -171,9 +171,10 @@ impl Screen<Option<Work>, Work> for WorkEditor {
|
||||||
row.add_suffix(&delete_button);
|
row.add_suffix(&delete_button);
|
||||||
|
|
||||||
row.upcast()
|
row.upcast()
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
add_instrument_button.connect_clicked(clone!(@weak this => move |_| {
|
add_instrument_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(instrument) = push!(this.handle, InstrumentSelector).await {
|
if let Some(instrument) = push!(this.handle, InstrumentSelector).await {
|
||||||
let length = {
|
let length = {
|
||||||
|
|
@ -187,13 +188,13 @@ impl Screen<Option<Work>, Work> for WorkEditor {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.part_list.set_make_widget_cb(clone!(@weak this => move |index| {
|
this.part_list.set_make_widget_cb(clone!(@weak this => @default-panic, move |index| {
|
||||||
let pos = &this.structure.borrow()[index];
|
let pos = &this.structure.borrow()[index];
|
||||||
|
|
||||||
let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic"));
|
let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic"));
|
||||||
delete_button.set_valign(gtk::Align::Center);
|
delete_button.set_valign(gtk::Align::Center);
|
||||||
|
|
||||||
delete_button.connect_clicked(clone!(@weak this => move |_| {
|
delete_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
let length = {
|
let length = {
|
||||||
let mut structure = this.structure.borrow_mut();
|
let mut structure = this.structure.borrow_mut();
|
||||||
structure.remove(index);
|
structure.remove(index);
|
||||||
|
|
@ -206,7 +207,7 @@ impl Screen<Option<Work>, Work> for WorkEditor {
|
||||||
let edit_button = gtk::Button::from_icon_name(Some("document-edit-symbolic"));
|
let edit_button = gtk::Button::from_icon_name(Some("document-edit-symbolic"));
|
||||||
edit_button.set_valign(gtk::Align::Center);
|
edit_button.set_valign(gtk::Align::Center);
|
||||||
|
|
||||||
edit_button.connect_clicked(clone!(@weak this => move |_| {
|
edit_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
match this.structure.borrow()[index].clone() {
|
match this.structure.borrow()[index].clone() {
|
||||||
PartOrSection::Part(part) => {
|
PartOrSection::Part(part) => {
|
||||||
|
|
@ -251,7 +252,7 @@ impl Screen<Option<Work>, Work> for WorkEditor {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.part_list
|
this.part_list
|
||||||
.set_move_cb(clone!(@weak this => move |old_index, new_index| {
|
.set_move_cb(clone!(@weak this => move |old_index, new_index| {
|
||||||
let length = {
|
let length = {
|
||||||
let mut structure = this.structure.borrow_mut();
|
let mut structure = this.structure.borrow_mut();
|
||||||
structure.swap(old_index, new_index);
|
structure.swap(old_index, new_index);
|
||||||
|
|
@ -261,7 +262,7 @@ impl Screen<Option<Work>, Work> for WorkEditor {
|
||||||
this.part_list.update(length);
|
this.part_list.update(length);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
add_part_button.connect_clicked(clone!(@weak this => move |_| {
|
add_part_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(part) = push!(this.handle, WorkPartEditor, None).await {
|
if let Some(part) = push!(this.handle, WorkPartEditor, None).await {
|
||||||
let length = {
|
let length = {
|
||||||
|
|
@ -312,9 +313,8 @@ impl WorkEditor {
|
||||||
|
|
||||||
/// Validate inputs and enable/disable saving.
|
/// Validate inputs and enable/disable saving.
|
||||||
fn validate(&self) {
|
fn validate(&self) {
|
||||||
self.save_button.set_sensitive(
|
self.save_button
|
||||||
!self.title_entry.get_text().is_empty() && self.composer.borrow().is_some(),
|
.set_sensitive(!self.title_entry.text().is_empty() && self.composer.borrow().is_some());
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Save the work and possibly upload it to the server.
|
/// Save the work and possibly upload it to the server.
|
||||||
|
|
@ -337,7 +337,7 @@ impl WorkEditor {
|
||||||
|
|
||||||
let work = Work {
|
let work = Work {
|
||||||
id: self.id.clone(),
|
id: self.id.clone(),
|
||||||
title: self.title_entry.get_text().to_string(),
|
title: self.title_entry.text().to_string(),
|
||||||
composer: self
|
composer: self
|
||||||
.composer
|
.composer
|
||||||
.borrow()
|
.borrow()
|
||||||
|
|
@ -348,7 +348,7 @@ impl WorkEditor {
|
||||||
sections: sections,
|
sections: sections,
|
||||||
};
|
};
|
||||||
|
|
||||||
let upload = self.upload_switch.get_active();
|
let upload = self.upload_switch.state();
|
||||||
if upload {
|
if upload {
|
||||||
self.handle.backend.cl().post_work(&work).await?;
|
self.handle.backend.cl().post_work(&work).await?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,21 +39,21 @@ impl Screen<Option<WorkPart>, WorkPart> for WorkPartEditor {
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.save_button
|
this.save_button
|
||||||
.connect_clicked(clone!(@weak this => move |_| {
|
.connect_clicked(clone!(@weak this => move |_| {
|
||||||
let section = WorkPart {
|
let section = WorkPart {
|
||||||
title: this.title_entry.get_text().to_string(),
|
title: this.title_entry.text().to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.handle.pop(Some(section));
|
this.handle.pop(Some(section));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.title_entry
|
this.title_entry
|
||||||
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
||||||
|
|
||||||
this.validate();
|
this.validate();
|
||||||
|
|
||||||
|
|
@ -65,7 +65,7 @@ impl WorkPartEditor {
|
||||||
/// Validate inputs and enable/disable saving.
|
/// Validate inputs and enable/disable saving.
|
||||||
fn validate(&self) {
|
fn validate(&self) {
|
||||||
self.save_button
|
self.save_button
|
||||||
.set_sensitive(!self.title_entry.get_text().is_empty());
|
.set_sensitive(!self.title_entry.text().is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,22 +39,22 @@ impl Screen<Option<WorkSection>, WorkSection> for WorkSectionEditor {
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.save_button
|
this.save_button
|
||||||
.connect_clicked(clone!(@weak this => move |_| {
|
.connect_clicked(clone!(@weak this => move |_| {
|
||||||
let section = WorkSection {
|
let section = WorkSection {
|
||||||
before_index: 0,
|
before_index: 0,
|
||||||
title: this.title_entry.get_text().to_string(),
|
title: this.title_entry.text().to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.handle.pop(Some(section));
|
this.handle.pop(Some(section));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.title_entry
|
this.title_entry
|
||||||
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
||||||
|
|
||||||
this.validate();
|
this.validate();
|
||||||
|
|
||||||
|
|
@ -66,7 +66,7 @@ impl WorkSectionEditor {
|
||||||
/// Validate inputs and enable/disable saving.
|
/// Validate inputs and enable/disable saving.
|
||||||
fn validate(&self) {
|
fn validate(&self) {
|
||||||
self.save_button
|
self.save_button
|
||||||
.set_sensitive(!self.title_entry.get_text().is_empty());
|
.set_sensitive(!self.title_entry.text().is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ use glib::clone;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk_macros::get_widget;
|
use gtk_macros::get_widget;
|
||||||
use libadwaita::prelude::*;
|
use libadwaita::prelude::*;
|
||||||
use musicus_backend::Error;
|
|
||||||
use musicus_backend::db::Medium;
|
use musicus_backend::db::Medium;
|
||||||
use musicus_backend::import::ImportSession;
|
use musicus_backend::import::ImportSession;
|
||||||
|
use musicus_backend::Error;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|
@ -31,7 +31,7 @@ impl ImportScreen {
|
||||||
|
|
||||||
let this = self;
|
let this = self;
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
let mediums: Result<Vec<Medium>, Error> = if this.server_check_button.get_active() {
|
let mediums: Result<Vec<Medium>, Error> = if this.server_check_button.is_active() {
|
||||||
this.handle.backend.cl().get_mediums_by_discid(this.session.source_id()).await.map_err(|err| err.into())
|
this.handle.backend.cl().get_mediums_by_discid(this.session.source_id()).await.map_err(|err| err.into())
|
||||||
} else {
|
} else {
|
||||||
this.handle.backend.db().get_mediums_by_source_id(this.session.source_id()).await.map_err(|err| err.into())
|
this.handle.backend.db().get_mediums_by_source_id(this.session.source_id()).await.map_err(|err| err.into())
|
||||||
|
|
@ -56,9 +56,9 @@ impl ImportScreen {
|
||||||
|
|
||||||
/// Populate the list of matches
|
/// Populate the list of matches
|
||||||
fn show_matches(self: &Rc<Self>, mediums: Vec<Medium>) {
|
fn show_matches(self: &Rc<Self>, mediums: Vec<Medium>) {
|
||||||
if let Some(mut child) = self.matching_list.get_first_child() {
|
if let Some(mut child) = self.matching_list.first_child() {
|
||||||
loop {
|
loop {
|
||||||
let next_child = child.get_next_sibling();
|
let next_child = child.next_sibling();
|
||||||
self.matching_list.remove(&child);
|
self.matching_list.remove(&child);
|
||||||
|
|
||||||
match next_child {
|
match next_child {
|
||||||
|
|
@ -77,7 +77,7 @@ impl ImportScreen {
|
||||||
.activatable(true)
|
.activatable(true)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
let medium = medium.clone();
|
let medium = medium.clone();
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(()) = push!(this.handle, MediumPreview, (this.session.clone(), medium.clone())).await {
|
if let Some(()) = push!(this.handle, MediumPreview, (this.session.clone(), medium.clone())).await {
|
||||||
|
|
@ -133,20 +133,21 @@ impl Screen<Arc<ImportSession>, ()> for ImportScreen {
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.server_check_button.connect_toggled(clone!(@weak this => move |_| {
|
this.server_check_button
|
||||||
this.handle.backend.set_use_server(this.server_check_button.get_active());
|
.connect_toggled(clone!(@weak this => move |_| {
|
||||||
|
this.handle.backend.set_use_server(this.server_check_button.is_active());
|
||||||
|
this.load_matches();
|
||||||
|
}));
|
||||||
|
|
||||||
|
try_again_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.load_matches();
|
this.load_matches();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
try_again_button.connect_clicked(clone!(@weak this => move |_| {
|
select_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.load_matches();
|
|
||||||
}));
|
|
||||||
|
|
||||||
select_button.connect_clicked(clone!(@weak this => move |_| {
|
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(medium) = push!(this.handle, MediumSelector).await {
|
if let Some(medium) = push!(this.handle, MediumSelector).await {
|
||||||
this.select_medium(medium);
|
this.select_medium(medium);
|
||||||
|
|
@ -154,7 +155,7 @@ impl Screen<Arc<ImportSession>, ()> for ImportScreen {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
add_button.connect_clicked(clone!(@weak this => move |_| {
|
add_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(medium) = push!(this.handle, MediumEditor, (Arc::clone(&this.session), None)).await {
|
if let Some(medium) = push!(this.handle, MediumEditor, (Arc::clone(&this.session), None)).await {
|
||||||
this.select_medium(medium);
|
this.select_medium(medium);
|
||||||
|
|
|
||||||
|
|
@ -66,12 +66,12 @@ impl Screen<(Arc<ImportSession>, Option<Medium>), Medium> for MediumEditor {
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.done_button
|
this.done_button
|
||||||
.connect_clicked(clone!(@weak this => move |_| {
|
.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.widget.set_visible_child_name("loading");
|
this.widget.set_visible_child_name("loading");
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
match this.save().await {
|
match this.save().await {
|
||||||
|
|
@ -85,9 +85,9 @@ impl Screen<(Arc<ImportSession>, Option<Medium>), Medium> for MediumEditor {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.name_entry
|
this.name_entry
|
||||||
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
||||||
|
|
||||||
add_button.connect_clicked(clone!(@weak this => move |_| {
|
add_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(track_set) = push!(this.handle, TrackSetEditor, Arc::clone(&this.session)).await {
|
if let Some(track_set) = push!(this.handle, TrackSetEditor, Arc::clone(&this.session)).await {
|
||||||
let length = {
|
let length = {
|
||||||
|
|
@ -102,12 +102,13 @@ impl Screen<(Arc<ImportSession>, Option<Medium>), Medium> for MediumEditor {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.publish_switch.connect_property_state_notify(clone!(@weak this => move |_| {
|
this.publish_switch
|
||||||
this.handle.backend.set_use_server(this.publish_switch.get_state());
|
.connect_state_notify(clone!(@weak this => move |_| {
|
||||||
}));
|
this.handle.backend.set_use_server(this.publish_switch.state());
|
||||||
|
}));
|
||||||
|
|
||||||
this.track_set_list
|
this.track_set_list.set_make_widget_cb(
|
||||||
.set_make_widget_cb(clone!(@weak this => move |index| {
|
clone!(@weak this => @default-panic, move |index| {
|
||||||
let track_set = &this.track_sets.borrow()[index];
|
let track_set = &this.track_sets.borrow()[index];
|
||||||
|
|
||||||
let title = track_set.recording.work.get_title();
|
let title = track_set.recording.work.get_title();
|
||||||
|
|
@ -126,18 +127,19 @@ impl Screen<(Arc<ImportSession>, Option<Medium>), Medium> for MediumEditor {
|
||||||
row.add_suffix(&edit_button);
|
row.add_suffix(&edit_button);
|
||||||
row.set_activatable_widget(Some(&edit_button));
|
row.set_activatable_widget(Some(&edit_button));
|
||||||
|
|
||||||
edit_button.connect_clicked(clone!(@weak this => move |_| {
|
edit_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
// TODO: Implement editing.
|
// TODO: Implement editing.
|
||||||
}));
|
}));
|
||||||
|
|
||||||
row.upcast()
|
row.upcast()
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
try_again_button.connect_clicked(clone!(@weak this => move |_| {
|
try_again_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.widget.set_visible_child_name("content");
|
this.widget.set_visible_child_name("content");
|
||||||
}));
|
}));
|
||||||
|
|
||||||
cancel_button.connect_clicked(clone!(@weak this => move |_| {
|
cancel_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -182,7 +184,7 @@ impl MediumEditor {
|
||||||
/// Validate inputs and enable/disable saving.
|
/// Validate inputs and enable/disable saving.
|
||||||
fn validate(&self) {
|
fn validate(&self) {
|
||||||
self.done_button.set_sensitive(
|
self.done_button.set_sensitive(
|
||||||
!self.name_entry.get_text().is_empty() && !self.track_sets.borrow().is_empty(),
|
!self.name_entry.text().is_empty() && !self.track_sets.borrow().is_empty(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,12 +209,12 @@ impl MediumEditor {
|
||||||
|
|
||||||
let medium = Medium {
|
let medium = Medium {
|
||||||
id: generate_id(),
|
id: generate_id(),
|
||||||
name: self.name_entry.get_text().to_string(),
|
name: self.name_entry.text().to_string(),
|
||||||
discid: Some(self.session.source_id().to_owned()),
|
discid: Some(self.session.source_id().to_owned()),
|
||||||
tracks: tracks,
|
tracks: tracks,
|
||||||
};
|
};
|
||||||
|
|
||||||
let upload = self.publish_switch.get_active();
|
let upload = self.publish_switch.state();
|
||||||
if upload {
|
if upload {
|
||||||
self.handle.backend.cl().post_medium(&medium).await?;
|
self.handle.backend.cl().post_medium(&medium).await?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,11 +60,11 @@ impl Screen<(Arc<ImportSession>, Medium), ()> for MediumPreview {
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
edit_button.connect_clicked(clone!(@weak this => move |_| {
|
edit_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
let old_medium = this.medium.borrow().clone().unwrap();
|
let old_medium = this.medium.borrow().clone().unwrap();
|
||||||
if let Some(medium) = push!(this.handle, MediumEditor, (this.session.clone(), Some(old_medium))).await {
|
if let Some(medium) = push!(this.handle, MediumEditor, (this.session.clone(), Some(old_medium))).await {
|
||||||
|
|
@ -74,7 +74,7 @@ impl Screen<(Arc<ImportSession>, Medium), ()> for MediumPreview {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.import_button
|
this.import_button
|
||||||
.connect_clicked(clone!(@weak this => move |_| {
|
.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.widget.set_visible_child_name("loading");
|
this.widget.set_visible_child_name("loading");
|
||||||
|
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
|
|
@ -88,7 +88,7 @@ impl Screen<(Arc<ImportSession>, Medium), ()> for MediumPreview {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
try_again_button.connect_clicked(clone!(@weak this => move |_| {
|
try_again_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.widget.set_visible_child_name("content");
|
this.widget.set_visible_child_name("content");
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -116,11 +116,11 @@ impl MediumPreview {
|
||||||
fn set_medium(&self, medium: Medium) {
|
fn set_medium(&self, medium: Medium) {
|
||||||
self.name_label.set_text(&medium.name);
|
self.name_label.set_text(&medium.name);
|
||||||
|
|
||||||
if let Some(widget) = self.medium_box.get_first_child() {
|
if let Some(widget) = self.medium_box.first_child() {
|
||||||
let mut child = widget;
|
let mut child = widget;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let next_child = child.get_next_sibling();
|
let next_child = child.next_sibling();
|
||||||
self.medium_box.remove(&child);
|
self.medium_box.remove(&child);
|
||||||
|
|
||||||
match next_child {
|
match next_child {
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,11 @@ impl Screen<(), ()> for SourceSelector {
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
folder_button.connect_clicked(clone!(@weak this => move |_| {
|
folder_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
let dialog = gtk::FileChooserDialog::new(
|
let dialog = gtk::FileChooserDialog::new(
|
||||||
Some(&gettext("Select folder")),
|
Some(&gettext("Select folder")),
|
||||||
Some(&this.handle.window),
|
Some(&this.handle.window),
|
||||||
|
|
@ -54,12 +54,12 @@ impl Screen<(), ()> for SourceSelector {
|
||||||
|
|
||||||
dialog.set_modal(true);
|
dialog.set_modal(true);
|
||||||
|
|
||||||
dialog.connect_response(clone!(@weak this => move |dialog, response| {
|
dialog.connect_response(clone!(@weak this => move |dialog, response| {
|
||||||
dialog.hide();
|
dialog.hide();
|
||||||
|
|
||||||
if let gtk::ResponseType::Accept = response {
|
if let gtk::ResponseType::Accept = response {
|
||||||
if let Some(file) = dialog.get_file() {
|
if let Some(file) = dialog.file() {
|
||||||
if let Some(path) = file.get_path() {
|
if let Some(path) = file.path() {
|
||||||
this.widget.set_visible_child_name("loading");
|
this.widget.set_visible_child_name("loading");
|
||||||
|
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
|
|
@ -82,7 +82,7 @@ impl Screen<(), ()> for SourceSelector {
|
||||||
dialog.show();
|
dialog.show();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
disc_button.connect_clicked(clone!(@weak this => move |_| {
|
disc_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.widget.set_visible_child_name("loading");
|
this.widget.set_visible_child_name("loading");
|
||||||
|
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
|
|
@ -99,7 +99,7 @@ impl Screen<(), ()> for SourceSelector {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
try_again_button.connect_clicked(clone!(@weak this => move |_| {
|
try_again_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.widget.set_visible_child_name("content");
|
this.widget.set_visible_child_name("content");
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,10 @@ pub struct TrackEditor {
|
||||||
|
|
||||||
impl Screen<(Recording, Vec<usize>), Vec<usize>> for TrackEditor {
|
impl Screen<(Recording, Vec<usize>), Vec<usize>> for TrackEditor {
|
||||||
/// Create a new track editor.
|
/// Create a new track editor.
|
||||||
fn new((recording, selection): (Recording, Vec<usize>), handle: NavigationHandle<Vec<usize>>) -> Rc<Self> {
|
fn new(
|
||||||
|
(recording, selection): (Recording, Vec<usize>),
|
||||||
|
handle: NavigationHandle<Vec<usize>>,
|
||||||
|
) -> Rc<Self> {
|
||||||
// Create UI
|
// Create UI
|
||||||
|
|
||||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/track_editor.ui");
|
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/track_editor.ui");
|
||||||
|
|
@ -41,11 +44,11 @@ impl Screen<(Recording, Vec<usize>), Vec<usize>> for TrackEditor {
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
select_button.connect_clicked(clone!(@weak this => move |_| {
|
select_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
let selection = this.selection.borrow().clone();
|
let selection = this.selection.borrow().clone();
|
||||||
this.handle.pop(Some(selection));
|
this.handle.pop(Some(selection));
|
||||||
}));
|
}));
|
||||||
|
|
@ -54,9 +57,9 @@ impl Screen<(Recording, Vec<usize>), Vec<usize>> for TrackEditor {
|
||||||
let check = gtk::CheckButton::new();
|
let check = gtk::CheckButton::new();
|
||||||
check.set_active(this.selection.borrow().contains(&index));
|
check.set_active(this.selection.borrow().contains(&index));
|
||||||
|
|
||||||
check.connect_toggled(clone!(@weak this => move |check| {
|
check.connect_toggled(clone!(@weak this => move |check| {
|
||||||
let mut selection = this.selection.borrow_mut();
|
let mut selection = this.selection.borrow_mut();
|
||||||
if check.get_active() {
|
if check.is_active() {
|
||||||
selection.push(index);
|
selection.push(index);
|
||||||
} else {
|
} else {
|
||||||
if let Some(pos) = selection.iter().position(|part| *part == index) {
|
if let Some(pos) = selection.iter().position(|part| *part == index) {
|
||||||
|
|
|
||||||
|
|
@ -46,23 +46,24 @@ impl Screen<Arc<ImportSession>, Vec<usize>> for TrackSelector {
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.select_button.connect_clicked(clone!(@weak this => move |_| {
|
this.select_button
|
||||||
let selection = this.selection.borrow().clone();
|
.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.handle.pop(Some(selection));
|
let selection = this.selection.borrow().clone();
|
||||||
}));
|
this.handle.pop(Some(selection));
|
||||||
|
}));
|
||||||
|
|
||||||
let tracks = this.session.tracks();
|
let tracks = this.session.tracks();
|
||||||
|
|
||||||
for (index, track) in tracks.iter().enumerate() {
|
for (index, track) in tracks.iter().enumerate() {
|
||||||
let check = gtk::CheckButton::new();
|
let check = gtk::CheckButton::new();
|
||||||
|
|
||||||
check.connect_toggled(clone!(@weak this => move |check| {
|
check.connect_toggled(clone!(@weak this => move |check| {
|
||||||
let mut selection = this.selection.borrow_mut();
|
let mut selection = this.selection.borrow_mut();
|
||||||
if check.get_active() {
|
if check.is_active() {
|
||||||
selection.push(index);
|
selection.push(index);
|
||||||
} else {
|
} else {
|
||||||
if let Some(pos) = selection.iter().position(|part| *part == index) {
|
if let Some(pos) = selection.iter().position(|part| *part == index) {
|
||||||
|
|
|
||||||
|
|
@ -74,12 +74,12 @@ impl Screen<Arc<ImportSession>, TrackSetData> for TrackSetEditor {
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.save_button
|
this.save_button
|
||||||
.connect_clicked(clone!(@weak this => move |_| {
|
.connect_clicked(clone!(@weak this => move |_| {
|
||||||
let data = TrackSetData {
|
let data = TrackSetData {
|
||||||
recording: this.recording.borrow().clone().unwrap(),
|
recording: this.recording.borrow().clone().unwrap(),
|
||||||
tracks: this.tracks.borrow().clone(),
|
tracks: this.tracks.borrow().clone(),
|
||||||
|
|
@ -88,7 +88,7 @@ impl Screen<Arc<ImportSession>, TrackSetData> for TrackSetEditor {
|
||||||
this.handle.pop(Some(data));
|
this.handle.pop(Some(data));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
select_recording_button.connect_clicked(clone!(@weak this => move |_| {
|
select_recording_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(recording) = push!(this.handle, RecordingSelector).await {
|
if let Some(recording) = push!(this.handle, RecordingSelector).await {
|
||||||
this.recording.replace(Some(recording));
|
this.recording.replace(Some(recording));
|
||||||
|
|
@ -97,7 +97,7 @@ impl Screen<Arc<ImportSession>, TrackSetData> for TrackSetEditor {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
edit_tracks_button.connect_clicked(clone!(@weak this => move |_| {
|
edit_tracks_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(selection) = push!(this.handle, TrackSelector, Arc::clone(&this.session)).await {
|
if let Some(selection) = push!(this.handle, TrackSelector, Arc::clone(&this.session)).await {
|
||||||
let mut tracks = Vec::new();
|
let mut tracks = Vec::new();
|
||||||
|
|
@ -119,7 +119,7 @@ impl Screen<Arc<ImportSession>, TrackSetData> for TrackSetEditor {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.track_list.set_make_widget_cb(clone!(@weak this => move |index| {
|
this.track_list.set_make_widget_cb(clone!(@weak this => @default-panic, move |index| {
|
||||||
let track = &this.tracks.borrow()[index];
|
let track = &this.tracks.borrow()[index];
|
||||||
|
|
||||||
let mut title_parts = Vec::<String>::new();
|
let mut title_parts = Vec::<String>::new();
|
||||||
|
|
@ -152,7 +152,7 @@ impl Screen<Arc<ImportSession>, TrackSetData> for TrackSetEditor {
|
||||||
row.add_suffix(&edit_button);
|
row.add_suffix(&edit_button);
|
||||||
row.set_activatable_widget(Some(&edit_button));
|
row.set_activatable_widget(Some(&edit_button));
|
||||||
|
|
||||||
edit_button.connect_clicked(clone!(@weak this => move |_| {
|
edit_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
let recording = this.recording.borrow().clone();
|
let recording = this.recording.borrow().clone();
|
||||||
if let Some(recording) = recording {
|
if let Some(recording) = recording {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
|
|
|
||||||
|
|
@ -68,19 +68,13 @@ macro_rules! replace {
|
||||||
/// });
|
/// });
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! spawn {
|
macro_rules! spawn {
|
||||||
($future:expr) => {
|
($future:expr) => {{
|
||||||
{
|
let context = glib::MainContext::default();
|
||||||
let context = glib::MainContext::default();
|
context.spawn_local($future);
|
||||||
context.spawn_local($future);
|
}};
|
||||||
|
(@clone $data:ident, $future:expr) => {{
|
||||||
}
|
let context = glib::MainContext::default();
|
||||||
};
|
let $data = Rc::clone(&$data);
|
||||||
(@clone $data:ident, $future:expr) => {
|
context.spawn_local($future);
|
||||||
{
|
}};
|
||||||
let context = glib::MainContext::default();
|
|
||||||
let $data = Rc::clone(&$data);
|
|
||||||
context.spawn_local($future);
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,7 @@ fn main() {
|
||||||
libadwaita::init();
|
libadwaita::init();
|
||||||
resources::init().expect("Failed to initialize resources!");
|
resources::init().expect("Failed to initialize resources!");
|
||||||
|
|
||||||
let app = gtk::Application::new(Some("de.johrpan.musicus"), gio::ApplicationFlags::empty())
|
let app = gtk::Application::new(Some("de.johrpan.musicus"), gio::ApplicationFlags::empty());
|
||||||
.expect("Failed to initialize GTK application!");
|
|
||||||
|
|
||||||
let window: RefCell<Option<Rc<Window>>> = RefCell::new(None);
|
let window: RefCell<Option<Rc<Window>>> = RefCell::new(None);
|
||||||
|
|
||||||
app.connect_activate(clone!(@strong app => move |_| {
|
app.connect_activate(clone!(@strong app => move |_| {
|
||||||
|
|
@ -43,6 +41,5 @@ fn main() {
|
||||||
window.as_ref().unwrap().present();
|
window.as_ref().unwrap().present();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let args = std::env::args().collect::<Vec<String>>();
|
app.run();
|
||||||
app.run(&args);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@ pub use window::*;
|
||||||
/// that optionally resolves to a specific return value.
|
/// that optionally resolves to a specific return value.
|
||||||
pub trait Screen<I, O>: Widget {
|
pub trait Screen<I, O>: Widget {
|
||||||
/// Create a new screen and initialize it with the provided input value.
|
/// Create a new screen and initialize it with the provided input value.
|
||||||
fn new(input: I, navigation_handle: NavigationHandle<O>) -> Rc<Self> where Self: Sized;
|
fn new(input: I, navigation_handle: NavigationHandle<O>) -> Rc<Self>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An accessor to navigation functionality for screens.
|
/// An accessor to navigation functionality for screens.
|
||||||
|
|
@ -46,7 +48,9 @@ impl<O> NavigationHandle<O> {
|
||||||
pub fn pop(&self, output: Option<O>) {
|
pub fn pop(&self, output: Option<O>) {
|
||||||
self.unwrap_navigator().pop();
|
self.unwrap_navigator().pop();
|
||||||
|
|
||||||
let sender = self.sender.take()
|
let sender = self
|
||||||
|
.sender
|
||||||
|
.take()
|
||||||
.expect("Tried to send result from screen through a dropped sender.");
|
.expect("Tried to send result from screen through a dropped sender.");
|
||||||
|
|
||||||
if sender.send(output).is_err() {
|
if sender.send(output).is_err() {
|
||||||
|
|
@ -112,11 +116,12 @@ impl Navigator {
|
||||||
back_cb: RefCell::new(None),
|
back_cb: RefCell::new(None),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.widget.connect_property_transition_running_notify(clone!(@strong this => move |_| {
|
this.widget
|
||||||
if !this.widget.get_transition_running() {
|
.connect_transition_running_notify(clone!(@strong this => move |_| {
|
||||||
this.clear_old_widgets();
|
if !this.widget.is_transition_running() {
|
||||||
}
|
this.clear_old_widgets();
|
||||||
}));
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
@ -135,7 +140,7 @@ impl Navigator {
|
||||||
|
|
||||||
let receiver = self.push::<I, O, S>(input);
|
let receiver = self.push::<I, O, S>(input);
|
||||||
|
|
||||||
if !self.widget.get_transition_running() {
|
if !self.widget.is_transition_running() {
|
||||||
self.clear_old_widgets();
|
self.clear_old_widgets();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,7 +148,6 @@ impl Navigator {
|
||||||
receiver.await.unwrap_or(None)
|
receiver.await.unwrap_or(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Drop all screens and go back to the initial screen. The back callback
|
/// Drop all screens and go back to the initial screen. The back callback
|
||||||
/// will not be called.
|
/// will not be called.
|
||||||
pub fn reset(&self) {
|
pub fn reset(&self) {
|
||||||
|
|
@ -153,7 +157,7 @@ impl Navigator {
|
||||||
self.old_widgets.borrow_mut().push(screen.get_widget());
|
self.old_widgets.borrow_mut().push(screen.get_widget());
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.widget.get_transition_running() {
|
if !self.widget.is_transition_running() {
|
||||||
self.clear_old_widgets();
|
self.clear_old_widgets();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +207,7 @@ impl Navigator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.widget.get_transition_running() {
|
if !self.widget.is_transition_running() {
|
||||||
self.clear_old_widgets();
|
self.clear_old_widgets();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ impl NavigatorWindow {
|
||||||
window.set_default_size(600, 424);
|
window.set_default_size(600, 424);
|
||||||
let placeholder = gtk::Label::new(None);
|
let placeholder = gtk::Label::new(None);
|
||||||
let navigator = Navigator::new(backend, &window, &placeholder);
|
let navigator = Navigator::new(backend, &window, &placeholder);
|
||||||
libadwaita::WindowExt::set_child(&window, Some(&navigator.widget));
|
window.set_child(Some(&navigator.widget));
|
||||||
|
|
||||||
let this = Rc::new(Self { navigator, window });
|
let this = Rc::new(Self { navigator, window });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use super::register::RegisterDialog;
|
use super::register::RegisterDialog;
|
||||||
use crate::push;
|
|
||||||
use crate::navigator::{NavigationHandle, Screen};
|
use crate::navigator::{NavigationHandle, Screen};
|
||||||
|
use crate::push;
|
||||||
use crate::widgets::Widget;
|
use crate::widgets::Widget;
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
|
|
@ -49,16 +49,16 @@ impl Screen<Option<LoginData>, Option<LoginData>> for LoginDialog {
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
cancel_button.connect_clicked(clone!(@weak this => move |_| {
|
cancel_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
login_button.connect_clicked(clone!(@weak this => move |_| {
|
login_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.widget.set_visible_child_name("loading");
|
this.widget.set_visible_child_name("loading");
|
||||||
|
|
||||||
let data = LoginData {
|
let data = LoginData {
|
||||||
username: this.username_entry.get_text().to_string(),
|
username: this.username_entry.text().to_string(),
|
||||||
password: this.password_entry.get_text().to_string(),
|
password: this.password_entry.text().to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
|
|
@ -72,7 +72,7 @@ impl Screen<Option<LoginData>, Option<LoginData>> for LoginDialog {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
register_button.connect_clicked(clone!(@weak this => move |_| {
|
register_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(data) = push!(this.handle, RegisterDialog).await {
|
if let Some(data) = push!(this.handle, RegisterDialog).await {
|
||||||
this.handle.pop(Some(Some(data)));
|
this.handle.pop(Some(Some(data)));
|
||||||
|
|
@ -80,7 +80,7 @@ impl Screen<Option<LoginData>, Option<LoginData>> for LoginDialog {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
logout_button.connect_clicked(clone!(@weak this => move |_| {
|
logout_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
this.handle.backend.set_login_data(None).await;
|
this.handle.backend.set_login_data(None).await;
|
||||||
this.handle.pop(Some(None));
|
this.handle.pop(Some(None));
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ use gettextrs::gettext;
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk_macros::get_widget;
|
use gtk_macros::get_widget;
|
||||||
use musicus_backend::Backend;
|
|
||||||
use libadwaita::prelude::*;
|
use libadwaita::prelude::*;
|
||||||
|
use musicus_backend::Backend;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
mod login;
|
mod login;
|
||||||
|
|
@ -64,8 +64,8 @@ impl Preferences {
|
||||||
|
|
||||||
dialog.connect_response(clone!(@strong this => move |dialog, response| {
|
dialog.connect_response(clone!(@strong this => move |dialog, response| {
|
||||||
if let gtk::ResponseType::Accept = response {
|
if let gtk::ResponseType::Accept = response {
|
||||||
if let Some(file) = dialog.get_file() {
|
if let Some(file) = dialog.file() {
|
||||||
if let Some(path) = file.get_path() {
|
if let Some(path) = file.path() {
|
||||||
this.music_library_path_row.set_subtitle(Some(path.to_str().unwrap()));
|
this.music_library_path_row.set_subtitle(Some(path.to_str().unwrap()));
|
||||||
|
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
|
|
|
||||||
|
|
@ -51,13 +51,13 @@ impl Screen<(), LoginData> for RegisterDialog {
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
cancel_button.connect_clicked(clone!(@weak this => move |_| {
|
cancel_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
register_button.connect_clicked(clone!(@weak this => move |_| {
|
register_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
let password = this.password_entry.get_text().to_string();
|
let password = this.password_entry.text().to_string();
|
||||||
let repeat = this.repeat_password_entry.get_text().to_string();
|
let repeat = this.repeat_password_entry.text().to_string();
|
||||||
|
|
||||||
if password != repeat {
|
if password != repeat {
|
||||||
// TODO: Show error and validate other input.
|
// TODO: Show error and validate other input.
|
||||||
|
|
@ -65,10 +65,10 @@ impl Screen<(), LoginData> for RegisterDialog {
|
||||||
this.widget.set_visible_child_name("loading");
|
this.widget.set_visible_child_name("loading");
|
||||||
|
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
let username = this.username_entry.get_text().to_string();
|
let username = this.username_entry.text().to_string();
|
||||||
let email = this.email_entry.get_text().to_string();
|
let email = this.email_entry.text().to_string();
|
||||||
let captcha_id = this.captcha_id.borrow().clone().unwrap();
|
let captcha_id = this.captcha_id.borrow().clone().unwrap();
|
||||||
let answer = this.captcha_entry.get_text().to_string();
|
let answer = this.captcha_entry.text().to_string();
|
||||||
|
|
||||||
let email = if email.len() == 0 {
|
let email = if email.len() == 0 {
|
||||||
None
|
None
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ impl ServerDialog {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
set_button.connect_clicked(clone!(@strong this => move |_| {
|
set_button.connect_clicked(clone!(@strong this => move |_| {
|
||||||
let url = this.url_entry.get_text().to_string();
|
let url = this.url_entry.text().to_string();
|
||||||
this.backend.set_server_url(&url);
|
this.backend.set_server_url(&url);
|
||||||
|
|
||||||
if let Some(cb) = &*this.selected_cb.borrow() {
|
if let Some(cb) = &*this.selected_cb.borrow() {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use super::{MediumScreen, RecordingScreen};
|
use super::{MediumScreen, RecordingScreen};
|
||||||
use crate::editors::EnsembleEditor;
|
use crate::editors::EnsembleEditor;
|
||||||
use crate::navigator::{NavigatorWindow, NavigationHandle, Screen};
|
use crate::navigator::{NavigationHandle, NavigatorWindow, Screen};
|
||||||
use crate::widgets;
|
use crate::widgets;
|
||||||
use crate::widgets::{List, Section, Widget};
|
use crate::widgets::{List, Section, Widget};
|
||||||
use gettextrs::gettext;
|
use gettextrs::gettext;
|
||||||
|
|
@ -42,80 +42,90 @@ impl Screen<Ensemble, ()> for EnsembleScreen {
|
||||||
mediums: RefCell::new(Vec::new()),
|
mediums: RefCell::new(Vec::new()),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.widget.set_back_cb(clone!(@weak this => move || {
|
this.widget.set_back_cb(clone!(@weak this => move || {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
this.widget.add_action(
|
||||||
|
&gettext("Edit ensemble"),
|
||||||
|
clone!(@weak this => move || {
|
||||||
|
spawn!(@clone this, async move {
|
||||||
|
let window = NavigatorWindow::new(this.handle.backend.clone());
|
||||||
|
replace!(window.navigator, EnsembleEditor, Some(this.ensemble.clone())).await;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
this.widget.add_action(&gettext("Edit ensemble"), clone!(@weak this => move || {
|
this.widget.add_action(
|
||||||
spawn!(@clone this, async move {
|
&gettext("Delete ensemble"),
|
||||||
let window = NavigatorWindow::new(this.handle.backend.clone());
|
clone!(@weak this => move || {
|
||||||
replace!(window.navigator, EnsembleEditor, Some(this.ensemble.clone())).await;
|
spawn!(@clone this, async move {
|
||||||
});
|
this.handle.backend.db().delete_ensemble(&this.ensemble.id).await.unwrap();
|
||||||
}));
|
this.handle.backend.library_changed();
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
this.widget.add_action(&gettext("Delete ensemble"), clone!(@weak this => move || {
|
this.widget.set_search_cb(clone!(@weak this => move || {
|
||||||
spawn!(@clone this, async move {
|
|
||||||
this.handle.backend.db().delete_ensemble(&this.ensemble.id).await.unwrap();
|
|
||||||
this.handle.backend.library_changed();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.widget.set_search_cb(clone!(@weak this => move || {
|
|
||||||
this.recording_list.invalidate_filter();
|
this.recording_list.invalidate_filter();
|
||||||
this.medium_list.invalidate_filter();
|
this.medium_list.invalidate_filter();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.recording_list.set_make_widget_cb(clone!(@weak this => move |index| {
|
this.recording_list.set_make_widget_cb(
|
||||||
let recording = &this.recordings.borrow()[index];
|
clone!(@weak this => @default-panic, move |index| {
|
||||||
|
let recording = &this.recordings.borrow()[index];
|
||||||
|
|
||||||
let row = libadwaita::ActionRow::new();
|
let row = libadwaita::ActionRow::new();
|
||||||
row.set_activatable(true);
|
row.set_activatable(true);
|
||||||
row.set_title(Some(&recording.work.get_title()));
|
row.set_title(Some(&recording.work.get_title()));
|
||||||
row.set_subtitle(Some(&recording.get_performers()));
|
row.set_subtitle(Some(&recording.get_performers()));
|
||||||
|
|
||||||
let recording = recording.to_owned();
|
let recording = recording.to_owned();
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
let recording = recording.clone();
|
let recording = recording.clone();
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
push!(this.handle, RecordingScreen, recording.clone()).await;
|
push!(this.handle, RecordingScreen, recording.clone()).await;
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
row.upcast()
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.recording_list
|
||||||
|
.set_filter_cb(clone!(@weak this => @default-panic, move |index| {
|
||||||
|
let recording = &this.recordings.borrow()[index];
|
||||||
|
let search = this.widget.get_search();
|
||||||
|
let text = recording.work.get_title() + &recording.get_performers();
|
||||||
|
search.is_empty() || text.to_lowercase().contains(&search)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
row.upcast()
|
this.medium_list
|
||||||
}));
|
.set_make_widget_cb(clone!(@weak this => @default-panic, move |index| {
|
||||||
|
let medium = &this.mediums.borrow()[index];
|
||||||
|
|
||||||
this.recording_list.set_filter_cb(clone!(@weak this => move |index| {
|
let row = libadwaita::ActionRow::new();
|
||||||
let recording = &this.recordings.borrow()[index];
|
row.set_activatable(true);
|
||||||
let search = this.widget.get_search();
|
row.set_title(Some(&medium.name));
|
||||||
let text = recording.work.get_title() + &recording.get_performers();
|
|
||||||
search.is_empty() || text.to_lowercase().contains(&search)
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.medium_list.set_make_widget_cb(clone!(@weak this => move |index| {
|
let medium = medium.to_owned();
|
||||||
let medium = &this.mediums.borrow()[index];
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
|
let medium = medium.clone();
|
||||||
|
spawn!(@clone this, async move {
|
||||||
|
push!(this.handle, MediumScreen, medium.clone()).await;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
let row = libadwaita::ActionRow::new();
|
row.upcast()
|
||||||
row.set_activatable(true);
|
|
||||||
row.set_title(Some(&medium.name));
|
|
||||||
|
|
||||||
let medium = medium.to_owned();
|
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
|
||||||
let medium = medium.clone();
|
|
||||||
spawn!(@clone this, async move {
|
|
||||||
push!(this.handle, MediumScreen, medium.clone()).await;
|
|
||||||
});
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
row.upcast()
|
this.medium_list
|
||||||
}));
|
.set_filter_cb(clone!(@weak this => @default-panic, move |index| {
|
||||||
|
let medium = &this.mediums.borrow()[index];
|
||||||
this.medium_list.set_filter_cb(clone!(@weak this => move |index| {
|
let search = this.widget.get_search();
|
||||||
let medium = &this.mediums.borrow()[index];
|
let name = medium.name.to_lowercase();
|
||||||
let search = this.widget.get_search();
|
search.is_empty() || name.contains(&search)
|
||||||
let name = medium.name.to_lowercase();
|
}));
|
||||||
search.is_empty() || name.contains(&search)
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Load the content asynchronously.
|
// Load the content asynchronously.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use super::{EnsembleScreen, PersonScreen, PlayerScreen};
|
use super::{EnsembleScreen, PersonScreen, PlayerScreen};
|
||||||
use crate::config;
|
use crate::config;
|
||||||
use crate::import::SourceSelector;
|
use crate::import::SourceSelector;
|
||||||
use crate::navigator::{Navigator, NavigatorWindow, NavigationHandle, Screen};
|
use crate::navigator::{NavigationHandle, Navigator, NavigatorWindow, Screen};
|
||||||
use crate::preferences::Preferences;
|
use crate::preferences::Preferences;
|
||||||
use crate::widgets::{List, PlayerBar, Widget};
|
use crate::widgets::{List, PlayerBar, Widget};
|
||||||
use gettextrs::gettext;
|
use gettextrs::gettext;
|
||||||
|
|
@ -87,64 +87,67 @@ impl Screen<(), ()> for MainScreen {
|
||||||
poes: RefCell::new(Vec::new()),
|
poes: RefCell::new(Vec::new()),
|
||||||
});
|
});
|
||||||
|
|
||||||
preferences_action.connect_activate(clone!(@weak this => move |_, _| {
|
preferences_action.connect_activate(clone!(@weak this => move |_, _| {
|
||||||
Preferences::new(Rc::clone(&this.handle.backend), &this.handle.window).show();
|
Preferences::new(Rc::clone(&this.handle.backend), &this.handle.window).show();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
about_action.connect_activate(clone!(@weak this => move |_, _| {
|
about_action.connect_activate(clone!(@weak this => move |_, _| {
|
||||||
this.show_about_dialog();
|
this.show_about_dialog();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
add_button.connect_clicked(clone!(@weak this => move |_| {
|
add_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
let window = NavigatorWindow::new(Rc::clone(&this.handle.backend));
|
let window = NavigatorWindow::new(Rc::clone(&this.handle.backend));
|
||||||
replace!(window.navigator, SourceSelector).await;
|
replace!(window.navigator, SourceSelector).await;
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.search_entry.connect_search_changed(clone!(@weak this => move |_| {
|
this.search_entry
|
||||||
this.poe_list.invalidate_filter();
|
.connect_search_changed(clone!(@weak this => move |_| {
|
||||||
}));
|
this.poe_list.invalidate_filter();
|
||||||
|
|
||||||
this.poe_list.set_make_widget_cb(clone!(@weak this => move |index| {
|
|
||||||
let poe = &this.poes.borrow()[index];
|
|
||||||
|
|
||||||
let row = libadwaita::ActionRow::new();
|
|
||||||
row.set_activatable(true);
|
|
||||||
row.set_title(Some(&poe.get_title()));
|
|
||||||
|
|
||||||
let poe = poe.to_owned();
|
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
|
||||||
let poe = poe.clone();
|
|
||||||
spawn!(@clone this, async move {
|
|
||||||
this.leaflet.set_visible_child(&this.navigator.widget);
|
|
||||||
|
|
||||||
match poe {
|
|
||||||
PersonOrEnsemble::Person(person) => {
|
|
||||||
replace!(this.navigator, PersonScreen, person).await;
|
|
||||||
}
|
|
||||||
PersonOrEnsemble::Ensemble(ensemble) => {
|
|
||||||
replace!(this.navigator, EnsembleScreen, ensemble).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
row.upcast()
|
this.poe_list
|
||||||
}));
|
.set_make_widget_cb(clone!(@weak this => @default-panic, move |index| {
|
||||||
|
let poe = &this.poes.borrow()[index];
|
||||||
|
|
||||||
this.poe_list.set_filter_cb(clone!(@weak this => move |index| {
|
let row = libadwaita::ActionRow::new();
|
||||||
let poe = &this.poes.borrow()[index];
|
row.set_activatable(true);
|
||||||
let search = this.search_entry.get_text().to_string().to_lowercase();
|
row.set_title(Some(&poe.get_title()));
|
||||||
let title = poe.get_title().to_lowercase();
|
|
||||||
search.is_empty() || title.contains(&search)
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.navigator.set_back_cb(clone!(@weak this => move || {
|
let poe = poe.to_owned();
|
||||||
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
|
let poe = poe.clone();
|
||||||
|
spawn!(@clone this, async move {
|
||||||
|
this.leaflet.set_visible_child(&this.navigator.widget);
|
||||||
|
|
||||||
|
match poe {
|
||||||
|
PersonOrEnsemble::Person(person) => {
|
||||||
|
replace!(this.navigator, PersonScreen, person).await;
|
||||||
|
}
|
||||||
|
PersonOrEnsemble::Ensemble(ensemble) => {
|
||||||
|
replace!(this.navigator, EnsembleScreen, ensemble).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
row.upcast()
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.poe_list
|
||||||
|
.set_filter_cb(clone!(@weak this => @default-panic, move |index| {
|
||||||
|
let poe = &this.poes.borrow()[index];
|
||||||
|
let search = this.search_entry.text().to_string().to_lowercase();
|
||||||
|
let title = poe.get_title().to_lowercase();
|
||||||
|
search.is_empty() || title.contains(&search)
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.navigator.set_back_cb(clone!(@weak this => move || {
|
||||||
this.leaflet.set_visible_child_name("sidebar");
|
this.leaflet.set_visible_child_name("sidebar");
|
||||||
}));
|
}));
|
||||||
|
|
||||||
player_bar.set_playlist_cb(clone!(@weak this => move || {
|
player_bar.set_playlist_cb(clone!(@weak this => move || {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
push!(this.handle, PlayerScreen).await;
|
push!(this.handle, PlayerScreen).await;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -35,47 +35,56 @@ impl Screen<Medium, ()> for MediumScreen {
|
||||||
list,
|
list,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.widget.set_back_cb(clone!(@weak this => move || {
|
this.widget.set_back_cb(clone!(@weak this => move || {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
this.widget.add_action(
|
||||||
|
&gettext("Edit medium"),
|
||||||
|
clone!(@weak this => move || {
|
||||||
|
// TODO: Show medium editor.
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
this.widget.add_action(&gettext("Edit medium"), clone!(@weak this => move || {
|
this.widget.add_action(
|
||||||
// TODO: Show medium editor.
|
&gettext("Delete medium"),
|
||||||
}));
|
clone!(@weak this => move || {
|
||||||
|
// TODO: Delete medium and maybe also the tracks?
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
this.widget.add_action(&gettext("Delete medium"), clone!(@weak this => move || {
|
section.add_action(
|
||||||
// TODO: Delete medium and maybe also the tracks?
|
"media-playback-start-symbolic",
|
||||||
}));
|
clone!(@weak this => move || {
|
||||||
|
for track in &this.medium.tracks {
|
||||||
|
this.handle.backend.pl().add_item(track.clone()).unwrap();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
section.add_action("media-playback-start-symbolic", clone!(@weak this => move || {
|
this.list
|
||||||
for track in &this.medium.tracks {
|
.set_make_widget_cb(clone!(@weak this => @default-panic, move |index| {
|
||||||
this.handle.backend.pl().add_item(track.clone()).unwrap();
|
let track = &this.medium.tracks[index];
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.list.set_make_widget_cb(clone!(@weak this => move |index| {
|
let mut parts = Vec::<String>::new();
|
||||||
let track = &this.medium.tracks[index];
|
for part in &track.work_parts {
|
||||||
|
parts.push(track.recording.work.parts[*part].title.clone());
|
||||||
|
}
|
||||||
|
|
||||||
let mut parts = Vec::<String>::new();
|
let title = if parts.is_empty() {
|
||||||
for part in &track.work_parts {
|
gettext("Unknown")
|
||||||
parts.push(track.recording.work.parts[*part].title.clone());
|
} else {
|
||||||
}
|
parts.join(", ")
|
||||||
|
};
|
||||||
|
|
||||||
let title = if parts.is_empty() {
|
let row = libadwaita::ActionRow::new();
|
||||||
gettext("Unknown")
|
row.set_selectable(false);
|
||||||
} else {
|
row.set_activatable(false);
|
||||||
parts.join(", ")
|
row.set_title(Some(&title));
|
||||||
};
|
row.set_margin_start(12);
|
||||||
|
|
||||||
let row = libadwaita::ActionRow::new();
|
row.upcast()
|
||||||
row.set_selectable(false);
|
}));
|
||||||
row.set_activatable(false);
|
|
||||||
row.set_title(Some(&title));
|
|
||||||
row.set_margin_start(12);
|
|
||||||
|
|
||||||
row.upcast()
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.list.update(this.medium.tracks.len());
|
this.list.update(this.medium.tracks.len());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use super::{MediumScreen, WorkScreen, RecordingScreen};
|
use super::{MediumScreen, RecordingScreen, WorkScreen};
|
||||||
use crate::editors::PersonEditor;
|
use crate::editors::PersonEditor;
|
||||||
use crate::navigator::{NavigatorWindow, NavigationHandle, Screen};
|
use crate::navigator::{NavigationHandle, NavigatorWindow, Screen};
|
||||||
use crate::widgets;
|
use crate::widgets;
|
||||||
use crate::widgets::{List, Section, Widget};
|
use crate::widgets::{List, Section, Widget};
|
||||||
use gettextrs::gettext;
|
use gettextrs::gettext;
|
||||||
|
|
@ -47,106 +47,118 @@ impl Screen<Person, ()> for PersonScreen {
|
||||||
mediums: RefCell::new(Vec::new()),
|
mediums: RefCell::new(Vec::new()),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.widget.set_back_cb(clone!(@weak this => move || {
|
this.widget.set_back_cb(clone!(@weak this => move || {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
this.widget.add_action(
|
||||||
|
&gettext("Edit person"),
|
||||||
|
clone!(@weak this => move || {
|
||||||
|
spawn!(@clone this, async move {
|
||||||
|
let window = NavigatorWindow::new(this.handle.backend.clone());
|
||||||
|
replace!(window.navigator, PersonEditor, Some(this.person.clone())).await;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
this.widget.add_action(&gettext("Edit person"), clone!(@weak this => move || {
|
this.widget.add_action(
|
||||||
spawn!(@clone this, async move {
|
&gettext("Delete person"),
|
||||||
let window = NavigatorWindow::new(this.handle.backend.clone());
|
clone!(@weak this => move || {
|
||||||
replace!(window.navigator, PersonEditor, Some(this.person.clone())).await;
|
spawn!(@clone this, async move {
|
||||||
});
|
this.handle.backend.db().delete_person(&this.person.id).await.unwrap();
|
||||||
}));
|
this.handle.backend.library_changed();
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
this.widget.add_action(&gettext("Delete person"), clone!(@weak this => move || {
|
this.widget.set_search_cb(clone!(@weak this => move || {
|
||||||
spawn!(@clone this, async move {
|
|
||||||
this.handle.backend.db().delete_person(&this.person.id).await.unwrap();
|
|
||||||
this.handle.backend.library_changed();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.widget.set_search_cb(clone!(@weak this => move || {
|
|
||||||
this.work_list.invalidate_filter();
|
this.work_list.invalidate_filter();
|
||||||
this.recording_list.invalidate_filter();
|
this.recording_list.invalidate_filter();
|
||||||
this.medium_list.invalidate_filter();
|
this.medium_list.invalidate_filter();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.work_list.set_make_widget_cb(clone!(@weak this => move |index| {
|
this.work_list
|
||||||
let work = &this.works.borrow()[index];
|
.set_make_widget_cb(clone!(@weak this => @default-panic, move |index| {
|
||||||
|
let work = &this.works.borrow()[index];
|
||||||
|
|
||||||
let row = libadwaita::ActionRow::new();
|
let row = libadwaita::ActionRow::new();
|
||||||
row.set_activatable(true);
|
row.set_activatable(true);
|
||||||
row.set_title(Some(&work.title));
|
row.set_title(Some(&work.title));
|
||||||
|
|
||||||
let work = work.to_owned();
|
let work = work.to_owned();
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
let work = work.clone();
|
let work = work.clone();
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
push!(this.handle, WorkScreen, work.clone()).await;
|
push!(this.handle, WorkScreen, work.clone()).await;
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
row.upcast()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
row.upcast()
|
this.work_list
|
||||||
}));
|
.set_filter_cb(clone!(@weak this => @default-panic, move|index| {
|
||||||
|
let work = &this.works.borrow()[index];
|
||||||
this.work_list.set_filter_cb(clone!(@weak this => move |index| {
|
let search = this.widget.get_search();
|
||||||
let work = &this.works.borrow()[index];
|
let title = work.title.to_lowercase();
|
||||||
let search = this.widget.get_search();
|
search.is_empty() || title.contains(&search)
|
||||||
let title = work.title.to_lowercase();
|
|
||||||
search.is_empty() || title.contains(&search)
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.recording_list.set_make_widget_cb(clone!(@weak this => move |index| {
|
|
||||||
let recording = &this.recordings.borrow()[index];
|
|
||||||
|
|
||||||
let row = libadwaita::ActionRow::new();
|
|
||||||
row.set_activatable(true);
|
|
||||||
row.set_title(Some(&recording.work.get_title()));
|
|
||||||
row.set_subtitle(Some(&recording.get_performers()));
|
|
||||||
|
|
||||||
let recording = recording.to_owned();
|
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
|
||||||
let recording = recording.clone();
|
|
||||||
spawn!(@clone this, async move {
|
|
||||||
push!(this.handle, RecordingScreen, recording.clone()).await;
|
|
||||||
});
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
row.upcast()
|
this.recording_list.set_make_widget_cb(
|
||||||
}));
|
clone!(@weak this => @default-panic, move |index| {
|
||||||
|
let recording = &this.recordings.borrow()[index];
|
||||||
|
|
||||||
this.recording_list.set_filter_cb(clone!(@weak this => move |index| {
|
let row = libadwaita::ActionRow::new();
|
||||||
let recording = &this.recordings.borrow()[index];
|
row.set_activatable(true);
|
||||||
let search = this.widget.get_search();
|
row.set_title(Some(&recording.work.get_title()));
|
||||||
let text = recording.work.get_title() + &recording.get_performers();
|
row.set_subtitle(Some(&recording.get_performers()));
|
||||||
search.is_empty() || text.to_lowercase().contains(&search)
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.medium_list.set_make_widget_cb(clone!(@weak this => move |index| {
|
let recording = recording.to_owned();
|
||||||
let medium = &this.mediums.borrow()[index];
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
|
let recording = recording.clone();
|
||||||
|
spawn!(@clone this, async move {
|
||||||
|
push!(this.handle, RecordingScreen, recording.clone()).await;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
let row = libadwaita::ActionRow::new();
|
row.upcast()
|
||||||
row.set_activatable(true);
|
}),
|
||||||
row.set_title(Some(&medium.name));
|
);
|
||||||
|
|
||||||
let medium = medium.to_owned();
|
this.recording_list
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
.set_filter_cb(clone!(@weak this => @default-panic,move |index| {
|
||||||
let medium = medium.clone();
|
let recording = &this.recordings.borrow()[index];
|
||||||
spawn!(@clone this, async move {
|
let search = this.widget.get_search();
|
||||||
push!(this.handle, MediumScreen, medium.clone()).await;
|
let text = recording.work.get_title() + &recording.get_performers();
|
||||||
});
|
search.is_empty() || text.to_lowercase().contains(&search)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
row.upcast()
|
this.medium_list
|
||||||
}));
|
.set_make_widget_cb(clone!(@weak this => @default-panic, move |index| {
|
||||||
|
let medium = &this.mediums.borrow()[index];
|
||||||
|
|
||||||
this.medium_list.set_filter_cb(clone!(@weak this => move |index| {
|
let row = libadwaita::ActionRow::new();
|
||||||
let medium = &this.mediums.borrow()[index];
|
row.set_activatable(true);
|
||||||
let search = this.widget.get_search();
|
row.set_title(Some(&medium.name));
|
||||||
let name = medium.name.to_lowercase();
|
|
||||||
search.is_empty() || name.contains(&search)
|
let medium = medium.to_owned();
|
||||||
}));
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
|
let medium = medium.clone();
|
||||||
|
spawn!(@clone this, async move {
|
||||||
|
push!(this.handle, MediumScreen, medium.clone()).await;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
row.upcast()
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.medium_list
|
||||||
|
.set_filter_cb(clone!(@weak this => @default-panic, move |index| {
|
||||||
|
let medium = &this.mediums.borrow()[index];
|
||||||
|
let search = this.widget.get_search();
|
||||||
|
let name = medium.name.to_lowercase();
|
||||||
|
search.is_empty() || name.contains(&search)
|
||||||
|
}));
|
||||||
|
|
||||||
// Load the content asynchronously.
|
// Load the content asynchronously.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ impl Screen<(), ()> for PlayerScreen {
|
||||||
|
|
||||||
let player = &this.handle.backend.pl();
|
let player = &this.handle.backend.pl();
|
||||||
|
|
||||||
player.add_playlist_cb(clone!(@weak this => @default-return (), move |playlist| {
|
player.add_playlist_cb(clone!(@weak this => move |playlist| {
|
||||||
if playlist.is_empty() {
|
if playlist.is_empty() {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}
|
}
|
||||||
|
|
@ -104,7 +104,7 @@ impl Screen<(), ()> for PlayerScreen {
|
||||||
this.show_playlist();
|
this.show_playlist();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
player.add_track_cb(clone!(@weak this, @weak player => @default-return (), move |current_track| {
|
player.add_track_cb(clone!(@weak this, @weak player => move |current_track| {
|
||||||
this.previous_button.set_sensitive(this.handle.backend.pl().has_previous());
|
this.previous_button.set_sensitive(this.handle.backend.pl().has_previous());
|
||||||
this.next_button.set_sensitive(this.handle.backend.pl().has_next());
|
this.next_button.set_sensitive(this.handle.backend.pl().has_next());
|
||||||
|
|
||||||
|
|
@ -129,14 +129,14 @@ impl Screen<(), ()> for PlayerScreen {
|
||||||
this.show_playlist();
|
this.show_playlist();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
player.add_duration_cb(clone!(@weak this => @default-return (), move |ms| {
|
player.add_duration_cb(clone!(@weak this => move |ms| {
|
||||||
let min = ms / 60000;
|
let min = ms / 60000;
|
||||||
let sec = (ms % 60000) / 1000;
|
let sec = (ms % 60000) / 1000;
|
||||||
this.duration_label.set_text(&format!("{}:{:02}", min, sec));
|
this.duration_label.set_text(&format!("{}:{:02}", min, sec));
|
||||||
this.position.set_upper(ms as f64);
|
this.position.set_upper(ms as f64);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
player.add_playing_cb(clone!(@weak this => @default-return (), move |playing| {
|
player.add_playing_cb(clone!(@weak this => move |playing| {
|
||||||
this.play_button.set_child(Some(if playing {
|
this.play_button.set_child(Some(if playing {
|
||||||
&this.pause_image
|
&this.pause_image
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -144,7 +144,7 @@ impl Screen<(), ()> for PlayerScreen {
|
||||||
}));
|
}));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
player.add_position_cb(clone!(@weak this => @default-return (), move |ms| {
|
player.add_position_cb(clone!(@weak this => move |ms| {
|
||||||
if !this.seeking.get() {
|
if !this.seeking.get() {
|
||||||
let min = ms / 60000;
|
let min = ms / 60000;
|
||||||
let sec = (ms % 60000) / 1000;
|
let sec = (ms % 60000) / 1000;
|
||||||
|
|
@ -153,35 +153,38 @@ impl Screen<(), ()> for PlayerScreen {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.previous_button.connect_clicked(clone!(@weak this => move |_| {
|
this.previous_button
|
||||||
this.handle.backend.pl().previous().unwrap();
|
.connect_clicked(clone!(@weak this => move |_| {
|
||||||
}));
|
this.handle.backend.pl().previous().unwrap();
|
||||||
|
}));
|
||||||
|
|
||||||
this.play_button.connect_clicked(clone!(@weak this => move |_| {
|
this.play_button
|
||||||
this.handle.backend.pl().play_pause();
|
.connect_clicked(clone!(@weak this => move |_| {
|
||||||
}));
|
this.handle.backend.pl().play_pause();
|
||||||
|
}));
|
||||||
|
|
||||||
this.next_button.connect_clicked(clone!(@weak this => move |_| {
|
this.next_button
|
||||||
this.handle.backend.pl().next().unwrap();
|
.connect_clicked(clone!(@weak this => move |_| {
|
||||||
}));
|
this.handle.backend.pl().next().unwrap();
|
||||||
|
}));
|
||||||
|
|
||||||
stop_button.connect_clicked(clone!(@weak this => move |_| {
|
stop_button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
this.handle.backend.pl().clear();
|
this.handle.backend.pl().clear();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
event_controller.connect_event(clone!(@weak this => move |_, event| {
|
event_controller.connect_event(clone!(@weak this => @default-panic, move |_, event| {
|
||||||
if let Some(event) = event.downcast_ref::<gdk::ButtonEvent>() {
|
if let Some(event) = event.downcast_ref::<gdk::ButtonEvent>() {
|
||||||
if event.get_button() == gdk::BUTTON_PRIMARY {
|
if event.button() == gdk::BUTTON_PRIMARY {
|
||||||
match event.get_event_type() {
|
match event.event_type() {
|
||||||
gdk::EventType::ButtonPress => {
|
gdk::EventType::ButtonPress => {
|
||||||
this.seeking.replace(true);
|
this.seeking.replace(true);
|
||||||
}
|
}
|
||||||
gdk::EventType::ButtonRelease => {
|
gdk::EventType::ButtonRelease => {
|
||||||
this.handle.backend.pl().seek(this.position.get_value() as u64);
|
this.handle.backend.pl().seek(this.position.value() as u64);
|
||||||
this.seeking.replace(false);
|
this.seeking.replace(false);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
|
@ -193,9 +196,9 @@ impl Screen<(), ()> for PlayerScreen {
|
||||||
glib::signal::Inhibit(false)
|
glib::signal::Inhibit(false)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
position_scale.connect_value_changed(clone!(@weak this => move |_| {
|
position_scale.connect_value_changed(clone!(@weak this => move |_| {
|
||||||
if this.seeking.get() {
|
if this.seeking.get() {
|
||||||
let ms = this.position.get_value() as u64;
|
let ms = this.position.value() as u64;
|
||||||
let min = ms / 60000;
|
let min = ms / 60000;
|
||||||
let sec = (ms % 60000) / 1000;
|
let sec = (ms % 60000) / 1000;
|
||||||
|
|
||||||
|
|
@ -203,7 +206,7 @@ impl Screen<(), ()> for PlayerScreen {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.list.set_make_widget_cb(clone!(@weak this => move |index| {
|
this.list.set_make_widget_cb(clone!(@weak this => @default-panic, move |index| {
|
||||||
let widget = match this.items.borrow()[index] {
|
let widget = match this.items.borrow()[index] {
|
||||||
ListItem::Track {index, first, playing} => {
|
ListItem::Track {index, first, playing} => {
|
||||||
let track = &this.playlist.borrow()[index];
|
let track = &this.playlist.borrow()[index];
|
||||||
|
|
@ -236,7 +239,7 @@ impl Screen<(), ()> for PlayerScreen {
|
||||||
row.set_subtitle(Some(&subtitle));
|
row.set_subtitle(Some(&subtitle));
|
||||||
}
|
}
|
||||||
|
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
this.handle.backend.pl().set_track(index).unwrap();
|
this.handle.backend.pl().set_track(index).unwrap();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::editors::RecordingEditor;
|
use crate::editors::RecordingEditor;
|
||||||
use crate::navigator::{NavigatorWindow, NavigationHandle, Screen};
|
use crate::navigator::{NavigationHandle, NavigatorWindow, Screen};
|
||||||
use crate::widgets;
|
use crate::widgets;
|
||||||
use crate::widgets::{List, Section, Widget};
|
use crate::widgets::{List, Section, Widget};
|
||||||
use gettextrs::gettext;
|
use gettextrs::gettext;
|
||||||
|
|
@ -39,49 +39,59 @@ impl Screen<Recording, ()> for RecordingScreen {
|
||||||
tracks: RefCell::new(Vec::new()),
|
tracks: RefCell::new(Vec::new()),
|
||||||
});
|
});
|
||||||
|
|
||||||
section.add_action("media-playback-start-symbolic", clone!(@weak this => move || {
|
section.add_action(
|
||||||
for track in &*this.tracks.borrow() {
|
"media-playback-start-symbolic",
|
||||||
this.handle.backend.pl().add_item(track.clone()).unwrap();
|
clone!(@weak this => move || {
|
||||||
}
|
for track in &*this.tracks.borrow() {
|
||||||
}));
|
this.handle.backend.pl().add_item(track.clone()).unwrap();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
this.widget.set_back_cb(clone!(@weak this => move || {
|
this.widget.set_back_cb(clone!(@weak this => move || {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.widget.add_action(&gettext("Edit recording"), clone!(@weak this => move || {
|
this.widget.add_action(
|
||||||
spawn!(@clone this, async move {
|
&gettext("Edit recording"),
|
||||||
let window = NavigatorWindow::new(this.handle.backend.clone());
|
clone!(@weak this => move || {
|
||||||
replace!(window.navigator, RecordingEditor, Some(this.recording.clone())).await;
|
spawn!(@clone this, async move {
|
||||||
});
|
let window = NavigatorWindow::new(this.handle.backend.clone());
|
||||||
}));
|
replace!(window.navigator, RecordingEditor, Some(this.recording.clone())).await;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
this.widget.add_action(&gettext("Delete recording"), clone!(@weak this => move || {
|
this.widget.add_action(
|
||||||
spawn!(@clone this, async move {
|
&gettext("Delete recording"),
|
||||||
this.handle.backend.db().delete_recording(&this.recording.id).await.unwrap();
|
clone!(@weak this => move || {
|
||||||
this.handle.backend.library_changed();
|
spawn!(@clone this, async move {
|
||||||
});
|
this.handle.backend.db().delete_recording(&this.recording.id).await.unwrap();
|
||||||
}));
|
this.handle.backend.library_changed();
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
this.list.set_make_widget_cb(clone!(@weak this => move |index| {
|
this.list
|
||||||
let track = &this.tracks.borrow()[index];
|
.set_make_widget_cb(clone!(@weak this => @default-panic, move |index| {
|
||||||
|
let track = &this.tracks.borrow()[index];
|
||||||
|
|
||||||
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 {
|
||||||
title_parts.push(this.recording.work.parts[*part].title.clone());
|
title_parts.push(this.recording.work.parts[*part].title.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = if title_parts.is_empty() {
|
let title = if title_parts.is_empty() {
|
||||||
gettext("Unknown")
|
gettext("Unknown")
|
||||||
} else {
|
} else {
|
||||||
title_parts.join(", ")
|
title_parts.join(", ")
|
||||||
};
|
};
|
||||||
|
|
||||||
let row = libadwaita::ActionRow::new();
|
let row = libadwaita::ActionRow::new();
|
||||||
row.set_title(Some(&title));
|
row.set_title(Some(&title));
|
||||||
|
|
||||||
row.upcast()
|
row.upcast()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Load the content asynchronously.
|
// Load the content asynchronously.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,10 @@ impl Screen<(), ()> for WelcomeScreen {
|
||||||
let welcome = libadwaita::StatusPageBuilder::new()
|
let welcome = libadwaita::StatusPageBuilder::new()
|
||||||
.icon_name("folder-music-symbolic")
|
.icon_name("folder-music-symbolic")
|
||||||
.title(&gettext("Welcome to Musicus!"))
|
.title(&gettext("Welcome to Musicus!"))
|
||||||
.description(&gettext("Get startet by selecting the folder containing your music \
|
.description(&gettext(
|
||||||
files! Musicus will create a new database there or open one that already exists."))
|
"Get startet by selecting the folder containing your music \
|
||||||
|
files! Musicus will create a new database there or open one that already exists.",
|
||||||
|
))
|
||||||
.child(&button)
|
.child(&button)
|
||||||
.vexpand(true)
|
.vexpand(true)
|
||||||
.build();
|
.build();
|
||||||
|
|
@ -42,12 +44,9 @@ impl Screen<(), ()> for WelcomeScreen {
|
||||||
widget.append(&header);
|
widget.append(&header);
|
||||||
widget.append(&welcome);
|
widget.append(&welcome);
|
||||||
|
|
||||||
let this = Rc::new(Self {
|
let this = Rc::new(Self { handle, widget });
|
||||||
handle,
|
|
||||||
widget,
|
|
||||||
});
|
|
||||||
|
|
||||||
button.connect_clicked(clone!(@weak this => move |_| {
|
button.connect_clicked(clone!(@weak this => move |_| {
|
||||||
let dialog = gtk::FileChooserDialog::new(
|
let dialog = gtk::FileChooserDialog::new(
|
||||||
Some(&gettext("Select music library folder")),
|
Some(&gettext("Select music library folder")),
|
||||||
Some(&this.handle.window),
|
Some(&this.handle.window),
|
||||||
|
|
@ -59,10 +58,10 @@ impl Screen<(), ()> for WelcomeScreen {
|
||||||
|
|
||||||
dialog.set_modal(true);
|
dialog.set_modal(true);
|
||||||
|
|
||||||
dialog.connect_response(clone!(@weak this => move |dialog, response| {
|
dialog.connect_response(clone!(@weak this => move |dialog, response| {
|
||||||
if let gtk::ResponseType::Accept = response {
|
if let gtk::ResponseType::Accept = response {
|
||||||
if let Some(file) = dialog.get_file() {
|
if let Some(file) = dialog.file() {
|
||||||
if let Some(path) = file.get_path() {
|
if let Some(path) = file.path() {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
this.handle.backend.set_music_library_path(path).await.unwrap();
|
this.handle.backend.set_music_library_path(path).await.unwrap();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use super::RecordingScreen;
|
use super::RecordingScreen;
|
||||||
use crate::editors::WorkEditor;
|
use crate::editors::WorkEditor;
|
||||||
use crate::navigator::{NavigatorWindow, NavigationHandle, Screen};
|
use crate::navigator::{NavigationHandle, NavigatorWindow, Screen};
|
||||||
use crate::widgets;
|
use crate::widgets;
|
||||||
use crate::widgets::{List, Section, Widget};
|
use crate::widgets::{List, Section, Widget};
|
||||||
use gettextrs::gettext;
|
use gettextrs::gettext;
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use libadwaita::prelude::*;
|
use libadwaita::prelude::*;
|
||||||
use musicus_backend::db::{Work, Recording};
|
use musicus_backend::db::{Recording, Work};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
|
@ -38,55 +38,63 @@ impl Screen<Work, ()> for WorkScreen {
|
||||||
recordings: RefCell::new(Vec::new()),
|
recordings: RefCell::new(Vec::new()),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.widget.set_back_cb(clone!(@weak this => move || {
|
this.widget.set_back_cb(clone!(@weak this => move || {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
this.widget.add_action(
|
||||||
|
&gettext("Edit work"),
|
||||||
|
clone!(@weak this => move || {
|
||||||
|
spawn!(@clone this, async move {
|
||||||
|
let window = NavigatorWindow::new(this.handle.backend.clone());
|
||||||
|
replace!(window.navigator, WorkEditor, Some(this.work.clone())).await;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
this.widget.add_action(&gettext("Edit work"), clone!(@weak this => move || {
|
this.widget.add_action(
|
||||||
spawn!(@clone this, async move {
|
&gettext("Delete work"),
|
||||||
let window = NavigatorWindow::new(this.handle.backend.clone());
|
clone!(@weak this => move || {
|
||||||
replace!(window.navigator, WorkEditor, Some(this.work.clone())).await;
|
spawn!(@clone this, async move {
|
||||||
});
|
this.handle.backend.db().delete_work(&this.work.id).await.unwrap();
|
||||||
}));
|
this.handle.backend.library_changed();
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
this.widget.add_action(&gettext("Delete work"), clone!(@weak this => move || {
|
this.widget.set_search_cb(clone!(@weak this => move || {
|
||||||
spawn!(@clone this, async move {
|
|
||||||
this.handle.backend.db().delete_work(&this.work.id).await.unwrap();
|
|
||||||
this.handle.backend.library_changed();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.widget.set_search_cb(clone!(@weak this => move || {
|
|
||||||
this.recording_list.invalidate_filter();
|
this.recording_list.invalidate_filter();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.recording_list.set_make_widget_cb(clone!(@weak this => move |index| {
|
this.recording_list.set_make_widget_cb(
|
||||||
let recording = &this.recordings.borrow()[index];
|
clone!(@weak this => @default-panic, move |index| {
|
||||||
|
let recording = &this.recordings.borrow()[index];
|
||||||
|
|
||||||
let row = libadwaita::ActionRow::new();
|
let row = libadwaita::ActionRow::new();
|
||||||
row.set_activatable(true);
|
row.set_activatable(true);
|
||||||
row.set_title(Some(&recording.work.get_title()));
|
row.set_title(Some(&recording.work.get_title()));
|
||||||
row.set_subtitle(Some(&recording.get_performers()));
|
row.set_subtitle(Some(&recording.get_performers()));
|
||||||
|
|
||||||
let recording = recording.to_owned();
|
let recording = recording.to_owned();
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
let recording = recording.clone();
|
let recording = recording.clone();
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
push!(this.handle, RecordingScreen, recording.clone()).await;
|
push!(this.handle, RecordingScreen, recording.clone()).await;
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
row.upcast()
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.recording_list
|
||||||
|
.set_filter_cb(clone!(@weak this => @default-panic, move |index| {
|
||||||
|
let recording = &this.recordings.borrow()[index];
|
||||||
|
let search = this.widget.get_search();
|
||||||
|
let text = recording.work.get_title() + &recording.get_performers();
|
||||||
|
search.is_empty() || text.to_lowercase().contains(&search)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
row.upcast()
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.recording_list.set_filter_cb(clone!(@weak this => move |index| {
|
|
||||||
let recording = &this.recordings.borrow()[index];
|
|
||||||
let search = this.widget.get_search();
|
|
||||||
let text = recording.work.get_title() + &recording.get_performers();
|
|
||||||
search.is_empty() || text.to_lowercase().contains(&search)
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Load the content asynchronously.
|
// Load the content asynchronously.
|
||||||
|
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
|
|
|
||||||
|
|
@ -23,18 +23,15 @@ impl Screen<(), Ensemble> for EnsembleSelector {
|
||||||
let selector = Selector::<Ensemble>::new(Rc::clone(&handle.backend));
|
let selector = Selector::<Ensemble>::new(Rc::clone(&handle.backend));
|
||||||
selector.set_title(&gettext("Select ensemble"));
|
selector.set_title(&gettext("Select ensemble"));
|
||||||
|
|
||||||
let this = Rc::new(Self {
|
let this = Rc::new(Self { handle, selector });
|
||||||
handle,
|
|
||||||
selector,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
this.selector.set_back_cb(clone!(@weak this => move || {
|
this.selector.set_back_cb(clone!(@weak this => move || {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_add_cb(clone!(@weak this => move || {
|
this.selector.set_add_cb(clone!(@weak this => move || {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(ensemble) = push!(this.handle, EnsembleEditor, None).await {
|
if let Some(ensemble) = push!(this.handle, EnsembleEditor, None).await {
|
||||||
this.handle.pop(Some(ensemble));
|
this.handle.pop(Some(ensemble));
|
||||||
|
|
@ -42,28 +39,31 @@ impl Screen<(), Ensemble> for EnsembleSelector {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_load_online(clone!(@weak this => move || {
|
this.selector
|
||||||
let clone = this.clone();
|
.set_load_online(clone!(@weak this => @default-panic, move || {
|
||||||
async move { Ok(clone.handle.backend.cl().get_ensembles().await?) }
|
let clone = this.clone();
|
||||||
}));
|
async move { Ok(clone.handle.backend.cl().get_ensembles().await?) }
|
||||||
|
|
||||||
this.selector.set_load_local(clone!(@weak this => move || {
|
|
||||||
let clone = this.clone();
|
|
||||||
async move { clone.handle.backend.db().get_ensembles().await.unwrap() }
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.selector.set_make_widget(clone!(@weak this => move |ensemble| {
|
|
||||||
let row = libadwaita::ActionRow::new();
|
|
||||||
row.set_activatable(true);
|
|
||||||
row.set_title(Some(&ensemble.name));
|
|
||||||
|
|
||||||
let ensemble = ensemble.to_owned();
|
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
|
||||||
this.handle.pop(Some(ensemble.clone()))
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
row.upcast()
|
this.selector
|
||||||
}));
|
.set_load_local(clone!(@weak this => @default-panic, move || {
|
||||||
|
let clone = this.clone();
|
||||||
|
async move { clone.handle.backend.db().get_ensembles().await.unwrap() }
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.selector
|
||||||
|
.set_make_widget(clone!(@weak this => @default-panic, move |ensemble| {
|
||||||
|
let row = libadwaita::ActionRow::new();
|
||||||
|
row.set_activatable(true);
|
||||||
|
row.set_title(Some(&ensemble.name));
|
||||||
|
|
||||||
|
let ensemble = ensemble.to_owned();
|
||||||
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
|
this.handle.pop(Some(ensemble.clone()))
|
||||||
|
}));
|
||||||
|
|
||||||
|
row.upcast()
|
||||||
|
}));
|
||||||
|
|
||||||
this.selector
|
this.selector
|
||||||
.set_filter(|search, ensemble| ensemble.name.to_lowercase().contains(search));
|
.set_filter(|search, ensemble| ensemble.name.to_lowercase().contains(search));
|
||||||
|
|
|
||||||
|
|
@ -23,18 +23,15 @@ impl Screen<(), Instrument> for InstrumentSelector {
|
||||||
let selector = Selector::<Instrument>::new(Rc::clone(&handle.backend));
|
let selector = Selector::<Instrument>::new(Rc::clone(&handle.backend));
|
||||||
selector.set_title(&gettext("Select instrument"));
|
selector.set_title(&gettext("Select instrument"));
|
||||||
|
|
||||||
let this = Rc::new(Self {
|
let this = Rc::new(Self { handle, selector });
|
||||||
handle,
|
|
||||||
selector,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
this.selector.set_back_cb(clone!(@weak this => move || {
|
this.selector.set_back_cb(clone!(@weak this => move || {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_add_cb(clone!(@weak this => move || {
|
this.selector.set_add_cb(clone!(@weak this => move || {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(instrument) = push!(this.handle, InstrumentEditor, None).await {
|
if let Some(instrument) = push!(this.handle, InstrumentEditor, None).await {
|
||||||
this.handle.pop(Some(instrument));
|
this.handle.pop(Some(instrument));
|
||||||
|
|
@ -42,28 +39,31 @@ impl Screen<(), Instrument> for InstrumentSelector {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_load_online(clone!(@weak this => move || {
|
this.selector
|
||||||
let clone = this.clone();
|
.set_load_online(clone!(@weak this => @default-panic, move || {
|
||||||
async move { Ok(clone.handle.backend.cl().get_instruments().await?) }
|
let clone = this.clone();
|
||||||
}));
|
async move { Ok(clone.handle.backend.cl().get_instruments().await?) }
|
||||||
|
|
||||||
this.selector.set_load_local(clone!(@weak this => move || {
|
|
||||||
let clone = this.clone();
|
|
||||||
async move { clone.handle.backend.db().get_instruments().await.unwrap() }
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.selector.set_make_widget(clone!(@weak this => move |instrument| {
|
|
||||||
let row = libadwaita::ActionRow::new();
|
|
||||||
row.set_activatable(true);
|
|
||||||
row.set_title(Some(&instrument.name));
|
|
||||||
|
|
||||||
let instrument = instrument.to_owned();
|
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
|
||||||
this.handle.pop(Some(instrument.clone()))
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
row.upcast()
|
this.selector
|
||||||
}));
|
.set_load_local(clone!(@weak this => @default-panic, move || {
|
||||||
|
let clone = this.clone();
|
||||||
|
async move { clone.handle.backend.db().get_instruments().await.unwrap() }
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.selector
|
||||||
|
.set_make_widget(clone!(@weak this => @default-panic, move |instrument| {
|
||||||
|
let row = libadwaita::ActionRow::new();
|
||||||
|
row.set_activatable(true);
|
||||||
|
row.set_title(Some(&instrument.name));
|
||||||
|
|
||||||
|
let instrument = instrument.to_owned();
|
||||||
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
|
this.handle.pop(Some(instrument.clone()))
|
||||||
|
}));
|
||||||
|
|
||||||
|
row.upcast()
|
||||||
|
}));
|
||||||
|
|
||||||
this.selector
|
this.selector
|
||||||
.set_filter(|search, instrument| instrument.name.to_lowercase().contains(search));
|
.set_filter(|search, instrument| instrument.name.to_lowercase().contains(search));
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use gettextrs::gettext;
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use libadwaita::prelude::*;
|
use libadwaita::prelude::*;
|
||||||
use musicus_backend::db::{Person, Ensemble, Medium};
|
use musicus_backend::db::{Ensemble, Medium, Person};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
/// Either a person or an ensemble to be shown in the list.
|
/// Either a person or an ensemble to be shown in the list.
|
||||||
|
|
@ -38,62 +38,61 @@ impl Screen<(), Medium> for MediumSelector {
|
||||||
let selector = Selector::<PersonOrEnsemble>::new(Rc::clone(&handle.backend));
|
let selector = Selector::<PersonOrEnsemble>::new(Rc::clone(&handle.backend));
|
||||||
selector.set_title(&gettext("Select performer"));
|
selector.set_title(&gettext("Select performer"));
|
||||||
|
|
||||||
let this = Rc::new(Self {
|
let this = Rc::new(Self { handle, selector });
|
||||||
handle,
|
|
||||||
selector,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
this.selector.set_back_cb(clone!(@weak this => move || {
|
this.selector.set_back_cb(clone!(@weak this => move || {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_load_online(clone!(@weak this => move || {
|
this.selector
|
||||||
async move {
|
.set_load_online(clone!(@weak this => @default-panic, move || {
|
||||||
let mut poes = Vec::new();
|
async move {
|
||||||
|
let mut poes = Vec::new();
|
||||||
|
|
||||||
let persons = this.handle.backend.cl().get_persons().await?;
|
let persons = this.handle.backend.cl().get_persons().await?;
|
||||||
let ensembles = this.handle.backend.cl().get_ensembles().await?;
|
let ensembles = this.handle.backend.cl().get_ensembles().await?;
|
||||||
|
|
||||||
for person in persons {
|
for person in persons {
|
||||||
poes.push(PersonOrEnsemble::Person(person));
|
poes.push(PersonOrEnsemble::Person(person));
|
||||||
|
}
|
||||||
|
|
||||||
|
for ensemble in ensembles {
|
||||||
|
poes.push(PersonOrEnsemble::Ensemble(ensemble));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(poes)
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
for ensemble in ensembles {
|
this.selector
|
||||||
poes.push(PersonOrEnsemble::Ensemble(ensemble));
|
.set_load_local(clone!(@weak this => @default-panic, move || {
|
||||||
|
async move {
|
||||||
|
let mut poes = Vec::new();
|
||||||
|
|
||||||
|
let persons = this.handle.backend.db().get_persons().await.unwrap();
|
||||||
|
let ensembles = this.handle.backend.db().get_ensembles().await.unwrap();
|
||||||
|
|
||||||
|
for person in persons {
|
||||||
|
poes.push(PersonOrEnsemble::Person(person));
|
||||||
|
}
|
||||||
|
|
||||||
|
for ensemble in ensembles {
|
||||||
|
poes.push(PersonOrEnsemble::Ensemble(ensemble));
|
||||||
|
}
|
||||||
|
|
||||||
|
poes
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
Ok(poes)
|
this.selector.set_make_widget(clone!(@weak this => @default-panic, move |poe| {
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.selector.set_load_local(clone!(@weak this => move || {
|
|
||||||
async move {
|
|
||||||
let mut poes = Vec::new();
|
|
||||||
|
|
||||||
let persons = this.handle.backend.db().get_persons().await.unwrap();
|
|
||||||
let ensembles = this.handle.backend.db().get_ensembles().await.unwrap();
|
|
||||||
|
|
||||||
for person in persons {
|
|
||||||
poes.push(PersonOrEnsemble::Person(person));
|
|
||||||
}
|
|
||||||
|
|
||||||
for ensemble in ensembles {
|
|
||||||
poes.push(PersonOrEnsemble::Ensemble(ensemble));
|
|
||||||
}
|
|
||||||
|
|
||||||
poes
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.selector.set_make_widget(clone!(@weak this => move |poe| {
|
|
||||||
let row = libadwaita::ActionRow::new();
|
let row = libadwaita::ActionRow::new();
|
||||||
row.set_activatable(true);
|
row.set_activatable(true);
|
||||||
row.set_title(Some(&poe.get_title()));
|
row.set_title(Some(&poe.get_title()));
|
||||||
|
|
||||||
let poe = poe.to_owned();
|
let poe = poe.to_owned();
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
let poe = poe.clone();
|
let poe = poe.clone();
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(medium) = push!(this.handle, MediumSelectorMediumScreen, poe).await {
|
if let Some(medium) = push!(this.handle, MediumSelectorMediumScreen, poe).await {
|
||||||
|
|
@ -137,43 +136,45 @@ impl Screen<PersonOrEnsemble, Medium> for MediumSelectorMediumScreen {
|
||||||
selector,
|
selector,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.selector.set_back_cb(clone!(@weak this => move || {
|
this.selector.set_back_cb(clone!(@weak this => move || {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
match this.poe.clone() {
|
match this.poe.clone() {
|
||||||
PersonOrEnsemble::Person(person) => {
|
PersonOrEnsemble::Person(person) => {
|
||||||
// this.selector.set_load_online(clone!(@weak this => move || {
|
// this.selector.set_load_online(clone!(@weak this => move || {
|
||||||
// async move { this.handle.backend.cl().get_mediums_for_person(&person.id).await }
|
// async move { this.handle.backend.cl().get_mediums_for_person(&person.id).await }
|
||||||
// }));
|
// }));
|
||||||
|
|
||||||
this.selector.set_load_local(clone!(@weak this => move || {
|
this.selector.set_load_local(clone!(@weak this => @default-panic, move || {
|
||||||
let person = person.clone();
|
let person = person.clone();
|
||||||
async move { this.handle.backend.db().get_mediums_for_person(&person.id).await.unwrap() }
|
async move { this.handle.backend.db().get_mediums_for_person(&person.id).await.unwrap() }
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
PersonOrEnsemble::Ensemble(ensemble) => {
|
PersonOrEnsemble::Ensemble(ensemble) => {
|
||||||
this.selector.set_load_local(clone!(@weak this => move || {
|
this.selector.set_load_local(clone!(@weak this => @default-panic, move || {
|
||||||
let ensemble = ensemble.clone();
|
let ensemble = ensemble.clone();
|
||||||
async move { this.handle.backend.db().get_mediums_for_ensemble(&ensemble.id).await.unwrap() }
|
async move { this.handle.backend.db().get_mediums_for_ensemble(&ensemble.id).await.unwrap() }
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selector.set_make_widget(clone!(@weak this => move |medium| {
|
this.selector
|
||||||
let row = libadwaita::ActionRow::new();
|
.set_make_widget(clone!(@weak this => @default-panic, move |medium| {
|
||||||
row.set_activatable(true);
|
let row = libadwaita::ActionRow::new();
|
||||||
row.set_title(Some(&medium.name));
|
row.set_activatable(true);
|
||||||
|
row.set_title(Some(&medium.name));
|
||||||
|
|
||||||
let medium = medium.to_owned();
|
let medium = medium.to_owned();
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
this.handle.pop(Some(medium.clone()));
|
this.handle.pop(Some(medium.clone()));
|
||||||
|
}));
|
||||||
|
|
||||||
|
row.upcast()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
row.upcast()
|
this.selector
|
||||||
}));
|
.set_filter(|search, medium| medium.name.to_lowercase().contains(search));
|
||||||
|
|
||||||
this.selector.set_filter(|search, medium| medium.name.to_lowercase().contains(search));
|
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,18 +23,15 @@ impl Screen<(), Person> for PersonSelector {
|
||||||
let selector = Selector::<Person>::new(Rc::clone(&handle.backend));
|
let selector = Selector::<Person>::new(Rc::clone(&handle.backend));
|
||||||
selector.set_title(&gettext("Select person"));
|
selector.set_title(&gettext("Select person"));
|
||||||
|
|
||||||
let this = Rc::new(Self {
|
let this = Rc::new(Self { handle, selector });
|
||||||
handle,
|
|
||||||
selector,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
this.selector.set_back_cb(clone!(@weak this => move || {
|
this.selector.set_back_cb(clone!(@weak this => move || {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_add_cb(clone!(@weak this => move || {
|
this.selector.set_add_cb(clone!(@weak this => move || {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(person) = push!(this.handle, PersonEditor, None).await {
|
if let Some(person) = push!(this.handle, PersonEditor, None).await {
|
||||||
this.handle.pop(Some(person));
|
this.handle.pop(Some(person));
|
||||||
|
|
@ -42,28 +39,31 @@ impl Screen<(), Person> for PersonSelector {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_load_online(clone!(@weak this => move || {
|
this.selector
|
||||||
let clone = this.clone();
|
.set_load_online(clone!(@weak this => @default-panic, move || {
|
||||||
async move { Ok(clone.handle.backend.cl().get_persons().await?) }
|
let clone = this.clone();
|
||||||
}));
|
async move { Ok(clone.handle.backend.cl().get_persons().await?) }
|
||||||
|
|
||||||
this.selector.set_load_local(clone!(@weak this => move || {
|
|
||||||
let clone = this.clone();
|
|
||||||
async move { clone.handle.backend.db().get_persons().await.unwrap() }
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.selector.set_make_widget(clone!(@weak this => move |person| {
|
|
||||||
let row = libadwaita::ActionRow::new();
|
|
||||||
row.set_activatable(true);
|
|
||||||
row.set_title(Some(&person.name_lf()));
|
|
||||||
|
|
||||||
let person = person.to_owned();
|
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
|
||||||
this.handle.pop(Some(person.clone()));
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
row.upcast()
|
this.selector
|
||||||
}));
|
.set_load_local(clone!(@weak this => @default-panic, move || {
|
||||||
|
let clone = this.clone();
|
||||||
|
async move { clone.handle.backend.db().get_persons().await.unwrap() }
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.selector
|
||||||
|
.set_make_widget(clone!(@weak this => @default-panic, move |person| {
|
||||||
|
let row = libadwaita::ActionRow::new();
|
||||||
|
row.set_activatable(true);
|
||||||
|
row.set_title(Some(&person.name_lf()));
|
||||||
|
|
||||||
|
let person = person.to_owned();
|
||||||
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
|
this.handle.pop(Some(person.clone()));
|
||||||
|
}));
|
||||||
|
|
||||||
|
row.upcast()
|
||||||
|
}));
|
||||||
|
|
||||||
this.selector
|
this.selector
|
||||||
.set_filter(|search, person| person.name_fl().to_lowercase().contains(search));
|
.set_filter(|search, person| person.name_fl().to_lowercase().contains(search));
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
use super::selector::Selector;
|
use super::selector::Selector;
|
||||||
use crate::editors::{PersonEditor, WorkEditor, RecordingEditor};
|
use crate::editors::{PersonEditor, RecordingEditor, WorkEditor};
|
||||||
use crate::navigator::{NavigationHandle, Screen};
|
use crate::navigator::{NavigationHandle, Screen};
|
||||||
use crate::widgets::Widget;
|
use crate::widgets::Widget;
|
||||||
use gettextrs::gettext;
|
use gettextrs::gettext;
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use libadwaita::prelude::*;
|
use libadwaita::prelude::*;
|
||||||
use musicus_backend::db::{Person, Work, Recording};
|
use musicus_backend::db::{Person, Recording, Work};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
/// A screen for selecting a recording.
|
/// A screen for selecting a recording.
|
||||||
|
|
@ -22,18 +22,15 @@ impl Screen<(), Recording> for RecordingSelector {
|
||||||
let selector = Selector::<Person>::new(Rc::clone(&handle.backend));
|
let selector = Selector::<Person>::new(Rc::clone(&handle.backend));
|
||||||
selector.set_title(&gettext("Select composer"));
|
selector.set_title(&gettext("Select composer"));
|
||||||
|
|
||||||
let this = Rc::new(Self {
|
let this = Rc::new(Self { handle, selector });
|
||||||
handle,
|
|
||||||
selector,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
this.selector.set_back_cb(clone!(@weak this => move || {
|
this.selector.set_back_cb(clone!(@weak this => move || {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_add_cb(clone!(@weak this => move || {
|
this.selector.set_add_cb(clone!(@weak this => move || {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(person) = push!(this.handle, PersonEditor, None).await {
|
if let Some(person) = push!(this.handle, PersonEditor, None).await {
|
||||||
// We can assume that there are no existing works of this composer and
|
// We can assume that there are no existing works of this composer and
|
||||||
|
|
@ -54,21 +51,23 @@ impl Screen<(), Recording> for RecordingSelector {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_load_online(clone!(@weak this => move || {
|
this.selector
|
||||||
async move { Ok(this.handle.backend.cl().get_persons().await?) }
|
.set_load_online(clone!(@weak this => @default-panic, move || {
|
||||||
}));
|
async move { Ok(this.handle.backend.cl().get_persons().await?) }
|
||||||
|
}));
|
||||||
|
|
||||||
this.selector.set_load_local(clone!(@weak this => move || {
|
this.selector
|
||||||
async move { this.handle.backend.db().get_persons().await.unwrap() }
|
.set_load_local(clone!(@weak this => @default-panic, move || {
|
||||||
}));
|
async move { this.handle.backend.db().get_persons().await.unwrap() }
|
||||||
|
}));
|
||||||
|
|
||||||
this.selector.set_make_widget(clone!(@weak this => move |person| {
|
this.selector.set_make_widget(clone!(@weak this => @default-panic, move |person| {
|
||||||
let row = libadwaita::ActionRow::new();
|
let row = libadwaita::ActionRow::new();
|
||||||
row.set_activatable(true);
|
row.set_activatable(true);
|
||||||
row.set_title(Some(&person.name_lf()));
|
row.set_title(Some(&person.name_lf()));
|
||||||
|
|
||||||
let person = person.to_owned();
|
let person = person.to_owned();
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
// Instead of returning the person from here, like the person selector does, we
|
// Instead of returning the person from here, like the person selector does, we
|
||||||
// show a second selector for choosing the work.
|
// show a second selector for choosing the work.
|
||||||
|
|
||||||
|
|
@ -119,11 +118,11 @@ impl Screen<Person, Work> for RecordingSelectorWorkScreen {
|
||||||
selector,
|
selector,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.selector.set_back_cb(clone!(@weak this => move || {
|
this.selector.set_back_cb(clone!(@weak this => move || {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_add_cb(clone!(@weak this => move || {
|
this.selector.set_add_cb(clone!(@weak this => move || {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
let work = Work::new(this.person.clone());
|
let work = Work::new(this.person.clone());
|
||||||
if let Some(work) = push!(this.handle, WorkEditor, Some(work)).await {
|
if let Some(work) = push!(this.handle, WorkEditor, Some(work)).await {
|
||||||
|
|
@ -132,28 +131,32 @@ impl Screen<Person, Work> for RecordingSelectorWorkScreen {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_load_online(clone!(@weak this => move || {
|
this.selector
|
||||||
async move { Ok(this.handle.backend.cl().get_works(&this.person.id).await?) }
|
.set_load_online(clone!(@weak this => @default-panic, move || {
|
||||||
}));
|
async move { Ok(this.handle.backend.cl().get_works(&this.person.id).await?) }
|
||||||
|
|
||||||
this.selector.set_load_local(clone!(@weak this => move || {
|
|
||||||
async move { this.handle.backend.db().get_works(&this.person.id).await.unwrap() }
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.selector.set_make_widget(clone!(@weak this => move |work| {
|
|
||||||
let row = libadwaita::ActionRow::new();
|
|
||||||
row.set_activatable(true);
|
|
||||||
row.set_title(Some(&work.title));
|
|
||||||
|
|
||||||
let work = work.to_owned();
|
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
|
||||||
this.handle.pop(Some(work.clone()));
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
row.upcast()
|
this.selector
|
||||||
}));
|
.set_load_local(clone!(@weak this => @default-panic, move || {
|
||||||
|
async move { this.handle.backend.db().get_works(&this.person.id).await.unwrap() }
|
||||||
|
}));
|
||||||
|
|
||||||
this.selector.set_filter(|search, work| work.title.to_lowercase().contains(search));
|
this.selector
|
||||||
|
.set_make_widget(clone!(@weak this => @default-panic, move |work| {
|
||||||
|
let row = libadwaita::ActionRow::new();
|
||||||
|
row.set_activatable(true);
|
||||||
|
row.set_title(Some(&work.title));
|
||||||
|
|
||||||
|
let work = work.to_owned();
|
||||||
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
|
this.handle.pop(Some(work.clone()));
|
||||||
|
}));
|
||||||
|
|
||||||
|
row.upcast()
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.selector
|
||||||
|
.set_filter(|search, work| work.title.to_lowercase().contains(search));
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
@ -184,11 +187,11 @@ impl Screen<Work, Recording> for RecordingSelectorRecordingScreen {
|
||||||
selector,
|
selector,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.selector.set_back_cb(clone!(@weak this => move || {
|
this.selector.set_back_cb(clone!(@weak this => move || {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_add_cb(clone!(@weak this => move || {
|
this.selector.set_add_cb(clone!(@weak this => move || {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
let recording = Recording::new(this.work.clone());
|
let recording = Recording::new(this.work.clone());
|
||||||
if let Some(recording) = push!(this.handle, RecordingEditor, Some(recording)).await {
|
if let Some(recording) = push!(this.handle, RecordingEditor, Some(recording)).await {
|
||||||
|
|
@ -197,29 +200,31 @@ impl Screen<Work, Recording> for RecordingSelectorRecordingScreen {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_load_online(clone!(@weak this => move || {
|
this.selector.set_load_online(clone!(@weak this => @default-panic, move || {
|
||||||
async move { Ok(this.handle.backend.cl().get_recordings_for_work(&this.work.id).await?) }
|
async move { Ok(this.handle.backend.cl().get_recordings_for_work(&this.work.id).await?) }
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_load_local(clone!(@weak this => move || {
|
this.selector.set_load_local(clone!(@weak this => @default-panic, move || {
|
||||||
async move { this.handle.backend.db().get_recordings_for_work(&this.work.id).await.unwrap() }
|
async move { this.handle.backend.db().get_recordings_for_work(&this.work.id).await.unwrap() }
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_make_widget(clone!(@weak this => move |recording| {
|
this.selector
|
||||||
let row = libadwaita::ActionRow::new();
|
.set_make_widget(clone!(@weak this => @default-panic, move |recording| {
|
||||||
row.set_activatable(true);
|
let row = libadwaita::ActionRow::new();
|
||||||
row.set_title(Some(&recording.get_performers()));
|
row.set_activatable(true);
|
||||||
|
row.set_title(Some(&recording.get_performers()));
|
||||||
|
|
||||||
let recording = recording.to_owned();
|
let recording = recording.to_owned();
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
this.handle.pop(Some(recording.clone()));
|
this.handle.pop(Some(recording.clone()));
|
||||||
|
}));
|
||||||
|
|
||||||
|
row.upcast()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
row.upcast()
|
this.selector.set_filter(|search, recording| {
|
||||||
}));
|
recording.get_performers().to_lowercase().contains(search)
|
||||||
|
});
|
||||||
this.selector
|
|
||||||
.set_filter(|search, recording| recording.get_performers().to_lowercase().contains(search));
|
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,13 +82,14 @@ impl<T> Selector<T> {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.search_entry.connect_search_changed(clone!(@strong this => move |_| {
|
this.search_entry
|
||||||
this.list.invalidate_filter();
|
.connect_search_changed(clone!(@strong this => move |_| {
|
||||||
}));
|
this.list.invalidate_filter();
|
||||||
|
}));
|
||||||
|
|
||||||
this.server_check_button
|
this.server_check_button
|
||||||
.connect_toggled(clone!(@strong this => move |_| {
|
.connect_toggled(clone!(@strong this => move |_| {
|
||||||
let active = this.server_check_button.get_active();
|
let active = this.server_check_button.is_active();
|
||||||
this.backend.set_use_server(active);
|
this.backend.set_use_server(active);
|
||||||
|
|
||||||
if active {
|
if active {
|
||||||
|
|
@ -98,25 +99,27 @@ impl<T> Selector<T> {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.list.set_make_widget_cb(clone!(@strong this => move |index| {
|
this.list
|
||||||
if let Some(cb) = &*this.make_widget.borrow() {
|
.set_make_widget_cb(clone!(@strong this => move |index| {
|
||||||
let item = &this.items.borrow()[index];
|
if let Some(cb) = &*this.make_widget.borrow() {
|
||||||
cb(item)
|
|
||||||
} else {
|
|
||||||
gtk::Label::new(None).upcast()
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.list.set_filter_cb(clone!(@strong this => move |index| {
|
|
||||||
match &*this.filter.borrow() {
|
|
||||||
Some(filter) => {
|
|
||||||
let item = &this.items.borrow()[index];
|
let item = &this.items.borrow()[index];
|
||||||
let search = this.search_entry.get_text().to_string().to_lowercase();
|
cb(item)
|
||||||
search.is_empty() || filter(&search, item)
|
} else {
|
||||||
|
gtk::Label::new(None).upcast()
|
||||||
}
|
}
|
||||||
None => true,
|
}));
|
||||||
}
|
|
||||||
}));
|
this.list
|
||||||
|
.set_filter_cb(clone!(@strong this => move |index| {
|
||||||
|
match &*this.filter.borrow() {
|
||||||
|
Some(filter) => {
|
||||||
|
let item = &this.items.borrow()[index];
|
||||||
|
let search = this.search_entry.text().to_string().to_lowercase();
|
||||||
|
search.is_empty() || filter(&search, item)
|
||||||
|
}
|
||||||
|
None => true,
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
try_again_button.connect_clicked(clone!(@strong this => move |_| {
|
try_again_button.connect_clicked(clone!(@strong this => move |_| {
|
||||||
this.clone().load_online();
|
this.clone().load_online();
|
||||||
|
|
|
||||||
|
|
@ -22,18 +22,15 @@ impl Screen<(), Work> for WorkSelector {
|
||||||
let selector = Selector::<Person>::new(Rc::clone(&handle.backend));
|
let selector = Selector::<Person>::new(Rc::clone(&handle.backend));
|
||||||
selector.set_title(&gettext("Select composer"));
|
selector.set_title(&gettext("Select composer"));
|
||||||
|
|
||||||
let this = Rc::new(Self {
|
let this = Rc::new(Self { handle, selector });
|
||||||
handle,
|
|
||||||
selector,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Connect signals and callbacks
|
// Connect signals and callbacks
|
||||||
|
|
||||||
this.selector.set_back_cb(clone!(@weak this => move || {
|
this.selector.set_back_cb(clone!(@weak this => move || {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_add_cb(clone!(@weak this => move || {
|
this.selector.set_add_cb(clone!(@weak this => move || {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
if let Some(person) = push!(this.handle, PersonEditor, None).await {
|
if let Some(person) = push!(this.handle, PersonEditor, None).await {
|
||||||
// We can assume that there are no existing works of this composer and
|
// We can assume that there are no existing works of this composer and
|
||||||
|
|
@ -48,21 +45,23 @@ impl Screen<(), Work> for WorkSelector {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_load_online(clone!(@weak this => move || {
|
this.selector
|
||||||
async move { Ok(this.handle.backend.cl().get_persons().await?) }
|
.set_load_online(clone!(@weak this => @default-panic, move || {
|
||||||
}));
|
async move { Ok(this.handle.backend.cl().get_persons().await?) }
|
||||||
|
}));
|
||||||
|
|
||||||
this.selector.set_load_local(clone!(@weak this => move || {
|
this.selector
|
||||||
async move { this.handle.backend.db().get_persons().await.unwrap() }
|
.set_load_local(clone!(@weak this => @default-panic, move || {
|
||||||
}));
|
async move { this.handle.backend.db().get_persons().await.unwrap() }
|
||||||
|
}));
|
||||||
|
|
||||||
this.selector.set_make_widget(clone!(@weak this => move |person| {
|
this.selector.set_make_widget(clone!(@weak this => @default-panic, move |person| {
|
||||||
let row = libadwaita::ActionRow::new();
|
let row = libadwaita::ActionRow::new();
|
||||||
row.set_activatable(true);
|
row.set_activatable(true);
|
||||||
row.set_title(Some(&person.name_lf()));
|
row.set_title(Some(&person.name_lf()));
|
||||||
|
|
||||||
let person = person.to_owned();
|
let person = person.to_owned();
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
// Instead of returning the person from here, like the person selector does, we
|
// Instead of returning the person from here, like the person selector does, we
|
||||||
// show a second selector for choosing the work.
|
// show a second selector for choosing the work.
|
||||||
|
|
||||||
|
|
@ -109,11 +108,11 @@ impl Screen<Person, Work> for WorkSelectorWorkScreen {
|
||||||
selector,
|
selector,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.selector.set_back_cb(clone!(@weak this => move || {
|
this.selector.set_back_cb(clone!(@weak this => move || {
|
||||||
this.handle.pop(None);
|
this.handle.pop(None);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_add_cb(clone!(@weak this => move || {
|
this.selector.set_add_cb(clone!(@weak this => move || {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
let work = Work::new(this.person.clone());
|
let work = Work::new(this.person.clone());
|
||||||
if let Some(work) = push!(this.handle, WorkEditor, Some(work)).await {
|
if let Some(work) = push!(this.handle, WorkEditor, Some(work)).await {
|
||||||
|
|
@ -122,28 +121,32 @@ impl Screen<Person, Work> for WorkSelectorWorkScreen {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selector.set_load_online(clone!(@weak this => move || {
|
this.selector
|
||||||
async move { Ok(this.handle.backend.cl().get_works(&this.person.id).await?) }
|
.set_load_online(clone!(@weak this => @default-panic, move || {
|
||||||
}));
|
async move { Ok(this.handle.backend.cl().get_works(&this.person.id).await?) }
|
||||||
|
|
||||||
this.selector.set_load_local(clone!(@weak this => move || {
|
|
||||||
async move { this.handle.backend.db().get_works(&this.person.id).await.unwrap() }
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.selector.set_make_widget(clone!(@weak this => move |work| {
|
|
||||||
let row = libadwaita::ActionRow::new();
|
|
||||||
row.set_activatable(true);
|
|
||||||
row.set_title(Some(&work.title));
|
|
||||||
|
|
||||||
let work = work.to_owned();
|
|
||||||
row.connect_activated(clone!(@weak this => move |_| {
|
|
||||||
this.handle.pop(Some(work.clone()));
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
row.upcast()
|
this.selector
|
||||||
}));
|
.set_load_local(clone!(@weak this => @default-panic, move || {
|
||||||
|
async move { this.handle.backend.db().get_works(&this.person.id).await.unwrap() }
|
||||||
|
}));
|
||||||
|
|
||||||
this.selector.set_filter(|search, work| work.title.to_lowercase().contains(search));
|
this.selector
|
||||||
|
.set_make_widget(clone!(@weak this => @default-panic, move |work| {
|
||||||
|
let row = libadwaita::ActionRow::new();
|
||||||
|
row.set_activatable(true);
|
||||||
|
row.set_title(Some(&work.title));
|
||||||
|
|
||||||
|
let work = work.to_owned();
|
||||||
|
row.connect_activated(clone!(@weak this => move |_| {
|
||||||
|
this.handle.pop(Some(work.clone()));
|
||||||
|
}));
|
||||||
|
|
||||||
|
row.upcast()
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.selector
|
||||||
|
.set_filter(|search, work| work.title.to_lowercase().contains(search));
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,7 @@ impl ButtonRow {
|
||||||
|
|
||||||
widget.add_suffix(&button);
|
widget.add_suffix(&button);
|
||||||
|
|
||||||
Self {
|
Self { widget, button }
|
||||||
widget,
|
|
||||||
button,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the subtitle of the row.
|
/// Set the subtitle of the row.
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,7 @@ impl EntryRow {
|
||||||
|
|
||||||
widget.add_suffix(&entry);
|
widget.add_suffix(&entry);
|
||||||
|
|
||||||
Self {
|
Self { widget, entry }
|
||||||
widget,
|
|
||||||
entry,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the text of the entry.
|
/// Set the text of the entry.
|
||||||
|
|
@ -39,6 +36,6 @@ impl EntryRow {
|
||||||
|
|
||||||
/// Get the text that was entered by the user.
|
/// Get the text that was entered by the user.
|
||||||
pub fn get_text(&self) -> String {
|
pub fn get_text(&self) -> String {
|
||||||
self.entry.get_text().to_string()
|
self.entry.text().to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
use glib::prelude::*;
|
|
||||||
use glib::subclass;
|
|
||||||
use glib::subclass::prelude::*;
|
|
||||||
use gio::prelude::*;
|
use gio::prelude::*;
|
||||||
use gio::subclass::prelude::*;
|
use gio::subclass::prelude::*;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
|
||||||
glib::wrapper! {
|
glib::wrapper! {
|
||||||
|
/// A thin list model managing only indices to an external data source.
|
||||||
pub struct IndexedListModel(ObjectSubclass<indexed_list_model::IndexedListModel>)
|
pub struct IndexedListModel(ObjectSubclass<indexed_list_model::IndexedListModel>)
|
||||||
@implements gio::ListModel;
|
@implements gio::ListModel;
|
||||||
}
|
}
|
||||||
|
|
@ -19,7 +17,7 @@ impl IndexedListModel {
|
||||||
|
|
||||||
/// Set the length of the list model.
|
/// Set the length of the list model.
|
||||||
pub fn set_length(&self, length: u32) {
|
pub fn set_length(&self, length: u32) {
|
||||||
let old_length = self.get_property("length").unwrap().get_some::<u32>().unwrap();
|
let old_length = self.property("length").unwrap().get::<u32>().unwrap();
|
||||||
self.set_property("length", &length).unwrap();
|
self.set_property("length", &length).unwrap();
|
||||||
self.items_changed(0, old_length, length);
|
self.items_changed(0, old_length, length);
|
||||||
}
|
}
|
||||||
|
|
@ -28,58 +26,54 @@ impl IndexedListModel {
|
||||||
mod indexed_list_model {
|
mod indexed_list_model {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub struct IndexedListModel {
|
pub struct IndexedListModel {
|
||||||
length: Cell<u32>,
|
length: Cell<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
impl ObjectSubclass for IndexedListModel {
|
impl ObjectSubclass for IndexedListModel {
|
||||||
const NAME: &'static str = "IndexedListModel";
|
const NAME: &'static str = "IndexedListModel";
|
||||||
|
|
||||||
type Type = super::IndexedListModel;
|
type Type = super::IndexedListModel;
|
||||||
type ParentType = glib::Object;
|
type ParentType = glib::Object;
|
||||||
type Interfaces = (gio::ListModel,);
|
type Interfaces = (gio::ListModel,);
|
||||||
type Instance = subclass::simple::InstanceStruct<Self>;
|
|
||||||
type Class = subclass::simple::ClassStruct<Self>;
|
|
||||||
|
|
||||||
glib::object_subclass!();
|
|
||||||
|
|
||||||
fn new() -> Self {
|
|
||||||
Self { length: Cell::new(0) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectImpl for IndexedListModel {
|
impl ObjectImpl for IndexedListModel {
|
||||||
fn properties() -> &'static [glib::ParamSpec] {
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||||
vec![
|
vec![glib::ParamSpec::new_uint(
|
||||||
glib::ParamSpec::uint(
|
"length",
|
||||||
"length",
|
"Length",
|
||||||
"Length",
|
"Length",
|
||||||
"Length",
|
0,
|
||||||
0,
|
std::u32::MAX,
|
||||||
std::u32::MAX,
|
0,
|
||||||
0,
|
glib::ParamFlags::READWRITE,
|
||||||
glib::ParamFlags::READWRITE,
|
)]
|
||||||
),
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
PROPERTIES.as_ref()
|
PROPERTIES.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_property(&self, _obj: &Self::Type, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
fn set_property(
|
||||||
match pspec.get_name() {
|
&self,
|
||||||
|
_: &Self::Type,
|
||||||
|
_: usize,
|
||||||
|
value: &glib::Value,
|
||||||
|
pspec: &glib::ParamSpec,
|
||||||
|
) {
|
||||||
|
match pspec.name() {
|
||||||
"length" => {
|
"length" => {
|
||||||
let length = value.get().unwrap().unwrap();
|
let length = value.get::<u32>().unwrap();
|
||||||
self.length.set(length);
|
self.length.set(length);
|
||||||
}
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||||
match pspec.get_name() {
|
match pspec.name() {
|
||||||
"length" => self.length.get().to_value(),
|
"length" => self.length.get().to_value(),
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
|
|
@ -87,21 +81,22 @@ mod indexed_list_model {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ListModelImpl for IndexedListModel {
|
impl ListModelImpl for IndexedListModel {
|
||||||
fn get_item_type(&self, _: &Self::Type) -> glib::Type {
|
fn item_type(&self, _: &Self::Type) -> glib::Type {
|
||||||
ItemIndex::static_type()
|
ItemIndex::static_type()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_n_items(&self, _: &Self::Type) -> u32 {
|
fn n_items(&self, _: &Self::Type) -> u32 {
|
||||||
self.length.get()
|
self.length.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_item(&self, _: &Self::Type, position: u32) -> Option<glib::Object> {
|
fn item(&self, _: &Self::Type, position: u32) -> Option<glib::Object> {
|
||||||
Some(ItemIndex::new(position).upcast())
|
Some(ItemIndex::new(position).upcast())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
glib::wrapper! {
|
glib::wrapper! {
|
||||||
|
/// A simple GObject holding just one integer.
|
||||||
pub struct ItemIndex(ObjectSubclass<item_index::ItemIndex>);
|
pub struct ItemIndex(ObjectSubclass<item_index::ItemIndex>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,65 +108,61 @@ impl ItemIndex {
|
||||||
|
|
||||||
/// Get the value of the item index..
|
/// Get the value of the item index..
|
||||||
pub fn get(&self) -> u32 {
|
pub fn get(&self) -> u32 {
|
||||||
self.get_property("value").unwrap().get_some::<u32>().unwrap()
|
self.property("value").unwrap().get::<u32>().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod item_index {
|
mod item_index {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub struct ItemIndex {
|
pub struct ItemIndex {
|
||||||
value: Cell<u32>,
|
value: Cell<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
impl ObjectSubclass for ItemIndex {
|
impl ObjectSubclass for ItemIndex {
|
||||||
const NAME: &'static str = "ItemIndex";
|
const NAME: &'static str = "ItemIndex";
|
||||||
|
|
||||||
type Type = super::ItemIndex;
|
type Type = super::ItemIndex;
|
||||||
type ParentType = glib::Object;
|
type ParentType = glib::Object;
|
||||||
type Interfaces = ();
|
type Interfaces = ();
|
||||||
type Instance = subclass::simple::InstanceStruct<Self>;
|
|
||||||
type Class = subclass::simple::ClassStruct<Self>;
|
|
||||||
|
|
||||||
glib::object_subclass!();
|
|
||||||
|
|
||||||
fn new() -> Self {
|
|
||||||
Self { value: Cell::new(0) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectImpl for ItemIndex {
|
impl ObjectImpl for ItemIndex {
|
||||||
fn properties() -> &'static [glib::ParamSpec] {
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||||
vec![
|
vec![glib::ParamSpec::new_uint(
|
||||||
glib::ParamSpec::uint(
|
"value",
|
||||||
"value",
|
"Value",
|
||||||
"Value",
|
"Value",
|
||||||
"Value",
|
0,
|
||||||
0,
|
std::u32::MAX,
|
||||||
std::u32::MAX,
|
0,
|
||||||
0,
|
glib::ParamFlags::READWRITE,
|
||||||
glib::ParamFlags::READWRITE,
|
)]
|
||||||
),
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
PROPERTIES.as_ref()
|
PROPERTIES.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_property(&self, _obj: &Self::Type, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
fn set_property(
|
||||||
match pspec.get_name() {
|
&self,
|
||||||
|
_: &Self::Type,
|
||||||
|
_: usize,
|
||||||
|
value: &glib::Value,
|
||||||
|
pspec: &glib::ParamSpec,
|
||||||
|
) {
|
||||||
|
match pspec.name() {
|
||||||
"value" => {
|
"value" => {
|
||||||
let value = value.get().unwrap().unwrap();
|
let value = value.get::<u32>().unwrap();
|
||||||
self.value.set(value);
|
self.value.set(value);
|
||||||
}
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||||
match pspec.get_name() {
|
match pspec.name() {
|
||||||
"value" => self.value.get().to_value(),
|
"value" => self.value.get().to_value(),
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,14 +40,15 @@ impl List {
|
||||||
move_cb: RefCell::new(None),
|
move_cb: RefCell::new(None),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.filter.set_filter_func(clone!(@strong this => move |index| {
|
this.filter
|
||||||
if let Some(cb) = &*this.filter_cb.borrow() {
|
.set_filter_func(clone!(@strong this => move |index| {
|
||||||
let index = index.downcast_ref::<ItemIndex>().unwrap().get() as usize;
|
if let Some(cb) = &*this.filter_cb.borrow() {
|
||||||
cb(index)
|
let index = index.downcast_ref::<ItemIndex>().unwrap().get() as usize;
|
||||||
} else {
|
cb(index)
|
||||||
true
|
} else {
|
||||||
}
|
true
|
||||||
}));
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
this.widget.bind_model(Some(&filter_model), clone!(@strong this => move |index| {
|
this.widget.bind_model(Some(&filter_model), clone!(@strong this => move |index| {
|
||||||
let index = index.downcast_ref::<ItemIndex>().unwrap().get() as usize;
|
let index = index.downcast_ref::<ItemIndex>().unwrap().get() as usize;
|
||||||
|
|
@ -64,13 +65,13 @@ impl List {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let drag_value = (index as u32).to_value();
|
let drag_value = (index as u32).to_value();
|
||||||
drag_source.set_content(Some(&gdk::ContentProvider::new_for_value(&drag_value)));
|
drag_source.set_content(Some(&gdk::ContentProvider::for_value(&drag_value)));
|
||||||
|
|
||||||
let drop_target = gtk::DropTarget::new(glib::Type::U32, gdk::DragAction::COPY);
|
let drop_target = gtk::DropTarget::new(glib::Type::U32, gdk::DragAction::COPY);
|
||||||
|
|
||||||
drop_target.connect_drop(clone!(@strong this => move |_, value, _, _| {
|
drop_target.connect_drop(clone!(@strong this => move |_, value, _, _| {
|
||||||
if let Some(cb) = &*this.move_cb.borrow() {
|
if let Some(cb) = &*this.move_cb.borrow() {
|
||||||
let old_index: u32 = value.get_some().unwrap();
|
let old_index: u32 = value.get().unwrap();
|
||||||
cb(old_index as usize, index);
|
cb(old_index as usize, index);
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ impl Screen {
|
||||||
widget.insert_action_group("widget", Some(&actions));
|
widget.insert_action_group("widget", Some(&actions));
|
||||||
|
|
||||||
search_button.connect_toggled(clone!(@strong search_entry => move |search_button| {
|
search_button.connect_toggled(clone!(@strong search_entry => move |search_button| {
|
||||||
if search_button.get_active() {
|
if search_button.is_active() {
|
||||||
search_entry.grab_focus();
|
search_entry.grab_focus();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
@ -88,7 +88,8 @@ impl Screen {
|
||||||
action.connect_activate(move |_, _| cb());
|
action.connect_activate(move |_, _| cb());
|
||||||
|
|
||||||
self.actions.add_action(&action);
|
self.actions.add_action(&action);
|
||||||
self.menu.append(Some(label), Some(&format!("widget.{}", name)));
|
self.menu
|
||||||
|
.append(Some(label), Some(&format!("widget.{}", name)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the closure to be called when the search string has changed.
|
/// Set the closure to be called when the search string has changed.
|
||||||
|
|
@ -98,7 +99,7 @@ impl Screen {
|
||||||
|
|
||||||
/// Get the current search string.
|
/// Get the current search string.
|
||||||
pub fn get_search(&self) -> String {
|
pub fn get_search(&self) -> String {
|
||||||
self.search_entry.get_text().to_string().to_lowercase()
|
self.search_entry.text().to_string().to_lowercase()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hide the loading page and switch to the content.
|
/// Hide the loading page and switch to the content.
|
||||||
|
|
|
||||||
|
|
@ -45,15 +45,16 @@ impl UploadSection {
|
||||||
switch,
|
switch,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.switch.connect_property_state_notify(clone!(@weak this => move |_| {
|
this.switch
|
||||||
this.backend.set_use_server(this.switch.get_state());
|
.connect_state_notify(clone!(@weak this => move |_| {
|
||||||
}));
|
this.backend.set_use_server(this.switch.state());
|
||||||
|
}));
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return whether the user has enabled the upload switch.
|
/// Return whether the user has enabled the upload switch.
|
||||||
pub fn get_active(&self) -> bool {
|
pub fn get_active(&self) -> bool {
|
||||||
self.switch.get_active()
|
self.switch.state()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::screens::{MainScreen, WelcomeScreen};
|
|
||||||
use crate::navigator::Navigator;
|
use crate::navigator::Navigator;
|
||||||
|
use crate::screens::{MainScreen, WelcomeScreen};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use musicus_backend::{Backend, BackendState};
|
use musicus_backend::{Backend, BackendState};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
@ -41,9 +41,8 @@ impl Window {
|
||||||
loading_screen.append(&header);
|
loading_screen.append(&header);
|
||||||
loading_screen.append(&spinner);
|
loading_screen.append(&spinner);
|
||||||
|
|
||||||
|
|
||||||
let navigator = Navigator::new(Rc::clone(&backend), &window, &loading_screen);
|
let navigator = Navigator::new(Rc::clone(&backend), &window, &loading_screen);
|
||||||
libadwaita::ApplicationWindowExt::set_child(&window, Some(&navigator.widget));
|
window.set_child(Some(&navigator.widget));
|
||||||
|
|
||||||
let this = Rc::new(Self {
|
let this = Rc::new(Self {
|
||||||
backend,
|
backend,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue