Files
lintunes/lintunes/gui/preferences_dialog.py
T
trav dcbd288b31 Add per-area color preference sliders
Five grayscale (black->white) sliders in Preferences that update live:
outer-chrome background (QPalette.Window), the rounded transport button
boxes, the now-playing panel, the now-playing text, and the row stripes
(QPalette.AlternateBase). Defaults are None ("inherit current") so nothing
changes until the user drags. Decouples the button boxes from the stripe
color (they used to share palette alternate-base). Live preview via
Preferences.set_live (no disk write); persisted ~0.4s after the drag.
2026-06-26 21:41:49 -04:00

197 lines
7.4 KiB
Python

from PyQt6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QGroupBox, QLabel, QSlider,
QRadioButton, QButtonGroup, QLineEdit, QPushButton, QCheckBox,
QFormLayout,
)
from PyQt6.QtCore import Qt, QTimer
from PyQt6.QtGui import QPixmap, QColor, QIcon
from lintunes import theme
from lintunes.theme import HIGHLIGHT_COLORS
_SCALES = ["small", "medium", "large"]
# (preference key, row label) for the per-area grayscale sliders.
_COLOR_ROWS = [
("color_background", "Background:"),
("color_button", "Buttons:"),
("color_now_playing_bg", "Now-playing panel:"),
("color_now_playing_text", "Now-playing text:"),
("color_stripe", "Stripes:"),
]
def _swatch(color_hex: str) -> QIcon:
pixmap = QPixmap(18, 18)
pixmap.fill(QColor(color_hex))
return QIcon(pixmap)
class PreferencesDialog(QDialog):
def __init__(self, prefs, lastfm, parent=None):
super().__init__(parent)
self._prefs = prefs
self._lastfm = lastfm
self.setWindowTitle("Preferences")
self.setMinimumWidth(420)
# Color sliders preview live (set_live emits without writing); persist
# once the user pauses so a drag doesn't hammer the disk.
self._color_save_timer = QTimer(self)
self._color_save_timer.setSingleShot(True)
self._color_save_timer.setInterval(400)
self._color_save_timer.timeout.connect(self._prefs.save)
layout = QVBoxLayout(self)
layout.addWidget(self._build_general_group())
layout.addWidget(self._build_colors_group())
layout.addWidget(self._build_lastfm_group())
layout.addStretch()
lastfm.login_finished.connect(self._on_login_finished)
self._refresh_lastfm_state()
# ---- General ----
def _build_general_group(self) -> QGroupBox:
group = QGroupBox("General")
form = QFormLayout(group)
# UI size: 3-position slider
size_row = QHBoxLayout()
self._size_slider = QSlider(Qt.Orientation.Horizontal)
self._size_slider.setRange(0, 2)
self._size_slider.setPageStep(1)
self._size_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
self._size_slider.setTickInterval(1)
self._size_slider.setFixedWidth(160)
current_scale = self._prefs.get("ui_scale", "medium")
self._size_slider.setValue(
_SCALES.index(current_scale) if current_scale in _SCALES else 1)
self._size_label = QLabel(current_scale.capitalize())
self._size_slider.valueChanged.connect(self._on_size_changed)
size_row.addWidget(QLabel("Small"))
size_row.addWidget(self._size_slider)
size_row.addWidget(QLabel("Large"))
size_row.addSpacing(8)
size_row.addWidget(self._size_label)
size_row.addStretch()
form.addRow("UI size:", size_row)
# Highlight color swatches
color_row = QHBoxLayout()
self._color_group = QButtonGroup(self)
for name, hex_value in HIGHLIGHT_COLORS.items():
radio = QRadioButton(name)
radio.setIcon(_swatch(hex_value))
radio.setChecked(self._prefs.get("highlight") == name)
radio.toggled.connect(
lambda checked, n=name: checked and self._prefs.set("highlight", n))
self._color_group.addButton(radio)
color_row.addWidget(radio)
color_row.addStretch()
form.addRow("Highlight:", color_row)
return group
def _on_size_changed(self, value: int):
scale = _SCALES[value]
self._size_label.setText(scale.capitalize())
self._prefs.set("ui_scale", scale)
# ---- Colors ----
def _build_colors_group(self) -> QGroupBox:
group = QGroupBox("Colors")
form = QFormLayout(group)
note = QLabel("Each slider runs black → white and updates instantly.")
note.setWordWrap(True)
form.addRow(note)
self._color_sliders = {}
for key, label in _COLOR_ROWS:
slider = QSlider(Qt.Orientation.Horizontal)
slider.setRange(0, 255)
slider.setValue(theme.gray_value(self._prefs, key)) # before connect
slider.valueChanged.connect(
lambda value, k=key: self._on_color_changed(k, value))
self._color_sliders[key] = slider
form.addRow(label, slider)
return group
def _on_color_changed(self, key: str, value: int):
self._prefs.set_live(key, value) # live preview, no disk write yet
self._color_save_timer.start() # persist once the drag settles
# ---- Last.fm ----
def _build_lastfm_group(self) -> QGroupBox:
group = QGroupBox("Last.fm")
form = QFormLayout(group)
hint = QLabel(
'Needs a free API account: '
'<a href="https://www.last.fm/api/account/create">'
'last.fm/api/account/create</a>')
hint.setOpenExternalLinks(True)
form.addRow(hint)
lf = self._prefs.lastfm
self._api_key = QLineEdit(lf.get("api_key", ""))
self._api_secret = QLineEdit(lf.get("api_secret", ""))
self._api_secret.setEchoMode(QLineEdit.EchoMode.Password)
self._username = QLineEdit(lf.get("username", ""))
self._password = QLineEdit()
self._password.setEchoMode(QLineEdit.EchoMode.Password)
form.addRow("API key:", self._api_key)
form.addRow("Shared secret:", self._api_secret)
form.addRow("Username:", self._username)
form.addRow("Password:", self._password)
button_row = QHBoxLayout()
self._login_button = QPushButton("Log in")
self._login_button.clicked.connect(self._on_login_clicked)
self._status_label = QLabel("")
button_row.addWidget(self._login_button)
button_row.addWidget(self._status_label)
button_row.addStretch()
form.addRow("", button_row)
self._scrobble_check = QCheckBox("Enable scrobbling")
self._scrobble_check.setChecked(lf.get("scrobble_enabled", False))
self._scrobble_check.toggled.connect(
lambda on: self._prefs.update_lastfm(scrobble_enabled=on))
form.addRow("", self._scrobble_check)
return group
def _on_login_clicked(self):
if self._lastfm.is_logged_in():
self._lastfm.logout()
self._refresh_lastfm_state()
return
api_key = self._api_key.text().strip()
secret = self._api_secret.text().strip()
username = self._username.text().strip()
password = self._password.text()
if not all((api_key, secret, username, password)):
self._status_label.setText("Fill in all four fields first")
return
self._login_button.setEnabled(False)
self._status_label.setText("Logging in…")
self._lastfm.login(api_key, secret, username, password)
def _on_login_finished(self, success: bool, message: str):
self._status_label.setText(message)
self._login_button.setEnabled(True)
if success:
self._password.clear()
self._refresh_lastfm_state()
def _refresh_lastfm_state(self):
logged_in = self._lastfm.is_logged_in()
self._login_button.setText("Log out" if logged_in else "Log in")
self._scrobble_check.setEnabled(logged_in)
if logged_in and not self._status_label.text():
self._status_label.setText(
f"Logged in as {self._prefs.lastfm.get('username', '')}")