Files
LinedanceAfspiller/linedance-app/ui/playlist_browser.py
2026-04-12 10:25:41 +02:00

347 lines
13 KiB
Python

"""
playlist_browser.py — Dialog til at hente og gemme danselister med tag-organisering.
Viser en liste over alle gemte danselister med:
- Navn, dato, antal sange
- Tag-filtrering i venstre side
- Gem ny liste med tags
"""
from PyQt6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
QPushButton, QListWidget, QListWidgetItem, QWidget,
QSplitter, QFrame, QMessageBox, QInputDialog,
)
from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtGui import QColor
class PlaylistBrowserDialog(QDialog):
"""Kombineret gem/hent dialog til danselister."""
playlist_selected = pyqtSignal(int, str) # playlist_id, name
def __init__(self, mode: str = "load", current_songs: list = None,
current_name: str = "", parent=None):
super().__init__(parent)
self._mode = mode # "load" eller "save"
self._current_songs = current_songs or []
self._current_name = current_name
self._all_playlists = []
self._active_tag = None
title = "Gem danseliste" if mode == "save" else "Hent danseliste"
self.setWindowTitle(title)
self.setMinimumSize(700, 480)
self.resize(780, 520)
self._build_ui()
self._load_data()
def _build_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(12, 12, 12, 12)
layout.setSpacing(8)
# Gem-felter (kun i save-mode)
if self._mode == "save":
save_frame = QFrame()
save_frame.setObjectName("track_display")
save_layout = QVBoxLayout(save_frame)
save_layout.setContentsMargins(10, 8, 10, 8)
row1 = QHBoxLayout()
row1.addWidget(QLabel("Navn:"))
self._name_input = QLineEdit()
self._name_input.setText(self._current_name)
self._name_input.setPlaceholderText("Navn på danselisten...")
row1.addWidget(self._name_input)
save_layout.addLayout(row1)
row2 = QHBoxLayout()
row2.addWidget(QLabel("Tags:"))
self._tags_input = QLineEdit()
self._tags_input.setPlaceholderText("stævne, øvning, workshop (komma-separeret)")
self._tags_input.textChanged.connect(self._suggest_tags)
row2.addWidget(self._tags_input)
save_layout.addLayout(row2)
# Tag-forslag
self._tag_suggestions = QListWidget()
self._tag_suggestions.setMaximumHeight(80)
self._tag_suggestions.hide()
self._tag_suggestions.itemClicked.connect(self._add_tag_suggestion)
save_layout.addWidget(self._tag_suggestions)
lbl = QLabel(f"{len(self._current_songs)} sange vil blive gemt")
lbl.setObjectName("result_count")
save_layout.addWidget(lbl)
layout.addWidget(save_frame)
# Splitter: tags til venstre, lister til højre
splitter = QSplitter(Qt.Orientation.Horizontal)
# ── Venstre: tag-filtrering ──
tag_panel = QWidget()
tag_layout = QVBoxLayout(tag_panel)
tag_layout.setContentsMargins(0, 0, 0, 0)
tag_layout.setSpacing(4)
lbl_tags = QLabel("FILTRÉR PÅ TAG")
lbl_tags.setObjectName("section_title")
tag_layout.addWidget(lbl_tags)
self._tag_list = QListWidget()
self._tag_list.currentItemChanged.connect(self._on_tag_selected)
tag_layout.addWidget(self._tag_list)
tag_panel.setMaximumWidth(180)
splitter.addWidget(tag_panel)
# ── Højre: danseliste-oversigt ──
list_panel = QWidget()
list_layout = QVBoxLayout(list_panel)
list_layout.setContentsMargins(0, 0, 0, 0)
list_layout.setSpacing(4)
# Søgefelt
self._search = QLineEdit()
self._search.setPlaceholderText("Søg i navn...")
self._search.textChanged.connect(self._filter)
list_layout.addWidget(self._search)
self._count_label = QLabel("")
self._count_label.setObjectName("result_count")
list_layout.addWidget(self._count_label)
self._list = QListWidget()
self._list.itemDoubleClicked.connect(self._on_double_click)
list_layout.addWidget(self._list)
splitter.addWidget(list_panel)
splitter.setSizes([160, 580])
layout.addWidget(splitter, stretch=1)
# Knapper
btn_row = QHBoxLayout()
if self._mode == "load":
btn_delete = QPushButton("🗑 Slet valgte")
btn_delete.clicked.connect(self._delete_selected)
btn_row.addWidget(btn_delete)
btn_tags = QPushButton("🏷 Rediger tags")
btn_tags.clicked.connect(self._edit_tags)
btn_row.addWidget(btn_tags)
btn_row.addStretch()
btn_cancel = QPushButton("Annuller")
btn_cancel.clicked.connect(self.reject)
btn_row.addWidget(btn_cancel)
if self._mode == "save":
btn_ok = QPushButton("💾 Gem")
btn_ok.setObjectName("btn_play")
btn_ok.clicked.connect(self._save)
else:
btn_ok = QPushButton("📂 Hent valgte")
btn_ok.setObjectName("btn_play")
btn_ok.clicked.connect(self._load_selected)
btn_row.addWidget(btn_ok)
layout.addLayout(btn_row)
def _load_data(self):
try:
from local.local_db import get_playlists, get_all_playlist_tags
self._all_playlists = [dict(r) for r in get_playlists()]
# Udfyld tag-liste
self._tag_list.clear()
all_item = QListWidgetItem("Alle lister")
all_item.setData(Qt.ItemDataRole.UserRole, None)
self._tag_list.addItem(all_item)
for tag in get_all_playlist_tags():
item = QListWidgetItem(f"# {tag}")
item.setData(Qt.ItemDataRole.UserRole, tag)
self._tag_list.addItem(item)
self._tag_list.setCurrentRow(0)
self._render(self._all_playlists)
except Exception as e:
print(f"Playlist browser load fejl: {e}")
def _on_tag_selected(self, current, previous):
if not current:
return
self._active_tag = current.data(Qt.ItemDataRole.UserRole)
self._filter()
def _suggest_tags(self, text: str):
"""Vis forslag til det sidst indtastede tag."""
if not hasattr(self, '_tag_suggestions'):
return
parts = text.split(",")
prefix = parts[-1].strip().lower()
if not prefix:
self._tag_suggestions.hide()
return
try:
from local.local_db import get_all_playlist_tags
all_tags = get_all_playlist_tags()
matches = [t for t in all_tags
if t.startswith(prefix) and t not in
[p.strip().lower() for p in parts[:-1]]]
if matches:
self._tag_suggestions.clear()
for t in matches[:5]:
self._tag_suggestions.addItem(t)
self._tag_suggestions.show()
else:
self._tag_suggestions.hide()
except Exception:
self._tag_suggestions.hide()
def _add_tag_suggestion(self, item):
"""Tilføj et foreslået tag til tekstfeltet."""
parts = self._tags_input.text().split(",")
parts[-1] = " " + item.text()
self._tags_input.setText(",".join(parts) + ", ")
self._tag_suggestions.hide()
self._tags_input.setFocus()
def _edit_tags(self):
"""Rediger tags på den valgte liste."""
item = self._list.currentItem()
if not item:
return
pl = item.data(Qt.ItemDataRole.UserRole)
if not pl or not isinstance(pl, dict):
return
from PyQt6.QtWidgets import QInputDialog
current = pl.get("tags", "")
new_tags, ok = QInputDialog.getText(
self, "Rediger tags", "Tags (komma-separeret):", text=current
)
if ok:
try:
from local.local_db import update_playlist_tags
update_playlist_tags(pl["id"], new_tags.strip())
self._load_data()
except Exception as e:
QMessageBox.warning(self, "Fejl", f"Kunne ikke opdatere tags: {e}")
def _filter(self):
query = self._search.text().strip().lower()
tag = self._active_tag
filtered = self._all_playlists
if tag:
filtered = [
p for p in filtered
if tag in [t.strip().lower() for t in p.get("tags", "").split(",")]
]
if query:
filtered = [p for p in filtered if query in p["name"].lower()]
self._render(filtered)
def _render(self, playlists: list):
self._list.clear()
self._count_label.setText(f"{len(playlists)} liste{'r' if len(playlists) != 1 else ''}")
for pl in playlists:
date = pl.get("created_at", "")[:10]
count = pl.get("song_count", 0)
tags = pl.get("tags", "")
tag_str = f" [{tags}]" if tags else ""
item = QListWidgetItem(
f"{pl['name']}\n"
f" {date} · {count} sange{tag_str}"
)
item.setData(Qt.ItemDataRole.UserRole, pl)
self._list.addItem(item)
def _on_double_click(self, item: QListWidgetItem):
if self._mode == "load":
self._load_selected()
else:
self._save()
def _load_selected(self):
item = self._list.currentItem()
if not item:
QMessageBox.information(self, "Vælg", "Vælg en liste først.")
return
pl = item.data(Qt.ItemDataRole.UserRole)
self.playlist_selected.emit(pl["id"], pl["name"])
self.accept()
def _save(self):
name = self._name_input.text().strip()
if not name:
QMessageBox.warning(self, "Navn mangler", "Angiv et navn til danselisten.")
self._name_input.setFocus()
return
tags = self._tags_input.text().strip()
# Tjek om navn allerede eksisterer
existing = [p for p in self._all_playlists
if p["name"].lower() == name.lower()]
if existing:
reply = QMessageBox.question(
self, "Navn eksisterer allerede",
f"Der findes allerede en liste med navnet '{name}'.\n"
f"Vil du overskrive den?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
)
if reply == QMessageBox.StandardButton.Yes:
try:
from local.local_db import get_db, add_song_to_playlist
pl_id = existing[0]["id"]
with get_db() as conn:
conn.execute(
"DELETE FROM playlist_songs WHERE playlist_id=?", (pl_id,)
)
if tags:
conn.execute(
"UPDATE playlists SET tags=? WHERE id=?", (tags, pl_id)
)
for i, song in enumerate(self._current_songs, start=1):
if song.get("id"):
add_song_to_playlist(pl_id, song["id"], position=i)
self.playlist_selected.emit(pl_id, name)
self.accept()
except Exception as e:
QMessageBox.warning(self, "Fejl", f"Kunne ikke overskrive: {e}")
return
try:
from local.local_db import create_playlist, add_song_to_playlist
pl_id = create_playlist(name, tags=tags)
for i, song in enumerate(self._current_songs, start=1):
if song.get("id"):
add_song_to_playlist(pl_id, song["id"], position=i)
self.playlist_selected.emit(pl_id, name)
self.accept()
except Exception as e:
QMessageBox.warning(self, "Fejl", f"Kunne ikke gemme: {e}")
def _delete_selected(self):
item = self._list.currentItem()
if not item:
return
pl = item.data(Qt.ItemDataRole.UserRole)
reply = QMessageBox.question(
self, "Slet danseliste",
f"Slet '{pl['name']}'?\n\nDette kan ikke fortrydes.",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
)
if reply == QMessageBox.StandardButton.Yes:
try:
from local.local_db import get_db
with get_db() as conn:
conn.execute("DELETE FROM playlists WHERE id=?", (pl["id"],))
self._load_data()
except Exception as e:
QMessageBox.warning(self, "Fejl", f"Kunne ikke slette: {e}")