feat(ui): polish About & Help tabs; theme-driven styles

About: modern card, clickable mail/URL (TextBrowserInteraction + openExternalLinks), single-line values, link color via palette; layout reuse to avoid warnings.
Help: scrollable card; moved all styling to theme QSS; added Quick Start, Tips, Warnings, Troubleshooting sections.
Theme: added HELP/ABOUT blocks for light & dark; cast font sizes to int to prevent light-mode crashes; consolidated #aboutCard rules; link colors per theme.
Fix: removed setLayout(None) and avoided re-adding layouts to the same widget.
main
Kirubakaran 2025-08-21 04:14:12 +05:30
parent 0012e5502c
commit 5d35b9a34d
3 changed files with 986 additions and 354 deletions

View File

@ -11,7 +11,7 @@ from PyQt6.QtWidgets import (
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTabWidget, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTabWidget,
QGroupBox, QFormLayout, QLineEdit, QPushButton, QLabel, QSpacerItem, QGroupBox, QFormLayout, QLineEdit, QPushButton, QLabel, QSpacerItem,
QSizePolicy, QGridLayout, QMessageBox, QComboBox, QPlainTextEdit, QSizePolicy, QGridLayout, QMessageBox, QComboBox, QPlainTextEdit,
QCheckBox, QFileDialog, QLayout, QFrame, QSizePolicy, QGraphicsOpacityEffect, QVBoxLayout, QTextBrowser, QScrollArea QCheckBox, QFileDialog, QLayout, QFrame, QSizePolicy, QGraphicsOpacityEffect, QVBoxLayout, QTextBrowser, QScrollArea, QGraphicsDropShadowEffect
) )
from PyQt6.QtSvgWidgets import QSvgWidget from PyQt6.QtSvgWidgets import QSvgWidget
from google.protobuf.json_format import MessageToDict from google.protobuf.json_format import MessageToDict
@ -104,110 +104,62 @@ class MainWindow(QMainWindow):
self.load_settings() self.load_settings()
self._apply_theme() self._apply_theme()
def setup_help_ui(self): def setup_help_ui(self):
""" """
Polished Help page: Help page (theme-driven):
- Card-style container with title - Card-style container with title
- Sections: Quick Start, Warnings, Troubleshooting, Shortcuts - Sections: Quick Start, Tips, Warnings, Troubleshooting
- Colored callouts for warnings/tips - Scrollable host
- Scrollable if content grows
""" """
# Clear existing layout
def clear_layout(w: QWidget):
lay = w.layout()
if not lay: return
while lay.count():
it = lay.takeAt(0)
if it.widget(): it.widget().deleteLater()
elif it.layout():
while it.layout().count():
sub = it.layout().takeAt(0)
if sub.widget(): sub.widget().deleteLater()
it.layout().deleteLater()
lay.deleteLater()
clear_layout(self.help_tab) # get or create root layout (never replace/delete the layout itself)
root = self.help_tab.layout()
if root is None:
root = QVBoxLayout()
root.setContentsMargins(12, 12, 12, 12)
root.setSpacing(10)
self.help_tab.setLayout(root)
else:
# clear previous contents
while root.count():
it = root.takeAt(0)
w = it.widget()
if w:
w.setParent(None)
w.deleteLater()
root = QVBoxLayout(self.help_tab) # scroll host
root.setContentsMargins(12, 12, 12, 12)
root.setSpacing(10)
# Scroll area so long help stays usable
scroll = QScrollArea() scroll = QScrollArea()
scroll.setWidgetResizable(True) scroll.setWidgetResizable(True)
scroll.setFrameShape(QFrame.Shape.NoFrame) scroll.setFrameShape(QFrame.Shape.NoFrame)
host = QWidget() host = QWidget()
scroll_lay = QVBoxLayout(host) host_lay = QVBoxLayout(host)
scroll_lay.setContentsMargins(0, 0, 0, 0) host_lay.setContentsMargins(0, 0, 0, 0)
scroll_lay.setSpacing(12) host_lay.setSpacing(12)
# Card container # card (styled by theme via objectNames)
card = QFrame() card = QFrame()
card.setObjectName("helpCard") card.setObjectName("helpCard")
card.setStyleSheet("""
#helpCard {
background: #2a2a2a;
border: 1px solid #3a3a3a;
border-radius: 12px;
}
QLabel.title {
color: #eaeaea;
font-weight: 700;
font-size: 18px;
}
QLabel.subtitle {
color: #c8c8c8;
font-size: 12px;
}
QFrame.divider {
background: #3a3a3a;
min-height: 1px; max-height: 1px; border: none;
}
QLabel.h3 {
color: #e6e6e6;
font-weight: 600;
margin-top: 6px;
}
QLabel.body {
color: #dcdcdc;
}
/* Callouts */
QFrame.warn {
background: #3b2e1b;
border: 1px solid #ffb74d;
border-radius: 8px;
}
QLabel.warnTitle { color: #ffd561; font-weight: 700; }
QLabel.warnText { color: #f0e0c0; }
QFrame.tip {
background: #1f3326;
border: 1px solid #62d39b;
border-radius: 8px;
}
QLabel.tipTitle { color: #a8f5c9; font-weight: 700; }
QLabel.tipText { color: #d6ffe9; }
QLabel.link { color: #6aa9ff; }
""")
card_lay = QVBoxLayout(card) card_lay = QVBoxLayout(card)
card_lay.setContentsMargins(18, 16, 18, 16) card_lay.setContentsMargins(18, 16, 18, 16)
card_lay.setSpacing(12) card_lay.setSpacing(12)
# Header # header
title = QLabel("Help & User Guide") title = QLabel("Help & User Guide"); title.setObjectName("helpTitle")
title.setProperty("class", "title")
subtitle = QLabel("Follow these steps to get connected, monitor the station, and troubleshoot issues.") subtitle = QLabel("Follow these steps to get connected, monitor the station, and troubleshoot issues.")
subtitle.setProperty("class", "subtitle") subtitle.setObjectName("helpSubtitle")
card_lay.addWidget(title) card_lay.addWidget(title)
card_lay.addWidget(subtitle) card_lay.addWidget(subtitle)
div1 = QFrame(); div1.setObjectName("divider"); div1.setFrameShape(QFrame.Shape.NoFrame); div1.setProperty("class", "divider") div1 = QFrame(); div1.setObjectName("helpDivider")
card_lay.addWidget(div1) card_lay.addWidget(div1)
# Quick Start # Quick Start
qs_title = QLabel("Quick Start") qs_title = QLabel("Quick Start"); qs_title.setObjectName("sectionTitle")
qs_title.setProperty("class", "h3")
qs = QLabel( qs = QLabel(
"<ul style='margin:0 0 0 16px'>" "<ul style='margin:0 0 0 16px'>"
"<li><b>Configure</b>: Open <b>Config</b>, fill MQTT details, click <b>Connect</b>.</li>" "<li><b>Configure</b>: Open <b>Config</b>, fill MQTT details, click <b>Connect</b>.</li>"
@ -218,16 +170,14 @@ class MainWindow(QMainWindow):
"</ul>" "</ul>"
) )
qs.setOpenExternalLinks(True) qs.setOpenExternalLinks(True)
qs.setProperty("class", "body") qs.setObjectName("bodyText")
card_lay.addWidget(qs_title) card_lay.addWidget(qs_title)
card_lay.addWidget(qs) card_lay.addWidget(qs)
# Tips (green) # Tips
tip_box = QFrame(); tip_box.setObjectName("tip"); tip_box.setProperty("class", "tip") tip_box = QFrame(); tip_box.setObjectName("tipBox")
tip_lay = QVBoxLayout(tip_box); tip_lay.setContentsMargins(12, 10, 12, 10) tip_lay = QVBoxLayout(tip_box); tip_lay.setContentsMargins(12, 10, 12, 10)
tip_h = QLabel("💡 Tips") tip_h = QLabel("💡 Tips"); tip_h.setObjectName("tipTitle")
tip_h.setProperty("class", "tipTitle")
tip_b = QLabel( tip_b = QLabel(
"<ul style='margin:0 0 0 16px'>" "<ul style='margin:0 0 0 16px'>"
"<li>Use a stable, low-latency network for best live updates.</li>" "<li>Use a stable, low-latency network for best live updates.</li>"
@ -235,17 +185,16 @@ class MainWindow(QMainWindow):
"<li>Use <b>Logs → Export</b> before clearing or reinstalling.</li>" "<li>Use <b>Logs → Export</b> before clearing or reinstalling.</li>"
"</ul>" "</ul>"
) )
tip_b.setProperty("class", "tipText")
tip_b.setOpenExternalLinks(True) tip_b.setOpenExternalLinks(True)
tip_b.setObjectName("tipText")
tip_lay.addWidget(tip_h) tip_lay.addWidget(tip_h)
tip_lay.addWidget(tip_b) tip_lay.addWidget(tip_b)
card_lay.addWidget(tip_box) card_lay.addWidget(tip_box)
# Warnings (amber) # Warnings
warn_box = QFrame(); warn_box.setObjectName("warn"); warn_box.setProperty("class", "warn") warn_box = QFrame(); warn_box.setObjectName("warnBox")
warn_lay = QVBoxLayout(warn_box); warn_lay.setContentsMargins(12, 10, 12, 10) warn_lay = QVBoxLayout(warn_box); warn_lay.setContentsMargins(12, 10, 12, 10)
warn_h = QLabel("⚠️ Important Warnings") warn_h = QLabel("⚠️ Important Warnings"); warn_h.setObjectName("warnTitle")
warn_h.setProperty("class", "warnTitle")
warn_b = QLabel( warn_b = QLabel(
"<ul style='margin:0 0 0 16px'>" "<ul style='margin:0 0 0 16px'>"
"<li><b>Disconnected</b> state means no live data. Check server, network, and credentials.</li>" "<li><b>Disconnected</b> state means no live data. Check server, network, and credentials.</li>"
@ -253,14 +202,13 @@ class MainWindow(QMainWindow):
"<li>Commands during disconnect may be lost. Reconnect before critical actions.</li>" "<li>Commands during disconnect may be lost. Reconnect before critical actions.</li>"
"</ul>" "</ul>"
) )
warn_b.setProperty("class", "warnText") warn_b.setObjectName("warnText")
warn_lay.addWidget(warn_h) warn_lay.addWidget(warn_h)
warn_lay.addWidget(warn_b) warn_lay.addWidget(warn_b)
card_lay.addWidget(warn_box) card_lay.addWidget(warn_box)
# Troubleshooting # Troubleshooting
tr_title = QLabel("Troubleshooting") tr_title = QLabel("Troubleshooting"); tr_title.setObjectName("sectionTitle")
tr_title.setProperty("class", "h3")
tr = QLabel( tr = QLabel(
"<ul style='margin:0 0 0 16px'>" "<ul style='margin:0 0 0 16px'>"
"<li><b>Cannot connect</b>: Verify broker/port, username/password, and firewall.</li>" "<li><b>Cannot connect</b>: Verify broker/port, username/password, and firewall.</li>"
@ -269,175 +217,197 @@ class MainWindow(QMainWindow):
"<li><b>Wrong station showing</b>: Confirm the <b>Device ID</b> matches the station you expect.</li>" "<li><b>Wrong station showing</b>: Confirm the <b>Device ID</b> matches the station you expect.</li>"
"</ul>" "</ul>"
) )
tr.setProperty("class", "body") tr.setObjectName("bodyText")
card_lay.addWidget(tr_title) card_lay.addWidget(tr_title)
card_lay.addWidget(tr) card_lay.addWidget(tr)
# Footer # footer
div2 = QFrame(); div2.setObjectName("divider"); div2.setProperty("class", "divider") div2 = QFrame(); div2.setObjectName("helpDivider")
card_lay.addWidget(div2) card_lay.addWidget(div2)
foot = QLabel("Need help? Email <a href='mailto:kirubakaran@vecmocon.com' class='link'>kirubakaran@vecmocon.com</a>") foot = QLabel("Need help? Email "
"<a href='mailto:kirubakaran@vecmocon.com' style='color:#0d6efd; text-decoration:none'>"
"kirubakaran@vecmocon.com</a>")
foot.setOpenExternalLinks(True) foot.setOpenExternalLinks(True)
foot.setProperty("class", "body") foot.setObjectName("bodyText")
card_lay.addWidget(foot) card_lay.addWidget(foot)
scroll_lay.addWidget(card) # assemble
scroll_lay.addStretch(1) host_lay.addWidget(card)
host_lay.addStretch(1)
scroll.setWidget(host) scroll.setWidget(host)
root.addWidget(scroll) root.addWidget(scroll)
def _about_stylesheet() -> str:
return """
/* Card */
#aboutCard {
background: #121417;
border: 1px solid #2a2f36;
border-radius: 16px;
}
/* Title + subtitle */
#title {
font-size: 22px;
font-weight: 700;
color: #e8edf2;
letter-spacing: 0.2px;
}
#subtitle {
font-size: 13px;
font-weight: 500;
color: #9aa4b2;
}
/* Divider */
#divider {
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
stop:0 #1b1f24, stop:0.5 #2a2f36, stop:1 #1b1f24);
min-height: 1px;
max-height: 1px;
border: none;
margin: 6px 0 8px 0;
}
/* Small label on the left column */
#kvLabel {
color: #aab4c0;
font-weight: 600;
}
/* Pill badge */
#badge {
color: #eaf3ff;
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
stop:0 #2563eb, stop:1 #7c3aed);
border-radius: 999px;
padding: 5px 12px;
font-weight: 700;
letter-spacing: 0.3px;
}
/* Links */
QLabel { color: #d7dde6; }
QLabel:hover { text-decoration: none; }
QLabel[link='true'] { color: #6aa8ff; }
QLabel[link='true']:hover { color: #8fc2ff; text-decoration: underline; }
/* Footer */
#footer {
color: #7e8895;
font-size: 12px;
}
"""
def setup_about_ui(self): def setup_about_ui(self):
""" # get or create root layout
A clean, native-Qt About page: root = self.about_tab.layout()
- Card-style container with rounded corners if root is None:
- Bold title + subtitle root = QVBoxLayout()
- Small badges for version/build root.setContentsMargins(24, 24, 24, 24)
- Left/right aligned keyvalue grid root.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignHCenter)
""" self.about_tab.setLayout(root)
else:
# Clear existing layout if re-building # clear existing items
def clear_layout(w: QWidget): while root.count():
lay = w.layout() item = root.takeAt(0)
if lay: w = item.widget()
while lay.count(): if w:
item = lay.takeAt(0) w.setParent(None)
if item.widget(): w.deleteLater()
item.widget().deleteLater()
elif item.layout():
while item.layout().count():
sub = item.layout().takeAt(0)
if sub.widget():
sub.widget().deleteLater()
item.layout().deleteLater()
lay.deleteLater()
clear_layout(self.about_tab)
build_num = "4.0"
# ---- Main container ----
root = QVBoxLayout(self.about_tab)
root.setContentsMargins(16, 16, 16, 16)
root.setSpacing(12)
# --- card ---
card = QFrame() card = QFrame()
card.setObjectName("aboutCard") card.setObjectName("aboutCard")
card.setFrameShape(QFrame.Shape.NoFrame) card.setMinimumWidth(560)
card.setStyleSheet(""" card.setMaximumWidth(760)
#aboutCard { card.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Maximum)
background: #2a2a2a;
border: 1px solid #3a3a3a;
border-radius: 12px;
}
QLabel#title {
color: #eaeaea;
font-weight: 700;
}
QLabel#subtitle {
color: #c7c7c7;
}
QLabel#label {
color: #b6b6b6;
font-weight: 600;
}
QLabel#value {
color: #e6e6e6;
}
QLabel.badge {
background: #343a40;
border: 1px solid #454d55;
border-radius: 999px;
padding: 2px 10px;
color: #e6e6e6;
font-size: 11px;
}
QFrame#divider {
background: #3a3a3a;
min-height: 1px;
max-height: 1px;
border: none;
}
QLabel.link {
color: #6aa9ff;
}
""")
card_lay = QVBoxLayout(card)
card_lay.setContentsMargins(18, 16, 18, 16)
card_lay.setSpacing(10)
# ---- Header ---- shadow = QGraphicsDropShadowEffect(blurRadius=28, xOffset=0, yOffset=12)
shadow.setColor(Qt.GlobalColor.black)
card.setGraphicsEffect(shadow)
card_layout = QVBoxLayout(card)
card_layout.setContentsMargins(28, 22, 28, 22)
card_layout.setSpacing(14)
# --- header row (logo + titles + badge) ---
header = QHBoxLayout()
header.setSpacing(12)
# optional logo (put a 48px logo at ./logo/app.png if you have one)
logo_label = QLabel()
logo_path = "logo/v_logo.png"
pix = QPixmap(logo_path)
if not pix.isNull():
logo_label.setPixmap(pix.scaled(40, 40, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
logo_label.setFixedSize(44, 44)
header.addWidget(logo_label, 0, Qt.AlignmentFlag.AlignTop)
title_box = QVBoxLayout()
title = QLabel("About This Application") title = QLabel("About This Application")
title.setObjectName("title") title.setObjectName("title")
tfont = QFont()
tfont.setPointSize(16)
tfont.setBold(True)
title.setFont(tfont)
subtitle = QLabel("Battery Swap Station Dashboard") subtitle = QLabel("Battery Swap Station Dashboard")
subtitle.setObjectName("subtitle") subtitle.setObjectName("subtitle")
title_box.addWidget(title)
title_box.addWidget(subtitle)
header.addLayout(title_box, 1)
header = QVBoxLayout() badge = QLabel("Version 4.0")
header.setSpacing(4) badge.setObjectName("badge")
header.addWidget(title) header.addWidget(badge, 0, Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
header.addWidget(subtitle)
# badges row card_layout.addLayout(header)
badges = QHBoxLayout()
badges.setSpacing(8)
ver = QLabel(f"Version: {build_num}")
ver.setObjectName("")
ver.setProperty("class", "badge")
ver.setAlignment(Qt.AlignmentFlag.AlignCenter)
ver.setMinimumHeight(20)
ver.setStyleSheet("")
badges.addWidget(ver, 0, Qt.AlignmentFlag.AlignLeft) # --- divider ---
badges.addStretch(1) divider = QFrame()
divider.setObjectName("divider")
card_layout.addWidget(divider)
card_lay.addLayout(header) # --- keyvalue grid ---
card_lay.addLayout(badges)
# divider
div = QFrame()
div.setObjectName("divider")
card_lay.addWidget(div)
# ---- KeyValue grid ----
grid = QGridLayout() grid = QGridLayout()
grid.setHorizontalSpacing(24) grid.setHorizontalSpacing(18)
grid.setVerticalSpacing(10) grid.setVerticalSpacing(10)
def add_row(r, key, val, is_link=False): def add_row(r: int, key: str, value: str, is_link: bool = False):
l = QLabel(key) k = QLabel(key)
l.setObjectName("label") k.setObjectName("kvLabel")
v = QLabel(val) v = QLabel(value)
v.setObjectName("value") v.setWordWrap(False) # instead of True
if is_link: if is_link:
v.setText(f'<a href="{val}">{val}</a>') v.setTextFormat(Qt.TextFormat.RichText)
v.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction) # links + selection + keyboard nav
v.setOpenExternalLinks(True) v.setOpenExternalLinks(True)
v.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction) v.setProperty("link", True)
v.setObjectName("") # style via link class
v.setProperty("class", "link")
grid.addWidget(l, r, 0, Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
grid.addWidget(v, r, 1, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
add_row(0, "Company", "VECMOCON TECHNOLOGIES") else:
v.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
grid.addWidget(k, r, 0, Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop)
grid.addWidget(v, r, 1, Qt.AlignmentFlag.AlignLeft)
add_row(0, "Company", "VECMOCON TECHNOLOGIES")
add_row(1, "Developed by", "Kirubakaran S") add_row(1, "Developed by", "Kirubakaran S")
add_row(2, "Support", "kirubakaran@vecmocon.com", is_link=True) # mailto auto-handled by client add_row(2, "Support",
add_row(3, "Website", "https://www.vecmocon.com", is_link=True) "<a href='mailto:kirubakaran@vecmocon.com' style='color:#0d6efd; text-decoration:none'>kirubakaran@vecmocon.com</a>",
is_link=True)
card_lay.addLayout(grid) add_row(3, "Website",
"<a href='https://www.vecmocon.com' style='color:#0d6efd; text-decoration:none'>www.vecmocon.com</a>",
is_link=True)
# footer card_layout.addLayout(grid)
footer = QLabel("© 2025 VECMOCON TECHNOLOGIES. All rights reserved. • Made with ♥ for reliability & clarity.")
footer.setAlignment(Qt.AlignmentFlag.AlignLeft)
footer.setObjectName("subtitle") # subtle color
card_lay.addSpacing(6)
card_lay.addWidget(footer)
# center the card horizontally # --- tiny space + footer ---
root.addWidget(card) spacer = QFrame(); spacer.setFixedHeight(4)
root.addStretch(1) card_layout.addWidget(spacer)
footer = QLabel("© 2025 VECMOCON TECHNOLOGIES. All rights reserved.")
footer.setObjectName("footer")
card_layout.addWidget(footer, 0, Qt.AlignmentFlag.AlignHCenter)
root.addWidget(card, 0, Qt.AlignmentFlag.AlignHCenter)
def load_settings(self): def load_settings(self):
@ -590,6 +560,8 @@ class MainWindow(QMainWindow):
# 3. Immediately apply the new theme underneath the overlay # 3. Immediately apply the new theme underneath the overlay
self.is_dark_theme = not self.is_dark_theme self.is_dark_theme = not self.is_dark_theme
self._apply_theme() self._apply_theme()
self.setup_about_ui()
self.setup_help_ui()
# 4. Set up the opacity effect and animation for the overlay # 4. Set up the opacity effect and animation for the overlay
opacity_effect = QGraphicsOpacityEffect(overlay) opacity_effect = QGraphicsOpacityEffect(overlay)
@ -609,6 +581,7 @@ class MainWindow(QMainWindow):
self.animation.start(self.animation.DeletionPolicy.DeleteWhenStopped) self.animation.start(self.animation.DeletionPolicy.DeleteWhenStopped)
def create_status_bar(self): def create_status_bar(self):
status_bar_widget = QWidget() status_bar_widget = QWidget()
status_bar_widget.setStyleSheet(f"background-color: #2c3e50; padding: {int(6*self.scale_factor)}px;") status_bar_widget.setStyleSheet(f"background-color: #2c3e50; padding: {int(6*self.scale_factor)}px;")
status_bar_layout = QHBoxLayout(status_bar_widget) status_bar_layout = QHBoxLayout(status_bar_widget)
@ -634,6 +607,17 @@ class MainWindow(QMainWindow):
right_layout.addStretch() right_layout.addStretch()
self.connect_button = QPushButton("Connect") self.connect_button = QPushButton("Connect")
self.disconnect_button = QPushButton("Disconnect") self.disconnect_button = QPushButton("Disconnect")
button_font_size = max(10, int(12 * self.scale_factor))
button_stylesheet = f"""
QPushButton {{
font-size: {button_font_size}px;
font-weight: bold;
padding: 4px 14px;
background-color: #3498db;
}}
"""
self.connect_button.setStyleSheet(button_stylesheet)
self.disconnect_button.setStyleSheet(button_stylesheet)
self.connect_button.setObjectName("ConnectButton") self.connect_button.setObjectName("ConnectButton")
self.disconnect_button.setObjectName("DisconnectButton") self.disconnect_button.setObjectName("DisconnectButton")
self.disconnect_button.setEnabled(False) self.disconnect_button.setEnabled(False)
@ -781,14 +765,19 @@ class MainWindow(QMainWindow):
int(8 * self.scale_factor), int(8 * self.scale_factor) int(8 * self.scale_factor), int(8 * self.scale_factor)
) )
# You correctly created the frame here
self.top_bar_frame = QFrame() self.top_bar_frame = QFrame()
self.top_bar_frame.setObjectName("topBarFrame") self.top_bar_frame.setObjectName("topBarFrame")
top_bar_layout = QHBoxLayout() top_bar_layout = QHBoxLayout()
top_bar_layout.addWidget(QLabel("LAST RECV TS:"))
ts_label = QLabel("LAST RECV TS:")
ts_label.setObjectName("TimestampTitleLabel")
top_bar_layout.addWidget(ts_label)
self.last_recv_ts_field = QLineEdit("No Data") self.last_recv_ts_field = QLineEdit("No Data")
self.last_recv_ts_field.setReadOnly(True) self.last_recv_ts_field.setReadOnly(True)
self.last_recv_ts_field.setObjectName("TimestampDataField")
top_bar_layout.addWidget(self.last_recv_ts_field) top_bar_layout.addWidget(self.last_recv_ts_field)
top_bar_layout.addSpacerItem(QSpacerItem(20, 20, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)) top_bar_layout.addSpacerItem(QSpacerItem(20, 20, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum))

