diff --git a/GamesPanel.cpp b/GamesPanel.cpp index 599faf8..62c6da7 100644 --- a/GamesPanel.cpp +++ b/GamesPanel.cpp @@ -3,9 +3,40 @@ #include #include #include +#include +#include #include #include +// --------------------------------------------------------------------------- +// Helper: parse "GAM CODE1Name1 CODE2Name2 ..." → name->code map +// --------------------------------------------------------------------------- +static QMap parseGamResponse(const QString &response, + const QString &subCmd) +{ + QMap result; + const QStringList tokens = response.split(' ', Qt::SkipEmptyParts); + if (tokens.size() < 3 || tokens[0] != "GAM" || tokens[1] != subCmd) + return result; + + for (const QString &entry : tokens.mid(2)) { + if (entry.isEmpty()) continue; + // First up-to-4 uppercase chars = code, remainder = name + int splitPos = 0; + while (splitPos < entry.size() && entry[splitPos].isUpper()) + ++splitPos; + const QString code = entry.left(qMin(splitPos, 4)); + QString name = entry.mid(code.size()); + name.replace("_S", " "); + if (code.isEmpty() || name.isEmpty()) continue; + result[name] = code; + } + return result; +} + +// --------------------------------------------------------------------------- +// Constructor +// --------------------------------------------------------------------------- GamesPanel::GamesPanel(QWidget *parent) : QWidget(parent) { auto *layout = new QHBoxLayout(this); @@ -13,74 +44,97 @@ GamesPanel::GamesPanel(QWidget *parent) : QWidget(parent) auto *splitter = new QSplitter(Qt::Horizontal, this); - m_gameList = new QListWidget(splitter); - m_gameList->setMinimumWidth(180); - m_gameList->setMaximumWidth(300); + // ---- Left pane: Available (not installed) games ---- + auto *leftWidget = new QWidget(splitter); + auto *leftLayout = new QVBoxLayout(leftWidget); + leftLayout->setContentsMargins(4, 4, 4, 4); + leftLayout->setSpacing(4); - m_detailWidget = new QWidget(splitter); - auto *detailLayout = new QVBoxLayout(m_detailWidget); - detailLayout->setContentsMargins(8, 8, 8, 8); + auto *availLabel = new QLabel("Available Games", leftWidget); + availLabel->setStyleSheet("color: #555;"); + m_availableList = new QListWidget(leftWidget); + m_availableList->addItem("Connect to load available games..."); + m_addBtn = new QPushButton("Add to Installed →", leftWidget); + m_addBtn->setEnabled(false); - m_nameLabel = new QLabel(m_detailWidget); - m_nameLabel->setStyleSheet("font-size: 14pt; font-weight: bold;"); - m_codeLabel = new QLabel(m_detailWidget); - m_codeLabel->setStyleSheet("font-size: 11pt; color: gray;"); - m_hintLabel = new QLabel("Select a game to view details.", m_detailWidget); - m_hintLabel->setAlignment(Qt::AlignTop); + leftLayout->addWidget(availLabel); + leftLayout->addWidget(m_availableList, 1); + leftLayout->addWidget(m_addBtn); - detailLayout->addWidget(m_nameLabel); - detailLayout->addWidget(m_codeLabel); - detailLayout->addWidget(m_hintLabel); - detailLayout->addStretch(1); + // ---- Right pane: Installed games ---- + auto *rightWidget = new QWidget(splitter); + auto *rightLayout = new QVBoxLayout(rightWidget); + rightLayout->setContentsMargins(4, 4, 4, 4); + rightLayout->setSpacing(4); - splitter->addWidget(m_gameList); - splitter->addWidget(m_detailWidget); - splitter->setStretchFactor(0, 0); + auto *instLabel = new QLabel("Installed Games", rightWidget); + instLabel->setStyleSheet("color: #555;"); + m_installedList = new QListWidget(rightWidget); + m_installedList->addItem("Connect to load installed games..."); + + rightLayout->addWidget(instLabel); + rightLayout->addWidget(m_installedList, 1); + + splitter->addWidget(leftWidget); + splitter->addWidget(rightWidget); + splitter->setStretchFactor(0, 1); splitter->setStretchFactor(1, 1); - splitter->setSizes({200, 600}); + splitter->setSizes({420, 420}); layout->addWidget(splitter); - connect(m_gameList, &QListWidget::currentTextChanged, - this, &GamesPanel::onGameSelected); + // Enable Add button only when something is selected in available list + connect(m_availableList, &QListWidget::currentRowChanged, this, + [this](int row) { m_addBtn->setEnabled(row >= 0); }); + connect(m_addBtn, &QPushButton::clicked, this, &GamesPanel::onAddClicked); } +// --------------------------------------------------------------------------- +// Load installed games (GAM list response) +// --------------------------------------------------------------------------- void GamesPanel::loadFromResponse(const QString &response) { - // Format: "GAM list LEADVision ARCHArchitect ..." - // Each token: 4 uppercase letters = code, rest = name (_S = space) - const QStringList tokens = response.split(' ', Qt::SkipEmptyParts); - if (tokens.size() < 3 || tokens[0] != "GAM" || tokens[1] != "list") return; - - m_games.clear(); - m_gameList->clear(); - - for (const QString &entry : tokens.mid(2)) { - if (entry.isEmpty()) continue; - - int splitPos = 0; - while (splitPos < entry.size() && entry[splitPos].isUpper()) - ++splitPos; - - const QString code = entry.left(qMin(splitPos, 4)); - QString name = entry.mid(code.size()); - name.replace("_S", " "); - - if (code.isEmpty() || name.isEmpty()) continue; - m_games[name] = code; - m_gameList->addItem(name); - } + m_installedGames = parseGamResponse(response, "list"); + m_installedList->clear(); + for (const QString &name : m_installedGames.keys()) + m_installedList->addItem(name); + rebuildAvailable(); } -void GamesPanel::onGameSelected(const QString &name) +// --------------------------------------------------------------------------- +// Load all available games (GAM listall response) +// --------------------------------------------------------------------------- +void GamesPanel::loadAllFromResponse(const QString &response) { - if (name.isEmpty() || !m_games.contains(name)) { - m_nameLabel->clear(); - m_codeLabel->clear(); - m_hintLabel->setText("Select a game to view details."); - return; - } - m_nameLabel->setText(name); - m_codeLabel->setText(QString("Code: %1").arg(m_games[name])); - m_hintLabel->setText("Game selected. Future controls will appear here."); + m_allGames = parseGamResponse(response, "listall"); + rebuildAvailable(); +} + +// --------------------------------------------------------------------------- +// Rebuild available list = allGames minus installedGames (by code) +// --------------------------------------------------------------------------- +void GamesPanel::rebuildAvailable() +{ + const QSet installedCodes(m_installedGames.values().begin(), + m_installedGames.values().end()); + m_availableList->clear(); + for (auto it = m_allGames.constBegin(); it != m_allGames.constEnd(); ++it) { + if (!installedCodes.contains(it.value())) + m_availableList->addItem(it.key()); + } + if (m_availableList->count() == 0 && !m_allGames.isEmpty()) + m_availableList->addItem("(all available games are installed)"); + m_addBtn->setEnabled(false); +} + +// --------------------------------------------------------------------------- +// Add button clicked → emit GAM add +// --------------------------------------------------------------------------- +void GamesPanel::onAddClicked() +{ + auto *item = m_availableList->currentItem(); + if (!item) return; + const QString name = item->text(); + if (!m_allGames.contains(name)) return; + emit commandRequested(QString("GAM add %1").arg(m_allGames[name])); } diff --git a/GamesPanel.h b/GamesPanel.h index 9aa5f30..787feb0 100644 --- a/GamesPanel.h +++ b/GamesPanel.h @@ -1,26 +1,33 @@ #pragma once -#include #include #include +#include class QLabel; class QListWidget; +class QPushButton; class GamesPanel : public QWidget { Q_OBJECT public: explicit GamesPanel(QWidget *parent = nullptr); - void loadFromResponse(const QString &response); + void loadFromResponse(const QString &response); // GAM list (installed) + void loadAllFromResponse(const QString &response); // GAM listall (all available) + +signals: + void commandRequested(const QString &cmd); private slots: - void onGameSelected(const QString &name); + void onAddClicked(); private: - QListWidget *m_gameList = nullptr; - QWidget *m_detailWidget = nullptr; - QLabel *m_nameLabel = nullptr; - QLabel *m_codeLabel = nullptr; - QLabel *m_hintLabel = nullptr; - QMap m_games; // display name -> 4-letter code + void rebuildAvailable(); + + QListWidget *m_availableList = nullptr; + QListWidget *m_installedList = nullptr; + QPushButton *m_addBtn = nullptr; + + QMap m_allGames; // name -> code (GAM listall) + QMap m_installedGames; // name -> code (GAM list) }; diff --git a/WebSocketController.cpp b/WebSocketController.cpp index 9525e85..ded4cc5 100644 --- a/WebSocketController.cpp +++ b/WebSocketController.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include WebSocketController::WebSocketController(QLineEdit *urlEdit, QLabel *statusLabel, @@ -27,10 +27,10 @@ void WebSocketController::setSettingsTree(SettingsTree *tree) this, &WebSocketController::onValueEdited); } -void WebSocketController::setGamesPanel(GamesPanel *panel) { m_gamesPanel = panel; } -void WebSocketController::setVersionsPanel(VersionsPanel *panel) { m_versionsPanel = panel; } -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; } bool WebSocketController::isConnected() const { @@ -71,6 +71,7 @@ void WebSocketController::onConnected() sendCommand(QStringLiteral("GBL List")); sendCommand(QStringLiteral("GAM list")); + sendCommand(QStringLiteral("GAM listall")); // <-- fetch full catalogue sendCommand(QStringLiteral("NAM")); sendCommand(QStringLiteral("VER")); sendCommand(QStringLiteral("UID")); @@ -123,38 +124,35 @@ void WebSocketController::handleProtocol(const QString &msg) } // ---- LOG channel list ---- - // Response: LOG name1 name2* name3 ... (* = enabled) if (cmd == "LOG" && tokens.size() > 1 && m_logPanel) { - // Only handle the full list response (no '=' in any token) bool isList = true; - for (int i = 1; i < tokens.size(); ++i) { + for (int i = 1; i < tokens.size(); ++i) if (tokens[i].contains('=')) { isList = false; break; } - } if (isList) m_logPanel->applyLogResponse(msg); return; } - // NAM + // ---- NAM ---- if (cmd == "NAM" && tokens.size() >= 2) { if (m_versionsPanel) m_versionsPanel->setDeviceName(tokens[1]); return; } - // VER + // ---- VER ---- if (cmd == "VER" && tokens.size() >= 3) { if (m_versionsPanel) m_versionsPanel->setVersion(tokens[1], tokens.mid(2).join(' ')); return; } - // UID + // ---- UID ---- if (cmd == "UID" && tokens.size() >= 3) { if (m_versionsPanel) m_versionsPanel->setUid(tokens[1], tokens.mid(2).join(' ')); return; } - // RNP + // ---- RNP ---- if (cmd == "RNP" && tokens.size() >= 2) { bool ok = false; const int count = tokens[1].toInt(&ok); @@ -169,13 +167,19 @@ void WebSocketController::handleProtocol(const QString &msg) return; } - // GAM list + // ---- GAM listall — full catalogue ---- + if (cmd == "GAM" && tokens.size() >= 2 && tokens[1] == "listall") { + if (m_gamesPanel) m_gamesPanel->loadAllFromResponse(msg); + return; + } + + // ---- GAM list — installed games ---- if (cmd == "GAM" && tokens.size() >= 2 && tokens[1] == "list") { if (m_gamesPanel) m_gamesPanel->loadFromResponse(msg); return; } - // GBL List key1 key2 ... + // ---- GBL List key1 key2 ... ---- if (cmd == "GBL" && tokens.size() > 2 && tokens[1] == "List") { m_settingsKeys = tokens.mid(2); if (m_settingsTree) { @@ -186,7 +190,7 @@ void WebSocketController::handleProtocol(const QString &msg) return; } - // GBL key=value + // ---- GBL key=value ---- if (cmd == "GBL" && tokens.size() >= 2 && m_settingsTree) { const QString payload = tokens[1]; const int eqIdx = payload.indexOf('='); diff --git a/main.cpp b/main.cpp index 261888f..810f42c 100644 --- a/main.cpp +++ b/main.cpp @@ -27,6 +27,9 @@ 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; }