Update gtk-rs crates

This commit is contained in:
Elias Projahn 2021-05-07 23:49:05 +02:00
parent df6e2e86c7
commit 7d7343ea8c
63 changed files with 3499 additions and 908 deletions

1
.gitignore vendored
View file

@ -4,4 +4,3 @@
/builddir
/flatpak
/target
Cargo.lock

2477
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -21,6 +21,4 @@ pub enum Error {
Other(String),
}
pub type Result<T> = std::result::Result<T, Error>;

View file

@ -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

View file

@ -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,7 +101,9 @@ impl Player {
#[cfg(target_os = "linux")]
{
result.mpris.connect_play_pause(clone!(@weak result => move || {
result
.mpris
.connect_play_pause(clone!(@weak result => move || {
result.play_pause();
}));
@ -117,7 +119,9 @@ impl Player {
}
}));
result.mpris.connect_previous(clone!(@weak result => move || {
result
.mpris
.connect_previous(clone!(@weak result => move || {
let _ = result.previous();
}));
@ -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);

View file

@ -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(())
}

View file

@ -31,6 +31,4 @@ pub enum Error {
Other(&'static str),
}
pub type Result<T> = std::result::Result<T, Error>;

View file

@ -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(())
}
}

View file

@ -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::*;
@ -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!"))
}
}

View file

@ -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)
}

View file

@ -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(())
}
}

View file

@ -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;

View file

@ -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?
}

View file

@ -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)
}

View file

@ -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>;

View file

@ -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
);
}
}
}

View file

@ -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;

View file

@ -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.

View file

@ -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]

View file

@ -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);
@ -122,7 +124,8 @@ impl Screen<Option<Performance>, Performance> for PerformanceEditor {
});
}));
this.reset_role_button.connect_clicked(clone!(@weak this => move |_| {
this.reset_role_button
.connect_clicked(clone!(@weak this => move |_| {
this.show_role(None);
this.role.replace(None);
}));

View file

@ -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;
@ -78,7 +78,8 @@ impl Screen<Option<Recording>, Recording> for RecordingEditor {
this.handle.pop(None);
}));
this.save_button.connect_clicked(clone!(@weak this => move |_| {
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 {
@ -102,7 +103,7 @@ 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"));
@ -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

View file

@ -149,8 +149,8 @@ impl Screen<Option<Work>, Work> for WorkEditor {
this.title_entry
.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,7 +171,8 @@ impl Screen<Option<Work>, Work> for WorkEditor {
row.add_suffix(&delete_button);
row.upcast()
}));
}),
);
add_instrument_button.connect_clicked(clone!(@weak this => move |_| {
spawn!(@clone this, async move {
@ -187,7 +188,7 @@ 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"));
@ -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?;
}

View file

@ -46,7 +46,7 @@ impl Screen<Option<WorkPart>, WorkPart> for WorkPartEditor {
this.save_button
.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));
@ -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());
}
}

View file

@ -47,7 +47,7 @@ impl Screen<Option<WorkSection>, WorkSection> for WorkSectionEditor {
.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));
@ -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());
}
}

View file

@ -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 {
@ -137,8 +137,9 @@ impl Screen<Arc<ImportSession>, ()> for ImportScreen {
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();
}));

View file