View File

@ -1,25 +1,451 @@
# # --- Dynamic Theme Stylesheets ---
# def get_light_theme_styles(scale=1.0):
# log_font_size = max(10, int(11 * scale))
# button_font_size = max(7, int(10 * scale))
# return f"""
# QMainWindow, QWidget {{
# background-color: #f0f0f0;
# color: #000;
# }}
# /* --- ADDED THIS RULE --- */
# #StatusBar {{
# background-color: #34495e; /* A dark blue-grey for light theme */
# padding: {int(6 * scale)}px;
# }}
# #LogPanel {{
# font-family: "Courier New", Consolas, monospace;
# font-size: {log_font_size}pt;
# background-color: #ffffff; /* White background for logs */
# color: #2b2b2b;
# border: 1px solid #c8c8c8;
# }}
# QGroupBox {{
# font-family: Arial;
# border: 1px solid #c8c8c8;
# border-radius: {int(8 * scale)}px;
# margin-top: {int(6 * scale)}px;
# }}
# QGroupBox::title {{
# subcontrol-origin: margin;
# subcontrol-position: top center;
# padding: 0 {int(10 * scale)}px;
# color: #000;
# }}
# QTabWidget::pane {{ border-top: 2px solid #c8c8c8; }}
# QTabBar::tab {{
# background: #e1e1e1; border: 1px solid #c8c8c8;
# padding: {int(6 * scale)}px {int(15 * scale)}px;
# border-top-left-radius: {int(4 * scale)}px; border-top-right-radius: {int(4 * scale)}px;
# }}
# QTabBar::tab:selected {{ background: #f0f0f0; border-bottom-color: #f0f0f0; }}
# QFormLayout::label {{ color: #000; padding-top: {int(3 * scale)}px; }}
# QLineEdit, QPlainTextEdit, QComboBox {{
# background-color: #fff; border: 1px solid #c8c8c8;
# border-radius: {int(4 * scale)}px; padding: {int(4 * scale)}px;
# font-size: {max(7, int(9 * scale))}pt;
# color: #000;
# }}
# QLineEdit:read-only {{ background-color: #e9e9e9; }}
# QPushButton {{
# background-color: #e1e1e1; border: 1px solid #c8c8c8;
# padding: {int(5 * scale)}px {int(10 * scale)}px;
# border-radius: {int(4 * scale)}px;
# color: #000;
# }}
# QPushButton:hover {{ background-color: #dcdcdc; }}
# QPushButton:pressed {{ background-color: #c8c8c8; }}
# QPushButton:disabled {{ background-color: #d3d3d3; color: #a0a0a0; }}
# #RefreshButton, #ResetButton {{
# padding: {int(6 * scale)}px {int(16 * scale)}px;
# font-size: {button_font_size * 1.3}pt;
# font-weight: bold;
# color: white;
# border-radius: {int(4*scale)}px;
# }}
# #RefreshButton {{
# background-color: #2e7d32; /* A slightly darker green */
# }}
# #ResetButton {{
# background-color: #c62828; /* A slightly darker red */
# }}
# /* --- UPDATED BUTTON STYLES FOR LIGHT THEME --- */
# #ConnectButton, #DisconnectButton, #StartSwapButton, #AbortSwapButton {{
# padding: {int(6 * scale)}px {int(16 * scale)}px;
# font-size: {button_font_size}pt;
# font-weight: bold;
# border-radius: {int(4 * scale)}px;
# color: white;
# border: none;
# }}
# /* Positive Actions (Green) */
# #ConnectButton, #StartSwapButton {{
# background-color: #28a745;
# }}
# #ConnectButton:hover, #StartSwapButton:hover {{
# background-color: #218838;
# }}
# /* Negative Actions (Red) */
# #DisconnectButton, #AbortSwapButton {{
# background-color: #dc3545;
# }}
# #DisconnectButton:hover, #AbortSwapButton:hover {{
# background-color: #c82333;
# }}
# /* Disabled States */
# #ConnectButton:disabled, #DisconnectButton:disabled {{
# background-color: #b0bec5;
# color: #78909c;
# }}
# /* Unique Buttons */
# #SendAudioButton {{
# background-color: #007bff;
# color: white;
# border: none;
# font-size: {max(10, int(14 * scale))}px;
# }}
# #SendAudioButton:hover {{
# background-color: #0069d9;
# }}
# #ChamberOpenDoorButton, #ChamberChgOnButton, #ChamberChgOffButton {{
# padding: {int(8 * scale)}px;
# font-size: {button_font_size}pt;
# font-weight: bold;
# border-radius: {int(4*scale)}px;
# }}
# #ChamberOpenDoorButton:hover {{ background-color: #607d8b; }}
# #ChamberChgOnButton:hover {{ background-color: #52be80; }}
# #ChamberChgOffButton:hover {{ background-color: #cd6155; }}
# /* --- STATUS & ALARM LABELS --- */
# QLabel[status="present"] {{ background-color: #2ecc71; color: white; border-radius: {int(4*scale)}px; padding: {int(3*scale)}px; }}
# QLabel[status="absent"] {{ background-color: #e74c3c; color: white; border-radius: {int(4*scale)}px; padding: {int(3*scale)}px; }}
# QLabel[alarm="active"] {{ background-color: #e74c3c; color: white; font-weight: bold; border-radius: {int(4*scale)}px; padding: {int(2*scale)}px; }}
# QLabel[alarm="inactive"] {{ background-color: transparent; color: black; }}
# QGroupBox#ChamberWidget {{ border: 2px solid #007bff; }}
# /* --- ADDED TIMESTAMP STYLES --- */
# QLabel#TimestampTitleLabel {{
# font-size: {max(9, int(11 * scale))}pt;
# font-weight: bold;
# color: #333;
# }}
# QLineEdit#TimestampDataField {{
# font-size: {max(11, int(13 * scale))}pt;
# font-weight: bold;
# color: #000;
# }}
# QGroupBox#ChamberWidget QFormLayout QLabel {{
# font-size: {max(10, int(12 * scale))}px;
# color: #555555;
# }}
# QGroupBox#ChamberWidget QLineEdit#BatIdField {{
# font-size: {max(14, int(16 * scale))}px;
# font-weight: bold;
# color: #000000;
# }}
# QGroupBox#ChamberWidget QLineEdit#DataField {{
# font-size: {max(13, int(14 * scale))}px;
# color: #222222;
# }}
# QGroupBox#ChamberWidget QLineEdit#DoorStatusField {{
# font-size: {max(12, int(14 * scale))}px;
# font-weight: bold;
# color: #222222;
# }}
# #aboutCard {{
# background-color: #ffffff; /* White background */
# border: 1px solid #dee2e6; /* Light grey border */
# border-radius: 12px;
# }}
# #aboutCard QLabel#title {{
# color: #212529; /* Dark text for title */
# font-size: 18pt;
# font-weight: bold;
# }}
# #aboutCard QLabel#subtitle {{
# color: #6c757d; /* Grey for subtitle */
# }}
# #aboutCard QLabel#label {{
# color: #495057; /* Dark grey for labels */
# font-weight: bold;
# }}
# #aboutCard QLabel {{ /* General text color inside the card */
# color: #212529;
# }}
# #aboutCard QLabel.badge {{
# background-color: #e9ecef; /* Light grey for badge */
# border-radius: 8px;
# padding: 3px 10px;
# font-size: 9pt;
# }}
# #aboutCard QFrame#divider {{
# background-color: #dee2e6; /* Light grey for divider line */
# max-height: 1px;
# border: none;
# }}
# #aboutCard a {{
# color: #007bff; /* Standard blue for links */
# text-decoration: none;
# }}
# """
# def get_dark_theme_styles(scale=1.0):
# log_font_size = max(10, int(11 * scale))
# button_font_size = max(7, int(10 * scale))
# return f"""
# QMainWindow, QWidget {{ background-color: #2b2b2b; color: #f0f0f0; }}
# #LogPanel {{
# font-family: "Courier New", Consolas, monospace;
# font-size: {log_font_size}pt;
# background-color: #212121;
# color: #eceff1;
# border: 1px solid #455a64;
# }}
# QGroupBox {{
# font-family: Arial; border: 1px solid #4a4a4a;
# border-radius: {int(8 * scale)}px; margin-top: {int(6 * scale)}px;
# }}
# QGroupBox::title {{ subcontrol-origin: margin; subcontrol-position: top center; padding: 0 {int(10 * scale)}px; color: #f0f0f0; }}
# QTabWidget::pane {{ border-top: 2px solid #4a4a4a; }}
# QTabBar::tab {{
# background: #3c3c3c; border: 1px solid #4a4a4a; color: #f0f0f0;
# padding: {int(6 * scale)}px {int(15 * scale)}px;
# border-top-left-radius: {int(4 * scale)}px; border-top-right-radius: {int(4 * scale)}px;
# }}
# QTabBar::tab:selected {{ background: #2b2b2b; border-bottom-color: #2b2b2b; }}
# QFormLayout::label {{ color: #f0f0f0; padding-top: {int(3 * scale)}px; }}
# QLineEdit, QPlainTextEdit, QComboBox {{
# background-color: #3c3c3c; border: 1px solid #4a4a4a;
# border-radius: {int(4 * scale)}px; padding: {int(4 * scale)}px; color: #f0f0f0;
# font-size: {max(7, int(9 * scale))}pt;
# }}
# QLineEdit:read-only {{ background-color: #333333; }}
# QPushButton {{
# background-color: #555555; border: 1px solid #4a4a4a;
# padding: {int(5 * scale)}px {int(10 * scale)}px;
# border-radius: {int(4 * scale)}px; color: #f0f0f0;
# }}
# QPushButton:hover {{ background-color: #6a6a6a; }}
# QPushButton:pressed {{ background-color: #4a4a4a; }}
# QPushButton:disabled {{ background-color: #404040; color: #888888; }}
# #RefreshButton, #ResetButton {{
# padding: {int(6 * scale)}px {int(16 * scale)}px;
# font-size: {button_font_size * 1.3}pt;
# font-weight: bold;
# border-radius: {int(4*scale)}px;
# }}
# #RefreshButton {{
# background-color: #2e7d32; /* A slightly darker green */
# }}
# #ResetButton {{
# background-color: #c62828; /* A slightly darker red */
# }}
# #ChamberOpenDoorButton, #ChamberChgOnButton, #ChamberChgOffButton {{
# padding: {int(8 * scale)}px;
# font-size: {button_font_size}pt;
# font-weight: bold;
# border-radius: {int(4*scale)}px;
# }}
# #ChamberOpenDoorButton {{ background-color: #3C3C3C; }}
# #ChamberChgOnButton {{ background-color: #3C3C3C; }}
# #ChamberChgOffButton {{ background-color: #3C3C3C; }}
# #ChamberOpenDoorButton:hover {{ background-color: #607d8b; }}
# #ChamberChgOnButton:hover {{ background-color: #52be80; }}
# #ChamberChgOffButton:hover {{ background-color: #cd6155; }}
# #ConnectButton, #DisconnectButton {{
# padding: {int(6 * scale)}px {int(16 * scale)}px;
# font-size: {button_font_size}pt;
# font-weight: bold;
# border-radius: {int(4*scale)}px;
# color: white;
# }}
# #ConnectButton {{ background-color: #27ae60; }} /* Green */
# #DisconnectButton {{ background-color: #c0392b; }} /* Red */
# #ConnectButton:hover {{ background-color: #52be80; }}
# #DisconnectButton:hover {{ background-color: #cd6155; }}
# #ConnectButton:pressed {{ background-color: #52be80; }}
# #DisconnectButton:pressed {{ background-color: #cd6155; }}
# #ConnectButton:disabled, #DisconnectButton:disabled {{
# background-color: #546e7a;
# color: #90a4ae;
# }}
# #RefreshButton, #StartSwapButton {{ background-color: #27ae60; color: white; border: none; }}
# #RefreshButton:hover, #StartSwapButton:hover {{ background-color: #52be80; }}
# #ResetButton, #AbortSwapButton {{ background-color: #c0392b; color: white; border: none; }}
# #ResetButton:hover, #AbortSwapButton:hover {{ background-color: #cd6155; }}
# #SendAudioButton {{ background-color: #3498db; color: white; border: none; font-size: {max(10, int(14 * scale))}px; }}
# #SendAudioButton:hover {{ background-color: #5dade2; }}
# QLabel[status="present"] {{ background-color: #2ecc71; color: white; border-radius: {int(4*scale)}px; padding: {int(3*scale)}px; }}
# QLabel[status="absent"] {{ background-color: #e74c3c; color: white; border-radius: {int(4*scale)}px; padding: {int(3*scale)}px; }}
# QLabel[alarm="active"] {{ background-color: #e74c3c; color: white; font-weight: bold; border-radius: {int(4*scale)}px; padding: {int(2*scale)}px; }}
# QLabel[alarm="inactive"] {{ background-color: transparent; color: #f0f0f0; }}
# QGroupBox#ChamberWidget {{ border: 2px solid #3498db; }}
# /* Style for the timestamp TITLE label */
# QLabel#TimestampTitleLabel {{
# font-size: {max(9, int(11 * scale))}pt; /* Normal font size */
# font-weight: bold;
# }}
# /* Style for the timestamp DATA field */
# QLineEdit#TimestampDataField {{
# font-size: {max(11, int(13 * scale))}pt; /* Larger font */
# font-weight: bold;
# color: #f0f0f0;
# }}
# QGroupBox#ChamberWidget QFormLayout QLabel {{
# font-size: {max(10, int(12 * scale))}px;
# color: #AAAAAA;
# }}
# QGroupBox#ChamberWidget QLineEdit#BatIdField {{
# font-size: {max(14, int(16 * scale))}px;
# font-weight: bold;
# color: #FFFFFF;
# }}
# QGroupBox#ChamberWidget QLineEdit#DataField {{
# font-size: {max(13, int(14 * scale))}px;
# color: #E0E0E0;
# }}
# QGroupBox#ChamberWidget QLineEdit#DoorStatusField {{
# font-size: {max(12, int(14 * scale))}px;
# font-weight: bold;
# color: #E0E0E0;
# }}
# QFrame#aboutCard {{
# background: #2a2a2a;
# border: 1px solid #3a3a3a;
# border-radius: 12px;
# }}
# QFrame#aboutCard QLabel#title {{
# color: #eaeaea;
# font-size: 16pt;
# font-weight: bold;
# }}
# QFrame#aboutCard QLabel#subtitle {{
# color: #c7c7c7;
# }}
# QFrame#aboutCard QLabel#label {{
# color: #b6b6b6;
# font-weight: 600;
# }}
# QFrame#aboutCard QLabel#value {{
# color: #e6e6e6;
# }}
# QFrame#aboutCard QLabel.badge {{
# background: #343a40;
# border: 1px solid #454d55;
# border-radius: 8px;
# padding: 2px 10px;
# color: #e6e6e6;
# font-size: 11px;
# }}
# QFrame#aboutCard QFrame#divider {{
# background: #3a3a3a;
# min-height: 1px; max-height: 1px; border: none;
# }}
# QFrame#aboutCard a {{
# color: #6aa9ff;
# text-decoration: none;
# }}
# #aboutCard {{
# background-color: #3c3c3c;
# border: 1px solid #4a4a4a;
# border-radius: 12px;
# }}
# #aboutCard QLabel#title {{ color: #eaeaea; font-size: 18pt; font-weight: bold; }}
# #aboutCard QLabel#subtitle {{ color: #a0a0a0; }}
# #aboutCard QLabel#label {{ color: #b0b0b0; font-weight: bold; }}
# #aboutCard QLabel.badge {{
# background-color: #555555; border-radius: 8px;
# padding: 3px 10px; font-size: 9pt;
# }}
# #aboutCard QFrame#divider {{ background-color: #4a4a4a; max-height: 1px; border: none; }}
# #aboutCard a {{ color: #6aa9ff; text-decoration: none; }}
# """
# --- Dynamic Theme Stylesheets --- # --- Dynamic Theme Stylesheets ---
def get_light_theme_styles(scale=1.0): def get_light_theme_styles(scale=1.0):
log_font_size = max(10, int(11 * scale)) log_font_size = max(10, int(11 * scale))
button_font_size = max(7, int(10 * scale)) button_font_size = max(7, int(10 * scale))
big_button_font = int(button_font_size * 13 // 10) # 1.3x as int
return f""" return f"""
QMainWindow, QWidget {{ QMainWindow, QWidget {{
background-color: #f0f0f0; background-color: #f0f0f0;
color: #000; color: #000;
}} }}
/* Status Bar */
#StatusBar {{
background-color: #34495e;
padding: {int(6 * scale)}px;
}}
/* Logs */
#LogPanel {{ #LogPanel {{
font-family: "Courier New", Consolas, monospace; font-family: "Courier New", Consolas, monospace;
font-size: {log_font_size}pt; font-size: {log_font_size}pt;
background-color: #212121; background-color: #ffffff;
color: #eceff1; color: #2b2b2b;
border: 1px solid #455a64; border: 1px solid #c8c8c8;
}} }}
/* Containers */
QGroupBox {{ QGroupBox {{
font-family: Arial; font-family: Arial;
border: 1px solid #4a4a4a; border: 1px solid #c8c8c8;
border-radius: {int(8 * scale)}px; border-radius: {int(8 * scale)}px;
margin-top: {int(6 * scale)}px; margin-top: {int(6 * scale)}px;
}} }}
@ -29,6 +455,8 @@ def get_light_theme_styles(scale=1.0):
padding: 0 {int(10 * scale)}px; padding: 0 {int(10 * scale)}px;
color: #000; color: #000;
}} }}
/* Tabs */
QTabWidget::pane {{ border-top: 2px solid #c8c8c8; }} QTabWidget::pane {{ border-top: 2px solid #c8c8c8; }}
QTabBar::tab {{ QTabBar::tab {{
background: #e1e1e1; border: 1px solid #c8c8c8; background: #e1e1e1; border: 1px solid #c8c8c8;
@ -36,91 +464,212 @@ def get_light_theme_styles(scale=1.0):
border-top-left-radius: {int(4 * scale)}px; border-top-right-radius: {int(4 * scale)}px; border-top-left-radius: {int(4 * scale)}px; border-top-right-radius: {int(4 * scale)}px;
}} }}
QTabBar::tab:selected {{ background: #f0f0f0; border-bottom-color: #f0f0f0; }} QTabBar::tab:selected {{ background: #f0f0f0; border-bottom-color: #f0f0f0; }}
/* Forms */
QFormLayout::label {{ color: #000; padding-top: {int(3 * scale)}px; }} QFormLayout::label {{ color: #000; padding-top: {int(3 * scale)}px; }}
QLineEdit, QPlainTextEdit, QComboBox {{ QLineEdit, QPlainTextEdit, QComboBox {{
background-color: #fff; border: 1px solid #c8c8c8; background-color: #fff; border: 1px solid #c8c8c8;
border-radius: {int(4 * scale)}px; padding: {int(4 * scale)}px; border-radius: {int(4 * scale)}px; padding: {int(4 * scale)}px;
font-size: {max(7, int(9 * scale))}pt; font-size: {max(7, int(9 * scale))}pt;
color: #000;
}} }}
QLineEdit:read-only {{ background-color: #e9e9e9; }} QLineEdit:read-only {{ background-color: #e9e9e9; }}
/* Buttons (generic) */
QPushButton {{ QPushButton {{
background-color: #e1e1e1; border: 1px solid #c8c8c8; background-color: #e1e1e1; border: 1px solid #c8c8c8;
padding: {int(5 * scale)}px {int(10 * scale)}px; padding: {int(5 * scale)}px {int(10 * scale)}px;
border-radius: {int(4 * scale)}px; border-radius: {int(4 * scale)}px;
color: #000;
}} }}
QPushButton:hover {{ background-color: #dcdcdc; }} QPushButton:hover {{ background-color: #dcdcdc; }}
QPushButton:pressed {{ background-color: #c8c8c8; }} QPushButton:pressed {{ background-color: #c8c8c8; }}
#RefreshButton, #ResetButton {{
padding: {int(6 * scale)}px {int(16 * scale)}px;
font-size: {button_font_size * 1.3}pt;
font-weight: bold;
border-radius: {int(4*scale)}px;
}}
#RefreshButton {{
background-color: #2e7d32; /* A slightly darker green */
}}
#ResetButton {{
background-color: #c62828; /* A slightly darker red */
}}
#ChamberOpenDoorButton, #ChamberChgOnButton, #ChamberChgOffButton {{
padding: {int(8 * scale)}px;
font-size: {button_font_size}pt;
font-weight: bold;
border-radius: {int(4*scale)}px;
}}
#ChamberOpenDoorButton {{ background-color: #E1E1E1; }}
#ChamberChgOnButton {{ background-color: #E1E1E1; }}
#ChamberChgOffButton {{ background-color: #E1E1E1; }}
#ChamberOpenDoorButton:hover {{ background-color: #3498DB; }}
#ChamberChgOnButton:hover {{ background-color: #229954; }}
#ChamberChgOffButton:hover {{ background-color: #c0392b; }}
QPushButton:disabled {{ background-color: #d3d3d3; color: #a0a0a0; }} QPushButton:disabled {{ background-color: #d3d3d3; color: #a0a0a0; }}
#ConnectButton, #DisconnectButton {{ /* Primary/Destructive (larger) */
padding: {int(6 * scale)}px {int(16 * scale)}px; #RefreshButton, #ResetButton {{
font-size: {button_font_size}pt; padding: {int(6 * scale)}px {int(16 * scale)}px;
font-weight: bold; font-size: {big_button_font}pt;
border-radius: {int(4 * scale)}px; font-weight: bold;
color: white; color: white;
border-radius: {int(4*scale)}px;
}} }}
#RefreshButton {{ background-color: #2e7d32; }}
#ResetButton {{ background-color: #c62828; }}
#ConnectButton {{ background-color: #27ae60; }} /* Green */ /* Action buttons */
#DisconnectButton {{ background-color: #c0392b; }} /* Red */ #ConnectButton, #DisconnectButton, #StartSwapButton, #AbortSwapButton {{
padding: {int(6 * scale)}px {int(16 * scale)}px;
#ConnectButton:hover {{ background-color: #52be80; }} font-size: {button_font_size}pt;
#DisconnectButton:hover {{ background-color: #cd6155; }} font-weight: bold;
border-radius: {int(4 * scale)}px;
#ConnectButton:pressed {{ background-color: #52be80; }} color: white;
#DisconnectButton:pressed {{ background-color: #cd6155; }} border: none;
}}
#ConnectButton, #StartSwapButton {{ background-color: #28a745; }}
#ConnectButton:hover, #StartSwapButton:hover {{ background-color: #218838; }}
#DisconnectButton, #AbortSwapButton {{ background-color: #dc3545; }}
#DisconnectButton:hover, #AbortSwapButton:hover {{ background-color: #c82333; }}
#ConnectButton:disabled, #DisconnectButton:disabled {{ #ConnectButton:disabled, #DisconnectButton:disabled {{
background-color: #546e7a; background-color: #b0bec5; color: #78909c;
color: #90a4ae;
}} }}
#RefreshButton, #StartSwapButton {{ background-color: #27ae60; color: white; border: none; }} /* Unique */
#RefreshButton:hover, #StartSwapButton:hover {{ background-color: #229954; }} #SendAudioButton {{
#ResetButton, #AbortSwapButton {{ background-color: #c0392b; color: white; border: none; }} background-color: #007bff; color: white; border: none;
#ResetButton:hover, #AbortSwapButton:hover {{ background-color: #c0392b; }} font-size: {max(10, int(14 * scale))}px;
#SendAudioButton {{ background-color: #3498db; color: white; border: none; font-size: {max(10, int(14 * scale))}px; }} }}
#SendAudioButton:hover {{ background-color: #2980b9; }} #SendAudioButton:hover {{ background-color: #0069d9; }}
QLabel[status="present"] {{ background-color: #2ecc71; color: white; border-radius: {int(4*scale)}px; padding: {int(3*scale)}px; }}
QLabel[status="absent"] {{ background-color: #e74c3c; color: white; border-radius: {int(4*scale)}px; padding: {int(3*scale)}px; }} #ChamberOpenDoorButton, #ChamberChgOnButton, #ChamberChgOffButton {{
QLabel[alarm="active"] {{ background-color: #e74c3c; color: white; font-weight: bold; border-radius: {int(4*scale)}px; padding: {int(2*scale)}px; }} padding: {int(8 * scale)}px;
font-size: {button_font_size}pt;
font-weight: bold;
border-radius: {int(4*scale)}px;
}}
#ChamberOpenDoorButton:hover {{ background-color: #607d8b; }}
#ChamberChgOnButton:hover {{ background-color: #52be80; }}
#ChamberChgOffButton:hover {{ background-color: #cd6155; }}
/* Status & alarms */
QLabel[status="present"] {{ background-color: #2ecc71; color: white; border-radius: {int(4*scale)}px; padding: {int(3*scale)}px; }}
QLabel[status="absent"] {{ background-color: #e74c3c; color: white; border-radius: {int(4*scale)}px; padding: {int(3*scale)}px; }}
QLabel[alarm="active"] {{ background-color: #e74c3c; color: white; font-weight: bold; border-radius: {int(4*scale)}px; padding: {int(2*scale)}px; }}
QLabel[alarm="inactive"] {{ background-color: transparent; color: black; }} QLabel[alarm="inactive"] {{ background-color: transparent; color: black; }}
QGroupBox#ChamberWidget {{ border: 2px solid #3498db; }} QGroupBox#ChamberWidget {{ border: 2px solid #007bff; }}
/* Timestamp */
QLabel#TimestampTitleLabel {{
font-size: {max(9, int(11 * scale))}pt; font-weight: bold; color: #333;
}}
QLineEdit#TimestampDataField {{
font-size: {max(11, int(13 * scale))}pt; font-weight: bold; color: #000;
}}
/* ---------- ABOUT CARD (LIGHT) ---------- */
#aboutCard {{
background: #ffffff;
border: 1px solid #dee2e6;
border-radius: 12px;
}}
#aboutCard #title {{
color: #212529;
font-size: {max(16, int(18 * scale))}pt;
font-weight: 700;
letter-spacing: 0.2px;
}}
#aboutCard #subtitle {{
color: #6c757d;
font-size: {max(9, int(11 * scale))}pt;
font-weight: 500;
}}
#aboutCard #kvLabel {{
color: #495057;
font-weight: 600;
}}
#aboutCard #badge {{
color: #0b1220;
background: #e9ecef;
border-radius: 999px;
padding: {int(5*scale)}px {int(12*scale)}px;
font-weight: 700;
letter-spacing: 0.3px;
font-size: {max(8, int(10*scale))}pt;
}}
#aboutCard #divider {{
background: #dee2e6;
min-height: 1px; max-height: 1px; border: none;
margin: {int(6*scale)}px 0 {int(8*scale)}px 0;
}}
#aboutCard #footer {{
color: #7a8794;
font-size: {max(8, int(10*scale))}pt;
}}
#aboutCard a {{
color: #007bff; text-decoration: none;
}}
#aboutCard a:hover {{
text-decoration: underline;
}}
#aboutCard [link="true"] {{
color: #0d6efd; /* Bootstrap dark blue */
text-decoration: none;
}}
#aboutCard [link="true"]:hover {{
color: #0a58ca; /* darker blue on hover */
text-decoration: underline;
}}
/* ---------- HELP CARD (LIGHT) ---------- */
#helpCard {{
background: #ffffff;
border: 1px solid #dee2e6;
border-radius: 12px;
}}
#helpTitle {{
color: #212529;
font-weight: 700;
font-size: 18px;
}}
#helpSubtitle {{
color: #495057;
font-size: 12px;
}}
#helpDivider {{
background: #dee2e6;
min-height: 1px; max-height: 1px; border: none;
}}
#sectionTitle {{
color: #212529;
font-weight: 600;
margin-top: 6px;
}}
#bodyText {{
color: #343a40;
}}
/* Callouts (light theme) */
#tipBox {{
background: #e6ffed;
border: 1px solid #28a745;
border-radius: 8px;
}}
#tipTitle {{ color: #155724; font-weight: 700; }}
#tipText {{ color: #155724; }}
#warnBox {{
background: #fff3cd;
border: 1px solid #ffb74d;
border-radius: 8px;
}}
#warnTitle {{ color: #856404; font-weight: 700; }}
#warnText {{ color: #856404; }}
#link {{
color: #0827F5; /* Current */
text-decoration: none;
}}
#link:hover {{
color: #00008b; /* Hover color */
text-decoration: underline;
}}
""" """
def get_dark_theme_styles(scale=1.0): def get_dark_theme_styles(scale=1.0):
log_font_size = max(10, int(11 * scale)) log_font_size = max(10, int(11 * scale))
button_font_size = max(7, int(10 * scale)) button_font_size = max(7, int(10 * scale))
return f""" return f"""
QMainWindow, QWidget {{ background-color: #2b2b2b; color: #f0f0f0; }} QMainWindow, QWidget {{ background-color: #2b2b2b; color: #f0f0f0; }}
#LogPanel {{ #LogPanel {{
font-family: "Courier New", Consolas, monospace; font-family: "Courier New", Consolas, monospace;
font-size: {log_font_size}pt; font-size: {log_font_size}pt;
@ -128,11 +677,13 @@ def get_dark_theme_styles(scale=1.0):
color: #eceff1; color: #eceff1;
border: 1px solid #455a64; border: 1px solid #455a64;
}} }}
QGroupBox {{ QGroupBox {{
font-family: Arial; border: 1px solid #4a4a4a; font-family: Arial; border: 1px solid #4a4a4a;
border-radius: {int(8 * scale)}px; margin-top: {int(6 * scale)}px; border-radius: {int(8 * scale)}px; margin-top: {int(6 * scale)}px;
}} }}
QGroupBox::title {{ subcontrol-origin: margin; subcontrol-position: top center; padding: 0 {int(10 * scale)}px; color: #f0f0f0; }} QGroupBox::title {{ subcontrol-origin: margin; subcontrol-position: top center; padding: 0 {int(10 * scale)}px; color: #f0f0f0; }}
QTabWidget::pane {{ border-top: 2px solid #4a4a4a; }} QTabWidget::pane {{ border-top: 2px solid #4a4a4a; }}
QTabBar::tab {{ QTabBar::tab {{
background: #3c3c3c; border: 1px solid #4a4a4a; color: #f0f0f0; background: #3c3c3c; border: 1px solid #4a4a4a; color: #f0f0f0;
@ -140,6 +691,7 @@ def get_dark_theme_styles(scale=1.0):
border-top-left-radius: {int(4 * scale)}px; border-top-right-radius: {int(4 * scale)}px; border-top-left-radius: {int(4 * scale)}px; border-top-right-radius: {int(4 * scale)}px;
}} }}
QTabBar::tab:selected {{ background: #2b2b2b; border-bottom-color: #2b2b2b; }} QTabBar::tab:selected {{ background: #2b2b2b; border-bottom-color: #2b2b2b; }}
QFormLayout::label {{ color: #f0f0f0; padding-top: {int(3 * scale)}px; }} QFormLayout::label {{ color: #f0f0f0; padding-top: {int(3 * scale)}px; }}
QLineEdit, QPlainTextEdit, QComboBox {{ QLineEdit, QPlainTextEdit, QComboBox {{
background-color: #3c3c3c; border: 1px solid #4a4a4a; background-color: #3c3c3c; border: 1px solid #4a4a4a;
@ -147,6 +699,7 @@ def get_dark_theme_styles(scale=1.0):
font-size: {max(7, int(9 * scale))}pt; font-size: {max(7, int(9 * scale))}pt;
}} }}
QLineEdit:read-only {{ background-color: #333333; }} QLineEdit:read-only {{ background-color: #333333; }}
QPushButton {{ QPushButton {{
background-color: #555555; border: 1px solid #4a4a4a; background-color: #555555; border: 1px solid #4a4a4a;
padding: {int(5 * scale)}px {int(10 * scale)}px; padding: {int(5 * scale)}px {int(10 * scale)}px;
@ -155,53 +708,44 @@ def get_dark_theme_styles(scale=1.0):
QPushButton:hover {{ background-color: #6a6a6a; }} QPushButton:hover {{ background-color: #6a6a6a; }}
QPushButton:pressed {{ background-color: #4a4a4a; }} QPushButton:pressed {{ background-color: #4a4a4a; }}
QPushButton:disabled {{ background-color: #404040; color: #888888; }} QPushButton:disabled {{ background-color: #404040; color: #888888; }}
#RefreshButton, #ResetButton {{ #RefreshButton, #ResetButton {{
padding: {int(6 * scale)}px {int(16 * scale)}px; padding: {int(6 * scale)}px {int(16 * scale)}px;
font-size: {button_font_size * 1.3}pt; font-size: {int(button_font_size * 1.3)}pt;
font-weight: bold; font-weight: bold;
border-radius: {int(4*scale)}px; border-radius: {int(4*scale)}px;
}} }}
#RefreshButton {{ #RefreshButton {{ background-color: #2e7d32; }}
background-color: #2e7d32; /* A slightly darker green */ #ResetButton {{ background-color: #c62828; }}
}}
#ResetButton {{
background-color: #c62828; /* A slightly darker red */
}}
#ChamberOpenDoorButton, #ChamberChgOnButton, #ChamberChgOffButton {{ #ChamberOpenDoorButton, #ChamberChgOnButton, #ChamberChgOffButton {{
padding: {int(8 * scale)}px; padding: {int(8 * scale)}px;
font-size: {button_font_size}pt; font-size: {button_font_size}pt;
font-weight: bold; font-weight: bold;
border-radius: {int(4*scale)}px; border-radius: {int(4*scale)}px;
}} }}
#ChamberOpenDoorButton {{ background-color: #3C3C3C; }}
#ChamberOpenDoorButton {{ background-color: #3C3C3C; }} #ChamberChgOnButton {{ background-color: #3C3C3C; }}
#ChamberChgOnButton {{ background-color: #3C3C3C; }} #ChamberChgOffButton {{ background-color: #3C3C3C; }}
#ChamberChgOffButton {{ background-color: #3C3C3C; }} #ChamberOpenDoorButton:hover {{ background-color: #607d8b; }}
#ChamberChgOnButton:hover {{ background-color: #52be80; }}
#ChamberOpenDoorButton:hover {{ background-color: #607d8b; }} #ChamberChgOffButton:hover {{ background-color: #cd6155; }}
#ChamberChgOnButton:hover {{ background-color: #52be80; }}
#ChamberChgOffButton:hover {{ background-color: #cd6155; }}
#ConnectButton, #DisconnectButton {{
padding: {int(6 * scale)}px {int(16 * scale)}px;
font-size: {button_font_size}pt;
font-weight: bold;
border-radius: {int(4*scale)}px;
color: white;
}}
#ConnectButton {{ background-color: #27ae60; }} /* Green */
#DisconnectButton {{ background-color: #c0392b; }} /* Red */
#ConnectButton, #DisconnectButton {{
padding: {int(6 * scale)}px {int(16 * scale)}px;
font-size: {button_font_size}pt;
font-weight: bold;
border-radius: {int(4*scale)}px;
color: white;
}}
#ConnectButton {{ background-color: #27ae60; }}
#DisconnectButton {{ background-color: #c0392b; }}
#ConnectButton:hover {{ background-color: #52be80; }} #ConnectButton:hover {{ background-color: #52be80; }}
#DisconnectButton:hover {{ background-color: #cd6155; }} #DisconnectButton:hover {{ background-color: #cd6155; }}
#ConnectButton:pressed {{ background-color: #52be80; }} #ConnectButton:pressed {{ background-color: #52be80; }}
#DisconnectButton:pressed {{ background-color: #cd6155; }} #DisconnectButton:pressed {{ background-color: #cd6155; }}
#ConnectButton:disabled, #DisconnectButton:disabled {{ #ConnectButton:disabled, #DisconnectButton:disabled {{
background-color: #546e7a; background-color: #546e7a; color: #90a4ae;
color: #90a4ae;
}} }}
#RefreshButton, #StartSwapButton {{ background-color: #27ae60; color: white; border: none; }} #RefreshButton, #StartSwapButton {{ background-color: #27ae60; color: white; border: none; }}
@ -210,9 +754,126 @@ def get_dark_theme_styles(scale=1.0):
#ResetButton:hover, #AbortSwapButton:hover {{ background-color: #cd6155; }} #ResetButton:hover, #AbortSwapButton:hover {{ background-color: #cd6155; }}
#SendAudioButton {{ background-color: #3498db; color: white; border: none; font-size: {max(10, int(14 * scale))}px; }} #SendAudioButton {{ background-color: #3498db; color: white; border: none; font-size: {max(10, int(14 * scale))}px; }}
#SendAudioButton:hover {{ background-color: #5dade2; }} #SendAudioButton:hover {{ background-color: #5dade2; }}
QLabel[status="present"] {{ background-color: #2ecc71; color: white; border-radius: {int(4*scale)}px; padding: {int(3*scale)}px; }} QLabel[status="present"] {{ background-color: #2ecc71; color: white; border-radius: {int(4*scale)}px; padding: {int(3*scale)}px; }}
QLabel[status="absent"] {{ background-color: #e74c3c; color: white; border-radius: {int(4*scale)}px; padding: {int(3*scale)}px; }} QLabel[status="absent"] {{ background-color: #e74c3c; color: white; border-radius: {int(4*scale)}px; padding: {int(3*scale)}px; }}
QLabel[alarm="active"] {{ background-color: #e74c3c; color: white; font-weight: bold; border-radius: {int(4*scale)}px; padding: {int(2*scale)}px; }} QLabel[alarm="active"] {{ background-color: #e74c3c; color: white; font-weight: bold; border-radius: {int(4*scale)}px; padding: {int(2*scale)}px; }}
QLabel[alarm="inactive"] {{ background-color: transparent; color: #f0f0f0; }} QLabel[alarm="inactive"] {{ background-color: transparent; color: #f0f0f0; }}
QGroupBox#ChamberWidget {{ border: 2px solid #3498db; }} QGroupBox#ChamberWidget {{ border: 2px solid #3498db; }}
"""
QLabel#TimestampTitleLabel {{
font-size: {max(9, int(11 * scale))}pt;
font-weight: bold;
}}
QLineEdit#TimestampDataField {{
font-size: {max(11, int(13 * scale))}pt;
font-weight: bold;
color: #f0f0f0;
}}
QGroupBox#ChamberWidget QFormLayout QLabel {{
font-size: {max(10, int(12 * scale))}px;
color: #AAAAAA;
}}
QGroupBox#ChamberWidget QLineEdit#BatIdField {{
font-size: {max(14, int(16 * scale))}px;
font-weight: bold;
color: #FFFFFF;
}}
QGroupBox#ChamberWidget QLineEdit#DataField {{
font-size: {max(13, int(14 * scale))}px;
color: #E0E0E0;
}}
QGroupBox#ChamberWidget QLineEdit#DoorStatusField {{
font-size: {max(12, int(14 * scale))}px;
font-weight: bold;
color: #E0E0E0;
}}
/* ---------- ABOUT CARD (DARK) ---------- */
#aboutCard {{
background: #2a2a2a;
border: 1px solid #3a3a3a;
border-radius: 12px;
}}
#aboutCard #title {{
color: #eaeaea;
font-size: {max(16, int(18 * scale))}pt;
font-weight: 700;
letter-spacing: 0.2px;
}}
#aboutCard #subtitle {{
color: #aab4c0;
font-size: {max(9, int(11 * scale))}pt;
font-weight: 500;
}}
#aboutCard #kvLabel {{
color: #b6b6b6;
font-weight: 600;
}}
#aboutCard #badge {{
color: #eaf3ff;
background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #2563eb, stop:1 #7c3aed);
border-radius: 999px;
padding: {int(5*scale)}px {int(12*scale)}px;
font-weight: 700;
letter-spacing: 0.3px;
font-size: {max(8, int(10*scale))}pt;
text-transform: uppercase;
}}
#aboutCard #divider {{
background: #3a3a3a;
margin: {int(6*scale)}px 0 {int(8*scale)}px 0;
min-height: 1px;
max-height: 1px;
border: none;
}}
#aboutCard #footer {{
color: #7e8895;
font-size: {max(8, int(10*scale))}pt;
}}
#aboutCard a {{
color: #6aa9ff; text-decoration: none;
}}
#aboutCard a:hover {{ color: #82b1ff; text-decoration: underline; }}
/* ---------- HELP CARD (DARK) ---------- */
#helpCard {{
background: #2a2a2a;
border: 1px solid #3a3a3a;
border-radius: 12px;
}}
#helpTitle {{ color: #eaeaea; font-weight: 700; font-size: 18px; }}
#helpSubtitle {{ color: #c8c8c8; font-size: 12px; }}
#helpDivider {{ background: #3a3a3a; min-height: 1px; max-height: 1px; border: none; }}
#sectionTitle {{ color: #e6e6e6; font-weight: 600; margin-top: 6px; }}
#bodyText {{ color: #dcdcdc; }}
/* Callouts (dark theme) */
#tipBox {{
background: #1f3326;
border: 1px solid #62d39b;
border-radius: 8px;
}}
#tipTitle {{ color: #a8f5c9; font-weight: 700; }}
#tipText {{ color: #d6ffe9; }}
#warnBox {{
background: #3b2e1b;
border: 1px solid #ffb74d;
border-radius: 8px;
}}
#warnTitle {{ color: #ffd561; font-weight: 700; }}
#warnText {{ color: #f0e0c0; }}
#link {{
color: #007bff; /* Standard Bootstrap blue */
text-decoration: none;
}}
#link:hover {{
color: #0056b3; /* Darker blue on hover */
text-decoration: underline;
}}
"""

View File

@ -17,24 +17,6 @@ class ChamberWidget(QGroupBox):
self.setObjectName("ChamberWidget") self.setObjectName("ChamberWidget")
self.setFont(QFont("Arial", max(8, int(9 * scale)), QFont.Weight.Bold)) self.setFont(QFont("Arial", max(8, int(9 * scale)), QFont.Weight.Bold))
# --- ADD THIS STYLESHEET TO CONTROL FONT SIZES ---
self.setStyleSheet(f"""
QFormLayout QLabel {{
font-size: {max(10, int(12 * scale))}px;
color: #AAAAAA;
}}
QLineEdit#BatIdField {{
font-size: {max(14, int(16 * scale))}px;
font-weight: bold;
color: #FFFFFF;
}}
QLineEdit#DataField {{
font-size: {max(13, int(14 * scale))}px;
color: #E0E0E0;
}}
""")
# --- END OF STYLESHEET ---
main_layout = QVBoxLayout(self) main_layout = QVBoxLayout(self)
main_layout.setSpacing(max(2, int(4 * scale))) main_layout.setSpacing(max(2, int(4 * scale)))
@ -90,7 +72,7 @@ class ChamberWidget(QGroupBox):
self.chg_temp_field.setObjectName("DataField") self.chg_temp_field.setObjectName("DataField")
self.door_status_field = self._create_data_field(scale) self.door_status_field = self._create_data_field(scale)
self.door_status_field.setObjectName("DataField") self.door_status_field.setObjectName("DoorStatusField")
self.charger_fault_field = self._create_data_field(scale) self.charger_fault_field = self._create_data_field(scale)
self.charger_fault_field.setObjectName("DataField") self.charger_fault_field.setObjectName("DataField")