- New prefs: visualizer_mode (on/dim/off, saved on every click) and color_visualizer_gray (None = keep the derived alternateBase look). - VisualizerWidget takes prefs (optional, so existing call sites/tests keep working); restores its mode at startup and validates junk values. - New "Visualizer (gray mode):" row in the Preferences color sliders; theme.default_gray mirrors the widget's 0.88-lightness derivation so an untouched slider shows the current appearance. - TransportBar.refresh_theme() repaints the visualizer so slider drags preview live.
198 lines
7.4 KiB
Python
198 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:"),
|
|
("color_visualizer_gray", "Visualizer (gray mode):"),
|
|
]
|
|
|
|
|
|
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', '')}")
|