347 lines
13 KiB
Python
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}")
|