Add preferences for default program

This commit is contained in:
Elias Projahn 2025-03-16 14:06:57 +01:00
parent 653d5cd629
commit fa94d61e1e
11 changed files with 367 additions and 28 deletions

View file

@ -17,19 +17,39 @@
<default>''</default>
<summary>Path to the music library</summary>
</key>
<key name="prefer-least-recently-played" type="i">
<default>20</default>
<summary>How much recently played items should be penalized (0100)</summary>
</key>
<key name="prefer-recently-added" type="i">
<default>0</default>
<summary>How much recently added items should be preferred (0100)</summary>
</key>
<key name="avoid-repeated-composers" type="i">
<default>60</default>
<summary>For how many minutes a composer should be penalized</summary>
</key>
<key name="avoid-repeated-instruments" type="i">
<default>60</default>
<summary>For how many minutes an instrument should be penalized</summary>
</key>
<key name="play-full-recordings" type="b">
<default>true</default>
<summary>Whether to play full recordings</summary>
</key>
<key name="program1" type="s">
<!-- Translators: Configuration for the default programs in JSON. Please only translate the values of "title" and "description". -->
<default l10n="messages">'{"title":"Just play some music","description":"Randomly select some music. Customize programs using the button in the top right.","design":"Program1","prefer_recently_added":0.0,"prefer_least_recently_played":0.1,"avoid_repeated_composers_seconds":3600,"avoid_repeated_instruments_seconds":3600,"play_full_recordings":true}'</default>
<default l10n="messages">'{"title":"Just play some music","description":"Randomly select some music. Customize programs using the button in the top right.","design":"Program1","prefer_recently_added":0.0,"prefer_least_recently_played":0.1,"avoid_repeated_composers":60,"avoid_repeated_instruments":60,"play_full_recordings":true}'</default>
<summary>Default settings for program 1</summary>
</key>
<key name="program2" type="s">
<!-- Translators: Configuration for the default programs in JSON. Please only translate the values of "title" and "description". -->
<default l10n="messages">'{"title":"What\'s new?","description":"Recordings that you recently added to your music library.","design":"Program2","prefer_recently_added":1.0,"prefer_least_recently_played":0.0,"avoid_repeated_composers_seconds":3600,"avoid_repeated_instruments_seconds":3600,"play_full_recordings":true}'</default>
<default l10n="messages">'{"title":"What\'s new?","description":"Recordings that you recently added to your music library.","design":"Program2","prefer_recently_added":1.0,"prefer_least_recently_played":0.0,"avoid_repeated_composers":60,"avoid_repeated_instruments":60,"play_full_recordings":true}'</default>
<summary>Default settings for program 2</summary>
</key>
<key name="program3" type="s">
<!-- Translators: Configuration for the default programs in JSON. Please only translate the values of "title" and "description". -->
<default l10n="messages">'{"title":"A long time ago","description":"Works that you haven\'t listened to for a long time.","design":"Program3","prefer_recently_added":0.0,"prefer_least_recently_played":1.0,"avoid_repeated_composers_seconds":3600,"avoid_repeated_instruments_seconds":3600,"play_full_recordings":true}'</default>
<default l10n="messages">'{"title":"A long time ago","description":"Works that you haven\'t listened to for a long time.","design":"Program3","prefer_recently_added":0.0,"prefer_least_recently_played":1.0,"avoid_repeated_composers":60,"avoid_repeated_instruments":60,"play_full_recordings":true}'</default>
<summary>Default settings for program 3</summary>
</key>
</schema>

View file

