167 lines
6.0 KiB
Python
167 lines
6.0 KiB
Python
"""
|
||
playlist_info_dialog.py — Flydende danseliste-info vindue med dynamisk opdatering.
|
||
"""
|
||
|
||
from PyQt6.QtWidgets import (
|
||
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||
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):
|
||
|
||
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 moveEvent(self, event):
|
||
from PyQt6.QtCore import QSettings
|
||
QSettings("LineDance", "Player").setValue("window/info_pos", self.pos())
|
||
super().moveEvent(event)
|
||
|
||
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)
|
||
|
||
# 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 _update(self):
|
||
songs = self._panel.get_songs()
|
||
statuses = self._panel.get_statuses()
|
||
total = len(songs)
|
||
played = statuses.count("played")
|
||
skipped = statuses.count("skipped")
|
||
done = played + skipped # samlet "overstået"
|
||
remaining = total - done
|
||
|
||
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(done / total * 100) if total > 0 else 0
|
||
self._lbl_eta.setText(
|
||
f"{pct}% færdig · {fmt_time(remain_time)} tilbage"
|
||
if done > 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("")
|