From 75fd43577eb0470e45ad8a40a617181500f75eb6 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Mon, 6 Apr 2026 12:51:46 +0200 Subject: [PATCH] feat: PlaylistWidget with drop support and auto-advance Co-Authored-By: Claude Sonnet 4.6 --- main.py | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/main.py b/main.py index d51bf2c..8df762d 100644 --- a/main.py +++ b/main.py @@ -10,6 +10,7 @@ from pathlib import Path from PyQt6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QLineEdit, QFileDialog, QFrame, QStatusBar, + QListWidget, QListWidgetItem, QAbstractItemView, QSplitter, ) from PyQt6.QtCore import Qt, QThread, QTimer, pyqtSignal from PyQt6.QtGui import QPainter, QColor, QPen, QDragEnterEvent, QDropEvent @@ -251,6 +252,65 @@ class MpvWidget(QFrame): super().closeEvent(event) +class PlaylistWidget(QListWidget): + file_selected = pyqtSignal(str) # emits full path of selected file + + def __init__(self): + super().__init__() + self.setAcceptDrops(True) + self.setDragDropMode(QAbstractItemView.DragDropMode.DropOnly) + self.setFixedWidth(200) + self.setWordWrap(True) + self._paths: list[str] = [] + self.itemClicked.connect(self._on_item_clicked) + + def add_files(self, paths: list[str]) -> None: + """Append paths not already in queue; auto-select first if queue was empty.""" + was_empty = len(self._paths) == 0 + for path in paths: + if path not in self._paths and os.path.isfile(path): + self._paths.append(path) + self.addItem(os.path.basename(path)) + if was_empty and self._paths: + self._select(0) + + def advance(self) -> None: + """Move to next item in queue. Does nothing if at end.""" + row = self.currentRow() + if row < self.count() - 1: + self._select(row + 1) + + def current_path(self) -> str | None: + row = self.currentRow() + return self._paths[row] if 0 <= row < len(self._paths) else None + + def _select(self, row: int) -> None: + self.setCurrentRow(row) + self._refresh_labels() + self.file_selected.emit(self._paths[row]) + + def _refresh_labels(self) -> None: + current = self.currentRow() + for i in range(self.count()): + name = os.path.basename(self._paths[i]) + self.item(i).setText(f"▶ {name}" if i == current else name) + + def _on_item_clicked(self, item: QListWidgetItem) -> None: + self._select(self.row(item)) + + def dragEnterEvent(self, event: QDragEnterEvent) -> None: + if event.mimeData().hasUrls(): + event.acceptProposedAction() + + def dropEvent(self, event: QDropEvent) -> None: + paths = [ + u.toLocalFile() for u in event.mimeData().urls() + if os.path.isfile(u.toLocalFile()) + ] + if paths: + self.add_files(paths) + + def main(): # Force X11/XCB mode so mpv can embed via wid — Wayland uses a different # surface handle that mpv's wid parameter cannot accept.