@ -0,0 +1,65 @@
using Gtk 4.0;
using Adw 1;
template $MusicusPreferencesDialog: Adw.PreferencesDialog {
Adw.PreferencesPage {
title: _("Playback");
Adw.PreferencesGroup {
title: _("Default program");
description: _("These settings apply when you add search results to the playlist.");
$MusicusSliderRow {
title: _("Prefer recordings that haven't been played for a long time");
suffix: _("%");
adjustment: Gtk.Adjustment prefer_least_recently_played_adjustment {
lower: 0;
upper: 100;
step-increment: 1;
page-increment: 10;
};
}
$MusicusSliderRow {
title: _("Prefer recordings that were recently added");
suffix: _("%");
adjustment: Gtk.Adjustment prefer_recently_added_adjustment {
lower: 0;
upper: 100;
step-increment: 1;
page-increment: 10;
};
}
$MusicusSliderRow {
title: _("Avoid repeating composers");
suffix: _(" min");
adjustment: Gtk.Adjustment avoid_repeated_composers_adjustment {
lower: 0;
upper: 120;
step-increment: 10;
page-increment: 30;
};
}
$MusicusSliderRow {
title: _("Avoid repeating instruments");
suffix: _(" min");
adjustment: Gtk.Adjustment avoid_repeated_instruments_adjustment {
lower: 0;
upper: 120;
step-increment: 10;
page-increment: 30;
};
}
Adw.SwitchRow play_full_recordings_row {
title: _("Play full recordings");
}
}
}
}

View file

