mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 03:47:23 +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
|
||||
/flatpak
|
||||
/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),
|
||||
}
|
||||
|
||||
|
||||
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.
|
||||
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()) {
|
||||
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);
|
||||
if let Err(err) = self
|
||||
.settings
|
||||
.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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{Error, Result};
|
||||
use musicus_database::Track;
|
||||
use glib::clone;
|
||||
use gstreamer_player::prelude::*;
|
||||
use musicus_database::Track;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
|
@ -101,9 +101,11 @@ impl Player {
|
|||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
result.mpris.connect_play_pause(clone!(@weak result => move || {
|
||||
result.play_pause();
|
||||
}));
|
||||
result
|
||||
.mpris
|
||||
.connect_play_pause(clone!(@weak result => move || {
|
||||
result.play_pause();
|
||||
}));
|
||||
|
||||
result.mpris.connect_play(clone!(@weak result => move || {
|
||||
if !result.is_playing() {
|
||||
|
|
@ -117,9 +119,11 @@ impl Player {
|
|||
}
|
||||
}));
|
||||
|
||||
result.mpris.connect_previous(clone!(@weak result => move || {
|
||||
let _ = result.previous();
|
||||
}));
|
||||
result
|
||||
.mpris
|
||||
.connect_previous(clone!(@weak result => move || {
|
||||
let _ = result.previous();
|
||||
}));
|
||||
|
||||
result.mpris.connect_next(clone!(@weak result => move || {
|
||||
let _ = result.next();
|
||||
|
|
@ -246,10 +250,9 @@ impl Player {
|
|||
}
|
||||
|
||||
pub fn previous(&self) -> Result<()> {
|
||||
let mut current_track = self
|
||||
.current_track
|
||||
.get()
|
||||
.ok_or(Error::Other(String::from("Player tried to access non existant current track.")))?;
|
||||
let mut current_track = self.current_track.get().ok_or(Error::Other(String::from(
|
||||
"Player tried to access non existant current track.",
|
||||
)))?;
|
||||
|
||||
if current_track > 0 {
|
||||
current_track -= 1;
|
||||
|
|
@ -270,10 +273,9 @@ impl Player {
|
|||
}
|
||||
|
||||
pub fn next(&self) -> Result<()> {
|
||||
let mut current_track = self
|
||||
.current_track
|
||||
.get()
|
||||
.ok_or(Error::Other(String::from("Player tried to access non existant current track.")))?;
|
||||
let mut current_track = self.current_track.get().ok_or(Error::Other(String::from(
|
||||
"Player tried to access non existant current track.",
|
||||
)))?;
|
||||
|
||||
let playlist = self.playlist.borrow();
|
||||
|
||||
|
|
@ -289,11 +291,17 @@ impl Player {
|
|||
pub fn set_track(&self, current_track: usize) -> Result<()> {
|
||||
let track = &self.playlist.borrow()[current_track];
|
||||
|
||||
let path = self.music_library_path.join(track.path.clone())
|
||||
.into_os_string().into_string().unwrap();
|
||||
let path = self
|
||||
.music_library_path
|
||||
.join(track.path.clone())
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap();
|
||||
|
||||
let uri = glib::filename_to_uri(&path, None)
|
||||
.or(Err(Error::Other(format!("Failed to create URI from path: {}", path))))?;
|
||||
let uri = glib::filename_to_uri(&path, None).or(Err(Error::Other(format!(
|
||||
"Failed to create URI from path: {}",
|
||||
path
|
||||
))))?;
|
||||
|
||||
self.player.set_uri(&uri);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{Backend, Error, Result};
|
||||
use musicus_client::LoginData;
|
||||
use futures_channel::oneshot;
|
||||
use musicus_client::LoginData;
|
||||
use secret_service::{Collection, EncryptionType, SecretService};
|
||||
use std::collections::HashMap;
|
||||
use std::thread;
|
||||
|
|
@ -35,14 +35,18 @@ impl Backend {
|
|||
let items = collection.get_all_items()?;
|
||||
|
||||
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 {
|
||||
Some(item) => {
|
||||
let username = item
|
||||
.get_attributes()?
|
||||
.get("username")
|
||||
.ok_or(Error::Other("Missing username in SecretService attributes."))?
|
||||
.ok_or(Error::Other(
|
||||
"Missing username in SecretService attributes.",
|
||||
))?
|
||||
.to_owned();
|
||||
|
||||
let password = std::str::from_utf8(&item.get_secret()?)?.to_owned();
|
||||
|
|
@ -63,7 +67,13 @@ impl Backend {
|
|||
|
||||
let mut attributes = HashMap::new();
|
||||
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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,4 @@ pub enum Error {
|
|||
Other(&'static str),
|
||||
}
|
||||
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ impl Client {
|
|||
/// Post a new instrument to the server.
|
||||
pub async fn post_instrument(&self, data: &Instrument) -> Result<()> {
|
||||
info!("Post instrument {:?}", data);
|
||||
self.post("instruments", serde_json::to_string(data)?).await?;
|
||||
self.post("instruments", serde_json::to_string(data)?)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use isahc::{AsyncBody, Request, Response};
|
||||
use isahc::http::StatusCode;
|
||||
use isahc::prelude::*;
|
||||
use isahc::{AsyncBody, Request, Response};
|
||||
use log::info;
|
||||
use serde::Serialize;
|
||||
use std::time::Duration;
|
||||
use std::cell::RefCell;
|
||||
use std::time::Duration;
|
||||
|
||||
pub mod ensembles;
|
||||
pub use ensembles::*;
|
||||
|
|
@ -112,7 +112,7 @@ impl Client {
|
|||
.body(())?
|
||||
.send_async()
|
||||
.await?;
|
||||
|
||||
|
||||
match response.status() {
|
||||
StatusCode::OK => Ok(response.text().await?),
|
||||
status_code => Err(Error::UnexpectedResponse(status_code)),
|
||||
|
|
@ -164,16 +164,21 @@ impl Client {
|
|||
|
||||
/// Require the server URL to be set.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
pub async fn get_mediums_for_recording(&self, recording_id: &str) -> Result<Vec<Medium>> {
|
||||
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)?;
|
||||
Ok(mediums)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ impl Client {
|
|||
/// Post a new recording to the server.
|
||||
pub async fn post_recording(&self, data: &Recording) -> Result<()> {
|
||||
info!("Post recording {:?}", data);
|
||||
self.post("recordings", serde_json::to_string(data)?).await?;
|
||||
self.post("recordings", serde_json::to_string(data)?)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{Client, Result};
|
||||
use isahc::Request;
|
||||
use isahc::http::StatusCode;
|
||||
use isahc::prelude::*;
|
||||
use isahc::Request;
|
||||
use log::info;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use super::*;
|
||||
use log::debug;
|
||||
use tokio::sync::oneshot::{self, Sender};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use tokio::sync::oneshot::{self, Sender};
|
||||
|
||||
/// An action the database thread can perform.
|
||||
#[derive(Debug)]
|
||||
|
|
@ -359,28 +359,32 @@ impl DbThread {
|
|||
/// Get all mediums with the specified source ID.
|
||||
pub async fn get_mediums_by_source_id(&self, id: &str) -> Result<Vec<Medium>> {
|
||||
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?
|
||||
}
|
||||
|
||||
/// Get all mediums on which a person performs.
|
||||
pub async fn get_mediums_for_person(&self, id: &str) -> Result<Vec<Medium>> {
|
||||
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?
|
||||
}
|
||||
|
||||
/// Get all mediums on which an ensemble performs.
|
||||
pub async fn get_mediums_for_ensemble(&self, id: &str) -> Result<Vec<Medium>> {
|
||||
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?
|
||||
}
|
||||
|
||||
/// Get all tracks for a recording.
|
||||
pub async fn get_tracks(&self, recording_id: &str) -> Result<Vec<Track>> {
|
||||
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?
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use crate::error::{Error, Result};
|
||||
use crate::session::{ImportSession, ImportTrack, State};
|
||||
use gstreamer::prelude::*;
|
||||
use gstreamer::{ClockTime, ElementFactory, MessageType, MessageView, TocEntryType};
|
||||
use gstreamer::tags::{Duration, TrackNumber};
|
||||
use gstreamer::{ClockTime, ElementFactory, MessageType, MessageView, TocEntryType};
|
||||
use log::info;
|
||||
use sha2::{Sha256, Digest};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::path::PathBuf;
|
||||
use tokio::sync::watch;
|
||||
|
||||
|
|
@ -28,23 +28,31 @@ pub(super) fn new() -> Result<ImportSession> {
|
|||
pipeline.add_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.
|
||||
|
||||
pipeline.set_state(gstreamer::State::Paused)?;
|
||||
|
||||
let msg = bus.timed_pop_filtered(ClockTime::from_seconds(5),
|
||||
&vec![MessageType::Toc, MessageType::Error]);
|
||||
let msg = bus.timed_pop_filtered(
|
||||
ClockTime::from_seconds(5),
|
||||
&vec![MessageType::Toc, MessageType::Error],
|
||||
);
|
||||
|
||||
let toc = match msg {
|
||||
Some(msg) => match msg.view() {
|
||||
MessageView::Error(err) => Err(Error::os(err.get_error())),
|
||||
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(
|
||||
format!("Timeout while waiting for first message from GStreamer."))),
|
||||
None => Err(Error::Timeout(format!(
|
||||
"Timeout while waiting for first message from GStreamer."
|
||||
))),
|
||||
}?;
|
||||
|
||||
pipeline.set_state(gstreamer::State::Ready)?;
|
||||
|
|
@ -66,22 +74,31 @@ pub(super) fn new() -> Result<ImportSession> {
|
|||
|
||||
for entry in toc.get_entries() {
|
||||
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.")))?
|
||||
.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()
|
||||
.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()
|
||||
.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.")))?
|
||||
.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()
|
||||
.ok_or(Error::u(
|
||||
String::from("Failed to unwrap track number tag from TOC entry.")))?;
|
||||
.ok_or(Error::u(String::from(
|
||||
"Failed to unwrap track number tag from TOC entry.",
|
||||
)))?;
|
||||
|
||||
hasher.update(duration.to_le_bytes());
|
||||
|
||||
|
|
@ -129,11 +146,11 @@ pub(super) fn new() -> Result<ImportSession> {
|
|||
info!("Finished ripping track {}.", track.number);
|
||||
pipeline.set_state(gstreamer::State::Ready)?;
|
||||
break;
|
||||
},
|
||||
}
|
||||
MessageView::Error(err) => {
|
||||
pipeline.set_state(gstreamer::State::Null)?;
|
||||
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.
|
||||
fn create_tmp_dir() -> Result<PathBuf> {
|
||||
let mut tmp_dir = glib::get_tmp_dir().ok_or(Error::u(
|
||||
String::from("Failed to get temporary directory using glib::get_tmp_dir().")))?;
|
||||
let mut tmp_dir = glib::tmp_dir().ok_or(Error::u(String::from(
|
||||
"Failed to get temporary directory using glib::get_tmp_dir().",
|
||||
)))?;
|
||||
|
||||
let dir_name = format!("musicus-{}", rand::random::<u64>());
|
||||
tmp_dir.push(dir_name);
|
||||
|
|
@ -167,4 +185,3 @@ fn create_tmp_dir() -> Result<PathBuf> {
|
|||
|
||||
Ok(tmp_dir)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,10 +39,7 @@ impl Error {
|
|||
|
||||
/// Create a new unexpected error without an explicit source.
|
||||
pub(super) fn u(msg: String) -> Self {
|
||||
Self::Unexpected {
|
||||
msg,
|
||||
source: None,
|
||||
}
|
||||
Self::Unexpected { msg, source: None }
|
||||
}
|
||||
|
||||
/// 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>;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ use crate::error::{Error, Result};
|
|||
use crate::session::{ImportSession, ImportTrack, State};
|
||||
use gstreamer::ClockTime;
|
||||
use gstreamer_pbutils::Discoverer;
|
||||
use log::{warn, info};
|
||||
use sha2::{Sha256, Digest};
|
||||
use log::{info, warn};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fs::DirEntry;
|
||||
use std::path::PathBuf;
|
||||
use tokio::sync::watch;
|
||||
|
|
@ -17,26 +17,32 @@ pub(super) fn new(path: PathBuf) -> Result<ImportSession> {
|
|||
let mut hasher = Sha256::new();
|
||||
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()));
|
||||
|
||||
for entry in entries {
|
||||
if entry.file_type()?.is_file() {
|
||||
let path = entry.path();
|
||||
|
||||
let uri = glib::filename_to_uri(&path, None)
|
||||
.or(Err(Error::u(format!("Failed to create URI from path: {:?}", path))))?;
|
||||
let uri = glib::filename_to_uri(&path, None).or(Err(Error::u(format!(
|
||||
"Failed to create URI from path: {:?}",
|
||||
path
|
||||
))))?;
|
||||
|
||||
let info = discoverer.discover_uri(&uri)?;
|
||||
|
||||
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)))?;
|
||||
|
||||
let file_name = entry.file_name();
|
||||
let name = file_name.into_string()
|
||||
.or(Err(Error::u(format!(
|
||||
"Failed to convert OsString to String: {:?}", entry.file_name()))))?;
|
||||
let name = file_name.into_string().or(Err(Error::u(format!(
|
||||
"Failed to convert OsString to String: {:?}",
|
||||
entry.file_name()
|
||||
))))?;
|
||||
|
||||
hasher.update(duration.to_le_bytes());
|
||||
|
||||
|
|
@ -50,7 +56,10 @@ pub(super) fn new(path: PathBuf) -> Result<ImportSession> {
|
|||
tracks.push(track);
|
||||
number += 1;
|
||||
} 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 session::{ImportSession, ImportTrack, State};
|
||||
|
||||
pub mod error;
|
||||
pub mod session;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use crate::{disc, folder};
|
||||
use crate::error::Result;
|
||||
use crate::{disc, folder};
|
||||
use std::path::PathBuf;
|
||||
use std::thread;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use tokio::sync::{oneshot, watch};
|
||||
|
||||
/// The current state of the import process.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ async-trait = "0.1.42"
|
|||
futures-channel = "0.3.5"
|
||||
gettext-rs = { version = "0.5.0", features = ["gettext-system"] }
|
||||
gstreamer = "0.16.4"
|
||||
gtk-macros = "0.2.0"
|
||||
gtk-macros = "0.3.0"
|
||||
log = "0.4.14"
|
||||
musicus_backend = { version = "0.1.0", path = "../backend" }
|
||||
once_cell = "1.5.2"
|
||||
|
|
@ -33,7 +33,7 @@ git = "https://github.com/gtk-rs/gtk4-rs"
|
|||
package = "gtk4"
|
||||
|
||||
[dependencies.libadwaita]
|
||||
git = "https://gitlab.gnome.org/bilelmoussaoui/libadwaita-rs"
|
||||
git = "https://gitlab.gnome.org/World/Rust/libadwaita-rs.git"
|
||||
package = "libadwaita"
|
||||
|
||||
[dependencies.pango]
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ impl Screen<Option<Ensemble>, Ensemble> for EnsembleEditor {
|
|||
this.name
|
||||
.entry
|
||||
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
||||
|
||||
|
||||
this.validate();
|
||||
|
||||
this
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use crate::navigator::{NavigationHandle, Screen};
|
||||
use crate::selectors::{EnsembleSelector, InstrumentSelector, PersonSelector};
|
||||
use crate::widgets::{Editor, Section, ButtonRow, Widget};
|
||||
use crate::widgets::{ButtonRow, Editor, Section, Widget};
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::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::rc::Rc;
|
||||
|
||||
|
|
@ -40,8 +40,9 @@ impl Screen<Option<Performance>, Performance> for PerformanceEditor {
|
|||
performer_list.append(&ensemble_row.get_widget());
|
||||
|
||||
let performer_section = Section::new(&gettext("Performer"), &performer_list);
|
||||
performer_section.set_subtitle(
|
||||
&gettext("Select either a person or an ensemble as a performer."));
|
||||
performer_section.set_subtitle(&gettext(
|
||||
"Select either a person or an ensemble as a performer.",
|
||||
));
|
||||
|
||||
let role_list = gtk::ListBoxBuilder::new()
|
||||
.selection_mode(gtk::SelectionMode::None)
|
||||
|
|
@ -59,8 +60,9 @@ impl Screen<Option<Performance>, Performance> for PerformanceEditor {
|
|||
role_list.append(&role_row.get_widget());
|
||||
|
||||
let role_section = Section::new(&gettext("Role"), &role_list);
|
||||
role_section.set_subtitle(
|
||||
&gettext("Optionally, choose a role to specify what the performer does."));
|
||||
role_section.set_subtitle(&gettext(
|
||||
"Optionally, choose a role to specify what the performer does.",
|
||||
));
|
||||
|
||||
editor.add_content(&performer_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 {
|
||||
if let Some(ensemble) = push!(this.handle, EnsembleSelector).await {
|
||||
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 {
|
||||
if let Some(role) = push!(this.handle, InstrumentSelector).await {
|
||||
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.show_role(None);
|
||||
this.role.replace(None);
|
||||
}));
|
||||
this.reset_role_button
|
||||
.connect_clicked(clone!(@weak this => move |_| {
|
||||
this.show_role(None);
|
||||
this.role.replace(None);
|
||||
}));
|
||||
|
||||
// Initialize
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ impl Screen<Option<Person>, Person> for PersonEditor {
|
|||
|
||||
// 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);
|
||||
}));
|
||||
|
||||
|
|
@ -84,11 +84,11 @@ impl Screen<Option<Person>, Person> for PersonEditor {
|
|||
|
||||
this.first_name
|
||||
.entry
|
||||
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
||||
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
||||
|
||||
this.last_name
|
||||
.entry
|
||||
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
||||
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
||||
|
||||
this.validate();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use super::performance::PerformanceEditor;
|
||||
use crate::navigator::{NavigationHandle, Screen};
|
||||
use crate::selectors::WorkSelector;
|
||||
use crate::widgets::{List, Widget};
|
||||
use crate::navigator::{NavigationHandle, Screen};
|
||||
use anyhow::Result;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
|
|
@ -74,26 +74,27 @@ impl Screen<Option<Recording>, Recording> for RecordingEditor {
|
|||
|
||||
// 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.save_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
spawn!(@clone this, async move {
|
||||
this.widget.set_visible_child_name("loading");
|
||||
match this.save().await {
|
||||
Ok(recording) => {
|
||||
this.handle.pop(Some(recording));
|
||||
this.save_button
|
||||
.connect_clicked(clone!(@weak this => move |_| {
|
||||
spawn!(@clone this, async move {
|
||||
this.widget.set_visible_child_name("loading");
|
||||
match this.save().await {
|
||||
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 {
|
||||
if let Some(work) = push!(this.handle, WorkSelector).await {
|
||||
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 delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic"));
|
||||
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 mut performances = this.performances.borrow_mut();
|
||||
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"));
|
||||
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 {
|
||||
let performance = &this.performances.borrow()[index];
|
||||
if let Some(performance) = push!(this.handle, PerformanceEditor, Some(performance.to_owned())).await {
|
||||
|
|
@ -190,16 +191,17 @@ impl RecordingEditor {
|
|||
.borrow()
|
||||
.clone()
|
||||
.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(),
|
||||
};
|
||||
|
||||
let upload = self.upload_switch.get_active();
|
||||
let upload = self.upload_switch.state();
|
||||
if upload {
|
||||
self.handle.backend.cl().post_recording(&recording).await?;
|
||||
}
|
||||
|
||||
self.handle.backend
|
||||
self.handle
|
||||
.backend
|
||||
.db()
|
||||
.update_recording(recording.clone().into())
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -117,12 +117,12 @@ impl Screen<Option<Work>, Work> for WorkEditor {
|
|||
|
||||
// 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.save_button
|
||||
.connect_clicked(clone!(@weak this => move |_| {
|
||||
.connect_clicked(clone!(@weak this => move |_| {
|
||||
spawn!(@clone this, async move {
|
||||
this.widget.set_visible_child_name("loading");
|
||||
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 {
|
||||
if let Some(person) = push!(this.handle, PersonSelector).await {
|
||||
this.show_composer(&person);
|
||||
|
|
@ -147,10 +147,10 @@ impl Screen<Option<Work>, Work> for WorkEditor {
|
|||
}));
|
||||
|
||||
this.title_entry
|
||||
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
||||
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
||||
|
||||
this.instrument_list
|
||||
.set_make_widget_cb(clone!(@weak this => move |index| {
|
||||
this.instrument_list.set_make_widget_cb(
|
||||
clone!(@weak this => @default-panic, move |index| {
|
||||
let instrument = &this.instruments.borrow()[index];
|
||||
|
||||
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.upcast()
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
add_instrument_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
add_instrument_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
spawn!(@clone this, async move {
|
||||
if let Some(instrument) = push!(this.handle, InstrumentSelector).await {
|
||||
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 delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic"));
|
||||
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 mut structure = this.structure.borrow_mut();
|
||||
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"));
|
||||
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 {
|
||||
match this.structure.borrow()[index].clone() {
|
||||
PartOrSection::Part(part) => {
|
||||
|
|
@ -251,7 +252,7 @@ impl Screen<Option<Work>, Work> for WorkEditor {
|
|||
}));
|
||||
|
||||
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 mut structure = this.structure.borrow_mut();
|
||||
structure.swap(old_index, new_index);
|
||||
|
|
@ -261,7 +262,7 @@ impl Screen<Option<Work>, Work> for WorkEditor {
|
|||
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 {
|
||||
if let Some(part) = push!(this.handle, WorkPartEditor, None).await {
|
||||
let length = {
|
||||
|
|
@ -312,9 +313,8 @@ impl WorkEditor {
|
|||
|
||||
/// Validate inputs and enable/disable saving.
|
||||
fn validate(&self) {
|
||||
self.save_button.set_sensitive(
|
||||
!self.title_entry.get_text().is_empty() && self.composer.borrow().is_some(),
|
||||
);
|
||||
self.save_button
|
||||
.set_sensitive(!self.title_entry.text().is_empty() && self.composer.borrow().is_some());
|
||||
}
|
||||
|
||||
/// Save the work and possibly upload it to the server.
|
||||
|
|
@ -337,7 +337,7 @@ impl WorkEditor {
|
|||
|
||||
let work = Work {
|
||||
id: self.id.clone(),
|
||||
title: self.title_entry.get_text().to_string(),
|
||||
title: self.title_entry.text().to_string(),
|
||||
composer: self
|
||||
.composer
|
||||
.borrow()
|
||||
|
|
@ -348,7 +348,7 @@ impl WorkEditor {
|
|||
sections: sections,
|
||||
};
|
||||
|
||||
let upload = self.upload_switch.get_active();
|
||||
let upload = self.upload_switch.state();
|
||||
if upload {
|
||||
self.handle.backend.cl().post_work(&work).await?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,21 +39,21 @@ impl Screen<Option<WorkPart>, WorkPart> for WorkPartEditor {
|
|||
|
||||
// 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.save_button
|
||||
.connect_clicked(clone!(@weak this => move |_| {
|
||||
.connect_clicked(clone!(@weak this => move |_| {
|
||||
let section = WorkPart {
|
||||
title: this.title_entry.get_text().to_string(),
|
||||
title: this.title_entry.text().to_string(),
|
||||
};
|
||||
|
||||
this.handle.pop(Some(section));
|
||||
}));
|
||||
|
||||
this.title_entry
|
||||
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
||||
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
||||
|
||||
this.validate();
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ impl WorkPartEditor {
|
|||
/// Validate inputs and enable/disable saving.
|
||||
fn validate(&self) {
|
||||
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
|
||||
|
||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
this.handle.pop(None);
|
||||
}));
|
||||
|
||||
this.save_button
|
||||
.connect_clicked(clone!(@weak this => move |_| {
|
||||
.connect_clicked(clone!(@weak this => move |_| {
|
||||
let section = WorkSection {
|
||||
before_index: 0,
|
||||
title: this.title_entry.get_text().to_string(),
|
||||
title: this.title_entry.text().to_string(),
|
||||
};
|
||||
|
||||
this.handle.pop(Some(section));
|
||||
}));
|
||||
|
||||
this.title_entry
|
||||
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
||||
.connect_changed(clone!(@weak this => move |_| this.validate()));
|
||||
|
||||
this.validate();
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ impl WorkSectionEditor {
|
|||
/// Validate inputs and enable/disable saving.
|
||||
fn validate(&self) {
|
||||
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_macros::get_widget;
|
||||
use libadwaita::prelude::*;
|
||||
use musicus_backend::Error;
|
||||
use musicus_backend::db::Medium;
|
||||
use musicus_backend::import::ImportSession;
|
||||
use musicus_backend::Error;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ impl ImportScreen {
|
|||
|
||||
let this = self;
|
||||
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())
|
||||
} else {
|
||||
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
|
||||
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 {
|
||||
let next_child = child.get_next_sibling();
|
||||
let next_child = child.next_sibling();
|
||||
self.matching_list.remove(&child);
|
||||
|
||||
match next_child {
|
||||
|
|
@ -77,7 +77,7 @@ impl ImportScreen {
|
|||
.activatable(true)
|
||||
.build();
|
||||
|
||||
row.connect_activated(clone!(@weak this => move |_| {
|
||||
row.connect_activated(clone!(@weak this => move |_| {
|
||||
let medium = medium.clone();
|
||||
spawn!(@clone this, async move {
|
||||
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
|
||||
|
||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
this.handle.pop(None);
|
||||
}));
|
||||
|
||||
this.server_check_button.connect_toggled(clone!(@weak this => move |_| {
|
||||
this.handle.backend.set_use_server(this.server_check_button.get_active());
|
||||
this.server_check_button
|
||||
.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();
|
||||
}));
|
||||
|
||||
try_again_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
this.load_matches();
|
||||
}));
|
||||
|
||||
select_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
select_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
spawn!(@clone this, async move {
|
||||
if let Some(medium) = push!(this.handle, MediumSelector).await {
|
||||
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 {
|
||||
if let Some(medium) = push!(this.handle, MediumEditor, (Arc::clone(&this.session), None)).await {
|
||||
this.select_medium(medium);
|
||||
|
|
|
|||
|
|
@ -66,12 +66,12 @@ impl Screen<(Arc<ImportSession>, Option<Medium>), Medium> for MediumEditor {
|
|||
|
||||
// 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.done_button
|
||||
.connect_clicked(clone!(@weak this => move |_| {
|
||||
.connect_clicked(clone!(@weak this => move |_| {
|
||||
this.widget.set_visible_child_name("loading");
|
||||
spawn!(@clone this, async move {
|
||||
match this.save().await {
|
||||
|
|
@ -85,9 +85,9 @@ impl Screen<(Arc<ImportSession>, Option<Medium>), Medium> for MediumEditor {
|
|||
}));
|
||||
|
||||
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 {
|
||||
if let Some(track_set) = push!(this.handle, TrackSetEditor, Arc::clone(&this.session)).await {
|
||||
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.handle.backend.set_use_server(this.publish_switch.get_state());
|
||||
}));
|
||||
this.publish_switch
|
||||
.connect_state_notify(clone!(@weak this => move |_| {
|
||||
this.handle.backend.set_use_server(this.publish_switch.state());
|
||||
}));
|
||||
|
||||
this.track_set_list
|
||||
.set_make_widget_cb(clone!(@weak this => move |index| {
|
||||
this.track_set_list.set_make_widget_cb(
|
||||
clone!(@weak this => @default-panic, move |index| {
|
||||
let track_set = &this.track_sets.borrow()[index];
|
||||
|
||||
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.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.
|
||||
}));
|
||||
|
||||
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");
|
||||
}));
|
||||
|
||||
cancel_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
cancel_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
this.handle.pop(None);
|
||||
}));
|
||||
|
||||
|
|
@ -182,7 +184,7 @@ impl MediumEditor {
|
|||
/// Validate inputs and enable/disable saving.
|
||||
fn validate(&self) {
|
||||
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 {
|
||||
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()),
|
||||
tracks: tracks,
|
||||
};
|
||||
|
||||
let upload = self.publish_switch.get_active();
|
||||
let upload = self.publish_switch.state();
|
||||
if upload {
|
||||
self.handle.backend.cl().post_medium(&medium).await?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,11 +60,11 @@ impl Screen<(Arc<ImportSession>, Medium), ()> for MediumPreview {
|
|||
|
||||
// Connect signals and callbacks
|
||||
|
||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
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 {
|
||||
let old_medium = this.medium.borrow().clone().unwrap();
|
||||
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
|
||||
.connect_clicked(clone!(@weak this => move |_| {
|
||||
.connect_clicked(clone!(@weak this => move |_| {
|
||||
this.widget.set_visible_child_name("loading");
|
||||
|
||||
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");
|
||||
}));
|
||||
|
||||
|
|
@ -116,11 +116,11 @@ impl MediumPreview {
|
|||
fn set_medium(&self, medium: Medium) {
|
||||
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;
|
||||
|
||||
loop {
|
||||
let next_child = child.get_next_sibling();
|
||||
let next_child = child.next_sibling();
|
||||
self.medium_box.remove(&child);
|
||||
|
||||
match next_child {
|
||||
|
|
|
|||
|
|
@ -38,11 +38,11 @@ impl Screen<(), ()> for SourceSelector {
|
|||
|
||||
// Connect signals and callbacks
|
||||
|
||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
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(
|
||||
Some(&gettext("Select folder")),
|
||||
Some(&this.handle.window),
|
||||
|
|
@ -54,12 +54,12 @@ impl Screen<(), ()> for SourceSelector {
|
|||
|
||||
dialog.set_modal(true);
|
||||
|
||||
dialog.connect_response(clone!(@weak this => move |dialog, response| {
|
||||
dialog.connect_response(clone!(@weak this => move |dialog, response| {
|
||||
dialog.hide();
|
||||
|
||||
if let gtk::ResponseType::Accept = response {
|
||||
if let Some(file) = dialog.get_file() {
|
||||
if let Some(path) = file.get_path() {
|
||||
if let Some(file) = dialog.file() {
|
||||
if let Some(path) = file.path() {
|
||||
this.widget.set_visible_child_name("loading");
|
||||
|
||||
spawn!(@clone this, async move {
|
||||
|
|
@ -82,7 +82,7 @@ impl Screen<(), ()> for SourceSelector {
|
|||
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");
|
||||
|
||||
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");
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,10 @@ pub struct TrackEditor {
|
|||
|
||||
impl Screen<(Recording, Vec<usize>), Vec<usize>> for TrackEditor {
|
||||
/// 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
|
||||
|
||||
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
|
||||
|
||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
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();
|
||||
this.handle.pop(Some(selection));
|
||||
}));
|
||||
|
|
@ -54,9 +57,9 @@ impl Screen<(Recording, Vec<usize>), Vec<usize>> for TrackEditor {
|
|||
let check = gtk::CheckButton::new();
|
||||
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();
|
||||
if check.get_active() {
|
||||
if check.is_active() {
|
||||
selection.push(index);
|
||||
} else {
|
||||
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
|
||||
|
||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
this.handle.pop(None);
|
||||
}));
|
||||
|
||||
this.select_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
let selection = this.selection.borrow().clone();
|
||||
this.handle.pop(Some(selection));
|
||||
}));
|
||||
this.select_button
|
||||
.connect_clicked(clone!(@weak this => move |_| {
|
||||
let selection = this.selection.borrow().clone();
|
||||
this.handle.pop(Some(selection));
|
||||
}));
|
||||
|
||||
let tracks = this.session.tracks();
|
||||
|
||||
for (index, track) in tracks.iter().enumerate() {
|
||||
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();
|
||||
if check.get_active() {
|
||||
if check.is_active() {
|
||||
selection.push(index);
|
||||
} else {
|
||||
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
|
||||
|
||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
this.handle.pop(None);
|
||||
}));
|
||||
|
||||
this.save_button
|
||||
.connect_clicked(clone!(@weak this => move |_| {
|
||||
.connect_clicked(clone!(@weak this => move |_| {
|
||||
let data = TrackSetData {
|
||||
recording: this.recording.borrow().clone().unwrap(),
|
||||
tracks: this.tracks.borrow().clone(),
|
||||
|
|
@ -88,7 +88,7 @@ impl Screen<Arc<ImportSession>, TrackSetData> for TrackSetEditor {
|
|||
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 {
|
||||
if let Some(recording) = push!(this.handle, RecordingSelector).await {
|
||||
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 {
|
||||
if let Some(selection) = push!(this.handle, TrackSelector, Arc::clone(&this.session)).await {
|
||||
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 mut title_parts = Vec::<String>::new();
|
||||
|
|
@ -152,7 +152,7 @@ impl Screen<Arc<ImportSession>, TrackSetData> for TrackSetEditor {
|
|||
row.add_suffix(&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();
|
||||
if let Some(recording) = recording {
|
||||
spawn!(@clone this, async move {
|
||||
|
|
|
|||
|
|
@ -68,19 +68,13 @@ macro_rules! replace {
|
|||
/// });
|
||||
#[macro_export]
|
||||
macro_rules! spawn {
|
||||
($future:expr) => {
|
||||
{
|
||||
let context = glib::MainContext::default();
|
||||
context.spawn_local($future);
|
||||
|
||||
}
|
||||
};
|
||||
(@clone $data:ident, $future:expr) => {
|
||||
{
|
||||
let context = glib::MainContext::default();
|
||||
let $data = Rc::clone(&$data);
|
||||
context.spawn_local($future);
|
||||
|
||||
}
|
||||
};
|
||||
($future:expr) => {{
|
||||
let context = glib::MainContext::default();
|
||||
context.spawn_local($future);
|
||||
}};
|
||||
(@clone $data:ident, $future:expr) => {{
|
||||
let context = glib::MainContext::default();
|
||||
let $data = Rc::clone(&$data);
|
||||
context.spawn_local($future);
|
||||
}};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,9 +30,7 @@ fn main() {
|
|||
libadwaita::init();
|
||||
resources::init().expect("Failed to initialize resources!");
|
||||
|
||||
let app = gtk::Application::new(Some("de.johrpan.musicus"), gio::ApplicationFlags::empty())
|
||||
.expect("Failed to initialize GTK application!");
|
||||
|
||||
let app = gtk::Application::new(Some("de.johrpan.musicus"), gio::ApplicationFlags::empty());
|
||||
let window: RefCell<Option<Rc<Window>>> = RefCell::new(None);
|
||||
|
||||
app.connect_activate(clone!(@strong app => move |_| {
|
||||
|
|
@ -43,6 +41,5 @@ fn main() {
|
|||
window.as_ref().unwrap().present();
|
||||
}));
|
||||
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
app.run(&args);
|
||||
app.run();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ pub use window::*;
|
|||
/// that optionally resolves to a specific return value.
|
||||
pub trait Screen<I, O>: Widget {
|
||||
/// 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.
|
||||
|
|
@ -46,7 +48,9 @@ impl<O> NavigationHandle<O> {
|
|||
pub fn pop(&self, output: Option<O>) {
|
||||
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.");
|
||||
|
||||
if sender.send(output).is_err() {
|
||||
|
|
@ -112,11 +116,12 @@ impl Navigator {
|
|||
back_cb: RefCell::new(None),
|
||||
});
|
||||
|
||||
this.widget.connect_property_transition_running_notify(clone!(@strong this => move |_| {
|
||||
if !this.widget.get_transition_running() {
|
||||
this.clear_old_widgets();
|
||||
}
|
||||
}));
|
||||
this.widget
|
||||
.connect_transition_running_notify(clone!(@strong this => move |_| {
|
||||
if !this.widget.is_transition_running() {
|
||||
this.clear_old_widgets();
|
||||
}
|
||||
}));
|
||||
|
||||
this
|
||||
}
|
||||
|
|
@ -135,7 +140,7 @@ impl Navigator {
|
|||
|
||||
let receiver = self.push::<I, O, S>(input);
|
||||
|
||||
if !self.widget.get_transition_running() {
|
||||
if !self.widget.is_transition_running() {
|
||||
self.clear_old_widgets();
|
||||
}
|
||||
|
||||
|
|
@ -143,7 +148,6 @@ impl Navigator {
|
|||
receiver.await.unwrap_or(None)
|
||||
}
|
||||
|
||||
|
||||
/// Drop all screens and go back to the initial screen. The back callback
|
||||
/// will not be called.
|
||||
pub fn reset(&self) {
|
||||
|
|
@ -153,7 +157,7 @@ impl Navigator {
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -203,7 +207,7 @@ impl Navigator {
|
|||
}
|
||||
}
|
||||
|
||||
if !self.widget.get_transition_running() {
|
||||
if !self.widget.is_transition_running() {
|
||||
self.clear_old_widgets();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ impl NavigatorWindow {
|
|||
window.set_default_size(600, 424);
|
||||
let placeholder = gtk::Label::new(None);
|
||||
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 });
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use super::register::RegisterDialog;
|
||||
use crate::push;
|
||||
use crate::navigator::{NavigationHandle, Screen};
|
||||
use crate::push;
|
||||
use crate::widgets::Widget;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
|
|
@ -49,16 +49,16 @@ impl Screen<Option<LoginData>, Option<LoginData>> for LoginDialog {
|
|||
|
||||
// Connect signals and callbacks
|
||||
|
||||
cancel_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
cancel_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
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");
|
||||
|
||||
let data = LoginData {
|
||||
username: this.username_entry.get_text().to_string(),
|
||||
password: this.password_entry.get_text().to_string(),
|
||||
username: this.username_entry.text().to_string(),
|
||||
password: this.password_entry.text().to_string(),
|
||||
};
|
||||
|
||||
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 {
|
||||
if let Some(data) = push!(this.handle, RegisterDialog).await {
|
||||
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 {
|
||||
this.handle.backend.set_login_data(None).await;
|
||||
this.handle.pop(Some(None));
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ use gettextrs::gettext;
|
|||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use musicus_backend::Backend;
|
||||
use libadwaita::prelude::*;
|
||||
use musicus_backend::Backend;
|
||||
use std::rc::Rc;
|
||||
|
||||
mod login;
|
||||
|
|
@ -64,8 +64,8 @@ impl Preferences {
|
|||
|
||||
dialog.connect_response(clone!(@strong this => move |dialog, response| {
|
||||
if let gtk::ResponseType::Accept = response {
|
||||
if let Some(file) = dialog.get_file() {
|
||||
if let Some(path) = file.get_path() {
|
||||
if let Some(file) = dialog.file() {
|
||||
if let Some(path) = file.path() {
|
||||
this.music_library_path_row.set_subtitle(Some(path.to_str().unwrap()));
|
||||
|
||||
spawn!(@clone this, async move {
|
||||
|
|
|
|||
|
|
@ -51,13 +51,13 @@ impl Screen<(), LoginData> for RegisterDialog {
|
|||
|
||||
// Connect signals and callbacks
|
||||
|
||||
cancel_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
cancel_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
this.handle.pop(None);
|
||||
}));
|
||||
|
||||
register_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
let password = this.password_entry.get_text().to_string();
|
||||
let repeat = this.repeat_password_entry.get_text().to_string();
|
||||
register_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
let password = this.password_entry.text().to_string();
|
||||
let repeat = this.repeat_password_entry.text().to_string();
|
||||
|
||||
if password != repeat {
|
||||
// TODO: Show error and validate other input.
|
||||
|
|
@ -65,10 +65,10 @@ impl Screen<(), LoginData> for RegisterDialog {
|
|||
this.widget.set_visible_child_name("loading");
|
||||
|
||||
spawn!(@clone this, async move {
|
||||
let username = this.username_entry.get_text().to_string();
|
||||
let email = this.email_entry.get_text().to_string();
|
||||
let username = this.username_entry.text().to_string();
|
||||
let email = this.email_entry.text().to_string();
|
||||
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 {
|
||||
None
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ impl ServerDialog {
|
|||
}));
|
||||
|
||||
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);
|
||||
|
||||
if let Some(cb) = &*this.selected_cb.borrow() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use super::{MediumScreen, RecordingScreen};
|
||||
use crate::editors::EnsembleEditor;
|
||||
use crate::navigator::{NavigatorWindow, NavigationHandle, Screen};
|
||||
use crate::navigator::{NavigationHandle, NavigatorWindow, Screen};
|
||||
use crate::widgets;
|
||||
use crate::widgets::{List, Section, Widget};
|
||||
use gettextrs::gettext;
|
||||
|
|
@ -42,80 +42,90 @@ impl Screen<Ensemble, ()> for EnsembleScreen {
|
|||
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.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 || {
|
||||
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("Delete ensemble"),
|
||||
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.add_action(&gettext("Delete ensemble"), 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.widget.set_search_cb(clone!(@weak this => move || {
|
||||
this.recording_list.invalidate_filter();
|
||||
this.medium_list.invalidate_filter();
|
||||
}));
|
||||
|
||||
this.recording_list.set_make_widget_cb(clone!(@weak this => move |index| {
|
||||
let recording = &this.recordings.borrow()[index];
|
||||
this.recording_list.set_make_widget_cb(
|
||||
clone!(@weak this => @default-panic, 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 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;
|
||||
});
|
||||
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_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 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)
|
||||
}));
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&medium.name));
|
||||
|
||||
this.medium_list.set_make_widget_cb(clone!(@weak this => move |index| {
|
||||
let medium = &this.mediums.borrow()[index];
|
||||
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;
|
||||
});
|
||||
}));
|
||||
|
||||
let row = libadwaita::ActionRow::new();
|
||||
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()
|
||||
}));
|
||||
|
||||
row.upcast()
|
||||
}));
|
||||
|
||||
this.medium_list.set_filter_cb(clone!(@weak this => 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)
|
||||
}));
|
||||
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.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use super::{EnsembleScreen, PersonScreen, PlayerScreen};
|
||||
use crate::config;
|
||||
use crate::import::SourceSelector;
|
||||
use crate::navigator::{Navigator, NavigatorWindow, NavigationHandle, Screen};
|
||||
use crate::navigator::{NavigationHandle, Navigator, NavigatorWindow, Screen};
|
||||
use crate::preferences::Preferences;
|
||||
use crate::widgets::{List, PlayerBar, Widget};
|
||||
use gettextrs::gettext;
|
||||
|
|
@ -87,64 +87,67 @@ impl Screen<(), ()> for MainScreen {
|
|||
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();
|
||||
}));
|
||||
|
||||
about_action.connect_activate(clone!(@weak this => move |_, _| {
|
||||
about_action.connect_activate(clone!(@weak this => move |_, _| {
|
||||
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 {
|
||||
let window = NavigatorWindow::new(Rc::clone(&this.handle.backend));
|
||||
replace!(window.navigator, SourceSelector).await;
|
||||
});
|
||||
}));
|
||||
|
||||
this.search_entry.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;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.search_entry
|
||||
.connect_search_changed(clone!(@weak this => move |_| {
|
||||
this.poe_list.invalidate_filter();
|
||||
}));
|
||||
|
||||
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 poe = &this.poes.borrow()[index];
|
||||
let search = this.search_entry.get_text().to_string().to_lowercase();
|
||||
let title = poe.get_title().to_lowercase();
|
||||
search.is_empty() || title.contains(&search)
|
||||
}));
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&poe.get_title()));
|
||||
|
||||
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");
|
||||
}));
|
||||
|
||||
player_bar.set_playlist_cb(clone!(@weak this => move || {
|
||||
player_bar.set_playlist_cb(clone!(@weak this => move || {
|
||||
spawn!(@clone this, async move {
|
||||
push!(this.handle, PlayerScreen).await;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -35,47 +35,56 @@ impl Screen<Medium, ()> for MediumScreen {
|
|||
list,
|
||||
});
|
||||
|
||||
this.widget.set_back_cb(clone!(@weak this => move || {
|
||||
this.widget.set_back_cb(clone!(@weak this => move || {
|
||||
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 || {
|
||||
// TODO: Show medium editor.
|
||||
}));
|
||||
this.widget.add_action(
|
||||
&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 || {
|
||||
// TODO: Delete medium and maybe also the tracks?
|
||||
}));
|
||||
section.add_action(
|
||||
"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 || {
|
||||
for track in &this.medium.tracks {
|
||||
this.handle.backend.pl().add_item(track.clone()).unwrap();
|
||||
}
|
||||
}));
|
||||
this.list
|
||||
.set_make_widget_cb(clone!(@weak this => @default-panic, move |index| {
|
||||
let track = &this.medium.tracks[index];
|
||||
|
||||
this.list.set_make_widget_cb(clone!(@weak this => move |index| {
|
||||
let track = &this.medium.tracks[index];
|
||||
let mut parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
parts.push(track.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
|
||||
let mut parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
parts.push(track.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
let title = if parts.is_empty() {
|
||||
gettext("Unknown")
|
||||
} else {
|
||||
parts.join(", ")
|
||||
};
|
||||
|
||||
let title = if parts.is_empty() {
|
||||
gettext("Unknown")
|
||||
} else {
|
||||
parts.join(", ")
|
||||
};
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_selectable(false);
|
||||
row.set_activatable(false);
|
||||
row.set_title(Some(&title));
|
||||
row.set_margin_start(12);
|
||||
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_selectable(false);
|
||||
row.set_activatable(false);
|
||||
row.set_title(Some(&title));
|
||||
row.set_margin_start(12);
|
||||
|
||||
row.upcast()
|
||||
}));
|
||||
row.upcast()
|
||||
}));
|
||||
|
||||
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::navigator::{NavigatorWindow, NavigationHandle, Screen};
|
||||
use crate::navigator::{NavigationHandle, NavigatorWindow, Screen};
|
||||
use crate::widgets;
|
||||
use crate::widgets::{List, Section, Widget};
|
||||
use gettextrs::gettext;
|
||||
|
|
@ -47,106 +47,118 @@ impl Screen<Person, ()> for PersonScreen {
|
|||
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.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 || {
|
||||
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("Delete person"),
|
||||
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.add_action(&gettext("Delete person"), 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.widget.set_search_cb(clone!(@weak this => move || {
|
||||
this.work_list.invalidate_filter();
|
||||
this.recording_list.invalidate_filter();
|
||||
this.medium_list.invalidate_filter();
|
||||
}));
|
||||
|
||||
this.work_list.set_make_widget_cb(clone!(@weak this => move |index| {
|
||||
let work = &this.works.borrow()[index];
|
||||
this.work_list
|
||||
.set_make_widget_cb(clone!(@weak this => @default-panic, move |index| {
|
||||
let work = &this.works.borrow()[index];
|
||||
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&work.title));
|
||||
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 |_| {
|
||||
let work = work.clone();
|
||||
spawn!(@clone this, async move {
|
||||
push!(this.handle, WorkScreen, work.clone()).await;
|
||||
});
|
||||
let work = work.to_owned();
|
||||
row.connect_activated(clone!(@weak this => move |_| {
|
||||
let work = work.clone();
|
||||
spawn!(@clone this, async move {
|
||||
push!(this.handle, WorkScreen, work.clone()).await;
|
||||
});
|
||||
}));
|
||||
|
||||
row.upcast()
|
||||
}));
|
||||
|
||||
row.upcast()
|
||||
}));
|
||||
|
||||
this.work_list.set_filter_cb(clone!(@weak this => move |index| {
|
||||
let work = &this.works.borrow()[index];
|
||||
let search = this.widget.get_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;
|
||||
});
|
||||
this.work_list
|
||||
.set_filter_cb(clone!(@weak this => @default-panic, move|index| {
|
||||
let work = &this.works.borrow()[index];
|
||||
let search = this.widget.get_search();
|
||||
let title = work.title.to_lowercase();
|
||||
search.is_empty() || title.contains(&search)
|
||||
}));
|
||||
|
||||
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 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)
|
||||
}));
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&recording.work.get_title()));
|
||||
row.set_subtitle(Some(&recording.get_performers()));
|
||||
|
||||
this.medium_list.set_make_widget_cb(clone!(@weak this => move |index| {
|
||||
let medium = &this.mediums.borrow()[index];
|
||||
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;
|
||||
});
|
||||
}));
|
||||
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&medium.name));
|
||||
row.upcast()
|
||||
}),
|
||||
);
|
||||
|
||||
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;
|
||||
});
|
||||
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.medium_list.set_filter_cb(clone!(@weak this => 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)
|
||||
}));
|
||||
let row = libadwaita::ActionRow::new();
|
||||
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];
|
||||
let search = this.widget.get_search();
|
||||
let name = medium.name.to_lowercase();
|
||||
search.is_empty() || name.contains(&search)
|
||||
}));
|
||||
|
||||
// Load the content asynchronously.
|
||||
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ impl Screen<(), ()> for PlayerScreen {
|
|||
|
||||
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() {
|
||||
this.handle.pop(None);
|
||||
}
|
||||
|
|
@ -104,7 +104,7 @@ impl Screen<(), ()> for PlayerScreen {
|
|||
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.next_button.set_sensitive(this.handle.backend.pl().has_next());
|
||||
|
||||
|
|
@ -129,14 +129,14 @@ impl Screen<(), ()> for PlayerScreen {
|
|||
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 sec = (ms % 60000) / 1000;
|
||||
this.duration_label.set_text(&format!("{}:{:02}", min, sec));
|
||||
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.pause_image
|
||||
} 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() {
|
||||
let min = ms / 60000;
|
||||
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.previous_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
this.handle.backend.pl().previous().unwrap();
|
||||
}));
|
||||
this.previous_button
|
||||
.connect_clicked(clone!(@weak this => move |_| {
|
||||
this.handle.backend.pl().previous().unwrap();
|
||||
}));
|
||||
|
||||
this.play_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
this.handle.backend.pl().play_pause();
|
||||
}));
|
||||
this.play_button
|
||||
.connect_clicked(clone!(@weak this => move |_| {
|
||||
this.handle.backend.pl().play_pause();
|
||||
}));
|
||||
|
||||
this.next_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
this.handle.backend.pl().next().unwrap();
|
||||
}));
|
||||
this.next_button
|
||||
.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();
|
||||
}));
|
||||
|
||||
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 event.get_button() == gdk::BUTTON_PRIMARY {
|
||||
match event.get_event_type() {
|
||||
if event.button() == gdk::BUTTON_PRIMARY {
|
||||
match event.event_type() {
|
||||
gdk::EventType::ButtonPress => {
|
||||
this.seeking.replace(true);
|
||||
}
|
||||
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);
|
||||
}
|
||||
_ => (),
|
||||
|
|
@ -193,9 +196,9 @@ impl Screen<(), ()> for PlayerScreen {
|
|||
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() {
|
||||
let ms = this.position.get_value() as u64;
|
||||
let ms = this.position.value() as u64;
|
||||
let min = ms / 60000;
|
||||
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] {
|
||||
ListItem::Track {index, first, playing} => {
|
||||
let track = &this.playlist.borrow()[index];
|
||||
|
|
@ -236,7 +239,7 @@ impl Screen<(), ()> for PlayerScreen {
|
|||
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();
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::editors::RecordingEditor;
|
||||
use crate::navigator::{NavigatorWindow, NavigationHandle, Screen};
|
||||
use crate::navigator::{NavigationHandle, NavigatorWindow, Screen};
|
||||
use crate::widgets;
|
||||
use crate::widgets::{List, Section, Widget};
|
||||
use gettextrs::gettext;
|
||||
|
|
@ -39,49 +39,59 @@ impl Screen<Recording, ()> for RecordingScreen {
|
|||
tracks: RefCell::new(Vec::new()),
|
||||
});
|
||||
|
||||
section.add_action("media-playback-start-symbolic", clone!(@weak this => move || {
|
||||
for track in &*this.tracks.borrow() {
|
||||
this.handle.backend.pl().add_item(track.clone()).unwrap();
|
||||
}
|
||||
}));
|
||||
section.add_action(
|
||||
"media-playback-start-symbolic",
|
||||
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.widget.add_action(&gettext("Edit recording"), clone!(@weak this => move || {
|
||||
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("Edit recording"),
|
||||
clone!(@weak this => move || {
|
||||
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 || {
|
||||
spawn!(@clone this, async move {
|
||||
this.handle.backend.db().delete_recording(&this.recording.id).await.unwrap();
|
||||
this.handle.backend.library_changed();
|
||||
});
|
||||
}));
|
||||
this.widget.add_action(
|
||||
&gettext("Delete recording"),
|
||||
clone!(@weak this => move || {
|
||||
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| {
|
||||
let track = &this.tracks.borrow()[index];
|
||||
this.list
|
||||
.set_make_widget_cb(clone!(@weak this => @default-panic, move |index| {
|
||||
let track = &this.tracks.borrow()[index];
|
||||
|
||||
let mut title_parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
title_parts.push(this.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
let mut title_parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
title_parts.push(this.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
|
||||
let title = if title_parts.is_empty() {
|
||||
gettext("Unknown")
|
||||
} else {
|
||||
title_parts.join(", ")
|
||||
};
|
||||
let title = if title_parts.is_empty() {
|
||||
gettext("Unknown")
|
||||
} else {
|
||||
title_parts.join(", ")
|
||||
};
|
||||
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_title(Some(&title));
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_title(Some(&title));
|
||||
|
||||
row.upcast()
|
||||
}));
|
||||
row.upcast()
|
||||
}));
|
||||
|
||||
// Load the content asynchronously.
|
||||
|
||||
|
|
|
|||
|
|
@ -31,8 +31,10 @@ impl Screen<(), ()> for WelcomeScreen {
|
|||
let welcome = libadwaita::StatusPageBuilder::new()
|
||||
.icon_name("folder-music-symbolic")
|
||||
.title(&gettext("Welcome to Musicus!"))
|
||||
.description(&gettext("Get startet by selecting the folder containing your music \
|
||||
files! Musicus will create a new database there or open one that already exists."))
|
||||
.description(&gettext(
|
||||
"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)
|
||||
.vexpand(true)
|
||||
.build();
|
||||
|
|
@ -42,12 +44,9 @@ impl Screen<(), ()> for WelcomeScreen {
|
|||
widget.append(&header);
|
||||
widget.append(&welcome);
|
||||
|
||||
let this = Rc::new(Self {
|
||||
handle,
|
||||
widget,
|
||||
});
|
||||
let this = Rc::new(Self { handle, widget });
|
||||
|
||||
button.connect_clicked(clone!(@weak this => move |_| {
|
||||
button.connect_clicked(clone!(@weak this => move |_| {
|
||||
let dialog = gtk::FileChooserDialog::new(
|
||||
Some(&gettext("Select music library folder")),
|
||||
Some(&this.handle.window),
|
||||
|
|
@ -59,10 +58,10 @@ impl Screen<(), ()> for WelcomeScreen {
|
|||
|
||||
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 Some(file) = dialog.get_file() {
|
||||
if let Some(path) = file.get_path() {
|
||||
if let Some(file) = dialog.file() {
|
||||
if let Some(path) = file.path() {
|
||||
spawn!(@clone this, async move {
|
||||
this.handle.backend.set_music_library_path(path).await.unwrap();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use super::RecordingScreen;
|
||||
use crate::editors::WorkEditor;
|
||||
use crate::navigator::{NavigatorWindow, NavigationHandle, Screen};
|
||||
use crate::navigator::{NavigationHandle, NavigatorWindow, Screen};
|
||||
use crate::widgets;
|
||||
use crate::widgets::{List, Section, Widget};
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use libadwaita::prelude::*;
|
||||
use musicus_backend::db::{Work, Recording};
|
||||
use musicus_backend::db::{Recording, Work};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
|
@ -38,55 +38,63 @@ impl Screen<Work, ()> for WorkScreen {
|
|||
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.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 || {
|
||||
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("Delete work"),
|
||||
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.add_action(&gettext("Delete work"), 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.widget.set_search_cb(clone!(@weak this => move || {
|
||||
this.recording_list.invalidate_filter();
|
||||
}));
|
||||
|
||||
this.recording_list.set_make_widget_cb(clone!(@weak this => move |index| {
|
||||
let recording = &this.recordings.borrow()[index];
|
||||
this.recording_list.set_make_widget_cb(
|
||||
clone!(@weak this => @default-panic, 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 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;
|
||||
});
|
||||
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_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.
|
||||
|
||||
spawn!(@clone this, async move {
|
||||
|
|
|
|||
|
|
@ -23,18 +23,15 @@ impl Screen<(), Ensemble> for EnsembleSelector {
|
|||
let selector = Selector::<Ensemble>::new(Rc::clone(&handle.backend));
|
||||
selector.set_title(&gettext("Select ensemble"));
|
||||
|
||||
let this = Rc::new(Self {
|
||||
handle,
|
||||
selector,
|
||||
});
|
||||
let this = Rc::new(Self { handle, selector });
|
||||
|
||||
// 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.selector.set_add_cb(clone!(@weak this => move || {
|
||||
this.selector.set_add_cb(clone!(@weak this => move || {
|
||||
spawn!(@clone this, async move {
|
||||
if let Some(ensemble) = push!(this.handle, EnsembleEditor, None).await {
|
||||
this.handle.pop(Some(ensemble));
|
||||
|
|
@ -42,28 +39,31 @@ impl Screen<(), Ensemble> for EnsembleSelector {
|
|||
});
|
||||
}));
|
||||
|
||||
this.selector.set_load_online(clone!(@weak this => move || {
|
||||
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()))
|
||||
this.selector
|
||||
.set_load_online(clone!(@weak this => @default-panic, move || {
|
||||
let clone = this.clone();
|
||||
async move { Ok(clone.handle.backend.cl().get_ensembles().await?) }
|
||||
}));
|
||||
|
||||
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
|
||||
.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));
|
||||
selector.set_title(&gettext("Select instrument"));
|
||||
|
||||
let this = Rc::new(Self {
|
||||
handle,
|
||||
selector,
|
||||
});
|
||||
let this = Rc::new(Self { handle, selector });
|
||||
|
||||
// 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.selector.set_add_cb(clone!(@weak this => move || {
|
||||
this.selector.set_add_cb(clone!(@weak this => move || {
|
||||
spawn!(@clone this, async move {
|
||||
if let Some(instrument) = push!(this.handle, InstrumentEditor, None).await {
|
||||
this.handle.pop(Some(instrument));
|
||||
|
|
@ -42,28 +39,31 @@ impl Screen<(), Instrument> for InstrumentSelector {
|
|||
});
|
||||
}));
|
||||
|
||||
this.selector.set_load_online(clone!(@weak this => move || {
|
||||
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()))
|
||||
this.selector
|
||||
.set_load_online(clone!(@weak this => @default-panic, move || {
|
||||
let clone = this.clone();
|
||||
async move { Ok(clone.handle.backend.cl().get_instruments().await?) }
|
||||
}));
|
||||
|
||||
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
|
||||
.set_filter(|search, instrument| instrument.name.to_lowercase().contains(search));
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use gettextrs::gettext;
|
|||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use libadwaita::prelude::*;
|
||||
use musicus_backend::db::{Person, Ensemble, Medium};
|
||||
use musicus_backend::db::{Ensemble, Medium, Person};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// 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));
|
||||
selector.set_title(&gettext("Select performer"));
|
||||
|
||||
let this = Rc::new(Self {
|
||||
handle,
|
||||
selector,
|
||||
});
|
||||
let this = Rc::new(Self { handle, selector });
|
||||
|
||||
// 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.selector.set_load_online(clone!(@weak this => move || {
|
||||
async move {
|
||||
let mut poes = Vec::new();
|
||||
this.selector
|
||||
.set_load_online(clone!(@weak this => @default-panic, move || {
|
||||
async move {
|
||||
let mut poes = Vec::new();
|
||||
|
||||
let persons = this.handle.backend.cl().get_persons().await?;
|
||||
let ensembles = this.handle.backend.cl().get_ensembles().await?;
|
||||
let persons = this.handle.backend.cl().get_persons().await?;
|
||||
let ensembles = this.handle.backend.cl().get_ensembles().await?;
|
||||
|
||||
for person in persons {
|
||||
poes.push(PersonOrEnsemble::Person(person));
|
||||
for person in persons {
|
||||
poes.push(PersonOrEnsemble::Person(person));
|
||||
}
|
||||
|
||||
for ensemble in ensembles {
|
||||
poes.push(PersonOrEnsemble::Ensemble(ensemble));
|
||||
}
|
||||
|
||||
Ok(poes)
|
||||
}
|
||||
}));
|
||||
|
||||
for ensemble in ensembles {
|
||||
poes.push(PersonOrEnsemble::Ensemble(ensemble));
|
||||
this.selector
|
||||
.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_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| {
|
||||
this.selector.set_make_widget(clone!(@weak this => @default-panic, move |poe| {
|
||||
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 |_| {
|
||||
row.connect_activated(clone!(@weak this => move |_| {
|
||||
let poe = poe.clone();
|
||||
spawn!(@clone this, async move {
|
||||
if let Some(medium) = push!(this.handle, MediumSelectorMediumScreen, poe).await {
|
||||
|
|
@ -137,43 +136,45 @@ impl Screen<PersonOrEnsemble, Medium> for MediumSelectorMediumScreen {
|
|||
selector,
|
||||
});
|
||||
|
||||
this.selector.set_back_cb(clone!(@weak this => move || {
|
||||
this.selector.set_back_cb(clone!(@weak this => move || {
|
||||
this.handle.pop(None);
|
||||
}));
|
||||
|
||||
match this.poe.clone() {
|
||||
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 }
|
||||
// }));
|
||||
|
||||
this.selector.set_load_local(clone!(@weak this => move || {
|
||||
this.selector.set_load_local(clone!(@weak this => @default-panic, move || {
|
||||
let person = person.clone();
|
||||
async move { this.handle.backend.db().get_mediums_for_person(&person.id).await.unwrap() }
|
||||
}));
|
||||
}
|
||||
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();
|
||||
async move { this.handle.backend.db().get_mediums_for_ensemble(&ensemble.id).await.unwrap() }
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
this.selector.set_make_widget(clone!(@weak this => move |medium| {
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&medium.name));
|
||||
this.selector
|
||||
.set_make_widget(clone!(@weak this => @default-panic, move |medium| {
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&medium.name));
|
||||
|
||||
let medium = medium.to_owned();
|
||||
row.connect_activated(clone!(@weak this => move |_| {
|
||||
this.handle.pop(Some(medium.clone()));
|
||||
let medium = medium.to_owned();
|
||||
row.connect_activated(clone!(@weak this => move |_| {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,18 +23,15 @@ impl Screen<(), Person> for PersonSelector {
|
|||
let selector = Selector::<Person>::new(Rc::clone(&handle.backend));
|
||||
selector.set_title(&gettext("Select person"));
|
||||
|
||||
let this = Rc::new(Self {
|
||||
handle,
|
||||
selector,
|
||||
});
|
||||
let this = Rc::new(Self { handle, selector });
|
||||
|
||||
// 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.selector.set_add_cb(clone!(@weak this => move || {
|
||||
this.selector.set_add_cb(clone!(@weak this => move || {
|
||||
spawn!(@clone this, async move {
|
||||
if let Some(person) = push!(this.handle, PersonEditor, None).await {
|
||||
this.handle.pop(Some(person));
|
||||
|
|
@ -42,28 +39,31 @@ impl Screen<(), Person> for PersonSelector {
|
|||
});
|
||||
}));
|
||||
|
||||
this.selector.set_load_online(clone!(@weak this => move || {
|
||||
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()));
|
||||
this.selector
|
||||
.set_load_online(clone!(@weak this => @default-panic, move || {
|
||||
let clone = this.clone();
|
||||
async move { Ok(clone.handle.backend.cl().get_persons().await?) }
|
||||
}));
|
||||
|
||||
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
|
||||
.set_filter(|search, person| person.name_fl().to_lowercase().contains(search));
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use super::selector::Selector;
|
||||
use crate::editors::{PersonEditor, WorkEditor, RecordingEditor};
|
||||
use crate::editors::{PersonEditor, RecordingEditor, WorkEditor};
|
||||
use crate::navigator::{NavigationHandle, Screen};
|
||||
use crate::widgets::Widget;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use libadwaita::prelude::*;
|
||||
use musicus_backend::db::{Person, Work, Recording};
|
||||
use musicus_backend::db::{Person, Recording, Work};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A screen for selecting a recording.
|
||||
|
|
@ -22,18 +22,15 @@ impl Screen<(), Recording> for RecordingSelector {
|
|||
let selector = Selector::<Person>::new(Rc::clone(&handle.backend));
|
||||
selector.set_title(&gettext("Select composer"));
|
||||
|
||||
let this = Rc::new(Self {
|
||||
handle,
|
||||
selector,
|
||||
});
|
||||
let this = Rc::new(Self { handle, selector });
|
||||
|
||||
// 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.selector.set_add_cb(clone!(@weak this => move || {
|
||||
this.selector.set_add_cb(clone!(@weak this => move || {
|
||||
spawn!(@clone this, async move {
|
||||
if let Some(person) = push!(this.handle, PersonEditor, None).await {
|
||||
// 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 || {
|
||||
async move { Ok(this.handle.backend.cl().get_persons().await?) }
|
||||
}));
|
||||
this.selector
|
||||
.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 || {
|
||||
async move { this.handle.backend.db().get_persons().await.unwrap() }
|
||||
}));
|
||||
this.selector
|
||||
.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();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&person.name_lf()));
|
||||
|
||||
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
|
||||
// show a second selector for choosing the work.
|
||||
|
||||
|
|
@ -119,11 +118,11 @@ impl Screen<Person, Work> for RecordingSelectorWorkScreen {
|
|||
selector,
|
||||
});
|
||||
|
||||
this.selector.set_back_cb(clone!(@weak this => move || {
|
||||
this.selector.set_back_cb(clone!(@weak this => move || {
|
||||
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 {
|
||||
let work = Work::new(this.person.clone());
|
||||
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 || {
|
||||
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()));
|
||||
this.selector
|
||||
.set_load_online(clone!(@weak this => @default-panic, move || {
|
||||
async move { Ok(this.handle.backend.cl().get_works(&this.person.id).await?) }
|
||||
}));
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -184,11 +187,11 @@ impl Screen<Work, Recording> for RecordingSelectorRecordingScreen {
|
|||
selector,
|
||||
});
|
||||
|
||||
this.selector.set_back_cb(clone!(@weak this => move || {
|
||||
this.selector.set_back_cb(clone!(@weak this => move || {
|
||||
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 {
|
||||
let recording = Recording::new(this.work.clone());
|
||||
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?) }
|
||||
}));
|
||||
|
||||
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() }
|
||||
}));
|
||||
|
||||
this.selector.set_make_widget(clone!(@weak this => move |recording| {
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&recording.get_performers()));
|
||||
this.selector
|
||||
.set_make_widget(clone!(@weak this => @default-panic, move |recording| {
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&recording.get_performers()));
|
||||
|
||||
let recording = recording.to_owned();
|
||||
row.connect_activated(clone!(@weak this => move |_| {
|
||||
this.handle.pop(Some(recording.clone()));
|
||||
let recording = recording.to_owned();
|
||||
row.connect_activated(clone!(@weak this => move |_| {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,13 +82,14 @@ impl<T> Selector<T> {
|
|||
}
|
||||
}));
|
||||
|
||||
this.search_entry.connect_search_changed(clone!(@strong this => move |_| {
|
||||
this.list.invalidate_filter();
|
||||
}));
|
||||
this.search_entry
|
||||
.connect_search_changed(clone!(@strong this => move |_| {
|
||||
this.list.invalidate_filter();
|
||||
}));
|
||||
|
||||
this.server_check_button
|
||||
.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);
|
||||
|
||||
if active {
|
||||
|
|
@ -98,25 +99,27 @@ impl<T> Selector<T> {
|
|||
}
|
||||
}));
|
||||
|
||||
this.list.set_make_widget_cb(clone!(@strong this => move |index| {
|
||||
if let Some(cb) = &*this.make_widget.borrow() {
|
||||
let item = &this.items.borrow()[index];
|
||||
cb(item)
|
||||
} else {
|
||||
gtk::Label::new(None).upcast()
|
||||
}
|
||||
}));
|
||||
|
||||
this.list.set_filter_cb(clone!(@strong this => move |index| {
|
||||
match &*this.filter.borrow() {
|
||||
Some(filter) => {
|
||||
this.list
|
||||
.set_make_widget_cb(clone!(@strong this => move |index| {
|
||||
if let Some(cb) = &*this.make_widget.borrow() {
|
||||
let item = &this.items.borrow()[index];
|
||||
let search = this.search_entry.get_text().to_string().to_lowercase();
|
||||
search.is_empty() || filter(&search, item)
|
||||
cb(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 |_| {
|
||||
this.clone().load_online();
|
||||
|
|
|
|||
|
|
@ -22,18 +22,15 @@ impl Screen<(), Work> for WorkSelector {
|
|||
let selector = Selector::<Person>::new(Rc::clone(&handle.backend));
|
||||
selector.set_title(&gettext("Select composer"));
|
||||
|
||||
let this = Rc::new(Self {
|
||||
handle,
|
||||
selector,
|
||||
});
|
||||
let this = Rc::new(Self { handle, selector });
|
||||
|
||||
// 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.selector.set_add_cb(clone!(@weak this => move || {
|
||||
this.selector.set_add_cb(clone!(@weak this => move || {
|
||||
spawn!(@clone this, async move {
|
||||
if let Some(person) = push!(this.handle, PersonEditor, None).await {
|
||||
// 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 || {
|
||||
async move { Ok(this.handle.backend.cl().get_persons().await?) }
|
||||
}));
|
||||
this.selector
|
||||
.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 || {
|
||||
async move { this.handle.backend.db().get_persons().await.unwrap() }
|
||||
}));
|
||||
this.selector
|
||||
.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();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&person.name_lf()));
|
||||
|
||||
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
|
||||
// show a second selector for choosing the work.
|
||||
|
||||
|
|
@ -109,11 +108,11 @@ impl Screen<Person, Work> for WorkSelectorWorkScreen {
|
|||
selector,
|
||||
});
|
||||
|
||||
this.selector.set_back_cb(clone!(@weak this => move || {
|
||||
this.selector.set_back_cb(clone!(@weak this => move || {
|
||||
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 {
|
||||
let work = Work::new(this.person.clone());
|
||||
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 || {
|
||||
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()));
|
||||
this.selector
|
||||
.set_load_online(clone!(@weak this => @default-panic, move || {
|
||||
async move { Ok(this.handle.backend.cl().get_works(&this.person.id).await?) }
|
||||
}));
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,10 +27,7 @@ impl ButtonRow {
|
|||
|
||||
widget.add_suffix(&button);
|
||||
|
||||
Self {
|
||||
widget,
|
||||
button,
|
||||
}
|
||||
Self { widget, button }
|
||||
}
|
||||
|
||||
/// Set the subtitle of the row.
|
||||
|
|
|
|||
|
|
@ -26,10 +26,7 @@ impl EntryRow {
|
|||
|
||||
widget.add_suffix(&entry);
|
||||
|
||||
Self {
|
||||
widget,
|
||||
entry,
|
||||
}
|
||||
Self { widget, entry }
|
||||
}
|
||||
|
||||
/// Set the text of the entry.
|
||||
|
|
@ -39,6 +36,6 @@ impl EntryRow {
|
|||
|
||||
/// Get the text that was entered by the user.
|
||||
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::subclass::prelude::*;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::cell::Cell;
|
||||
|
||||
glib::wrapper! {
|
||||
/// A thin list model managing only indices to an external data source.
|
||||
pub struct IndexedListModel(ObjectSubclass<indexed_list_model::IndexedListModel>)
|
||||
@implements gio::ListModel;
|
||||
}
|
||||
|
|
@ -19,7 +17,7 @@ impl IndexedListModel {
|
|||
|
||||
/// Set the length of the list model.
|
||||
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.items_changed(0, old_length, length);
|
||||
}
|
||||
|
|
@ -28,58 +26,54 @@ impl IndexedListModel {
|
|||
mod indexed_list_model {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct IndexedListModel {
|
||||
length: Cell<u32>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for IndexedListModel {
|
||||
const NAME: &'static str = "IndexedListModel";
|
||||
|
||||
type Type = super::IndexedListModel;
|
||||
type ParentType = glib::Object;
|
||||
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 {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![
|
||||
glib::ParamSpec::uint(
|
||||
"length",
|
||||
"Length",
|
||||
"Length",
|
||||
0,
|
||||
std::u32::MAX,
|
||||
0,
|
||||
glib::ParamFlags::READWRITE,
|
||||
),
|
||||
]
|
||||
vec![glib::ParamSpec::new_uint(
|
||||
"length",
|
||||
"Length",
|
||||
"Length",
|
||||
0,
|
||||
std::u32::MAX,
|
||||
0,
|
||||
glib::ParamFlags::READWRITE,
|
||||
)]
|
||||
});
|
||||
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(&self, _obj: &Self::Type, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
match pspec.get_name() {
|
||||
fn set_property(
|
||||
&self,
|
||||
_: &Self::Type,
|
||||
_: usize,
|
||||
value: &glib::Value,
|
||||
pspec: &glib::ParamSpec,
|
||||
) {
|
||||
match pspec.name() {
|
||||
"length" => {
|
||||
let length = value.get().unwrap().unwrap();
|
||||
let length = value.get::<u32>().unwrap();
|
||||
self.length.set(length);
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
match pspec.get_name() {
|
||||
fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
match pspec.name() {
|
||||
"length" => self.length.get().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
|
|
@ -87,21 +81,22 @@ mod indexed_list_model {
|
|||
}
|
||||
|
||||
impl ListModelImpl for IndexedListModel {
|
||||
fn get_item_type(&self, _: &Self::Type) -> glib::Type {
|
||||
fn item_type(&self, _: &Self::Type) -> glib::Type {
|
||||
ItemIndex::static_type()
|
||||
}
|
||||
|
||||
fn get_n_items(&self, _: &Self::Type) -> u32 {
|
||||
fn n_items(&self, _: &Self::Type) -> u32 {
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
/// A simple GObject holding just one integer.
|
||||
pub struct ItemIndex(ObjectSubclass<item_index::ItemIndex>);
|
||||
}
|
||||
|
||||
|
|
@ -113,65 +108,61 @@ impl ItemIndex {
|
|||
|
||||
/// Get the value of the item index..
|
||||
pub fn get(&self) -> u32 {
|
||||
self.get_property("value").unwrap().get_some::<u32>().unwrap()
|
||||
self.property("value").unwrap().get::<u32>().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
mod item_index {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ItemIndex {
|
||||
value: Cell<u32>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for ItemIndex {
|
||||
const NAME: &'static str = "ItemIndex";
|
||||
|
||||
type Type = super::ItemIndex;
|
||||
type ParentType = glib::Object;
|
||||
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 {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![
|
||||
glib::ParamSpec::uint(
|
||||
"value",
|
||||
"Value",
|
||||
"Value",
|
||||
0,
|
||||
std::u32::MAX,
|
||||
0,
|
||||
glib::ParamFlags::READWRITE,
|
||||
),
|
||||
]
|
||||
vec![glib::ParamSpec::new_uint(
|
||||
"value",
|
||||
"Value",
|
||||
"Value",
|
||||
0,
|
||||
std::u32::MAX,
|
||||
0,
|
||||
glib::ParamFlags::READWRITE,
|
||||
)]
|
||||
});
|
||||
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(&self, _obj: &Self::Type, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
match pspec.get_name() {
|
||||
fn set_property(
|
||||
&self,
|
||||
_: &Self::Type,
|
||||
_: usize,
|
||||
value: &glib::Value,
|
||||
pspec: &glib::ParamSpec,
|
||||
) {
|
||||
match pspec.name() {
|
||||
"value" => {
|
||||
let value = value.get().unwrap().unwrap();
|
||||
let value = value.get::<u32>().unwrap();
|
||||
self.value.set(value);
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
match pspec.get_name() {
|
||||
fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
match pspec.name() {
|
||||
"value" => self.value.get().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,14 +40,15 @@ impl List {
|
|||
move_cb: RefCell::new(None),
|
||||
});
|
||||
|
||||
this.filter.set_filter_func(clone!(@strong this => move |index| {
|
||||
if let Some(cb) = &*this.filter_cb.borrow() {
|
||||
let index = index.downcast_ref::<ItemIndex>().unwrap().get() as usize;
|
||||
cb(index)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}));
|
||||
this.filter
|
||||
.set_filter_func(clone!(@strong this => move |index| {
|
||||
if let Some(cb) = &*this.filter_cb.borrow() {
|
||||
let index = index.downcast_ref::<ItemIndex>().unwrap().get() as usize;
|
||||
cb(index)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}));
|
||||
|
||||
this.widget.bind_model(Some(&filter_model), clone!(@strong this => move |index| {
|
||||
let index = index.downcast_ref::<ItemIndex>().unwrap().get() as usize;
|
||||
|
|
@ -64,13 +65,13 @@ impl List {
|
|||
}));
|
||||
|
||||
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);
|
||||
|
||||
drop_target.connect_drop(clone!(@strong this => move |_, value, _, _| {
|
||||
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);
|
||||
true
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ impl Screen {
|
|||
widget.insert_action_group("widget", Some(&actions));
|
||||
|
||||
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();
|
||||
}
|
||||
}));
|
||||
|
|
@ -88,7 +88,8 @@ impl Screen {
|
|||
action.connect_activate(move |_, _| cb());
|
||||
|
||||
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.
|
||||
|
|
@ -98,7 +99,7 @@ impl Screen {
|
|||
|
||||
/// Get the current search 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.
|
||||
|
|
|
|||
|
|
@ -45,15 +45,16 @@ impl UploadSection {
|
|||
switch,
|
||||
});
|
||||
|
||||
this.switch.connect_property_state_notify(clone!(@weak this => move |_| {
|
||||
this.backend.set_use_server(this.switch.get_state());
|
||||
}));
|
||||
this.switch
|
||||
.connect_state_notify(clone!(@weak this => move |_| {
|
||||
this.backend.set_use_server(this.switch.state());
|
||||
}));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Return whether the user has enabled the upload switch.
|
||||
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::screens::{MainScreen, WelcomeScreen};
|
||||
use gtk::prelude::*;
|
||||
use musicus_backend::{Backend, BackendState};
|
||||
use std::rc::Rc;
|
||||
|
|
@ -41,9 +41,8 @@ impl Window {
|
|||
loading_screen.append(&header);
|
||||
loading_screen.append(&spinner);
|
||||
|
||||
|
||||
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 {
|
||||
backend,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue