feat: PlaylistWidget with drop support and auto-advance
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ from pathlib import Path
|
|||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||||
QLabel, QPushButton, QLineEdit, QFileDialog, QFrame, QStatusBar,
|
QLabel, QPushButton, QLineEdit, QFileDialog, QFrame, QStatusBar,
|
||||||
|
QListWidget, QListWidgetItem, QAbstractItemView, QSplitter,
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, QThread, QTimer, pyqtSignal
|
from PyQt6.QtCore import Qt, QThread, QTimer, pyqtSignal
|
||||||
from PyQt6.QtGui import QPainter, QColor, QPen, QDragEnterEvent, QDropEvent
|
from PyQt6.QtGui import QPainter, QColor, QPen, QDragEnterEvent, QDropEvent
|
||||||
@@ -251,6 +252,65 @@ class MpvWidget(QFrame):
|
|||||||
super().closeEvent(event)
|
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():
|
def main():
|
||||||
# Force X11/XCB mode so mpv can embed via wid — Wayland uses a different
|
# Force X11/XCB mode so mpv can embed via wid — Wayland uses a different
|
||||||
# surface handle that mpv's wid parameter cannot accept.
|
# surface handle that mpv's wid parameter cannot accept.
|
||||||
|
|||||||
Reference in New Issue
Block a user