feat: drag-and-drop tracks onto playlist (sidebar) and iPod (tab + nav icon)
This commit is contained in:
+114
-3
@@ -17,8 +17,8 @@ from PyQt6.QtWidgets import (
|
||||
QTableWidget, QTableWidgetItem, QSlider, QMenu, QInputDialog,
|
||||
QStackedWidget, QListWidget, QListWidgetItem, QListView,
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QTimer, pyqtSignal, QSize
|
||||
from PyQt6.QtGui import QPixmap, QColor, QIcon, QPainter, QPainterPath
|
||||
from PyQt6.QtCore import Qt, QTimer, pyqtSignal, QSize, QUrl, QMimeData
|
||||
from PyQt6.QtGui import QPixmap, QColor, QIcon, QPainter, QPainterPath, QDrag
|
||||
|
||||
from track_info import TrackInfo
|
||||
from metadata_handler import MetadataHandler
|
||||
@@ -41,6 +41,51 @@ EQ_FRAMES = [
|
||||
"\u2585\u2586\u2583", "\u2586\u2584\u2581",
|
||||
]
|
||||
|
||||
class DraggableLibraryTable(QTableWidget):
|
||||
"""QTableWidget subclass that supports dragging tracks as file URLs."""
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setDragEnabled(True)
|
||||
|
||||
def startDrag(self, supportedActions):
|
||||
paths = []
|
||||
for item in self.selectedItems():
|
||||
data_item = self.item(item.row(), 0)
|
||||
if data_item is None:
|
||||
continue
|
||||
_is_ready, track_data = data_item.data(Qt.ItemDataRole.UserRole)
|
||||
if track_data and track_data.get("path"):
|
||||
paths.append(track_data["path"])
|
||||
|
||||
if not paths:
|
||||
return
|
||||
|
||||
paths = list(set(paths))
|
||||
mime = QMimeData()
|
||||
mime.setUrls([QUrl.fromLocalFile(p) for p in paths])
|
||||
|
||||
drag = QDrag(self)
|
||||
drag.setMimeData(mime)
|
||||
|
||||
count = len(paths)
|
||||
label = f"{count} track{'s' if count != 1 else ''}"
|
||||
pixmap = QPixmap(160, 28)
|
||||
pixmap.fill(Qt.GlobalColor.transparent)
|
||||
painter = QPainter(pixmap)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||
bg = self.palette().color(self.palette().ColorRole.Highlight)
|
||||
painter.setBrush(bg)
|
||||
painter.setPen(Qt.PenStyle.NoPen)
|
||||
painter.drawRoundedRect(0, 0, 160, 28, 6, 6)
|
||||
painter.setPen(self.palette().color(self.palette().ColorRole.HighlightedText))
|
||||
painter.drawText(pixmap.rect(), Qt.AlignmentFlag.AlignCenter, label)
|
||||
painter.end()
|
||||
drag.setPixmap(pixmap)
|
||||
drag.setHotSpot(pixmap.rect().center())
|
||||
|
||||
drag.exec(Qt.DropAction.CopyAction)
|
||||
|
||||
|
||||
class NumericTableItem(QTableWidgetItem):
|
||||
def __lt__(self, other):
|
||||
_, self_data = self.data(Qt.ItemDataRole.UserRole) or (False, {})
|
||||
@@ -95,7 +140,7 @@ class LibraryTab(QWidget):
|
||||
|
||||
self._setup_library_toolbar(layout)
|
||||
|
||||
self.library_table = QTableWidget()
|
||||
self.library_table = DraggableLibraryTable()
|
||||
self.library_table.setColumnCount(8)
|
||||
self.library_table.setHorizontalHeaderLabels(
|
||||
["#", "Artist", "Title", "Album", "Duration", "Genre", "Size", "Plays"]
|
||||
@@ -1060,6 +1105,72 @@ class LibraryTab(QWidget):
|
||||
|
||||
self._cleanup_worker()
|
||||
|
||||
def transfer_tracks_by_paths(self, paths: List[str]):
|
||||
"""Transfer tracks by file paths (used for drag-drop onto iPod tab)."""
|
||||
ready_by_path = {}
|
||||
for t in self.library_ready:
|
||||
p = t.get("path", "")
|
||||
if p:
|
||||
ready_by_path[p] = t
|
||||
|
||||
handler = MetadataHandler()
|
||||
tracks = []
|
||||
for path in paths:
|
||||
if not os.path.exists(path):
|
||||
continue
|
||||
if path in ready_by_path:
|
||||
d = ready_by_path[path]
|
||||
ti = TrackInfo(
|
||||
source_id=f"local_{hash(path)}",
|
||||
title=d["title"], artist=d["artist"],
|
||||
album=d.get("album", ""), thumbnail_url="",
|
||||
duration=d.get("duration_s", 0),
|
||||
track_number=d.get("track_num"),
|
||||
genre=d.get("genre", ""), download_path=path,
|
||||
)
|
||||
tracks.append((ti, path))
|
||||
else:
|
||||
try:
|
||||
info = handler.extract_tags(path)
|
||||
ti = TrackInfo(
|
||||
source_id=f"local_{hash(path)}",
|
||||
title=info.title if info else os.path.basename(path),
|
||||
artist=info.artist if info else "Unknown",
|
||||
album=info.album if info else "", thumbnail_url="",
|
||||
duration=info.duration if info else 0,
|
||||
track_number=info.track_number if info else 0,
|
||||
genre=info.genre if info else "", download_path=path,
|
||||
)
|
||||
tracks.append((ti, path))
|
||||
except Exception as e:
|
||||
logger.warning(f"Skipping {path}: {e}")
|
||||
continue
|
||||
|
||||
if not tracks:
|
||||
QMessageBox.warning(self, "Error", "No valid tracks to transfer")
|
||||
return
|
||||
|
||||
if not self._current_mount_point:
|
||||
QMessageBox.warning(self, "Error", "No iPod device mounted. Go to iPod tab and mount first.")
|
||||
return
|
||||
|
||||
if self._is_worker_running():
|
||||
QMessageBox.information(self, "Info", "Please wait for the current task to finish")
|
||||
return
|
||||
|
||||
self.library_transfer_btn.setEnabled(False)
|
||||
self.library_status.setText("Transferring...")
|
||||
self.library_progress.setValue(0)
|
||||
|
||||
self.worker_thread = WorkerThread(
|
||||
task_type="transfer",
|
||||
tracks=tracks,
|
||||
mount_point=self._current_mount_point,
|
||||
)
|
||||
self.worker_thread.progress_signal.connect(self._on_library_transfer_progress)
|
||||
self.worker_thread.finished_signal.connect(self._on_library_transfer_finished)
|
||||
self.worker_thread.start()
|
||||
|
||||
def _on_sync_metadata_clicked(self):
|
||||
selected_tracks = self._get_selected_ready_tracks()
|
||||
if not selected_tracks:
|
||||
|
||||
Reference in New Issue
Block a user