@ -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();
@ -131,7 +132,8 @@ impl Screen<(Arc<ImportSession>, Option<Medium>), Medium> for MediumEditor {
}));
row.upcast()
}));
}),
);
try_again_button.connect_clicked(clone!(@weak this => move |_| {
this.widget.set_visible_child_name("content");
@ -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?;
}

View file

@ -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 {

View file

@ -58,8 +58,8 @@ impl Screen<(), ()> for SourceSelector {
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 {

View file

@ -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");
@ -56,7 +59,7 @@ impl Screen<(Recording, Vec<usize>), Vec<usize>> for TrackEditor {
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) {

View file

@ -50,7 +50,8 @@ impl Screen<Arc<ImportSession>, Vec<usize>> for TrackSelector {
this.handle.pop(None);
}));
this.select_button.connect_clicked(clone!(@weak this => move |_| {
this.select_button
.connect_clicked(clone!(@weak this => move |_| {
let selection = this.selection.borrow().clone();
this.handle.pop(Some(selection));
}));
@ -62,7 +63,7 @@ impl Screen<Arc<ImportSession>, Vec<usize>> for TrackSelector {
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) {

View file

@ -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();

View file

@ -68,19 +68,13 @@ macro_rules! replace {
/// });
#[macro_export]
macro_rules! spawn {
($future:expr) => {
{
($future:expr) => {{
let context = glib::MainContext::default();
context.spawn_local($future);
}
};
(@clone $data:ident, $future:expr) => {
{
}};
(@clone $data:ident, $future:expr) => {{
let context = glib::MainContext::default();
let $data = Rc::clone(&$data);
context.spawn_local($future);
}
};
}};
}

View file

@ -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();
}

View file

@ -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,8 +116,9 @@ 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.widget
.connect_transition_running_notify(clone!(@strong this => move |_| {
if !this.widget.is_transition_running() {
this.clear_old_widgets();
}
}));
@ -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();
}
}

View file

@ -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 });

View file

@ -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::*;
@ -57,8 +57,8 @@ impl Screen<Option<LoginData>, Option<LoginData>> for LoginDialog {
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 {

View file

@ -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 {

View file

@ -56,8 +56,8 @@ impl Screen<(), LoginData> for RegisterDialog {
}));
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();
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

View file

@ -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() {

View file

@ -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;
@ -46,27 +46,33 @@ impl Screen<Ensemble, ()> for EnsembleScreen {
this.handle.pop(None);
}));
this.widget.add_action(&gettext("Edit ensemble"), clone!(@weak this => move || {
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 || {
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.recording_list.invalidate_filter();
this.medium_list.invalidate_filter();
}));
this.recording_list.set_make_widget_cb(clone!(@weak this => move |index| {
this.recording_list.set_make_widget_cb(
clone!(@weak this => @default-panic, move |index| {
let recording = &this.recordings.borrow()[index];
let row = libadwaita::ActionRow::new();
@ -83,16 +89,19 @@ impl Screen<Ensemble, ()> for EnsembleScreen {
}));
row.upcast()
}));
}),
);
this.recording_list.set_filter_cb(clone!(@weak this => move |index| {
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)
}));
this.medium_list.set_make_widget_cb(clone!(@weak this => move |index| {
this.medium_list
.set_make_widget_cb(clone!(@weak this => @default-panic, move |index| {
let medium = &this.mediums.borrow()[index];
let row = libadwaita::ActionRow::new();
@ -110,7 +119,8 @@ impl Screen<Ensemble, ()> for EnsembleScreen {
row.upcast()
}));
this.medium_list.set_filter_cb(clone!(@weak this => move |index| {
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();

View file

@ -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;
@ -102,11 +102,13 @@ impl Screen<(), ()> for MainScreen {
});
}));
this.search_entry.connect_search_changed(clone!(@weak this => move |_| {
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| {
this.poe_list
.set_make_widget_cb(clone!(@weak this => @default-panic, move |index| {
let poe = &this.poes.borrow()[index];
let row = libadwaita::ActionRow::new();
@ -133,9 +135,10 @@ impl Screen<(), ()> for MainScreen {
row.upcast()
}));
this.poe_list.set_filter_cb(clone!(@weak this => move |index| {
this.poe_list
.set_filter_cb(clone!(@weak this => @default-panic, move |index| {
let poe = &this.poes.borrow()[index];
let search = this.search_entry.get_text().to_string().to_lowercase();
let search = this.search_entry.text().to_string().to_lowercase();
let title = poe.get_title().to_lowercase();
search.is_empty() || title.contains(&search)
}));

View file

@ -39,22 +39,31 @@ impl Screen<Medium, ()> for MediumScreen {
this.handle.pop(None);
}));
this.widget.add_action(&gettext("Edit medium"), clone!(@weak this => move || {
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 || {
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 || {
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 => move |index| {
this.list
.set_make_widget_cb(clone!(@weak this => @default-panic, move |index| {
let track = &this.medium.tracks[index];
let mut parts = Vec::<String>::new();

View file

@ -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;
@ -51,20 +51,25 @@ impl Screen<Person, ()> for PersonScreen {
this.handle.pop(None);
}));
this.widget.add_action(&gettext("Edit person"), clone!(@weak this => move || {
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 || {
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.work_list.invalidate_filter();
@ -72,7 +77,8 @@ impl Screen<Person, ()> for PersonScreen {
this.medium_list.invalidate_filter();
}));
this.work_list.set_make_widget_cb(clone!(@weak this => move |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();
@ -90,14 +96,16 @@ impl Screen<Person, ()> for PersonScreen {
row.upcast()
}));
this.work_list.set_filter_cb(clone!(@weak this => move |index| {
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)
}));
this.recording_list.set_make_widget_cb(clone!(@weak this => move |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();
@ -114,16 +122,19 @@ impl Screen<Person, ()> for PersonScreen {
}));
row.upcast()
}));
}),
);
this.recording_list.set_filter_cb(clone!(@weak this => move |index| {
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)
}));
this.medium_list.set_make_widget_cb(clone!(@weak this => move |index| {
this.medium_list
.set_make_widget_cb(clone!(@weak this => @default-panic, move |index| {
let medium = &this.mediums.borrow()[index];
let row = libadwaita::ActionRow::new();
@ -141,7 +152,8 @@ impl Screen<Person, ()> for PersonScreen {
row.upcast()
}));
this.medium_list.set_filter_cb(clone!(@weak this => move |index| {
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();

View file

@ -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;
@ -157,15 +157,18 @@ impl Screen<(), ()> for PlayerScreen {
this.handle.pop(None);
}));
this.previous_button.connect_clicked(clone!(@weak this => move |_| {
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.play_button
.connect_clicked(clone!(@weak this => move |_| {
this.handle.backend.pl().play_pause();
}));
this.next_button.connect_clicked(clone!(@weak this => move |_| {
this.next_button
.connect_clicked(clone!(@weak this => move |_| {
this.handle.backend.pl().next().unwrap();
}));
@ -173,15 +176,15 @@ impl Screen<(), ()> for PlayerScreen {
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);
}
_ => (),
@ -195,7 +198,7 @@ impl Screen<(), ()> for PlayerScreen {
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];

View file

@ -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,31 +39,41 @@ impl Screen<Recording, ()> for RecordingScreen {
tracks: RefCell::new(Vec::new()),
});
section.add_action("media-playback-start-symbolic", clone!(@weak this => move || {
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.handle.pop(None);
}));
this.widget.add_action(&gettext("Edit recording"), clone!(@weak this => move || {
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 || {
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| {
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();

View file

@ -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,10 +44,7 @@ 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 |_| {
let dialog = gtk::FileChooserDialog::new(
@ -61,8 +60,8 @@ impl Screen<(), ()> for WelcomeScreen {
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();
});

View file

@ -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;
@ -42,26 +42,32 @@ impl Screen<Work, ()> for WorkScreen {
this.handle.pop(None);
}));
this.widget.add_action(&gettext("Edit work"), clone!(@weak this => move || {
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 || {
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.recording_list.invalidate_filter();
}));
this.recording_list.set_make_widget_cb(clone!(@weak this => move |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();
@ -78,9 +84,11 @@ impl Screen<Work, ()> for WorkScreen {
}));
row.upcast()
}));
}),
);
this.recording_list.set_filter_cb(clone!(@weak this => move |index| {
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();

View file

@ -23,10 +23,7 @@ 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
@ -42,17 +39,20 @@ impl Screen<(), Ensemble> for EnsembleSelector {
});
}));
this.selector.set_load_online(clone!(@weak this => move || {
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?) }
}));
this.selector.set_load_local(clone!(@weak this => move || {
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 => move |ensemble| {
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));

View file

@ -23,10 +23,7 @@ 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
@ -42,17 +39,20 @@ impl Screen<(), Instrument> for InstrumentSelector {
});
}));
this.selector.set_load_online(clone!(@weak this => move || {
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?) }
}));
this.selector.set_load_local(clone!(@weak this => move || {
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 => move |instrument| {
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));

View file

@ -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,10 +38,7 @@ 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
@ -49,7 +46,8 @@ impl Screen<(), Medium> for MediumSelector {
this.handle.pop(None);
}));
this.selector.set_load_online(clone!(@weak this => move || {
this.selector
.set_load_online(clone!(@weak this => @default-panic, move || {
async move {
let mut poes = Vec::new();
@ -68,7 +66,8 @@ impl Screen<(), Medium> for MediumSelector {
}
}));
this.selector.set_load_local(clone!(@weak this => move || {
this.selector
.set_load_local(clone!(@weak this => @default-panic, move || {
async move {
let mut poes = Vec::new();
@ -87,7 +86,7 @@ impl Screen<(), Medium> for MediumSelector {
}
}));
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()));
@ -147,20 +146,21 @@ impl Screen<PersonOrEnsemble, Medium> for MediumSelectorMediumScreen {
// 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| {
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));
@ -173,7 +173,8 @@ impl Screen<PersonOrEnsemble, Medium> for MediumSelectorMediumScreen {
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
}

View file

@ -23,10 +23,7 @@ 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
@ -42,17 +39,20 @@ impl Screen<(), Person> for PersonSelector {
});
}));
this.selector.set_load_online(clone!(@weak this => move || {
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?) }
}));
this.selector.set_load_local(clone!(@weak this => move || {
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 => 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()));

View file

@ -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,10 +22,7 @@ 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
@ -54,15 +51,17 @@ impl Screen<(), Recording> for RecordingSelector {
});
}));
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_persons().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_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()));
@ -132,15 +131,18 @@ impl Screen<Person, Work> for RecordingSelectorWorkScreen {
});
}));
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_works(&this.person.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_works(&this.person.id).await.unwrap() }
}));
this.selector.set_make_widget(clone!(@weak this => move |work| {
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));
@ -153,7 +155,8 @@ impl Screen<Person, Work> for RecordingSelectorWorkScreen {
row.upcast()
}));
this.selector.set_filter(|search, work| work.title.to_lowercase().contains(search));
this.selector
.set_filter(|search, work| work.title.to_lowercase().contains(search));
this
}
@ -197,15 +200,16 @@ 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| {
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()));
@ -218,8 +222,9 @@ impl Screen<Work, Recording> for RecordingSelectorRecordingScreen {
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
}

