""" 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}")