Næste version
This commit is contained in:
@@ -4,15 +4,47 @@ library_panel.py — Musikbibliotek med søgning og drag-and-drop til danseliste
|
||||
|
||||
from PyQt6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QListWidget, QListWidgetItem,
|
||||
QLineEdit, QLabel, QHBoxLayout, QPushButton, QProgressBar,
|
||||
QAbstractItemView,
|
||||
QLineEdit, QLabel, QHBoxLayout, QPushButton,
|
||||
QAbstractItemView, QStyledItemDelegate,
|
||||
)
|
||||
from PyQt6.QtCore import Qt, pyqtSignal, QTimer, QMimeData, QByteArray
|
||||
from PyQt6.QtGui import QColor, QDrag
|
||||
from PyQt6.QtCore import Qt, pyqtSignal, QTimer, QMimeData, QByteArray, QRect
|
||||
from PyQt6.QtGui import QColor, QDrag, QPainter, QBrush, QPen, QFont
|
||||
|
||||
|
||||
class DanseButtonDelegate(QStyledItemDelegate):
|
||||
"""Tegner en orange 'Danse' label i højre side af hvert list-item."""
|
||||
|
||||
BTN_W = 54
|
||||
BTN_H = 22
|
||||
BTN_MARGIN = 8
|
||||
|
||||
def paint(self, painter: QPainter, option, index):
|
||||
super().paint(painter, option, index)
|
||||
rect = option.rect
|
||||
btn_rect = QRect(
|
||||
rect.right() - self.BTN_W - self.BTN_MARGIN,
|
||||
rect.top() + (rect.height() - self.BTN_H) // 2,
|
||||
self.BTN_W,
|
||||
self.BTN_H,
|
||||
)
|
||||
painter.save()
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||
painter.setBrush(QBrush(QColor("#e8a020")))
|
||||
painter.setPen(Qt.PenStyle.NoPen)
|
||||
painter.drawRoundedRect(btn_rect, 4, 4)
|
||||
painter.setPen(QPen(QColor("#111111")))
|
||||
font = QFont()
|
||||
font.setPointSize(8)
|
||||
font.setBold(True)
|
||||
painter.setFont(font)
|
||||
painter.drawText(btn_rect, Qt.AlignmentFlag.AlignCenter, "Danse")
|
||||
painter.restore()
|
||||
|
||||
|
||||
class DraggableLibraryList(QListWidget):
|
||||
"""QListWidget der understøtter drag-start med sang-data som mime."""
|
||||
"""QListWidget med drag, dobbeltklik og klik på højre side for dans-tags."""
|
||||
|
||||
danse_clicked = __import__('PyQt6.QtCore', fromlist=['pyqtSignal']).pyqtSignal(dict)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
@@ -20,6 +52,28 @@ class DraggableLibraryList(QListWidget):
|
||||
self.setDragDropMode(QAbstractItemView.DragDropMode.DragOnly)
|
||||
self.setDefaultDropAction(Qt.DropAction.CopyAction)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.button() == Qt.MouseButton.LeftButton:
|
||||
item = self.itemAt(event.pos())
|
||||
if item and event.pos().x() > self.viewport().width() - 75:
|
||||
song = item.data(Qt.ItemDataRole.UserRole)
|
||||
if song:
|
||||
self.danse_clicked.emit(song)
|
||||
return
|
||||
super().mousePressEvent(event)
|
||||
|
||||
def mouseDoubleClickEvent(self, event):
|
||||
if event.button() == Qt.MouseButton.LeftButton:
|
||||
item = self.itemAt(event.pos())
|
||||
if item:
|
||||
# Dobbeltklik i højre 75px = Danse, ellers song_selected
|
||||
if event.pos().x() > self.viewport().width() - 75:
|
||||
song = item.data(Qt.ItemDataRole.UserRole)
|
||||
if song:
|
||||
self.danse_clicked.emit(song)
|
||||
return
|
||||
super().mouseDoubleClickEvent(event)
|
||||
|
||||
def startDrag(self, supported_actions):
|
||||
item = self.currentItem()
|
||||
if not item:
|
||||
@@ -71,34 +125,13 @@ class LibraryPanel(QWidget):
|
||||
header.addWidget(lbl)
|
||||
header.addStretch()
|
||||
|
||||
self._btn_bpm_scan = QPushButton("♩ BPM alle")
|
||||
self._btn_bpm_scan.setFixedHeight(24)
|
||||
self._btn_bpm_scan.setToolTip("Analysér BPM på alle sange uden BPM (kører i baggrunden)")
|
||||
self._btn_bpm_scan.clicked.connect(self._start_bulk_bpm_scan)
|
||||
header.addWidget(self._btn_bpm_scan)
|
||||
|
||||
btn_manage = QPushButton("⚙ Mapper")
|
||||
btn_manage.setFixedHeight(24)
|
||||
btn_manage.setFixedHeight(28)
|
||||
btn_manage.setToolTip("Tilføj, fjern og scan musikbiblioteker")
|
||||
btn_manage.clicked.connect(self._manage_libraries)
|
||||
header.addWidget(btn_manage)
|
||||
layout.addLayout(header)
|
||||
|
||||
# Scan status
|
||||
self._scan_bar = QProgressBar()
|
||||
self._scan_bar.setObjectName("scan_bar")
|
||||
self._scan_bar.setTextVisible(True)
|
||||
self._scan_bar.setFormat("Scanner...")
|
||||
self._scan_bar.setFixedHeight(16)
|
||||
self._scan_bar.setRange(0, 0)
|
||||
self._scan_bar.hide()
|
||||
layout.addWidget(self._scan_bar)
|
||||
|
||||
self._scan_label = QLabel("")
|
||||
self._scan_label.setObjectName("result_count")
|
||||
self._scan_label.hide()
|
||||
layout.addWidget(self._scan_label)
|
||||
|
||||
# Søgefelt
|
||||
self._search = QLineEdit()
|
||||
self._search.setPlaceholderText("Søg i titel, artist, album, dans...")
|
||||
@@ -121,8 +154,10 @@ class LibraryPanel(QWidget):
|
||||
self._list = DraggableLibraryList()
|
||||
self._list.setObjectName("library_list")
|
||||
self._list.itemDoubleClicked.connect(self._on_double_click)
|
||||
self._list.danse_clicked.connect(self.edit_tags_requested)
|
||||
self._list.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||
self._list.customContextMenuRequested.connect(self._show_context_menu)
|
||||
self._list.setItemDelegate(DanseButtonDelegate(self._list))
|
||||
layout.addWidget(self._list)
|
||||
|
||||
# ── Scanning ──────────────────────────────────────────────────────────────
|
||||
@@ -131,16 +166,10 @@ class LibraryPanel(QWidget):
|
||||
self.scan_requested.emit()
|
||||
|
||||
def set_scanning(self, scanning: bool, status_text: str = ""):
|
||||
if scanning:
|
||||
self._scan_bar.show()
|
||||
self._scan_label.setText(status_text or "Starter...")
|
||||
self._scan_label.show()
|
||||
else:
|
||||
self._scan_bar.hide()
|
||||
self._scan_label.hide()
|
||||
pass # Status vises i statuslinjen
|
||||
|
||||
def update_scan_status(self, text: str):
|
||||
self._scan_label.setText(text)
|
||||
pass # Status vises i statuslinjen
|
||||
|
||||
# ── Sange ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -185,41 +214,34 @@ class LibraryPanel(QWidget):
|
||||
dance_parts.append(f"{d} / {lvl}" if lvl else d)
|
||||
dance_str = " · " + " | ".join(dance_parts) if dance_parts else ""
|
||||
|
||||
line1 = ("⚠ " if missing else "") + song.get("title", "—")
|
||||
bpm = song.get("bpm", 0)
|
||||
def _render(self):
|
||||
self._list.clear()
|
||||
q = self._search.text().strip().lower()
|
||||
for song in self._filtered:
|
||||
dances = song.get("dances", [])
|
||||
dance_levels = song.get("dance_levels", [])
|
||||
missing = song.get("file_missing", False)
|
||||
|
||||
dance_parts = []
|
||||
for i, d in enumerate(dances):
|
||||
lvl = dance_levels[i] if i < len(dance_levels) else ""
|
||||
dance_parts.append(f"{d} / {lvl}" if lvl else d)
|
||||
dance_str = " · " + " | ".join(dance_parts) if dance_parts else ""
|
||||
|
||||
prefix = "⚠ " if missing else ""
|
||||
bpm = song.get("bpm", 0)
|
||||
bpm_str = f"{bpm} BPM" if bpm else "? BPM"
|
||||
line2 = f" {song.get('artist','—')} · {bpm_str} · {song.get('file_format','').upper()}{dance_str}"
|
||||
line1 = prefix + song.get("title", "—")
|
||||
line2 = f" {song.get('artist','—')} · {bpm_str} · {song.get('file_format','').upper()}{dance_str}"
|
||||
|
||||
row_widget = QWidget()
|
||||
row_widget.setStyleSheet("background: transparent;")
|
||||
row_layout = QHBoxLayout(row_widget)
|
||||
row_layout.setContentsMargins(2, 2, 2, 2)
|
||||
row_layout.setSpacing(8)
|
||||
|
||||
lbl = QLabel(f"{line1}\n{line2}")
|
||||
lbl.setWordWrap(False)
|
||||
row_layout.addWidget(lbl, stretch=1)
|
||||
|
||||
btn_danse = QPushButton("Danse")
|
||||
btn_danse.setFixedHeight(30)
|
||||
btn_danse.setFixedWidth(70)
|
||||
btn_danse.setToolTip("Rediger dans-tags")
|
||||
btn_danse.setStyleSheet(
|
||||
"QPushButton { background: #e8a020; color: #111; border-radius: 4px; "
|
||||
"font-weight: bold; font-size: 12px; border: none; }"
|
||||
"QPushButton:hover { background: #f0b030; }"
|
||||
)
|
||||
btn_danse.clicked.connect(lambda _, s=song: self.edit_tags_requested.emit(s))
|
||||
row_layout.addWidget(btn_danse)
|
||||
|
||||
item = QListWidgetItem()
|
||||
item = QListWidgetItem(f"{line1}\n{line2}")
|
||||
item.setData(Qt.ItemDataRole.UserRole, song)
|
||||
row_widget.adjustSize()
|
||||
hint = row_widget.sizeHint()
|
||||
hint.setHeight(max(hint.height(), 52))
|
||||
item.setSizeHint(hint)
|
||||
item.setSizeHint(__import__('PyQt6.QtCore', fromlist=['QSize']).QSize(0, 52))
|
||||
if missing:
|
||||
item.setForeground(QColor("#5a6070"))
|
||||
elif q and any(q in d.lower() for d in dances):
|
||||
item.setForeground(QColor("#e8a020"))
|
||||
self._list.addItem(item)
|
||||
self._list.setItemWidget(item, row_widget)
|
||||
|
||||
def _start_bulk_bpm_scan(self):
|
||||
"""Start BPM-analyse på alle sange uden BPM i baggrundstråd med lav prioritet."""
|
||||
@@ -301,6 +323,7 @@ class LibraryPanel(QWidget):
|
||||
act_play = menu.addAction("Afspil")
|
||||
menu.addSeparator()
|
||||
act_tags = menu.addAction("✎ Rediger dans-tags...")
|
||||
act_info = menu.addAction("ℹ Dans-info...")
|
||||
act_bpm = menu.addAction("♩ Analysér BPM")
|
||||
menu.addSeparator()
|
||||
send_menu = menu.addMenu("Send til")
|
||||
@@ -312,6 +335,10 @@ class LibraryPanel(QWidget):
|
||||
self.song_selected.emit(song)
|
||||
elif action == act_tags:
|
||||
self.edit_tags_requested.emit(song)
|
||||
elif action == act_info:
|
||||
from ui.dance_info_dialog import DanceInfoDialog
|
||||
dlg = DanceInfoDialog(song, parent=self.window())
|
||||
dlg.exec()
|
||||
elif action == act_bpm:
|
||||
self._analyze_bpm(song)
|
||||
elif action == act_mail:
|
||||
|
||||
Reference in New Issue
Block a user