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.
197 lines
7.4 KiB
Python
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', '')}")
|