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:
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user