""" alt_dance_picker_dialog.py β€” VΓ¦lg alternativ dans til en sang i playlisten. Tre sektioner: 🟒 Mine egne alternativ-danse med min rating 🟑 Community alternativ-danse med community + min rating Alle andre danse """ from PyQt6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QListWidget, QListWidgetItem, QWidget, ) from PyQt6.QtCore import Qt, QTimer, pyqtSignal from PyQt6.QtGui import QColor STAR_FULL = "β˜…" STAR_EMPTY = "β˜†" GREEN = "#27ae60" YELLOW = "#e8a020" MUTED = "#5a6070" class StarRatingWidget(QWidget): """Klikbar stjerne-rating widget til brug i lister.""" rating_changed = pyqtSignal(int) # 1-5 def __init__(self, rating=None, max_stars=5, color=YELLOW, parent=None): # YELLOW er ikke defineret endnu ved import β€” bruges som string nedenfor super().__init__(parent) self._rating = rating self._max = max_stars self._color = color self._btns = [] layout = QHBoxLayout(self) layout.setContentsMargins(2, 0, 2, 0) layout.setSpacing(1) for i in range(1, max_stars + 1): btn = QPushButton("β˜…" if rating and i <= rating else "β˜†") btn.setFixedSize(18, 18) btn.setStyleSheet(f""" QPushButton {{ font-size: 13px; border: none; background: none; padding: 0; color: {color if rating and i <= rating else '#5a6070'}; }} QPushButton:hover {{ color: {color}; }} """) btn.clicked.connect(lambda checked, r=i: self._on_click(r)) layout.addWidget(btn) self._btns.append(btn) def _on_click(self, r): self._rating = r for i, btn in enumerate(self._btns): filled = i < r btn.setText("β˜…" if filled else "β˜†") btn.setStyleSheet(f""" QPushButton {{ font-size: 13px; border: none; background: none; padding: 0; color: {self._color if filled else '#5a6070'}; }} QPushButton:hover {{ color: {self._color}; }} """) self.rating_changed.emit(r) def get_rating(self): return self._rating def make_stars(rating, max_stars=5): if not rating: return STAR_EMPTY * max_stars full = min(max_stars, round(float(rating))) return STAR_FULL * full + STAR_EMPTY * (max_stars - full) class AltDancePickerDialog(QDialog): def __init__(self, song: dict, parent=None): super().__init__(parent) self._song = song self._chosen_dance = "" self._chosen_rating = None self._cleared = False self.setWindowTitle("VΓ¦lg alternativ dans") self.setMinimumWidth(600) self.setMinimumHeight(520) self._build_ui() self._load_suggestions("") def _build_ui(self): layout = QVBoxLayout(self) layout.setContentsMargins(12, 12, 12, 12) layout.setSpacing(8) # Sang-info title = self._song.get("title", "?") artist = self._song.get("artist", "") lbl = QLabel(f"{title} Β· {artist}" if artist else title) lbl.setObjectName("track_title") lbl.setWordWrap(True) layout.addWidget(lbl) # SΓΈgefelt self._edit = QLineEdit() self._edit.setPlaceholderText("SΓΈg dans-navn...") self._edit.textChanged.connect(self._on_text_changed) self._edit.returnPressed.connect(self._on_accept) layout.addWidget(self._edit) # Forslagsliste self._list = QListWidget() self._list.setMinimumHeight(320) self._list.itemClicked.connect(self._on_item_clicked) self._list.itemDoubleClicked.connect(self._on_selected) layout.addWidget(self._list) # Info-label self._info_lbl = QLabel("") self._info_lbl.setObjectName("result_count") self._info_lbl.setWordWrap(True) layout.addWidget(self._info_lbl) # Debounce timer self._timer = QTimer(self) self._timer.setSingleShot(True) self._timer.setInterval(150) self._timer.timeout.connect( lambda: self._load_suggestions(self._edit.text().strip()) ) # Knapper btn_row = QHBoxLayout() btn_none = QPushButton("βœ• Ingen alternativ") btn_none.clicked.connect(self._on_clear) btn_row.addWidget(btn_none) btn_row.addStretch() btn_cancel = QPushButton("Annuller") btn_cancel.clicked.connect(self.reject) btn_row.addWidget(btn_cancel) btn_ok = QPushButton("βœ“ VΓ¦lg") btn_ok.setObjectName("btn_play") btn_ok.clicked.connect(self._on_accept) btn_row.addWidget(btn_ok) layout.addLayout(btn_row) self._edit.setFocus() def _on_text_changed(self): self._timer.start() def _make_sep(self, text): sep = QListWidgetItem(text) sep.setForeground(QColor(MUTED)) sep.setFlags(Qt.ItemFlag.ItemIsEnabled) sep.setData(Qt.ItemDataRole.UserRole, None) return sep def _load_suggestions(self, prefix): try: from local.local_db import ( get_alt_dances_for_song_with_ratings, get_community_alts_for_song, get_dance_suggestions, ) self._list.clear() song_id = self._song.get("id", "") # ── Mine egne alternativ-danse ── own_alts = get_alt_dances_for_song_with_ratings(song_id) own_names = {a["name"].lower() for a in own_alts} matching_own = [a for a in own_alts if not prefix or prefix.lower() in a["name"].lower()] if matching_own: self._list.addItem(self._make_sep( f"── 🟒 Mine alternativ-danse ──" )) for a in matching_own: my_r = a.get("user_rating") my_s = make_stars(my_r) name = a["name"] level = a.get("level_name", "") disp = f"{name} / {level}" if level else name # Venstre: navn, hΓΈjre: mine stjerner label = f"🟒 {disp:<40} {my_s}" item = QListWidgetItem() item.setSizeHint(__import__('PyQt6.QtCore', fromlist=['QSize']).QSize(0, 34)) item.setData(Qt.ItemDataRole.UserRole, { "name": name, "level": level, "choreo": a.get("choreographer", ""), "my_rating": my_r, "comm_rating": None, "dance_id": a["id"], "is_own": True, }) self._list.addItem(item) # Widget med navn + klikbare stjerner w = QWidget() wl = QHBoxLayout(w) wl.setContentsMargins(4, 0, 4, 0) wl.setSpacing(6) lbl_name = QLabel(f"🟒 {disp}") lbl_name.setStyleSheet(f"color: {GREEN};") wl.addWidget(lbl_name, stretch=1) stars_w = StarRatingWidget(my_r, color=GREEN) stars_w.rating_changed.connect( lambda r, song_id=self._song.get("id",""), d_id=a["id"]: self._save_rating(song_id, d_id, r) ) wl.addWidget(stars_w) self._list.setItemWidget(item, w) # ── Community alternativ-danse ── comm_alts = get_community_alts_for_song(song_id) matching_comm = [c for c in comm_alts if (not prefix or prefix.lower() in c["name"].lower()) and c["name"].lower() not in own_names] if matching_comm: self._list.addItem(self._make_sep("── 🟑 Community ──")) for c in matching_comm: comm_r = c.get("avg_rating") my_r = c.get("my_rating") from PyQt6.QtCore import QSize name = c["name"] level = c.get("level_name", "") disp = f"{name} / {level}" if level else name item = QListWidgetItem() item.setSizeHint(QSize(0, 34)) item.setData(Qt.ItemDataRole.UserRole, { "name": name, "level": level, "choreo": c.get("choreographer", ""), "my_rating": my_r, "comm_rating": comm_r, "dance_id": c["id"], "is_community": True, }) self._list.addItem(item) # Widget: navn + community stjerner (ikke klikbare) + mine (klikbare) w = QWidget() wl = QHBoxLayout(w) wl.setContentsMargins(4, 0, 4, 0) wl.setSpacing(6) lbl_name = QLabel(f"🟑 {disp}") lbl_name.setStyleSheet(f"color: {YELLOW};") wl.addWidget(lbl_name, stretch=1) # Community rating β€” read-only label comm_lbl = QLabel(make_stars(comm_r) if comm_r else "β˜†β˜†β˜†β˜†β˜†") comm_lbl.setStyleSheet(f"color: {YELLOW}; font-size: 13px;") comm_lbl.setToolTip(f"Community: {comm_r:.1f}/5" if comm_r else "Ingen community rating") wl.addWidget(comm_lbl) # Min rating β€” klikbar my_stars_w = StarRatingWidget(my_r, color=GREEN) my_stars_w.rating_changed.connect( lambda r, song_id=self._song.get("id",""), d_id=c["id"]: self._save_rating(song_id, d_id, r) ) wl.addWidget(my_stars_w) self._list.setItemWidget(item, w) # ── Alle danse ── suggestions = get_dance_suggestions(prefix or "", limit=20) if suggestions: self._list.addItem(self._make_sep("── Alle danse ──")) for s in suggestions: s = dict(s) name = s["name"] is_own = name.lower() in own_names is_comm = any(c["name"].lower() == name.lower() for c in comm_alts) icon = "🟒 " if is_own else "🟑 " if is_comm else " " color = GREEN if is_own else YELLOW if is_comm else "#eceef4" disp = name if s.get("level_name"): disp += f" / {s['level_name']}" if s.get("choreographer"): disp += f" Β· {s['choreographer']}" item = QListWidgetItem(f"{icon}{disp}") item.setForeground(QColor(color)) item.setData(Qt.ItemDataRole.UserRole, { "name": name, "level": s.get("level_name", ""), "choreo": s.get("choreographer", ""), "my_rating": None, "comm_rating": None, "dance_id": s.get("id"), }) self._list.addItem(item) except Exception as e: import logging logging.getLogger(__name__).warning( f"AltDancePicker fejl: {e}", exc_info=True ) def _on_item_clicked(self, item): data = item.data(Qt.ItemDataRole.UserRole) if not data: return self._chosen_dance = data.get("name", "") self._edit.setText(self._chosen_dance) parts = [] if data.get("level"): parts.append(data["level"]) if data.get("choreo"): parts.append(data["choreo"]) info = " Β· ".join(parts) comm_r = data.get("comm_rating") my_r = data.get("my_rating") if comm_r: info += f" 🟑 Community: {make_stars(comm_r)} ({comm_r:.1f})" if my_r: info += f" 🟒 Min: {make_stars(my_r)}" self._info_lbl.setText(info) def _on_selected(self, item): data = item.data(Qt.ItemDataRole.UserRole) if not data: return self._on_item_clicked(item) self._on_accept() def _on_accept(self): self._chosen_dance = self._edit.text().strip() self.accept() def _save_rating(self, song_id: str, dance_id: int, rating: int): """Gem rating direkte fra stjerne-widget i listen.""" try: from local.local_db import get_db with get_db() as conn: conn.execute( "UPDATE song_alt_dances SET user_rating=? WHERE song_id=? AND dance_id=?", (rating, song_id, dance_id) ) except Exception as e: import logging logging.getLogger(__name__).warning(f"save_rating fejl: {e}") def _on_clear(self): self._chosen_dance = "" self._chosen_rating = None self._cleared = True self.accept() def get_dance(self) -> str: return self._chosen_dance def get_rating(self): return self._chosen_rating def was_cleared(self) -> bool: return self._cleared