View file

@ -82,13 +82,14 @@ impl<T> Selector<T> {
}
}));
this.search_entry.connect_search_changed(clone!(@strong this => move |_| {
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,7 +99,8 @@ impl<T> Selector<T> {
}
}));
this.list.set_make_widget_cb(clone!(@strong this => move |index| {
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)
@ -107,11 +109,12 @@ impl<T> Selector<T> {
}
}));
this.list.set_filter_cb(clone!(@strong this => move |index| {
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.get_text().to_string().to_lowercase();
let search = this.search_entry.text().to_string().to_lowercase();
search.is_empty() || filter(&search, item)
}
None => true,

View file

@ -22,10 +22,7 @@ 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
@ -48,15 +45,17 @@ impl Screen<(), Work> for WorkSelector {
});
}));
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_persons().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_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()));
@ -122,15 +121,18 @@ impl Screen<Person, Work> for WorkSelectorWorkScreen {
});
}));
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_works(&this.person.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_works(&this.person.id).await.unwrap() }
}));
this.selector.set_make_widget(clone!(@weak this => move |work| {
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));
@ -143,7 +145,8 @@ impl Screen<Person, Work> for WorkSelectorWorkScreen {
row.upcast()
}));
this.selector.set_filter(|search, work| work.title.to_lowercase().contains(search));
this.selector
.set_filter(|search, work| work.title.to_lowercase().contains(search));
this
}

View file

@ -27,10 +27,7 @@ impl ButtonRow {
widget.add_suffix(&button);
Self {
widget,
button,
}
Self { widget, button }
}
/// Set the subtitle of the row.

View file

@ -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()
}
}

View file

@ -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,32 +26,23 @@ 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(
vec![glib::ParamSpec::new_uint(
"length",
"Length",
"Length",
@ -61,25 +50,30 @@ mod indexed_list_model {
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,39 +108,30 @@ 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(
vec![glib::ParamSpec::new_uint(
"value",
"Value",
"Value",
@ -153,25 +139,30 @@ mod item_index {
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!(),
}

View file

@ -40,7 +40,8 @@ impl List {
move_cb: RefCell::new(None),
});
this.filter.set_filter_func(clone!(@strong this => move |index| {
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)
@ -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 {

View file

@ -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.

View file

@ -45,8 +45,9 @@ 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
@ -54,6 +55,6 @@ impl UploadSection {
/// Return whether the user has enabled the upload switch.
pub fn get_active(&self) -> bool {
self.switch.get_active()
self.switch.state()
}
}

View file

@ -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,