Næste version

This commit is contained in:
2026-04-12 10:25:41 +02:00
parent b678787236
commit 57f3c913b4
18 changed files with 2690 additions and 458 deletions

View File

@@ -0,0 +1,201 @@
"""
playlist_info_dialog.py — Flydende danseliste-info vindue med dynamisk opdatering.
"""
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QSpinBox,
QFrame, QGridLayout,
)
from PyQt6.QtCore import Qt, pyqtSignal
from datetime import datetime, timedelta
def fmt_time(seconds: int) -> str:
if seconds < 0:
seconds = 0
h = seconds // 3600
m = (seconds % 3600) // 60
s = seconds % 60
if h > 0:
return f"{h}:{m:02d}:{s:02d}"
return f"{m}:{s:02d}"
class PlaylistInfoWindow(QWidget):
pause_changed = pyqtSignal(int)
def __init__(self, playlist_panel, parent=None):
super().__init__(parent,
Qt.WindowType.Tool |
Qt.WindowType.WindowStaysOnTopHint
)
self._panel = playlist_panel
self._pause_seconds = getattr(playlist_panel, "_pause_seconds", 60)
self._workshop_seconds = getattr(playlist_panel, "_workshop_seconds", 600)
self.setWindowTitle("Danseliste-info")
self.setMinimumWidth(380)
self.setFixedWidth(440)
self._build_ui()
self._update()
playlist_panel.playlist_changed.connect(self._update)
playlist_panel.status_changed.connect(lambda *_: self._update())
def _build_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(12, 12, 12, 12)
layout.setSpacing(8)
# Stats
stats = QFrame()
stats.setObjectName("track_display")
grid = QGridLayout(stats)
grid.setContentsMargins(12, 10, 12, 10)
grid.setSpacing(5)
grid.setColumnStretch(1, 1)
def row(r, label, attr):
l = QLabel(label)
l.setObjectName("track_meta")
grid.addWidget(l, r, 0)
v = QLabel("")
v.setAlignment(Qt.AlignmentFlag.AlignRight)
grid.addWidget(v, r, 1)
setattr(self, attr, v)
row(0, "Antal sange:", "_lbl_count")
row(1, "Afspillet:", "_lbl_played")
row(2, "Workshop:", "_lbl_ws")
row(3, "Sprunget over:", "_lbl_skipped")
row(4, "Tilbage:", "_lbl_remaining")
sep = QFrame()
sep.setFrameShape(QFrame.Shape.HLine)
grid.addWidget(sep, 5, 0, 1, 2)
row(6, "Musik-tid total:", "_lbl_music_total")
row(7, "Musik-tid tilbage:", "_lbl_music_remain")
row(8, "Pause-tid total:", "_lbl_pause_total")
row(9, "Workshop-tid total:", "_lbl_ws_total")
row(10, "Samlet tid total:", "_lbl_total")
row(11, "Samlet tid tilbage:", "_lbl_total_remain")
layout.addWidget(stats)
# Indstillinger
cfg = QFrame()
cfg.setObjectName("track_display")
cfg_layout = QGridLayout(cfg)
cfg_layout.setContentsMargins(12, 8, 12, 8)
cfg_layout.setSpacing(6)
cfg_layout.addWidget(QLabel("Tid mellem musikstykker:"), 0, 0)
self._spin_pause = QSpinBox()
self._spin_pause.setRange(0, 600)
self._spin_pause.setValue(self._pause_seconds)
self._spin_pause.setSuffix(" sek")
self._spin_pause.setFixedWidth(90)
self._spin_pause.valueChanged.connect(self._on_pause_changed)
cfg_layout.addWidget(self._spin_pause, 0, 1)
cfg_layout.addWidget(QLabel("Tid per workshop:"), 1, 0)
self._spin_ws = QSpinBox()
self._spin_ws.setRange(0, 120)
self._spin_ws.setValue(self._workshop_seconds // 60)
self._spin_ws.setSuffix(" min")
self._spin_ws.setFixedWidth(90)
self._spin_ws.valueChanged.connect(self._on_ws_changed)
cfg_layout.addWidget(self._spin_ws, 1, 1)
layout.addWidget(cfg)
# Fremgang og ETA
eta_frame = QFrame()
eta_frame.setObjectName("track_display")
eta_layout = QVBoxLayout(eta_frame)
eta_layout.setContentsMargins(12, 8, 12, 8)
eta_layout.setSpacing(4)
self._lbl_eta = QLabel("")
self._lbl_eta.setWordWrap(True)
self._lbl_eta.setAlignment(Qt.AlignmentFlag.AlignCenter)
self._lbl_eta.setObjectName("track_title")
eta_layout.addWidget(self._lbl_eta)
self._lbl_finish = QLabel("")
self._lbl_finish.setWordWrap(True)
self._lbl_finish.setAlignment(Qt.AlignmentFlag.AlignCenter)
self._lbl_finish.setObjectName("track_title")
eta_layout.addWidget(self._lbl_finish)
layout.addWidget(eta_frame)
def _on_pause_changed(self, value: int):
self._pause_seconds = value
if hasattr(self._panel, "_pause_seconds"):
self._panel._pause_seconds = value
self.pause_changed.emit(value)
self._update()
def _on_ws_changed(self, minutes: int):
self._workshop_seconds = minutes * 60
if hasattr(self._panel, "_workshop_seconds"):
self._panel._workshop_seconds = self._workshop_seconds
self._update()
def _update(self):
songs = self._panel.get_songs()
statuses = self._panel.get_statuses()
total = len(songs)
played = statuses.count("played")
skipped = statuses.count("skipped")
remaining = total - played - skipped
ws_total = sum(1 for s in songs if s.get("is_workshop"))
ws_remain = sum(1 for s, st in zip(songs, statuses)
if s.get("is_workshop") and st == "pending")
music_total = sum(s.get("duration_sec", 0) for s in songs)
music_remain = sum(
s.get("duration_sec", 0)
for s, st in zip(songs, statuses) if st == "pending"
)
p = self._pause_seconds
w = self._workshop_seconds
pause_total = max(0, total - 1) * p
pause_remain = max(0, remaining - 1) * p
ws_time_total = ws_total * w
ws_time_remain = ws_remain * w
total_time = music_total + pause_total + ws_time_total
remain_time = music_remain + pause_remain + ws_time_remain
self._lbl_count.setText(str(total))
self._lbl_played.setText(str(played))
self._lbl_ws.setText(f"{ws_total} ({fmt_time(ws_time_total)})")
self._lbl_skipped.setText(str(skipped))
self._lbl_remaining.setText(str(remaining))
self._lbl_music_total.setText(fmt_time(music_total))
self._lbl_music_remain.setText(fmt_time(music_remain))
self._lbl_pause_total.setText(f"{fmt_time(pause_total)} ({max(0,total-1)} × {p}s)")
self._lbl_ws_total.setText(f"{fmt_time(ws_time_total)} ({ws_total} × {w//60}min)")
self._lbl_total.setText(fmt_time(total_time))
self._lbl_total_remain.setText(fmt_time(remain_time))
# ETA
if remaining == 0 and total > 0:
self._lbl_eta.setText("✓ Danselisten er afsluttet!")
self._lbl_finish.setText("")
elif total > 0:
pct = int(played / total * 100) if total > 0 else 0
self._lbl_eta.setText(
f"{pct}% færdig · {fmt_time(remain_time)} tilbage"
if played > 0 else f"Samlet varighed: {fmt_time(total_time)}"
)
finish = datetime.now() + timedelta(seconds=remain_time)
self._lbl_finish.setText(f"Estimeret sluttid: {finish.strftime('%H:%M')}")
else:
self._lbl_eta.setText("Ingen sange i listen")
self._lbl_finish.setText("")