feat: playlist separator can be added above or below a file

- Context menu offers both "Add/Remove separator above" and "below"
- "Below" anchors to the next visible file, or a trailing line via end sentinel when clicking the last file
- End sentinel preserved across rebuilds and persisted per profile

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 12:47:43 +02:00
parent 299779cf29
commit f1f8fd5244
+38 -14
View File
@@ -3130,6 +3130,7 @@ class SnapPreviewWindow(QWidget):
class PlaylistWidget(QListWidget): class PlaylistWidget(QListWidget):
file_selected = pyqtSignal(str) # emits full path of selected file file_selected = pyqtSignal(str) # emits full path of selected file
_SEP_END = "\x00END" # anchor for a separator after the last visible file
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@@ -3164,6 +3165,25 @@ class PlaylistWidget(QListWidget):
self._filter_text = text.lower() self._filter_text = text.lower()
self._rebuild() self._rebuild()
def _next_visible_path(self, path: str) -> str:
"""Return the next visible file after *path*, or _SEP_END if it's last."""
seen = False
for p in self._paths:
if seen and self._is_visible(p):
return p
if p == path:
seen = True
return self._SEP_END
def _toggle_separator(self, anchor: str) -> None:
"""Add or remove a separator anchored before *anchor* (or at the end)."""
if anchor in self._separators_before:
self._separators_before.discard(anchor)
else:
self._separators_before.add(anchor)
self._rebuild()
self.separators_changed.emit()
def _is_visible(self, path: str) -> bool: def _is_visible(self, path: str) -> bool:
if os.path.basename(path) in self._hidden_basenames: if os.path.basename(path) in self._hidden_basenames:
return self._show_hidden return self._show_hidden
@@ -3177,8 +3197,8 @@ class PlaylistWidget(QListWidget):
"""Rebuild the QListWidget from scratch with only visible items.""" """Rebuild the QListWidget from scratch with only visible items."""
self.blockSignals(True) self.blockSignals(True)
self.clear() self.clear()
# Drop separator anchors for paths no longer present. # Drop separator anchors for paths no longer present (keep end sentinel).
self._separators_before &= set(self._paths) self._separators_before &= set(self._paths) | {self._SEP_END}
visible_paths = [p for p in self._paths if self._is_visible(p)] visible_paths = [p for p in self._paths if self._is_visible(p)]
self._visible = [] self._visible = []
for path in visible_paths: for path in visible_paths:
@@ -3189,6 +3209,9 @@ class PlaylistWidget(QListWidget):
self._style_item(item, path) self._style_item(item, path)
self.addItem(item) self.addItem(item)
self._visible.append(path) self._visible.append(path)
if self._SEP_END in self._separators_before and visible_paths:
self.addItem(self._make_separator_item())
self._visible.append(None)
# Restore selection. # Restore selection.
if self._selected_path and self._selected_path in self._visible: if self._selected_path and self._selected_path in self._visible:
row = self._visible.index(self._selected_path) row = self._visible.index(self._selected_path)
@@ -3368,7 +3391,8 @@ class PlaylistWidget(QListWidget):
menu = QMenu(self) menu = QMenu(self)
# Check if any selected files are hidden. # Check if any selected files are hidden.
hidden_sel = [p for p in sel if os.path.basename(p) in self._hidden_basenames] hidden_sel = [p for p in sel if os.path.basename(p) in self._hidden_basenames]
act_remove = act_hide = act_unhide = act_delete = act_sep = None act_remove = act_hide = act_unhide = act_delete = None
act_sep_above = act_sep_below = None
disable_acts: dict = {} disable_acts: dict = {}
enable_acts: dict = {} enable_acts: dict = {}
if len(sel) == 1: if len(sel) == 1:
@@ -3392,10 +3416,13 @@ class PlaylistWidget(QListWidget):
base = f[:-len("_disabled")] base = f[:-len("_disabled")]
enable_acts[sub.addAction(f"{base} ({folders[f]})")] = f enable_acts[sub.addAction(f"{base} ({folders[f]})")] = f
menu.addSeparator() menu.addSeparator()
if sel[0] in self._separators_before: above_present = sel[0] in self._separators_before
act_sep = menu.addAction("Remove separator above") act_sep_above = menu.addAction(
else: "Remove separator above" if above_present else "Add separator above")
act_sep = menu.addAction("Add separator above") below_anchor = self._next_visible_path(sel[0])
below_present = below_anchor in self._separators_before
act_sep_below = menu.addAction(
"Remove separator below" if below_present else "Add separator below")
act_delete = menu.addAction(f"Delete from disk: {name}") act_delete = menu.addAction(f"Delete from disk: {name}")
else: else:
act_remove = menu.addAction(f"Remove {len(sel)} files") act_remove = menu.addAction(f"Remove {len(sel)} files")
@@ -3442,13 +3469,10 @@ class PlaylistWidget(QListWidget):
self.hide_requested.emit(sel) self.hide_requested.emit(sel)
elif chosen == act_unhide: elif chosen == act_unhide:
self.unhide_requested.emit(hidden_sel) self.unhide_requested.emit(hidden_sel)
elif chosen == act_sep: elif chosen == act_sep_above:
if sel[0] in self._separators_before: self._toggle_separator(sel[0])
self._separators_before.discard(sel[0]) elif chosen == act_sep_below:
else: self._toggle_separator(self._next_visible_path(sel[0]))
self._separators_before.add(sel[0])
self._rebuild()
self.separators_changed.emit()
elif chosen in disable_acts: elif chosen in disable_acts:
self.disable_requested.emit(sel[0], disable_acts[chosen]) self.disable_requested.emit(sel[0], disable_acts[chosen])
elif chosen in enable_acts: elif chosen in enable_acts: