Videre
This commit is contained in:
@@ -11,15 +11,15 @@ from PyQt6.QtWidgets import (
|
||||
from PyQt6.QtCore import Qt, QTimer
|
||||
from PyQt6.QtGui import QAction
|
||||
|
||||
from ui.vu_meter import VUMeter
|
||||
from ui.playlist_panel import PlaylistPanel
|
||||
from ui.library_panel import LibraryPanel
|
||||
from ui.next_up_bar import NextUpBar
|
||||
from ui.themes import apply_theme
|
||||
from ui.scan_worker import ScanWorker
|
||||
from ui.login_dialog import LoginDialog
|
||||
from ui.vu_meter import VUMeter
|
||||
from ui.playlist_panel import PlaylistPanel
|
||||
from ui.library_panel import LibraryPanel
|
||||
from ui.themes import apply_theme
|
||||
from ui.scan_worker import ScanWorker
|
||||
from ui.login_dialog import LoginDialog, API_URL
|
||||
from ui.playlist_manager import PlaylistManagerDialog
|
||||
from player.player import Player
|
||||
from ui.settings_dialog import SettingsDialog, load_settings
|
||||
from player.player import Player
|
||||
|
||||
|
||||
class ProgressBar(QWidget):
|
||||
@@ -63,8 +63,8 @@ class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("LineDance Player")
|
||||
self.setMinimumSize(860, 680)
|
||||
self.resize(960, 760)
|
||||
self.setMinimumSize(1000, 680)
|
||||
self.resize(1600, 820)
|
||||
|
||||
self._dark_theme = True
|
||||
self._player = Player(self)
|
||||
@@ -77,15 +77,28 @@ class MainWindow(QMainWindow):
|
||||
self._api_token: str | None = None
|
||||
self._api_username: str | None = None
|
||||
|
||||
# Indlæs indstillinger
|
||||
self._settings = load_settings()
|
||||
self._dark_theme = self._settings.get("dark_theme", True)
|
||||
self._demo_seconds = self._settings.get("demo_seconds", 10)
|
||||
|
||||
self._connect_player_signals()
|
||||
self._build_menu()
|
||||
self._build_ui()
|
||||
self._build_statusbar()
|
||||
apply_theme(self._app_ref(), dark=True)
|
||||
apply_theme(self._app_ref(), dark=self._dark_theme)
|
||||
self._theme_btn.setText("☀ LYS TEMA" if self._dark_theme else "● MØRKT TEMA")
|
||||
|
||||
# Gendan gemt vinduestørrelse og splitter-position
|
||||
self._restore_window_state()
|
||||
|
||||
# Start DB og scanning ved opstart
|
||||
QTimer.singleShot(200, self._init_local_db)
|
||||
|
||||
# Auto-login hvis aktiveret i indstillinger
|
||||
if self._settings.get("auto_login") and self._settings.get("password"):
|
||||
QTimer.singleShot(800, self._auto_login)
|
||||
|
||||
def _app_ref(self):
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
return QApplication.instance()
|
||||
@@ -102,38 +115,25 @@ class MainWindow(QMainWindow):
|
||||
def _build_menu(self):
|
||||
menubar = self.menuBar()
|
||||
|
||||
# Filer
|
||||
# ── Filer ─────────────────────────────────────────────────────────────
|
||||
file_menu = menubar.addMenu("Filer")
|
||||
|
||||
file_menu.addSeparator()
|
||||
|
||||
self._act_go_online = QAction("Gå online...", self)
|
||||
self._act_go_online.setShortcut("Ctrl+L")
|
||||
self._act_go_online.setToolTip("Log ind og synkroniser med server")
|
||||
self._act_go_online.triggered.connect(self._go_online)
|
||||
file_menu.addAction(self._act_go_online)
|
||||
|
||||
self._act_go_offline = QAction("Gå offline", self)
|
||||
self._act_go_offline.setToolTip("Log ud og arbejd lokalt")
|
||||
self._act_go_offline.triggered.connect(self._go_offline)
|
||||
self._act_go_offline.setEnabled(False) # kun aktiv når man er online
|
||||
self._act_go_offline.setEnabled(False)
|
||||
file_menu.addAction(self._act_go_offline)
|
||||
|
||||
file_menu.addSeparator()
|
||||
|
||||
act_add_folder = QAction("Tilføj musikmappe...", self)
|
||||
act_add_folder.setShortcut("Ctrl+O")
|
||||
act_add_folder.triggered.connect(self._menu_add_folder)
|
||||
file_menu.addAction(act_add_folder)
|
||||
|
||||
file_menu.addSeparator()
|
||||
|
||||
act_scan = QAction("Scan biblioteker", self)
|
||||
act_scan.setShortcut("Ctrl+R")
|
||||
act_scan.setToolTip("Gennemgå alle biblioteksmapper for nye og ændrede filer")
|
||||
act_scan.triggered.connect(self.start_scan)
|
||||
file_menu.addAction(act_scan)
|
||||
self._act_scan = act_scan
|
||||
act_settings = QAction("Indstillinger...", self)
|
||||
act_settings.setShortcut("Ctrl+,")
|
||||
act_settings.triggered.connect(self._open_settings)
|
||||
file_menu.addAction(act_settings)
|
||||
|
||||
file_menu.addSeparator()
|
||||
|
||||
@@ -142,26 +142,14 @@ class MainWindow(QMainWindow):
|
||||
act_quit.triggered.connect(self.close)
|
||||
file_menu.addAction(act_quit)
|
||||
|
||||
# Danseliste
|
||||
pl_menu = menubar.addMenu("Danseliste")
|
||||
# ── Ingen Danseliste- eller Visning-menu ──────────────────────────────
|
||||
# Ny/Gem/Hent ligger direkte i danseliste-panelet
|
||||
# Tema-skift ligger i topbar-knappen
|
||||
# Mapper og scan ligger i ⚙ Mapper dialogen
|
||||
|
||||
act_new_pl = QAction("Ny tom liste", self)
|
||||
act_new_pl.setShortcut("Ctrl+N")
|
||||
act_new_pl.triggered.connect(self._new_playlist)
|
||||
pl_menu.addAction(act_new_pl)
|
||||
|
||||
act_manage = QAction("Gem / Indlæs / Importer...", self)
|
||||
act_manage.setShortcut("Ctrl+M")
|
||||
act_manage.triggered.connect(self._open_playlist_manager)
|
||||
pl_menu.addAction(act_manage)
|
||||
|
||||
# Visning
|
||||
view_menu = menubar.addMenu("Visning")
|
||||
|
||||
act_theme = QAction("Skift tema (lyst/mørkt)", self)
|
||||
act_theme.setShortcut("Ctrl+T")
|
||||
act_theme.triggered.connect(self._toggle_theme)
|
||||
view_menu.addAction(act_theme)
|
||||
# Gem reference til scan-action (bruges stadig internt)
|
||||
self._act_scan = QAction("Scan", self)
|
||||
self._act_scan.triggered.connect(self.start_scan)
|
||||
|
||||
# ── Statuslinje ───────────────────────────────────────────────────────────
|
||||
|
||||
@@ -187,7 +175,6 @@ class MainWindow(QMainWindow):
|
||||
main_layout.addWidget(self._build_topbar())
|
||||
main_layout.addWidget(self._build_now_playing())
|
||||
main_layout.addWidget(self._build_progress())
|
||||
main_layout.addWidget(self._build_next_up())
|
||||
main_layout.addWidget(self._build_transport())
|
||||
main_layout.addWidget(self._build_panels(), stretch=1)
|
||||
|
||||
@@ -272,11 +259,6 @@ class MainWindow(QMainWindow):
|
||||
|
||||
return frame
|
||||
|
||||
def _build_next_up(self) -> NextUpBar:
|
||||
self._next_up = NextUpBar()
|
||||
self._next_up.play_next_clicked.connect(self._play_next)
|
||||
return self._next_up
|
||||
|
||||
def _build_transport(self) -> QFrame:
|
||||
frame = QFrame()
|
||||
frame.setObjectName("transport_frame")
|
||||
@@ -297,7 +279,7 @@ class MainWindow(QMainWindow):
|
||||
self._btn_play = btn("▶", "btn_play", size=72)
|
||||
self._btn_stop = btn("⏹", "btn_stop", size=52)
|
||||
self._btn_next = btn("⏭", size=52)
|
||||
self._btn_demo = btn("▶\n10 SEK", "btn_demo", size=64, checkable=True)
|
||||
self._btn_demo = btn(f"▶\n{self._demo_seconds} SEK", "btn_demo", size=64, checkable=True)
|
||||
|
||||
self._btn_prev.clicked.connect(self._prev_song)
|
||||
self._btn_play.clicked.connect(self._toggle_play)
|
||||
@@ -336,22 +318,43 @@ class MainWindow(QMainWindow):
|
||||
return frame
|
||||
|
||||
def _build_panels(self) -> QSplitter:
|
||||
splitter = QSplitter(Qt.Orientation.Horizontal)
|
||||
self._splitter = QSplitter(Qt.Orientation.Horizontal)
|
||||
|
||||
self._playlist_panel = PlaylistPanel()
|
||||
self._playlist_panel.song_selected.connect(self._load_song_by_idx)
|
||||
self._playlist_panel.song_dropped.connect(self._on_song_dropped)
|
||||
self._playlist_panel.event_started.connect(self._on_event_started)
|
||||
self._playlist_panel.next_song_ready.connect(self._load_song)
|
||||
|
||||
self._library_panel = LibraryPanel()
|
||||
self._library_panel.song_selected.connect(self._on_library_song_selected)
|
||||
self._library_panel.add_to_playlist.connect(self._add_song_to_playlist)
|
||||
self._library_panel.scan_requested.connect(self.start_scan)
|
||||
self._library_panel.edit_tags_requested.connect(self._open_tag_editor)
|
||||
self._library_panel.send_mail_requested.connect(self._send_mail)
|
||||
|
||||
splitter.addWidget(self._playlist_panel)
|
||||
splitter.addWidget(self._library_panel)
|
||||
splitter.setSizes([480, 480])
|
||||
self._splitter.addWidget(self._playlist_panel)
|
||||
self._splitter.addWidget(self._library_panel)
|
||||
self._splitter.setSizes([700, 900])
|
||||
|
||||
return splitter
|
||||
return self._splitter
|
||||
|
||||
def _restore_window_state(self):
|
||||
from PyQt6.QtCore import QSettings, QByteArray
|
||||
settings = QSettings("LineDance", "Player")
|
||||
geom = settings.value("window/geometry")
|
||||
if geom:
|
||||
self.restoreGeometry(geom)
|
||||
splitter_state = settings.value("window/splitter")
|
||||
if splitter_state and hasattr(self, "_splitter"):
|
||||
self._splitter.restoreState(splitter_state)
|
||||
|
||||
def _save_window_state(self):
|
||||
from PyQt6.QtCore import QSettings
|
||||
settings = QSettings("LineDance", "Player")
|
||||
settings.setValue("window/geometry", self.saveGeometry())
|
||||
if hasattr(self, "_splitter"):
|
||||
settings.setValue("window/splitter", self._splitter.saveState())
|
||||
|
||||
# ── Lokal DB + scanning ───────────────────────────────────────────────────
|
||||
|
||||
@@ -364,8 +367,10 @@ class MainWindow(QMainWindow):
|
||||
|
||||
init_db()
|
||||
|
||||
# Brug et Qt signal til thread-safe reload fra watcher-tråden
|
||||
from PyQt6.QtCore import QMetaObject, Q_ARG
|
||||
def on_file_change(event_type, path, song_id):
|
||||
QTimer.singleShot(500, self._reload_library)
|
||||
QTimer.singleShot(0, self._reload_library)
|
||||
|
||||
self._watcher = get_watcher(on_change=on_file_change)
|
||||
self._watcher.start()
|
||||
@@ -373,6 +378,23 @@ class MainWindow(QMainWindow):
|
||||
# Indlæs hvad vi allerede kender fra SQLite
|
||||
self._reload_library()
|
||||
|
||||
# Gendan sidst aktive danseliste
|
||||
restored = self._playlist_panel.restore_active_playlist()
|
||||
|
||||
# Gendan event-fremgang hvis liste blev gendannet
|
||||
if restored:
|
||||
if self._playlist_panel.restore_event_state():
|
||||
# Indlæs den sang vi var nået til
|
||||
idx = self._playlist_panel._current_idx
|
||||
song = self._playlist_panel.get_song(idx)
|
||||
if song:
|
||||
self._current_idx = idx
|
||||
self._load_song(song)
|
||||
self._set_status(
|
||||
f"Event genoptaget ved: {song.get('title','')} — tryk ▶ for at fortsætte",
|
||||
6000,
|
||||
)
|
||||
|
||||
# Kør automatisk scanning ved opstart
|
||||
self._set_status("Starter scanning af biblioteker...")
|
||||
QTimer.singleShot(100, self.start_scan)
|
||||
@@ -417,9 +439,11 @@ class MainWindow(QMainWindow):
|
||||
songs = []
|
||||
for row in songs_raw:
|
||||
with get_db() as conn:
|
||||
dances = conn.execute(
|
||||
"SELECT dance_name FROM song_dances "
|
||||
"WHERE song_id=? ORDER BY dance_order",
|
||||
dances_raw = conn.execute(
|
||||
"SELECT sd.dance_name, dl.name as level_name "
|
||||
"FROM song_dances sd "
|
||||
"LEFT JOIN dance_levels dl ON dl.id = sd.level_id "
|
||||
"WHERE sd.song_id=? ORDER BY sd.dance_order",
|
||||
(row["id"],)
|
||||
).fetchall()
|
||||
songs.append({
|
||||
@@ -432,7 +456,8 @@ class MainWindow(QMainWindow):
|
||||
"local_path": row["local_path"],
|
||||
"file_format": row["file_format"],
|
||||
"file_missing": bool(row["file_missing"]),
|
||||
"dances": [d["dance_name"] for d in dances],
|
||||
"dances": [d["dance_name"] for d in dances_raw],
|
||||
"dance_levels": [d["level_name"] or "" for d in dances_raw],
|
||||
})
|
||||
self._library_panel.load_songs(songs)
|
||||
count = len(songs)
|
||||
@@ -442,10 +467,65 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def add_library_path(self, path: str):
|
||||
try:
|
||||
if not self._watcher:
|
||||
self._set_status("Watcher ikke klar endnu — prøv igen om et øjeblik", 3000)
|
||||
return
|
||||
self._watcher.add_library(path)
|
||||
self._set_status(f"Tilføjet: {path} — scanner...")
|
||||
# Genindlæs bibliotekslisten og start scan
|
||||
QTimer.singleShot(500, self._reload_library)
|
||||
QTimer.singleShot(1000, self.start_scan)
|
||||
except Exception as e:
|
||||
self._set_status(f"Fejl: {e}")
|
||||
self._set_status(f"Fejl ved tilføjelse: {e}")
|
||||
|
||||
def _open_settings(self):
|
||||
dialog = SettingsDialog(parent=self)
|
||||
if dialog.exec():
|
||||
self._settings = dialog.get_values()
|
||||
self._demo_seconds = self._settings.get("demo_seconds", 10)
|
||||
# Opdater tema hvis ændret
|
||||
new_dark = self._settings.get("dark_theme", True)
|
||||
if new_dark != self._dark_theme:
|
||||
self._dark_theme = new_dark
|
||||
apply_theme(self._app_ref(), dark=self._dark_theme)
|
||||
self._theme_btn.setText(
|
||||
"☀ LYS TEMA" if self._dark_theme else "● MØRKT TEMA"
|
||||
)
|
||||
self._vu.set_dark(self._dark_theme)
|
||||
# Opdater demo-knap tekst
|
||||
self._btn_demo.setText(f"▶\n{self._demo_seconds} SEK")
|
||||
# Opdater demo-markør hvis en sang er indlæst
|
||||
if hasattr(self, "_current_song") and self._current_song:
|
||||
dur = self._current_song.get("duration_sec", 0)
|
||||
if dur > 0:
|
||||
self._progress.set_demo_marker(min(self._demo_seconds / dur, 1.0))
|
||||
self._set_status("Indstillinger gemt", 2000)
|
||||
|
||||
def _auto_login(self):
|
||||
"""Forsøg automatisk login med gemte oplysninger."""
|
||||
username = self._settings.get("username", "")
|
||||
password = self._settings.get("password", "")
|
||||
if not username or not password:
|
||||
return
|
||||
try:
|
||||
import urllib.request, urllib.parse, json
|
||||
data = urllib.parse.urlencode({"username": username, "password": password}).encode()
|
||||
req = urllib.request.Request(
|
||||
f"{API_URL}/auth/login", data=data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
method="POST",
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=8) as resp:
|
||||
body = json.loads(resp.read())
|
||||
self._api_token = body.get("access_token")
|
||||
self._api_url = API_URL
|
||||
self._api_username = username
|
||||
self._set_online_state(True)
|
||||
self._set_status(f"Automatisk logget ind som {username}", 4000)
|
||||
# Synkroniser dans-niveauer og navne
|
||||
QTimer.singleShot(500, self._sync_dance_data)
|
||||
except Exception:
|
||||
self._set_status("Auto-login fejlede — kør Filer → Gå online manuelt", 5000)
|
||||
|
||||
def _go_online(self):
|
||||
dialog = LoginDialog(self)
|
||||
@@ -456,6 +536,33 @@ class MainWindow(QMainWindow):
|
||||
self._api_username = username
|
||||
self._set_online_state(True)
|
||||
self._set_status(f"Online som {username}", 5000)
|
||||
QTimer.singleShot(500, self._sync_dance_data)
|
||||
|
||||
def _sync_dance_data(self):
|
||||
"""Synkroniser dans-niveauer og navne fra API."""
|
||||
if not self._api_token:
|
||||
return
|
||||
try:
|
||||
import urllib.request, json
|
||||
headers = {"Authorization": f"Bearer {self._api_token}"}
|
||||
|
||||
# Hent niveauer
|
||||
req = urllib.request.Request(f"{API_URL}/dances/levels", headers=headers)
|
||||
with urllib.request.urlopen(req, timeout=8) as resp:
|
||||
levels = json.loads(resp.read())
|
||||
from local.local_db import sync_dance_levels_from_api
|
||||
sync_dance_levels_from_api(levels)
|
||||
|
||||
# Hent populære dans-navne
|
||||
req = urllib.request.Request(f"{API_URL}/dances/names?limit=500", headers=headers)
|
||||
with urllib.request.urlopen(req, timeout=8) as resp:
|
||||
names = json.loads(resp.read())
|
||||
from local.local_db import sync_dance_names_from_api
|
||||
sync_dance_names_from_api(names)
|
||||
|
||||
self._set_status(f"Synkroniseret {len(levels)} niveauer og {len(names)} dans-navne", 4000)
|
||||
except Exception as e:
|
||||
print(f"Dans-sync fejl: {e}")
|
||||
|
||||
def _go_offline(self):
|
||||
self._api_url = self._api_token = self._api_username = None
|
||||
@@ -493,6 +600,120 @@ class MainWindow(QMainWindow):
|
||||
self._playlist_panel.set_playlist_name(name)
|
||||
self._set_status(f"Indlæst: {name} ({len(songs)} sange)", 3000)
|
||||
|
||||
def _open_tag_editor(self, song: dict):
|
||||
from ui.tag_editor import TagEditorDialog
|
||||
dialog = TagEditorDialog(song, parent=self)
|
||||
if dialog.exec():
|
||||
# Genindlæs biblioteket så ændringer vises
|
||||
QTimer.singleShot(200, self._reload_library)
|
||||
|
||||
def _send_mail(self, song: dict):
|
||||
import subprocess, sys, shutil, urllib.parse
|
||||
from pathlib import Path
|
||||
|
||||
path = song.get("local_path", "")
|
||||
title = song.get("title", "")
|
||||
artist = song.get("artist", "")
|
||||
|
||||
if not path or not Path(path).exists():
|
||||
self._set_status("Filen blev ikke fundet — kan ikke sende mail", 4000)
|
||||
return
|
||||
|
||||
# ── Auto-detekter mailklient ───────────────────────────────────────────
|
||||
|
||||
def try_thunderbird() -> bool:
|
||||
"""Thunderbird: thunderbird -compose attachment='file:///sti'"""
|
||||
candidates = []
|
||||
if sys.platform == "win32":
|
||||
import winreg
|
||||
for base in (winreg.HKEY_LOCAL_MACHINE, winreg.HKEY_CURRENT_USER):
|
||||
try:
|
||||
key = winreg.OpenKey(base,
|
||||
r"SOFTWARE\Mozilla\Mozilla Thunderbird")
|
||||
inst, _ = winreg.QueryValueEx(key, "Install Directory")
|
||||
candidates.append(str(Path(inst) / "thunderbird.exe"))
|
||||
except Exception:
|
||||
pass
|
||||
candidates += [
|
||||
r"C:\Program Files\Mozilla Thunderbird\thunderbird.exe",
|
||||
r"C:\Program Files (x86)\Mozilla Thunderbird\thunderbird.exe",
|
||||
]
|
||||
elif sys.platform == "darwin":
|
||||
candidates = [
|
||||
"/Applications/Thunderbird.app/Contents/MacOS/thunderbird",
|
||||
]
|
||||
else:
|
||||
candidates = [shutil.which("thunderbird") or "",
|
||||
"/usr/bin/thunderbird",
|
||||
"/usr/local/bin/thunderbird",
|
||||
"/snap/bin/thunderbird"]
|
||||
|
||||
tb = next((c for c in candidates if c and Path(c).exists()), None)
|
||||
if not tb:
|
||||
return False
|
||||
|
||||
file_uri = Path(path).as_uri()
|
||||
subject = f"Linedance sang: {title} — {artist}"
|
||||
compose = (
|
||||
f"subject='{subject}',"
|
||||
f"attachment='{file_uri}'"
|
||||
)
|
||||
subprocess.Popen([tb, "-compose", compose])
|
||||
return True
|
||||
|
||||
def try_outlook() -> bool:
|
||||
"""Outlook: outlook.exe /a 'filsti' (kun Windows)"""
|
||||
if sys.platform != "win32":
|
||||
return False
|
||||
candidates = [
|
||||
shutil.which("outlook") or "",
|
||||
r"C:\Program Files\Microsoft Office\root\Office16\OUTLOOK.EXE",
|
||||
r"C:\Program Files (x86)\Microsoft Office\root\Office16\OUTLOOK.EXE",
|
||||
r"C:\Program Files\Microsoft Office\Office16\OUTLOOK.EXE",
|
||||
]
|
||||
ol = next((c for c in candidates if c and Path(c).exists()), None)
|
||||
if not ol:
|
||||
return False
|
||||
subprocess.Popen([ol, "/a", path])
|
||||
return True
|
||||
|
||||
def fallback_mailto():
|
||||
"""Ingen vedhæftning — åbn standard-mailprogram via mailto:"""
|
||||
subject = urllib.parse.quote(f"Linedance sang: {title} — {artist}")
|
||||
body = urllib.parse.quote(
|
||||
f"Sang: {title}\nArtist: {artist}\nFil: {path}\n\n"
|
||||
f"(Vedhæft filen manuelt fra ovenstående sti)"
|
||||
)
|
||||
mailto = f"mailto:?subject={subject}&body={body}"
|
||||
if sys.platform == "win32":
|
||||
import os; os.startfile(mailto)
|
||||
elif sys.platform == "darwin":
|
||||
subprocess.Popen(["open", mailto])
|
||||
else:
|
||||
subprocess.Popen(["xdg-open", mailto])
|
||||
|
||||
# ── Prøv i rækkefølge ─────────────────────────────────────────────────
|
||||
if try_thunderbird():
|
||||
self._set_status(f"Thunderbird åbnet med {Path(path).name} vedh.", 4000)
|
||||
elif try_outlook():
|
||||
self._set_status(f"Outlook åbnet med {Path(path).name} vedh.", 4000)
|
||||
else:
|
||||
fallback_mailto()
|
||||
self._set_status(
|
||||
f"Ingen kendt mailklient fundet — åbnet mailto: (uden vedhæftning)", 5000
|
||||
)
|
||||
|
||||
def _on_event_started(self):
|
||||
"""Start event — indlæs første sang i afspilleren klar til afspilning."""
|
||||
first = self._playlist_panel.get_song(0)
|
||||
if not first:
|
||||
return
|
||||
self._stop()
|
||||
self._current_idx = 0
|
||||
self._song_ended = False
|
||||
self._load_song(first)
|
||||
self._set_status("Event klar — tryk ▶ for at starte", 5000)
|
||||
|
||||
def _on_song_dropped(self, song: dict):
|
||||
self._set_status(f"Tilføjet: {song.get('title','')}", 2000)
|
||||
|
||||
@@ -508,7 +729,6 @@ class MainWindow(QMainWindow):
|
||||
self._song_ended = False
|
||||
self._demo_active = False
|
||||
self._btn_demo.setChecked(False)
|
||||
self._next_up.hide_bar()
|
||||
|
||||
dur = song.get("duration_sec", 0)
|
||||
self._player.load(song.get("local_path", ""), dur)
|
||||
@@ -524,7 +744,7 @@ class MainWindow(QMainWindow):
|
||||
)
|
||||
|
||||
if dur > 0:
|
||||
self._progress.set_demo_marker(min(10 / dur, 1.0))
|
||||
self._progress.set_demo_marker(min(self._demo_seconds / dur, 1.0))
|
||||
|
||||
self._set_status(f"Indlæst: {song.get('title','—')}", 3000)
|
||||
|
||||
@@ -537,9 +757,6 @@ class MainWindow(QMainWindow):
|
||||
self._playlist_panel.set_current(idx)
|
||||
|
||||
def _toggle_play(self):
|
||||
if self._song_ended:
|
||||
self._play_next()
|
||||
return
|
||||
if self._demo_active:
|
||||
self._player.stop()
|
||||
self._demo_active = False
|
||||
@@ -549,14 +766,15 @@ class MainWindow(QMainWindow):
|
||||
if self._player.is_playing():
|
||||
self._player.pause()
|
||||
else:
|
||||
self._song_ended = False
|
||||
self._player.play()
|
||||
self._btn_play.setText("⏸")
|
||||
|
||||
def _stop(self):
|
||||
self._player.stop()
|
||||
self._song_ended = False
|
||||
self._demo_active = False
|
||||
self._btn_demo.setChecked(False)
|
||||
self._next_up.hide_bar()
|
||||
self._btn_play.setText("▶")
|
||||
self._vu.reset()
|
||||
|
||||
@@ -569,7 +787,7 @@ class MainWindow(QMainWindow):
|
||||
else:
|
||||
self._demo_active = True
|
||||
self._btn_demo.setChecked(True)
|
||||
self._player.play_demo(stop_at_sec=10)
|
||||
self._player.play_demo(stop_at_sec=self._demo_seconds)
|
||||
self._btn_play.setText("⏸")
|
||||
|
||||
def _prev_song(self):
|
||||
@@ -584,13 +802,9 @@ class MainWindow(QMainWindow):
|
||||
self._load_song_by_idx(self._current_idx + 1)
|
||||
|
||||
def _play_next(self):
|
||||
ni = self._current_idx + 1
|
||||
if ni < self._playlist_panel.count():
|
||||
self._song_ended = False
|
||||
self._next_up.hide_bar()
|
||||
self._load_song_by_idx(ni)
|
||||
self._player.play()
|
||||
self._btn_play.setText("⏸")
|
||||
self._song_ended = False
|
||||
self._player.play()
|
||||
self._btn_play.setText("⏸")
|
||||
|
||||
def _on_library_song_selected(self, song: dict):
|
||||
self._load_song(song)
|
||||
@@ -623,21 +837,50 @@ class MainWindow(QMainWindow):
|
||||
self._btn_demo.setChecked(False)
|
||||
self._btn_play.setText("▶")
|
||||
self._vu.reset()
|
||||
|
||||
# Markér den afspillede sang
|
||||
self._playlist_panel.mark_played(self._current_idx)
|
||||
|
||||
ni = self._current_idx + 1
|
||||
next_song = self._playlist_panel.get_song(ni)
|
||||
# Synkroniser event-status til den gemte navngivne liste
|
||||
self._sync_event_status_to_playlist()
|
||||
|
||||
# Find første ikke-afspillede og ikke-skippede sang fra TOPPEN
|
||||
ni = self._playlist_panel.next_playable_idx()
|
||||
next_song = self._playlist_panel.get_song(ni) if ni is not None else None
|
||||
if next_song:
|
||||
self._next_up.show_next(
|
||||
next_song.get("title", ""),
|
||||
next_song.get("artist", ""),
|
||||
next_song.get("dances", []),
|
||||
)
|
||||
self._playlist_panel.set_current(self._current_idx, song_ended=True)
|
||||
self._current_idx = ni
|
||||
self._playlist_panel.set_next_ready(ni)
|
||||
self._load_song(next_song)
|
||||
self._set_status(f"Klar: {next_song.get('title','')} — tryk ▶ for at starte")
|
||||
else:
|
||||
self._lbl_title.setText("— Danseliste afsluttet —")
|
||||
self._lbl_meta.setText("")
|
||||
self._lbl_dances.setText("")
|
||||
self._set_status("Danselisten er afsluttet")
|
||||
|
||||
def _sync_event_status_to_playlist(self):
|
||||
"""Gem event-fremgang i den aktive navngivne liste."""
|
||||
try:
|
||||
from local.local_db import get_db
|
||||
songs = self._playlist_panel.get_songs()
|
||||
statuses = self._playlist_panel.get_statuses()
|
||||
with get_db() as conn:
|
||||
# Find den aktive liste (ikke __aktiv__)
|
||||
pl = conn.execute(
|
||||
"SELECT id FROM playlists WHERE name != '__aktiv__' "
|
||||
"ORDER BY created_at DESC LIMIT 1"
|
||||
).fetchone()
|
||||
if not pl:
|
||||
return
|
||||
# Opdater status for hver sang i listen
|
||||
for i, (song, status) in enumerate(zip(songs, statuses)):
|
||||
conn.execute("""
|
||||
UPDATE playlist_songs SET status=?
|
||||
WHERE playlist_id=? AND song_id=?
|
||||
""", (status, pl["id"], song.get("id")))
|
||||
except Exception as e:
|
||||
print(f"Event-status sync fejl: {e}")
|
||||
|
||||
def _on_state_changed(self, state: str):
|
||||
if state == "playing":
|
||||
self._btn_play.setText("⏸")
|
||||
@@ -671,6 +914,7 @@ class MainWindow(QMainWindow):
|
||||
# ── Luk ───────────────────────────────────────────────────────────────────
|
||||
|
||||
def closeEvent(self, event):
|
||||
self._save_window_state()
|
||||
self._player.stop()
|
||||
if self._scan_worker and self._scan_worker.isRunning():
|
||||
self._scan_worker.quit()
|
||||
|
||||
Reference in New Issue
Block a user