Files
gti_radiostudio/cart_widget.py
T

295 lines
9.2 KiB
Python
Raw Normal View History

2026-04-29 16:21:44 +10:00
# cart_widget.py
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QFrame, QSizePolicy, QGridLayout
)
from PySide6.QtCore import Qt, QTimer, QSize
2026-04-29 16:21:44 +10:00
from PySide6.QtGui import QFont
import os
from icons import icon_play, icon_pause, icon_eject
2026-04-29 16:21:44 +10:00
class CartSlot(QFrame):
def __init__(self, cart_key: str, slot_number: int, engine, parent=None):
super().__init__(parent)
self.cart_key = cart_key
self.slot_number = slot_number
self.engine = engine
self._playing_icon = False
2026-04-29 16:21:44 +10:00
self.setFrameStyle(QFrame.Box | QFrame.Raised)
self.setLineWidth(1)
self.setStyleSheet("""
QFrame {
background-color: #1e1e1e;
border: 1px solid #444;
border-radius: 5px;
}
""")
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
2026-05-13 20:53:15 +10:00
self.setFixedHeight(80)
2026-04-29 16:21:44 +10:00
self._build_ui()
self._connect_signals()
self.timer = QTimer(self)
self.timer.setInterval(100)
self.timer.timeout.connect(self._refresh_display)
self.timer.start()
def _build_ui(self):
root = QHBoxLayout(self)
2026-05-13 20:53:15 +10:00
root.setContentsMargins(6, 4, 6, 4)
root.setSpacing(6)
2026-04-29 16:21:44 +10:00
badge = QLabel(str(self.slot_number))
2026-05-13 20:53:15 +10:00
badge.setFixedSize(22, 22)
2026-04-29 16:21:44 +10:00
badge.setAlignment(Qt.AlignCenter)
2026-05-13 20:53:15 +10:00
badge.setFont(QFont("Arial", 10, QFont.Bold))
2026-04-29 16:21:44 +10:00
badge.setStyleSheet("""
background-color: #cc3300;
color: white;
border-radius: 14px;
border: none;
""")
root.addWidget(badge, alignment=Qt.AlignVCenter)
2026-04-30 00:30:23 +10:00
self.lcd_frame = QFrame()
self.lcd_frame.setStyleSheet("""
2026-04-29 16:21:44 +10:00
QFrame {
background-color: #0a1a0a;
border: 1px solid #1a2a1a;
border-radius: 3px;
}
""")
2026-04-30 00:30:23 +10:00
self.lcd_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
2026-04-29 16:21:44 +10:00
2026-04-30 00:30:23 +10:00
lcd_layout = QVBoxLayout(self.lcd_frame)
2026-05-13 20:53:15 +10:00
lcd_layout.setContentsMargins(4, 2, 4, 2)
lcd_layout.setSpacing(1)
2026-04-29 16:21:44 +10:00
self.track_label = QLabel("EMPTY")
2026-05-13 20:53:15 +10:00
self.track_label.setFont(QFont("Courier New", 8, QFont.Bold))
2026-04-29 16:21:44 +10:00
self.track_label.setStyleSheet(
"color: #00ff41; background: transparent; border: none;"
)
self.track_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
time_row = QHBoxLayout()
self.elapsed_label = QLabel("00:00")
2026-05-13 20:53:15 +10:00
self.elapsed_label.setFont(QFont("Courier New", 11, QFont.Bold))
2026-04-29 16:21:44 +10:00
self.elapsed_label.setStyleSheet(
"color: #00ff41; background: transparent; border: none;"
)
self.remaining_label = QLabel("-00:00")
2026-05-13 20:53:15 +10:00
self.remaining_label.setFont(QFont("Courier New", 8))
2026-04-29 16:21:44 +10:00
self.remaining_label.setStyleSheet(
"color: #00cc33; background: transparent; border: none;"
)
self.remaining_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
time_row.addWidget(self.elapsed_label)
time_row.addStretch()
time_row.addWidget(self.remaining_label)
self.status_label = QLabel("STOPPED")
self.status_label.setFont(QFont("Courier New", 8))
self.status_label.setStyleSheet(
"color: #008822; background: transparent; border: none;"
)
lcd_layout.addWidget(self.track_label)
lcd_layout.addLayout(time_row)
lcd_layout.addWidget(self.status_label)
2026-04-30 00:30:23 +10:00
root.addWidget(self.lcd_frame)
2026-04-29 16:21:44 +10:00
btn_col = QVBoxLayout()
2026-05-13 20:53:15 +10:00
btn_col.setSpacing(3)
2026-04-29 16:21:44 +10:00
self.btn_play = QPushButton()
2026-05-13 20:53:15 +10:00
self.btn_play.setFixedSize(50, 32)
self.btn_play.setIconSize(QSize(18, 18))
self.btn_play.setIcon(icon_play("#00ff41"))
2026-04-29 16:21:44 +10:00
self.btn_play.setStyleSheet("""
QPushButton {
background-color: #1a6b3a;
border: 1px solid #555;
border-radius: 3px;
}
QPushButton:hover { background-color: #2a7b4a; }
QPushButton:pressed { background-color: #0a5a2a; }
""")
self.btn_eject = QPushButton()
2026-05-13 20:53:15 +10:00
self.btn_eject.setFixedSize(50, 24)
self.btn_eject.setIconSize(QSize(14, 14))
self.btn_eject.setIcon(icon_eject("#ff4444"))
2026-04-29 16:21:44 +10:00
self.btn_eject.setStyleSheet("""
QPushButton {
background-color: #8a1a1a;
border: 1px solid #555;
border-radius: 3px;
}
QPushButton:hover { background-color: #9a2a2a; }
QPushButton:pressed { background-color: #6a0a0a; }
""")
btn_col.addWidget(self.btn_play)
btn_col.addWidget(self.btn_eject)
root.addLayout(btn_col)
2026-04-30 00:30:23 +10:00
def apply_theme(self, theme: dict):
self.lcd_frame.setStyleSheet(f"""
QFrame {{
background-color: {theme['bg']};
border: 1px solid {theme['border']};
border-radius: 3px;
}}
""")
2026-04-29 16:21:44 +10:00
def _connect_signals(self):
self.btn_play.clicked.connect(self._toggle_play)
self.btn_eject.clicked.connect(self._eject)
def load_track(self, filepath: str):
self.engine.load_cart_track(self.cart_key, filepath)
2026-04-29 16:21:44 +10:00
name = os.path.basename(filepath)
short = name[:28] + "..." if len(name) > 28 else name
self.track_label.setText(short)
self.status_label.setText("LOADED")
self.elapsed_label.setText("00:00")
self.remaining_label.setText("-00:00")
def toggle(self):
deck = self.engine.decks[self.cart_key]
if deck.track is None:
return
if deck.playing:
deck.stop()
deck.go_to_start()
self._set_ui_stopped()
self.status_label.setText("READY")
else:
deck.play()
self._set_ui_playing()
def _set_ui_playing(self):
self.btn_play.setIcon(icon_pause("#ffffff"))
self._playing_icon = True
2026-04-29 16:21:44 +10:00
self.btn_play.setStyleSheet("""
QPushButton {
background-color: #00aa33;
border: 1px solid #00ff41;
border-radius: 3px;
}
QPushButton:hover { background-color: #00cc44; }
""")
self.status_label.setText("PLAYING")
self.elapsed_label.setStyleSheet(
"color: #00ff41; background: transparent; border: none;"
)
def _set_ui_stopped(self):
self.btn_play.setIcon(icon_play("#00ff41"))
self._playing_icon = False
2026-04-29 16:21:44 +10:00
self.btn_play.setStyleSheet("""
QPushButton {
background-color: #1a6b3a;
border: 1px solid #555;
border-radius: 3px;
}
QPushButton:hover { background-color: #2a7b4a; }
""")
self.elapsed_label.setText("00:00")
self.remaining_label.setText("-00:00")
self.elapsed_label.setStyleSheet(
"color: #00aa2a; background: transparent; border: none;"
)
def _toggle_play(self):
deck = self.engine.decks[self.cart_key]
if deck.track is None:
return
if deck.playing:
deck.pause()
self._set_ui_stopped()
self.status_label.setText("PAUSED")
else:
deck.play()
self._set_ui_playing()
def _eject(self):
deck = self.engine.decks[self.cart_key]
deck.stop()
self._set_ui_stopped()
self.track_label.setText("EMPTY")
self.status_label.setText("STOPPED")
def _refresh_display(self):
deck = self.engine.decks[self.cart_key]
track = deck.track
playing = deck.playing
2026-04-29 16:21:44 +10:00
if not playing and self._playing_icon:
2026-04-29 16:21:44 +10:00
self._set_ui_stopped()
self.status_label.setText("STOPPED")
if track:
2026-04-29 16:21:44 +10:00
self.elapsed_label.setText(deck.get_elapsed())
self.remaining_label.setText(deck.get_remaining())
class CartBankWidget(QWidget):
def __init__(self, engine, parent=None):
super().__init__(parent)
self.engine = engine
self._build_ui()
def _build_ui(self):
self.setStyleSheet("""
QWidget {
background-color: #111;
border: 2px solid #cc3300;
border-radius: 6px;
}
""")
root = QVBoxLayout(self)
root.setContentsMargins(8, 6, 8, 6)
root.setSpacing(6)
header = QLabel("CARTS")
header.setFont(QFont("Arial", 11, QFont.Bold))
header.setStyleSheet("""
color: #cc3300;
background: transparent;
border: none;
padding: 2px;
""")
header.setAlignment(Qt.AlignCenter)
root.addWidget(header)
grid = QGridLayout()
grid.setSpacing(6)
self.slots = []
for i in range(4):
cart_key = f"cart{i + 1}"
slot = CartSlot(cart_key, i + 1, self.engine)
row = i // 2
col = i % 2
grid.addWidget(slot, row, col)
self.slots.append(slot)
2026-04-30 00:30:23 +10:00
root.addLayout(grid)
def apply_theme(self, theme: dict):
for slot in self.slots:
slot.apply_theme(theme)