diff --git a/CMakeLists.txt b/CMakeLists.txt index ea93824..bfed308 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ qt_add_executable(wsapp main.cpp GamesPanel.cpp GamesPanel.h LogPanel.cpp LogPanel.h + PanelsPanel.cpp PanelsPanel.h PowerPanel.cpp PowerPanel.h SettingsTree.cpp SettingsTree.h VersionsPanel.cpp VersionsPanel.h @@ -27,7 +28,7 @@ if(EMSCRIPTEN) set_target_properties(wsapp PROPERTIES WIN32_EXECUTABLE TRUE QT_WASM_INITIAL_MEMORY "50MB" - QT_WASM_MAX_MEMORY "1GB" + QT_WASM_MAX_MEMORY "4GB" ) target_link_options(wsapp PRIVATE "SHELL:-s ASYNCIFY=1" diff --git a/PanelsPanel.cpp b/PanelsPanel.cpp new file mode 100644 index 0000000..2690d13 --- /dev/null +++ b/PanelsPanel.cpp @@ -0,0 +1,106 @@ +#include "PanelsPanel.h" + +#include +#include +#include +#include +#include + +PanelsPanel::PanelsPanel(QWidget *parent) : QWidget(parent) +{ + auto *layout = new QVBoxLayout(this); + layout->setContentsMargins(4, 4, 4, 4); + + m_statusLabel = new QLabel("Connect to load panels...", this); + m_statusLabel->setAlignment(Qt::AlignHCenter | Qt::AlignTop); + m_statusLabel->setStyleSheet("color: #888; font-style: italic;"); + layout->addWidget(m_statusLabel); + layout->addStretch(1); + + setMinimumSize(200, 200); +} + +void PanelsPanel::setRnpCount(int count) +{ + m_count = count; + m_colors.clear(); + m_statusLabel->setText(count > 0 + ? QString("%1 panel%2").arg(count).arg(count == 1 ? "" : "s") + : "No panels reported"); + update(); +} + +void PanelsPanel::setPanelColor(int index, const QColor &color) +{ + m_colors[index] = color; + update(); +} + +void PanelsPanel::reset() +{ + m_count = 0; + m_colors.clear(); + m_statusLabel->setText("Connect to load panels..."); + update(); +} + +void PanelsPanel::paintEvent(QPaintEvent *) +{ + if (m_count <= 0) return; + + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing, true); + + const QRectF bounds = rect(); + const QRectF drawArea = bounds.adjusted(0, 28, 0, 0); + const double side = qMin(drawArea.width(), drawArea.height()) * 0.88; + const double cx = drawArea.center().x(); + const double cy = drawArea.center().y(); + const double outerR = side / 2.0; + const double innerR = outerR * 0.44; + + const QRectF outerRect(cx - outerR, cy - outerR, side, side); + const QRectF innerRect(cx - innerR, cy - innerR, innerR * 2, innerR * 2); + + const double gapDeg = (m_count > 1) ? 3.0 : 0.0; + const double segSpan = (360.0 - m_count * gapDeg) / m_count; + + static const QColor kDefault(100, 140, 180); + + for (int i = 0; i < m_count; ++i) { + const double startAngle = 90.0 - i * (segSpan + gapDeg); + const double sweep = -segSpan; + + QPainterPath path; + path.arcMoveTo(outerRect, startAngle); + path.arcTo(outerRect, startAngle, sweep); + path.arcTo(innerRect, startAngle + sweep, -sweep); + path.closeSubpath(); + + p.setBrush(m_colors.value(i, kDefault)); + p.setPen(QPen(Qt::white, 2)); + p.drawPath(path); + + // Label at segment midpoint + const double midAngle = startAngle + sweep / 2.0; + const double midAngleRad = qDegreesToRadians(midAngle); + const double midR = (outerR + innerR) / 2.0; + const QPointF tc(cx + midR * qCos(midAngleRad), + cy - midR * qSin(midAngleRad)); + + QFont font = p.font(); + font.setBold(true); + font.setPointSize(qMax(7, qMin(12, static_cast(outerR / (m_count * 0.55 + 2))))); + p.setFont(font); + p.setPen(Qt::white); + p.drawText(QRectF(tc.x() - 22, tc.y() - 11, 44, 22), Qt::AlignCenter, QString::number(i)); + } + + // Centre hole: total count + QFont cf = p.font(); + cf.setBold(true); + cf.setPointSize(qMax(8, static_cast(innerR * 0.45))); + p.setFont(cf); + p.setPen(palette().text().color()); + p.drawText(innerRect, Qt::AlignCenter, QString::number(m_count)); +} diff --git a/PanelsPanel.h b/PanelsPanel.h new file mode 100644 index 0000000..10034d4 --- /dev/null +++ b/PanelsPanel.h @@ -0,0 +1,25 @@ +#pragma once +#include +#include +#include + +class QLabel; + +class PanelsPanel : public QWidget +{ + Q_OBJECT +public: + explicit PanelsPanel(QWidget *parent = nullptr); + + void setRnpCount(int count); + void setPanelColor(int index, const QColor &color); + void reset(); + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + int m_count = 0; + QMap m_colors; + QLabel *m_statusLabel = nullptr; +}; diff --git a/WebSocketController.cpp b/WebSocketController.cpp index ded4cc5..ff107c6 100644 --- a/WebSocketController.cpp +++ b/WebSocketController.cpp @@ -1,6 +1,7 @@ #include "WebSocketController.h" #include "GamesPanel.h" #include "LogPanel.h" +#include "PanelsPanel.h" #include "PowerPanel.h" #include "SettingsTree.h" #include "VersionsPanel.h" @@ -27,10 +28,11 @@ void WebSocketController::setSettingsTree(SettingsTree *tree) this, &WebSocketController::onValueEdited); } -void WebSocketController::setGamesPanel(GamesPanel *panel) { m_gamesPanel = panel; } -void WebSocketController::setVersionsPanel(VersionsPanel *p) { m_versionsPanel = p; } -void WebSocketController::setPowerPanel(PowerPanel *panel) { m_powerPanel = panel; } -void WebSocketController::setLogPanel(LogPanel *panel) { m_logPanel = panel; } +void WebSocketController::setGamesPanel(GamesPanel *panel) { m_gamesPanel = panel; } +void WebSocketController::setVersionsPanel(VersionsPanel *p) { m_versionsPanel = p; } +void WebSocketController::setPowerPanel(PowerPanel *panel) { m_powerPanel = panel; } +void WebSocketController::setLogPanel(LogPanel *panel) { m_logPanel = panel; } +void WebSocketController::setPanelsPanel(PanelsPanel *panel) { m_panelsPanel = panel; } bool WebSocketController::isConnected() const { @@ -53,6 +55,7 @@ void WebSocketController::closeConnection() m_rnpCount = -1; if (m_versionsPanel) m_versionsPanel->reset(); if (m_powerPanel) m_powerPanel->reset(); + if (m_panelsPanel) m_panelsPanel->reset(); m_socket.close(); } @@ -71,7 +74,7 @@ void WebSocketController::onConnected() sendCommand(QStringLiteral("GBL List")); sendCommand(QStringLiteral("GAM list")); - sendCommand(QStringLiteral("GAM listall")); // <-- fetch full catalogue + sendCommand(QStringLiteral("GAM listall")); sendCommand(QStringLiteral("NAM")); sendCommand(QStringLiteral("VER")); sendCommand(QStringLiteral("UID")); @@ -80,6 +83,12 @@ void WebSocketController::onConnected() sendCommand(QStringLiteral("#P0-P RTV")); sendCommand(QStringLiteral("#P0-P VTG")); sendCommand(QStringLiteral("LOG")); + sendCommand(QStringLiteral("GBL brightnessMin")); + sendCommand(QStringLiteral("GBL brightnessMax")); + sendCommand(QStringLiteral("GBL brightness")); + sendCommand(QStringLiteral("GBL sound/volumeMin")); + sendCommand(QStringLiteral("GBL sound/volumeMax")); + sendCommand(QStringLiteral("GBL sound/volume")); } void WebSocketController::onDisconnected() @@ -88,6 +97,7 @@ void WebSocketController::onDisconnected() m_statusLabel->setText("Disconnected"); m_settingsKeys.clear(); m_rnpCount = -1; + if (m_panelsPanel) m_panelsPanel->reset(); } void WebSocketController::onTextMessageReceived(const QString &msg) @@ -114,7 +124,7 @@ void WebSocketController::handleProtocol(const QString &msg) if (tokens.isEmpty()) return; const QString cmd = tokens[0]; - // ---- Power: #P0-P STA/RTV/VTG ---- + // ---- Power ---- if (cmd == "#P0-P" && tokens.size() >= 2 && m_powerPanel) { const QString sub = tokens[1]; if (sub == "STA" && tokens.size() >= 3) m_powerPanel->setStatus(tokens[2]); @@ -123,7 +133,7 @@ void WebSocketController::handleProtocol(const QString &msg) return; } - // ---- LOG channel list ---- + // ---- LOG ---- if (cmd == "LOG" && tokens.size() > 1 && m_logPanel) { bool isList = true; for (int i = 1; i < tokens.size(); ++i) @@ -140,15 +150,13 @@ void WebSocketController::handleProtocol(const QString &msg) // ---- VER ---- if (cmd == "VER" && tokens.size() >= 3) { - if (m_versionsPanel) - m_versionsPanel->setVersion(tokens[1], tokens.mid(2).join(' ')); + if (m_versionsPanel) m_versionsPanel->setVersion(tokens[1], tokens.mid(2).join(' ')); return; } // ---- UID ---- if (cmd == "UID" && tokens.size() >= 3) { - if (m_versionsPanel) - m_versionsPanel->setUid(tokens[1], tokens.mid(2).join(' ')); + if (m_versionsPanel) m_versionsPanel->setUid(tokens[1], tokens.mid(2).join(' ')); return; } @@ -159,6 +167,7 @@ void WebSocketController::handleProtocol(const QString &msg) if (ok && count > 0) { m_rnpCount = count; if (m_versionsPanel) m_versionsPanel->setRnpCount(count); + if (m_panelsPanel) m_panelsPanel->setRnpCount(count); for (int i = 0; i < count; ++i) { sendCommand(QString("VER %1").arg(i)); sendCommand(QString("UID %1").arg(i)); @@ -167,13 +176,13 @@ void WebSocketController::handleProtocol(const QString &msg) return; } - // ---- GAM listall — full catalogue ---- + // ---- GAM listall ---- if (cmd == "GAM" && tokens.size() >= 2 && tokens[1] == "listall") { if (m_gamesPanel) m_gamesPanel->loadAllFromResponse(msg); return; } - // ---- GAM list — installed games ---- + // ---- GAM list ---- if (cmd == "GAM" && tokens.size() >= 2 && tokens[1] == "list") { if (m_gamesPanel) m_gamesPanel->loadFromResponse(msg); return; diff --git a/WebSocketController.h b/WebSocketController.h index e65880a..3f436d8 100644 --- a/WebSocketController.h +++ b/WebSocketController.h @@ -1,14 +1,16 @@ #pragma once #include +#include #include #include +class GamesPanel; +class LogPanel; +class PanelsPanel; +class PowerPanel; class QLabel; class QLineEdit; class QTextEdit; -class GamesPanel; -class LogPanel; -class PowerPanel; class SettingsTree; class VersionsPanel; @@ -16,43 +18,48 @@ class WebSocketController : public QObject { Q_OBJECT public: - explicit WebSocketController(QLineEdit *urlEdit, - QLabel *statusLabel, - QObject *parent = nullptr); + explicit WebSocketController(QLineEdit *urlEdit, QLabel *statusLabel, + QObject *parent = nullptr); QWebSocket *socket(); + bool isConnected() const; + void addLogView(QTextEdit *log); void setSettingsTree(SettingsTree *tree); void setGamesPanel(GamesPanel *panel); void setVersionsPanel(VersionsPanel *panel); void setPowerPanel(PowerPanel *panel); void setLogPanel(LogPanel *panel); - bool isConnected() const; + void setPanelsPanel(PanelsPanel *panel); public slots: void startConnection(); void closeConnection(); void sendCommand(const QString &cmd); - void onConnected(); void onDisconnected(); - void onTextMessageReceived(const QString &msg); + void onTextMessageReceived(const QString &message); void onErrorOccurred(QAbstractSocket::SocketError error); + +private slots: void onValueEdited(const QString &key, const QString &newValue); private: void handleProtocol(const QString &msg); void broadcast(const QString &line); - QLineEdit *m_urlEdit = nullptr; - QLabel *m_statusLabel = nullptr; - QWebSocket m_socket; - QList m_logs; + QWebSocket m_socket; + QLineEdit *m_urlEdit = nullptr; + QLabel *m_statusLabel = nullptr; + QList m_logs; + SettingsTree *m_settingsTree = nullptr; GamesPanel *m_gamesPanel = nullptr; VersionsPanel *m_versionsPanel = nullptr; PowerPanel *m_powerPanel = nullptr; - LogPanel *m_logPanel = nullptr; - QStringList m_settingsKeys; - int m_rnpCount = -1; + LogPanel *m_logPanel = nullptr; + PanelsPanel *m_panelsPanel = nullptr; + + QStringList m_settingsKeys; + int m_rnpCount = -1; }; diff --git a/main.cpp b/main.cpp index c9c198f..4f7ab33 100644 --- a/main.cpp +++ b/main.cpp @@ -1,12 +1,11 @@ #include -#include -#include #include +#include +#include #include #include -#include +#include #include -#include #include #include #include @@ -22,6 +21,7 @@ #include "GamesPanel.h" #include "LogPanel.h" +#include "PanelsPanel.h" #include "PowerPanel.h" #include "SettingsTree.h" #include "VersionsPanel.h" @@ -32,9 +32,7 @@ #endif // --------------------------------------------------------------------------- -// Copy text to the OS clipboard. -// Uses navigator.clipboard (modern, async) with execCommand fallback (HTTP). -// Falls back to Qt clipboard on non-WASM builds. +// OS clipboard bridge (works from button clicks in WASM via navigator.clipboard) // --------------------------------------------------------------------------- static void copyToClipboard(const QString &text) { @@ -45,23 +43,15 @@ static void copyToClipboard(const QString &text) if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(txt).catch(function() { var ta = document.createElement('textarea'); - ta.value = txt; - ta.style.position = 'fixed'; - ta.style.opacity = '0'; - document.body.appendChild(ta); - ta.focus(); ta.select(); - document.execCommand('copy'); - document.body.removeChild(ta); + ta.value = txt; ta.style.position='fixed'; ta.style.opacity='0'; + document.body.appendChild(ta); ta.focus(); ta.select(); + document.execCommand('copy'); document.body.removeChild(ta); }); } else { var ta = document.createElement('textarea'); - ta.value = txt; - ta.style.position = 'fixed'; - ta.style.opacity = '0'; - document.body.appendChild(ta); - ta.focus(); ta.select(); - document.execCommand('copy'); - document.body.removeChild(ta); + ta.value = txt; ta.style.position='fixed'; ta.style.opacity='0'; + document.body.appendChild(ta); ta.focus(); ta.select(); + document.execCommand('copy'); document.body.removeChild(ta); } }, utf8.constData(), utf8.size()); #else @@ -69,11 +59,24 @@ static void copyToClipboard(const QString &text) #endif } +// --------------------------------------------------------------------------- +// ICON number → IP address +// --------------------------------------------------------------------------- +static const auto iconToIp = [](int icon) -> QString { + static const QHash overrides = { + {280, QStringLiteral("10.10.1.30")}, + }; + if (overrides.contains(icon)) return overrides[icon]; + const int subnet = (icon - 1) / 254; + const int host = ((icon - 1) % 254) + 1; + return QString("10.10.%1.%2").arg(subnet).arg(host); +}; + +// --------------------------------------------------------------------------- static QWidget *makeGamesTab(WebSocketController *ctrl, QWidget *parent) { auto *panel = new GamesPanel(parent); ctrl->setGamesPanel(panel); - // Route GAM add commands back through the controller QObject::connect(panel, &GamesPanel::commandRequested, ctrl, &WebSocketController::sendCommand); return panel; @@ -93,25 +96,20 @@ static QWidget *makeSettingsTab(WebSocketController *ctrl, QWidget *parent) layout->setContentsMargins(2, 2, 2, 2); layout->setSpacing(4); - // --- Sliders row: brightness and volume side by side (top) --- - auto *slidersRow = new QHBoxLayout(); - - auto *brightnessLabel = new QLabel("Brightness:", page); + // --- Sliders row (top) --- + auto *slidersRow = new QHBoxLayout(); + auto *brightnessLabel = new QLabel("Brightness:", page); auto *brightnessSlider = new QSlider(Qt::Horizontal, page); - brightnessSlider->setMinimum(1); - brightnessSlider->setMaximum(12); - brightnessSlider->setValue(1); - brightnessSlider->setEnabled(false); - auto *brightnessVal = new QLabel("–", page); + brightnessSlider->setMinimum(1); brightnessSlider->setMaximum(12); + brightnessSlider->setValue(1); brightnessSlider->setEnabled(false); + auto *brightnessVal = new QLabel("–", page); brightnessVal->setFixedWidth(28); auto *volumeLabel = new QLabel("Volume:", page); auto *volumeSlider = new QSlider(Qt::Horizontal, page); - volumeSlider->setMinimum(1); - volumeSlider->setMaximum(6); - volumeSlider->setValue(1); - volumeSlider->setEnabled(false); - auto *volumeVal = new QLabel("–", page); + volumeSlider->setMinimum(1); volumeSlider->setMaximum(6); + volumeSlider->setValue(1); volumeSlider->setEnabled(false); + auto *volumeVal = new QLabel("–", page); volumeVal->setFixedWidth(28); slidersRow->addWidget(brightnessLabel); @@ -123,57 +121,45 @@ static QWidget *makeSettingsTab(WebSocketController *ctrl, QWidget *parent) slidersRow->addWidget(volumeVal); layout->addLayout(slidersRow); - // --- Tree (takes all spare vertical space) --- + // --- Tree --- auto *tree = new SettingsTree(page); auto *ph = new QTreeWidgetItem(tree); ph->setText(0, "Connect to load settings..."); layout->addWidget(tree, 1); ctrl->setSettingsTree(tree); - // --- Single compact toolbar row: name input + 3 GBL buttons + checkbox (below tree) --- + // --- Toolbar (below tree) --- auto *toolbar = new QHBoxLayout(); auto *nameEdit = new QLineEdit(page); nameEdit->setPlaceholderText("Config name..."); nameEdit->setMaximumWidth(160); - auto *saveBtn = new QPushButton("Save to Device", page); + auto *saveBtn = new QPushButton("Save to Device", page); auto *loadBtn = new QPushButton("Restore from Device", page); - auto *resetBtn = new QPushButton("Restore Defaults", page); + auto *resetBtn = new QPushButton("Restore Defaults", page); resetBtn->setStyleSheet( - "QPushButton { color: white; background-color: #c62828; border-radius: 4px; padding: 3px 8px; }" - "QPushButton:hover { background-color: #b71c1c; }" - "QPushButton:disabled { background-color: #888888; color: #cccccc; }"); + "QPushButton { color:white; background:#c62828; border-radius:4px; padding:3px 8px; }" + "QPushButton:hover { background:#b71c1c; }" + "QPushButton:disabled { background:#888; color:#ccc; }"); auto *autoPowerOff = new QCheckBox("Auto Power Off", page); - toolbar->addWidget(nameEdit); - toolbar->addWidget(saveBtn); - toolbar->addWidget(loadBtn); - toolbar->addWidget(resetBtn); - toolbar->addStretch(1); - toolbar->addWidget(autoPowerOff); + toolbar->addWidget(nameEdit); toolbar->addWidget(saveBtn); + toolbar->addWidget(loadBtn); toolbar->addWidget(resetBtn); + toolbar->addStretch(1); toolbar->addWidget(autoPowerOff); layout->addLayout(toolbar); - // --- Reset to defaults (inline confirm row — no QMessageBox::exec on WASM) --- + // --- Reset confirm row --- auto *confirmRow = new QHBoxLayout(); - auto *confirmLabel = new QLabel(page); - confirmLabel->setText("Click again to confirm reset:"); - confirmLabel->setStyleSheet("color: #c62828;"); - confirmLabel->setVisible(false); + auto *confirmLabel = new QLabel("Click again to confirm reset:", page); + confirmLabel->setStyleSheet("color:#c62828;"); confirmLabel->setVisible(false); auto *confirmYesBtn = new QPushButton("Yes, Reset", page); - confirmYesBtn->setStyleSheet( - "QPushButton { color: white; background-color: #c62828; border-radius: 4px; padding: 2px 8px; }"); + confirmYesBtn->setStyleSheet("QPushButton{color:white;background:#c62828;border-radius:4px;padding:2px 8px;}"); confirmYesBtn->setVisible(false); auto *confirmNoBtn = new QPushButton("Cancel", page); confirmNoBtn->setVisible(false); - confirmRow->addWidget(confirmLabel); - confirmRow->addWidget(confirmYesBtn); - confirmRow->addWidget(confirmNoBtn); - confirmRow->addStretch(1); + confirmRow->addWidget(confirmLabel); confirmRow->addWidget(confirmYesBtn); + confirmRow->addWidget(confirmNoBtn); confirmRow->addStretch(1); layout->addLayout(confirmRow); - // ---------------------------------------------------------------- - // Connections - // ---------------------------------------------------------------- - - // Save config + // Save auto doSave = [ctrl, nameEdit]() { QString name = nameEdit->text().trimmed(); name.remove(QRegularExpression("[^A-Za-z0-9]")); @@ -183,44 +169,31 @@ static QWidget *makeSettingsTab(WebSocketController *ctrl, QWidget *parent) }; QObject::connect(saveBtn, &QPushButton::clicked, page, doSave); QObject::connect(nameEdit, &QLineEdit::returnPressed, page, doSave); - - // Restore from file - QObject::connect(loadBtn, &QPushButton::clicked, page, [ctrl, nameEdit]() { + QObject::connect(loadBtn, &QPushButton::clicked, page, [ctrl, nameEdit]() { QString name = nameEdit->text().trimmed(); name.remove(QRegularExpression("[^A-Za-z0-9]")); if (name.isEmpty()) return; ctrl->sendCommand(QString("GBL LoadFromFile %1").arg(name)); nameEdit->clear(); }); - - // Reset confirm QObject::connect(resetBtn, &QPushButton::clicked, page, [confirmLabel, confirmYesBtn, confirmNoBtn]() { - confirmLabel->setVisible(true); - confirmYesBtn->setVisible(true); - confirmNoBtn->setVisible(true); + confirmLabel->setVisible(true); confirmYesBtn->setVisible(true); confirmNoBtn->setVisible(true); }); QObject::connect(confirmYesBtn, &QPushButton::clicked, page, [ctrl, confirmLabel, confirmYesBtn, confirmNoBtn]() { ctrl->sendCommand(QStringLiteral("GBL ResetAllToDefaults")); - confirmLabel->setVisible(false); - confirmYesBtn->setVisible(false); - confirmNoBtn->setVisible(false); + confirmLabel->setVisible(false); confirmYesBtn->setVisible(false); confirmNoBtn->setVisible(false); }); QObject::connect(confirmNoBtn, &QPushButton::clicked, page, [confirmLabel, confirmYesBtn, confirmNoBtn]() { - confirmLabel->setVisible(false); - confirmYesBtn->setVisible(false); - confirmNoBtn->setVisible(false); + confirmLabel->setVisible(false); confirmYesBtn->setVisible(false); confirmNoBtn->setVisible(false); }); - - // Auto power off QObject::connect(autoPowerOff, &QCheckBox::toggled, page, [ctrl](bool checked) { - ctrl->sendCommand(checked ? QStringLiteral("POW EnableAutoOff") - : QStringLiteral("POW KeepOn")); + ctrl->sendCommand(checked ? QStringLiteral("POW EnableAutoOff") : QStringLiteral("POW KeepOn")); }); - // Query brightness/volume ranges+values on connect; disable on disconnect + // Slider: query ranges on connect, disable on disconnect QObject::connect(ctrl->socket(), &QWebSocket::connected, page, [ctrl]() { ctrl->sendCommand(QStringLiteral("GBL brightnessMin")); ctrl->sendCommand(QStringLiteral("GBL brightnessMax")); @@ -231,57 +204,28 @@ static QWidget *makeSettingsTab(WebSocketController *ctrl, QWidget *parent) }); QObject::connect(ctrl->socket(), &QWebSocket::disconnected, page, [brightnessSlider, brightnessVal, volumeSlider, volumeVal]() { - brightnessSlider->setEnabled(false); - brightnessVal->setText(QStringLiteral("–")); - volumeSlider->setEnabled(false); - volumeVal->setText(QStringLiteral("–")); + brightnessSlider->setEnabled(false); brightnessVal->setText("–"); + volumeSlider->setEnabled(false); volumeVal->setText("–"); }); - - // Parse incoming GBL brightness / sound/volume responses QObject::connect(ctrl->socket(), &QWebSocket::textMessageReceived, page, [brightnessSlider, brightnessVal, volumeSlider, volumeVal](const QString &msg) { - // helper: parse "GBL =" and return {ok, value} auto parseGbl = [&](const QString &key) -> std::pair { const QString prefix = QString("GBL %1=").arg(key); - if (!msg.startsWith(prefix)) return {false, 0}; - bool ok = false; - int v = msg.mid(prefix.length()).toInt(&ok); - return {ok, v}; + if (!msg.startsWith(prefix)) return {false,0}; + bool ok=false; int v=msg.mid(prefix.length()).toInt(&ok); + return {ok,v}; }; - - if (auto [ok, v] = parseGbl("brightness"); ok) { - brightnessSlider->setValue(v); - brightnessVal->setText(QString::number(v)); - brightnessSlider->setEnabled(true); - } else if (auto [ok, v] = parseGbl("brightnessMin"); ok) { - brightnessSlider->setMinimum(v); - } else if (auto [ok, v] = parseGbl("brightnessMax"); ok) { - brightnessSlider->setMaximum(v); - } else if (auto [ok, v] = parseGbl("sound/volume"); ok) { - volumeSlider->setValue(v); - volumeVal->setText(QString::number(v)); - volumeSlider->setEnabled(true); - } else if (auto [ok, v] = parseGbl("sound/volumeMin"); ok) { - volumeSlider->setMinimum(v); - } else if (auto [ok, v] = parseGbl("sound/volumeMax"); ok) { - volumeSlider->setMaximum(v); - } - }); - - // Live label update while dragging; send command only on release - QObject::connect(brightnessSlider, &QSlider::valueChanged, page, - [brightnessVal](int v) { brightnessVal->setText(QString::number(v)); }); - QObject::connect(brightnessSlider, &QSlider::sliderReleased, page, - [ctrl, brightnessSlider]() { - ctrl->sendCommand(QString("GBL brightness=%1").arg(brightnessSlider->value())); - }); - - QObject::connect(volumeSlider, &QSlider::valueChanged, page, - [volumeVal](int v) { volumeVal->setText(QString::number(v)); }); - QObject::connect(volumeSlider, &QSlider::sliderReleased, page, - [ctrl, volumeSlider]() { - ctrl->sendCommand(QString("GBL sound/volume=%1").arg(volumeSlider->value())); + if (auto [ok,v]=parseGbl("brightness"); ok) { brightnessSlider->setValue(v); brightnessVal->setText(QString::number(v)); brightnessSlider->setEnabled(true); } + else if (auto [ok,v]=parseGbl("brightnessMin"); ok) { brightnessSlider->setMinimum(v); } + else if (auto [ok,v]=parseGbl("brightnessMax"); ok) { brightnessSlider->setMaximum(v); } + else if (auto [ok,v]=parseGbl("sound/volume"); ok) { volumeSlider->setValue(v); volumeVal->setText(QString::number(v)); volumeSlider->setEnabled(true); } + else if (auto [ok,v]=parseGbl("sound/volumeMin"); ok) { volumeSlider->setMinimum(v); } + else if (auto [ok,v]=parseGbl("sound/volumeMax"); ok) { volumeSlider->setMaximum(v); } }); + QObject::connect(brightnessSlider, &QSlider::valueChanged, page, [brightnessVal](int v){ brightnessVal->setText(QString::number(v)); }); + QObject::connect(brightnessSlider, &QSlider::sliderReleased,page, [ctrl,brightnessSlider](){ ctrl->sendCommand(QString("GBL brightness=%1").arg(brightnessSlider->value())); }); + QObject::connect(volumeSlider, &QSlider::valueChanged, page, [volumeVal](int v){ volumeVal->setText(QString::number(v)); }); + QObject::connect(volumeSlider, &QSlider::sliderReleased,page, [ctrl,volumeSlider](){ ctrl->sendCommand(QString("GBL sound/volume=%1").arg(volumeSlider->value())); }); return page; } @@ -293,13 +237,7 @@ static QWidget *makeManualTab(WebSocketController *ctrl, QWidget *parent) layout->setContentsMargins(2, 2, 2, 2); layout->setSpacing(4); - // 4 independent command input boxes (Enter to send, no Send button) - const QStringList placeholders = { - "Command 1...", - "Command 2...", - "Command 3...", - "Command 4...", - }; + const QStringList placeholders = {"Command 1...","Command 2...","Command 3...","Command 4..."}; auto *inputGrid = new QGridLayout(); inputGrid->setSpacing(4); for (int i = 0; i < 4; ++i) { @@ -314,27 +252,21 @@ static QWidget *makeManualTab(WebSocketController *ctrl, QWidget *parent) } layout->addLayout(inputGrid); - // Log view + action buttons auto *log = new QTextEdit(page); log->setReadOnly(true); log->setFont(QFont("Courier", 9)); - auto *logBtnRow = new QHBoxLayout(); auto *copyLogBtn = new QPushButton("Copy Log", page); auto *clearBtn = new QPushButton("Clear Log", page); copyLogBtn->setMaximumWidth(110); clearBtn->setMaximumWidth(100); - logBtnRow->addWidget(copyLogBtn); - logBtnRow->addWidget(clearBtn); - logBtnRow->addStretch(1); - + logBtnRow->addWidget(copyLogBtn); logBtnRow->addWidget(clearBtn); logBtnRow->addStretch(1); layout->addWidget(log, 1); layout->addLayout(logBtnRow); ctrl->addLogView(log); QObject::connect(clearBtn, &QPushButton::clicked, log, &QTextEdit::clear); - QObject::connect(copyLogBtn, &QPushButton::clicked, page, - [log]() { copyToClipboard(log->toPlainText()); }); + QObject::connect(copyLogBtn, &QPushButton::clicked, page, [log](){ copyToClipboard(log->toPlainText()); }); return page; } @@ -354,13 +286,9 @@ static QWidget *makeLogsTab(WebSocketController *ctrl, QWidget *parent) static QWidget *makePanelsTab(WebSocketController *ctrl, QWidget *parent) { - auto *page = new QWidget(parent); - auto *layout = new QVBoxLayout(page); - auto *log = new QTextEdit(page); - log->setReadOnly(true); - layout->addWidget(log, 1); - ctrl->addLogView(log); - return page; + auto *panel = new PanelsPanel(parent); + ctrl->setPanelsPanel(panel); + return panel; } int main(int argc, char *argv[]) @@ -374,108 +302,66 @@ int main(int argc, char *argv[]) mainLayout->setContentsMargins(6, 6, 6, 6); mainLayout->setSpacing(4); - // --- ICON → IP helper (formula + hardcoded overrides) --- - // Returns the IP string for a given ICON number. - static const auto iconToIp = [](int icon) -> QString { - static const QHash overrides = { - {280, QStringLiteral("10.10.1.30")}, - }; - if (overrides.contains(icon)) - return overrides[icon]; - const int subnet = (icon - 1) / 254; - const int host = ((icon - 1) % 254) + 1; - return QString("10.10.%1.%2").arg(subnet).arg(host); - }; - - // --- Hidden urlEdit still used by WebSocketController::startConnection() --- - auto *urlEdit = new QLineEdit(&window); + // --- Hidden urlEdit used by WebSocketController --- + auto *urlEdit = new QLineEdit(&window); urlEdit->setVisible(false); - auto *statusLabel = new QLabel("Disconnected", &window); // --- Network connection row --- - auto *headerRow = new QHBoxLayout(); + auto *headerRow = new QHBoxLayout(); headerRow->addWidget(new QLabel("Network connection:", &window)); - // Option 1 – fixed IP - auto *radioFixed = new QRadioButton("10.110.110.10", &window); - - // Option 2 – ICON number → derived IP - auto *radioIcon = new QRadioButton(&window); - auto *iconSpin = new QSpinBox(&window); - iconSpin->setRange(1, 9999); - iconSpin->setValue(280); - iconSpin->setFixedWidth(68); - iconSpin->setAlignment(Qt::AlignRight); - auto *iconIpLabel = new QLabel(iconToIp(280), &window); - iconIpLabel->setStyleSheet("color: #555; font-style: italic;"); - - // Option 3 – free text (hostname or raw IP) - auto *radioOther = new QRadioButton("other:", &window); - auto *otherEdit = new QLineEdit(&window); - otherEdit->setText("ftsDevkit4.local"); - otherEdit->setFixedWidth(160); - otherEdit->setEnabled(false); // only active when radioOther selected + auto *radioFixed = new QRadioButton("10.110.110.10", &window); + auto *radioIcon = new QRadioButton(&window); + auto *iconSpin = new QSpinBox(&window); + iconSpin->setRange(1, 9999); iconSpin->setValue(280); + iconSpin->setFixedWidth(68); iconSpin->setAlignment(Qt::AlignRight); + auto *iconIpLabel = new QLabel(iconToIp(280), &window); + iconIpLabel->setStyleSheet("color:#555; font-style:italic;"); + auto *radioOther = new QRadioButton("other:", &window); + auto *otherEdit = new QLineEdit(&window); + otherEdit->setText("ftsDevkit4.local"); otherEdit->setFixedWidth(160); + otherEdit->setEnabled(false); auto *btnGroup = new QButtonGroup(&window); btnGroup->addButton(radioFixed, 0); btnGroup->addButton(radioIcon, 1); btnGroup->addButton(radioOther, 2); - radioIcon->setChecked(true); // default selection + radioIcon->setChecked(true); headerRow->addWidget(radioFixed); - headerRow->addWidget(radioIcon); - headerRow->addWidget(iconSpin); - headerRow->addWidget(iconIpLabel); + headerRow->addWidget(radioIcon); headerRow->addWidget(iconSpin); headerRow->addWidget(iconIpLabel); headerRow->addSpacing(8); - headerRow->addWidget(radioOther); - headerRow->addWidget(otherEdit); + headerRow->addWidget(radioOther); headerRow->addWidget(otherEdit); headerRow->addStretch(1); - // Buttons auto *connectBtn = new QPushButton("Connect", &window); auto *disconnectBtn = new QPushButton("Disconnect", &window); auto *shutdownBtn = new QPushButton("Shutdown", &window); - disconnectBtn->setEnabled(false); - shutdownBtn->setEnabled(false); + disconnectBtn->setEnabled(false); shutdownBtn->setEnabled(false); shutdownBtn->setStyleSheet( - "QPushButton { color: white; background-color: #c62828;" - " border-radius: 4px; padding: 3px 10px; }" - "QPushButton:hover { background-color: #b71c1c; }" - "QPushButton:disabled { background-color: #888888; color: #cccccc; }"); - headerRow->addWidget(connectBtn); - headerRow->addWidget(disconnectBtn); - headerRow->addWidget(shutdownBtn); - headerRow->addWidget(statusLabel); + "QPushButton{color:white;background:#c62828;border-radius:4px;padding:3px 10px;}" + "QPushButton:hover{background:#b71c1c;}" + "QPushButton:disabled{background:#888;color:#ccc;}"); + headerRow->addWidget(connectBtn); headerRow->addWidget(disconnectBtn); + headerRow->addWidget(shutdownBtn); headerRow->addWidget(statusLabel); mainLayout->addLayout(headerRow); - // --- Build URL from current selection into the hidden urlEdit --- auto updateUrl = [=]() { const int port = 5424; QString host; - if (radioFixed->isChecked()) { - host = QStringLiteral("10.110.110.10"); - } else if (radioIcon->isChecked()) { - host = iconToIp(iconSpin->value()); - } else { - host = otherEdit->text().trimmed(); - if (host.isEmpty()) host = QStringLiteral("127.0.0.1"); - } + if (radioFixed->isChecked()) host = QStringLiteral("10.110.110.10"); + else if (radioIcon->isChecked()) host = iconToIp(iconSpin->value()); + else { host = otherEdit->text().trimmed(); if (host.isEmpty()) host = "127.0.0.1"; } urlEdit->setText(QString("ws://%1:%2/").arg(host).arg(port)); }; - updateUrl(); // set initial value - - // Keep iconIpLabel in sync and rebuild URL whenever ICON changes + updateUrl(); QObject::connect(iconSpin, QOverload::of(&QSpinBox::valueChanged), &window, - [=](int v) { iconIpLabel->setText(iconToIp(v)); updateUrl(); }); + [=](int v){ iconIpLabel->setText(iconToIp(v)); updateUrl(); }); QObject::connect(btnGroup, QOverload::of(&QButtonGroup::idClicked), &window, - [=](int) { - otherEdit->setEnabled(radioOther->isChecked()); - updateUrl(); - }); - QObject::connect(otherEdit, &QLineEdit::textChanged, &window, [=]() { - if (radioOther->isChecked()) updateUrl(); - }); + [=](int){ otherEdit->setEnabled(radioOther->isChecked()); updateUrl(); }); + QObject::connect(otherEdit, &QLineEdit::textChanged, &window, + [=](){ if (radioOther->isChecked()) updateUrl(); }); // --- Controller --- auto *ctrl = new WebSocketController(urlEdit, statusLabel, &window); @@ -491,60 +377,46 @@ int main(int argc, char *argv[]) tabs->addTab(makePanelsTab (ctrl, &window), "Panels"); mainLayout->addWidget(tabs, 1); - // --- Shutdown: inline confirm (no QMessageBox::exec on WASM) --- + // --- Shutdown inline confirm --- auto *shutdownConfirmLabel = new QLabel("Confirm shutdown:", &window); auto *shutdownConfirmYes = new QPushButton("Yes, Shutdown", &window); auto *shutdownConfirmNo = new QPushButton("Cancel", &window); - shutdownConfirmLabel->setStyleSheet("color: #c62828; font-weight: bold;"); - shutdownConfirmYes->setStyleSheet( - "QPushButton { color: white; background-color: #c62828; border-radius: 4px; padding: 2px 8px; }"); - shutdownConfirmLabel->setVisible(false); - shutdownConfirmYes->setVisible(false); - shutdownConfirmNo->setVisible(false); + shutdownConfirmLabel->setStyleSheet("color:#c62828; font-weight:bold;"); + shutdownConfirmYes->setStyleSheet("QPushButton{color:white;background:#c62828;border-radius:4px;padding:2px 8px;}"); + shutdownConfirmLabel->setVisible(false); shutdownConfirmYes->setVisible(false); shutdownConfirmNo->setVisible(false); headerRow->addWidget(shutdownConfirmLabel); headerRow->addWidget(shutdownConfirmYes); headerRow->addWidget(shutdownConfirmNo); QObject::connect(shutdownBtn, &QPushButton::clicked, &window, - [shutdownConfirmLabel, shutdownConfirmYes, shutdownConfirmNo]() { - shutdownConfirmLabel->setVisible(true); - shutdownConfirmYes->setVisible(true); - shutdownConfirmNo->setVisible(true); + [shutdownConfirmLabel, shutdownConfirmYes, shutdownConfirmNo](){ + shutdownConfirmLabel->setVisible(true); shutdownConfirmYes->setVisible(true); shutdownConfirmNo->setVisible(true); }); QObject::connect(shutdownConfirmYes, &QPushButton::clicked, &window, - [ctrl, shutdownConfirmLabel, shutdownConfirmYes, shutdownConfirmNo]() { + [ctrl, shutdownConfirmLabel, shutdownConfirmYes, shutdownConfirmNo](){ ctrl->sendCommand(QStringLiteral("POW ShutDown")); - shutdownConfirmLabel->setVisible(false); - shutdownConfirmYes->setVisible(false); - shutdownConfirmNo->setVisible(false); + shutdownConfirmLabel->setVisible(false); shutdownConfirmYes->setVisible(false); shutdownConfirmNo->setVisible(false); }); QObject::connect(shutdownConfirmNo, &QPushButton::clicked, &window, - [shutdownConfirmLabel, shutdownConfirmYes, shutdownConfirmNo]() { - shutdownConfirmLabel->setVisible(false); - shutdownConfirmYes->setVisible(false); - shutdownConfirmNo->setVisible(false); + [shutdownConfirmLabel, shutdownConfirmYes, shutdownConfirmNo](){ + shutdownConfirmLabel->setVisible(false); shutdownConfirmYes->setVisible(false); shutdownConfirmNo->setVisible(false); }); - // --- Wire up buttons --- QObject::connect(connectBtn, &QPushButton::clicked, ctrl, &WebSocketController::startConnection); QObject::connect(disconnectBtn, &QPushButton::clicked, ctrl, &WebSocketController::closeConnection); - QObject::connect(ctrl->socket(), &QWebSocket::connected, ctrl, &WebSocketController::onConnected); - QObject::connect(ctrl->socket(), &QWebSocket::disconnected, ctrl, &WebSocketController::onDisconnected); - QObject::connect(ctrl->socket(), &QWebSocket::textMessageReceived, ctrl, &WebSocketController::onTextMessageReceived); - QObject::connect(ctrl->socket(), &QWebSocket::errorOccurred, ctrl, &WebSocketController::onErrorOccurred); + QObject::connect(ctrl->socket(), &QWebSocket::connected, ctrl, &WebSocketController::onConnected); + QObject::connect(ctrl->socket(), &QWebSocket::disconnected, ctrl, &WebSocketController::onDisconnected); + QObject::connect(ctrl->socket(), &QWebSocket::textMessageReceived, ctrl, &WebSocketController::onTextMessageReceived); + QObject::connect(ctrl->socket(), &QWebSocket::errorOccurred, ctrl, &WebSocketController::onErrorOccurred); QObject::connect(ctrl->socket(), &QWebSocket::connected, &window, - [connectBtn, disconnectBtn, shutdownBtn]() { - connectBtn->setEnabled(false); - disconnectBtn->setEnabled(true); - shutdownBtn->setEnabled(true); + [connectBtn, disconnectBtn, shutdownBtn](){ + connectBtn->setEnabled(false); disconnectBtn->setEnabled(true); shutdownBtn->setEnabled(true); }); QObject::connect(ctrl->socket(), &QWebSocket::disconnected, &window, - [connectBtn, disconnectBtn, shutdownBtn]() { - connectBtn->setEnabled(true); - disconnectBtn->setEnabled(false); - shutdownBtn->setEnabled(false); + [connectBtn, disconnectBtn, shutdownBtn](){ + connectBtn->setEnabled(true); disconnectBtn->setEnabled(false); shutdownBtn->setEnabled(false); }); window.resize(1050, 620);