@ -269,7 +269,7 @@ menu primary_menu {
item {
label: _("_Preferences");
action: "app.preferences";
action: "win.preferences";
}
item {

41
data/ui/slider_row.blp Normal file
View file

@ -0,0 +1,41 @@
using Gtk 4.0;
using Adw 1;
template $MusicusSliderRow: Adw.PreferencesRow {
activatable: false;
Gtk.Box {
orientation: vertical;
spacing: 12;
margin-top: 12;
margin-bottom: 12;
margin-start: 12;
margin-end: 12;
Gtk.Box {
spacing: 12;
Gtk.Label {
label: bind template.title;
wrap: true;
xalign: 0.0;
hexpand: true;
}
Gtk.Label value_label {
xalign: 1.0;
valign: center;
styles [
"numeric",
]
}
}
Gtk.Scale {
adjustment: bind template.adjustment;
hexpand: true;
valign: center;
}
}
}

View file

@ -32,7 +32,7 @@ template $MusicusWelcomePage : Adw.NavigationPage {
menu primary_menu {
item {
label: _("_Preferences");
action: "app.preferences";
action: "win.preferences";
}
item {
label: _("_About Musicus");

View file

@ -629,14 +629,14 @@ impl Library {
(
UNIXEPOCH('now', 'localtime') - UNIXEPOCH(instruments.last_played_at)
) * 1.0 / ")
.bind::<sql_types::Integer, _>(program.avoid_repeated_instruments_seconds())
.bind::<sql_types::Integer, _>(program.avoid_repeated_instruments())
.sql(",
1.0
),
IFNULL(
(
UNIXEPOCH('now', 'localtime') - UNIXEPOCH(persons.last_played_at)
) * 1.0 / ").bind::<sql_types::Integer, _>(program.avoid_repeated_composers_seconds()).sql(",
) * 1.0 / ").bind::<sql_types::Integer, _>(program.avoid_repeated_composers()).sql(",
1.0
),
1.0

View file

@ -11,6 +11,7 @@ mod player_bar;
mod playlist_item;
mod playlist_page;
mod playlist_tile;
mod preferences_dialog;
mod process;
mod process_manager;
mod process_row;
@ -20,6 +21,7 @@ mod recording_tile;
mod search_page;
mod search_tag;
mod selector;
mod slider_row;
mod tag_tile;
mod util;
mod welcome_page;

105
src/preferences_dialog.rs Normal file
View file

@ -0,0 +1,105 @@
use adw::{prelude::AdwDialogExt, subclass::prelude::*};
use gtk::{gio, glib, prelude::*};
use crate::{config, slider_row::SliderRow};
mod imp {
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate)]
#[template(file = "data/ui/preferences_dialog.blp")]
pub struct PreferencesDialog {
#[template_child]
pub prefer_least_recently_played_adjustment: TemplateChild<gtk::Adjustment>,
#[template_child]
pub prefer_recently_added_adjustment: TemplateChild<gtk::Adjustment>,
#[template_child]
pub avoid_repeated_composers_adjustment: TemplateChild<gtk::Adjustment>,
#[template_child]
pub avoid_repeated_instruments_adjustment: TemplateChild<gtk::Adjustment>,
#[template_child]
pub play_full_recordings_row: TemplateChild<adw::SwitchRow>,
}
#[glib::object_subclass]
impl ObjectSubclass for PreferencesDialog {
const NAME: &'static str = "MusicusPreferencesDialog";
type Type = super::PreferencesDialog;
type ParentType = adw::PreferencesDialog;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
klass.bind_template_instance_callbacks();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
SliderRow::static_type();
obj.init_template();
}
}
impl ObjectImpl for PreferencesDialog {
fn constructed(&self) {
self.parent_constructed();
let settings = gio::Settings::new(config::APP_ID);
settings
.bind(
"prefer-least-recently-played",
&*self.prefer_least_recently_played_adjustment,
"value",
)
.build();
settings
.bind(
"prefer-recently-added",
&*self.prefer_recently_added_adjustment,
"value",
)
.build();
settings
.bind(
"avoid-repeated-composers",
&*self.avoid_repeated_composers_adjustment,
"value",
)
.build();
settings
.bind(
"avoid-repeated-instruments",
&*self.avoid_repeated_instruments_adjustment,
"value",
)
.build();
settings
.bind(
"play-full-recordings",
&*self.play_full_recordings_row,
"active",
)
.build();
}
}
impl WidgetImpl for PreferencesDialog {}
impl AdwDialogImpl for PreferencesDialog {}
impl PreferencesDialogImpl for PreferencesDialog {}
}
glib::wrapper! {
pub struct PreferencesDialog(ObjectSubclass<imp::PreferencesDialog>)
@extends gtk::Widget, adw::Dialog, adw::PreferencesDialog;
}
#[gtk::template_callbacks]
impl PreferencesDialog {
pub fn show(parent: &impl IsA<gtk::Widget>) {
let obj: Self = glib::Object::new();
obj.present(Some(parent));
}
}

View file

@ -1,10 +1,10 @@
use std::cell::{Cell, RefCell};
use anyhow::Result;
use gtk::{glib, glib::Properties, prelude::*, subclass::prelude::*};
use gtk::{gio, glib, glib::Properties, prelude::*, subclass::prelude::*};
use serde::{Deserialize, Serialize};
use crate::library::LibraryQuery;
use crate::{config, library::LibraryQuery};
mod imp {
use super::*;
@ -47,10 +47,10 @@ mod imp {
pub prefer_least_recently_played: Cell<f64>,
#[property(get, set)]
pub avoid_repeated_composers_seconds: Cell<i32>,
pub avoid_repeated_composers: Cell<i32>,
#[property(get, set)]
pub avoid_repeated_instruments_seconds: Cell<i32>,
pub avoid_repeated_instruments: Cell<i32>,
#[property(get, set)]
pub play_full_recordings: Cell<bool>,
@ -80,6 +80,8 @@ impl Program {
}
pub fn from_query(query: LibraryQuery) -> Self {
let settings = gio::Settings::new(&config::APP_ID);
glib::Object::builder()
.property(
"composer-id",
@ -92,25 +94,34 @@ impl Program {
query.instrument.as_ref().map(|i| i.instrument_id.clone()),
)
.property("work-id", query.work.as_ref().map(|w| w.work_id.clone()))
.property("prefer-recently-added", 0.0)
.property("prefer-least-recently-played", 0.5)
.property(
"avoid-repeated-composers-seconds",
"prefer-recently-added",
settings.int("prefer-recently-added") as f64 / 100.0,
)
.property(
"prefer-least-recently-played",
settings.int("prefer-least-recently-played") as f64 / 100.0,
)
.property(
"avoid-repeated-composers",
if query.composer.is_none() && query.work.is_none() {
3600
settings.int("avoid-repeated-composers")
} else {
0
},
)
.property(
"avoid-repeated-instruments-seconds",
"avoid-repeated-instruments",
if query.instrument.is_none() && query.work.is_none() {
3600
settings.int("avoid-repeated-instruments")
} else {
0
},
)
.property("play-full-recordings", true)
.property(
"play-full-recordings",
settings.boolean("play-full-recordings"),
)
.build()
}
@ -127,12 +138,12 @@ impl Program {
data.prefer_least_recently_played.get(),
)
.property(
"avoid-repeated-composers-seconds",
data.avoid_repeated_composers_seconds.get(),
"avoid-repeated-composers",
data.avoid_repeated_composers.get(),
)
.property(
"avoid-repeated-instruments-seconds",
data.avoid_repeated_instruments_seconds.get(),
"avoid-repeated-instruments",
data.avoid_repeated_instruments.get(),
)
.property("play-full-recordings", data.play_full_recordings.get())
.build();

89
src/slider_row.rs Normal file
View file

@ -0,0 +1,89 @@
use std::cell::RefCell;
use adw::{prelude::*, subclass::prelude::*};
use gtk::glib::{self, clone};
mod imp {
use super::*;
#[derive(glib::Properties, gtk::CompositeTemplate, Debug, Default)]
#[properties(wrapper_type = super::SliderRow)]
#[template(file = "data/ui/slider_row.blp")]
pub struct SliderRow {
#[property(get, set)]
pub adjustment: RefCell<gtk::Adjustment>,
#[property(get, set)]
pub suffix: RefCell<String>,
#[template_child]
pub value_label: TemplateChild<gtk::Label>,
}
#[glib::object_subclass]
impl ObjectSubclass for SliderRow {
const NAME: &'static str = "MusicusSliderRow";
type Type = super::SliderRow;
type ParentType = adw::PreferencesRow;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
klass.bind_template_instance_callbacks();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
#[glib::derived_properties]
impl ObjectImpl for SliderRow {
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj().to_owned();
obj.connect_adjustment_notify(move |obj| {
obj.adjustment().connect_value_changed(clone!(
#[weak]
obj,
move |_| obj.update()
));
obj.update();
});
}
}
impl WidgetImpl for SliderRow {}
impl ListBoxRowImpl for SliderRow {}
impl PreferencesRowImpl for SliderRow {}
}
glib::wrapper! {
pub struct SliderRow(ObjectSubclass<imp::SliderRow>)
@extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow;
}
#[gtk::template_callbacks]
impl SliderRow {
/// Create a new slider row.
///
/// The adjustment can be used to control the range and initial value of the slider. Use the
/// adjustment's `value-changed` signal for getting updates. The current value is displayed
/// next to the slider followed by `suffix`.
pub fn new(title: &str, adjustment: &gtk::Adjustment, suffix: &str) -> Self {
glib::Object::builder()
.property("title", title)
.property("adjustment", adjustment)
.property("suffix", suffix)
.build()
}
pub fn update(&self) {
self.imp().value_label.set_label(&format!(
"{:.0}{}",
self.adjustment().value(),
self.suffix()
));
}
}

View file

@ -1,7 +1,8 @@
use std::{cell::RefCell, path::Path};
use adw::subclass::prelude::*;
use gtk::{gio, glib, glib::clone, prelude::*};
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{gio, glib, glib::clone};
use crate::{
config,
@ -11,15 +12,13 @@ use crate::{
player::Player,
player_bar::PlayerBar,
playlist_page::PlaylistPage,
preferences_dialog::PreferencesDialog,
process_manager::ProcessManager,
search_page::SearchPage,
welcome_page::WelcomePage,
};
mod imp {
use adw::prelude::{AlertDialogExt, AlertDialogExtManual};
use gettextrs::gettext;
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate)]
@ -89,8 +88,15 @@ mod imp {
})
.build();
let obj = self.obj().to_owned();
let preferences_action = gio::ActionEntry::builder("preferences")
.activate(move |_, _, _| {
PreferencesDialog::show(&obj);
})
.build();
self.obj()
.add_action_entries([import_action, library_action]);
.add_action_entries([import_action, library_action, preferences_action]);
let player_bar = PlayerBar::new(&self.player);
self.player_bar_revealer.set_child(Some(&player_bar));