Features:
- Rating hover dots: hovering a rating cell shows 5 clickable slots
(RatingDelegate); click slot k sets k stars, clicking the current count
clears. Undoable, library-only (ratings never rewrite music files).
- Search bar moved into the Library header strip, right of the Library
button, so the tracklist top aligns with the playlist tree.
- Right-click "Download Album Art…": iTunes Search API (no key), off-thread
fetch, preview/confirm dialog with Next Result, embeds via write_artwork
+ size refresh, invalidates the MPRIS art cache.
Fixes:
- MPRIS media keys: PropertiesChanged sent invalidated_properties as "av"
instead of "as", so gsd-media-keys dropped it and never MRU-bumped
lintunes (why the play/pause key kept waking stale players). Now an
explicit QDBusArgument string array; loopback-verified sa{sv}as.
- Exit segfault: ordered Player.shutdown() (stop, clear source, detach
buffer/audio outputs) from closeEvent/aboutToQuit; scripted run exits 0.
- BT zero-volume after pause/resume: volume re-applied on resume, device
swap, and BufferedMedia (needs verify on the affected machine).
Cruft sweep:
- Tag writes filtered to EDITABLE_FIELDS; failures logged + surfaced in
the status bar (was a swallowed print).
- O(n²) import fixed (location index + cached max track id); O(1)
refresh/reveal via TrackTableModel row index.
- lastfm: scrobble-queue thread lock, login prefs write marshalled to the
GUI thread, JSON/raise_for_status order fixed.
- Shared read_json/write_json; TableSettingsMixin dedupes view settings.
TASKS.md rewritten as a resumable board; tests in tests/test_round17.py
(251 total pass).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
39 lines
1.4 KiB
Python
39 lines
1.4 KiB
Python
"""Shared persistence of per-view table settings (sort, columns, widths).
|
|
|
|
LibraryView and PlaylistView both mirror TrackTableView's header signals into
|
|
a PlaylistSettings and mark it dirty; only where the settings live differs.
|
|
Subclasses implement ``_current_settings()`` (None when nothing is shown, e.g.
|
|
the playlist view before a playlist is opened) and ``_mark_settings_dirty()``.
|
|
"""
|
|
|
|
|
|
class TableSettingsMixin:
|
|
def _wire_table_settings(self):
|
|
"""Connect the table's header signals; call once self.table exists."""
|
|
self.table.sort_changed.connect(self._on_sort_changed)
|
|
self.table.columns_changed.connect(self._on_columns_changed)
|
|
self.table.column_width_changed.connect(self._on_width_changed)
|
|
|
|
def _on_sort_changed(self, field, ascending):
|
|
settings = self._current_settings()
|
|
if settings is None:
|
|
return
|
|
settings.sort_column = field
|
|
settings.sort_ascending = ascending
|
|
self._mark_settings_dirty()
|
|
|
|
def _on_columns_changed(self, columns):
|
|
settings = self._current_settings()
|
|
if settings is None:
|
|
return
|
|
settings.visible_columns = columns
|
|
self.table.apply_settings(settings)
|
|
self._mark_settings_dirty()
|
|
|
|
def _on_width_changed(self, field, width):
|
|
settings = self._current_settings()
|
|
if settings is None:
|
|
return
|
|
settings.column_widths[field] = width
|
|
self._mark_settings_dirty()
|