Added games installer and now its amazing

This commit is contained in:
Jon ESA
2026-04-01 19:44:03 +01:00
parent 0146bcb6f2
commit f0c231e935
4 changed files with 148 additions and 80 deletions

View File

@@ -3,9 +3,40 @@
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QListWidget> #include <QListWidget>
#include <QPushButton>
#include <QSet>
#include <QSplitter> #include <QSplitter>
#include <QVBoxLayout> #include <QVBoxLayout>
// ---------------------------------------------------------------------------
// Helper: parse "GAM <cmd> CODE1Name1 CODE2Name2 ..." → name->code map
// ---------------------------------------------------------------------------
static QMap<QString,QString> parseGamResponse(const QString &response,
const QString &subCmd)
{
QMap<QString,QString> 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) GamesPanel::GamesPanel(QWidget *parent) : QWidget(parent)
{ {
auto *layout = new QHBoxLayout(this); auto *layout = new QHBoxLayout(this);
@@ -13,74 +44,97 @@ GamesPanel::GamesPanel(QWidget *parent) : QWidget(parent)
auto *splitter = new QSplitter(Qt::Horizontal, this); auto *splitter = new QSplitter(Qt::Horizontal, this);
m_gameList = new QListWidget(splitter); // ---- Left pane: Available (not installed) games ----
m_gameList->setMinimumWidth(180); auto *leftWidget = new QWidget(splitter);
m_gameList->setMaximumWidth(300); auto *leftLayout = new QVBoxLayout(leftWidget);
leftLayout->setContentsMargins(4, 4, 4, 4);
leftLayout->setSpacing(4);
m_detailWidget = new QWidget(splitter); auto *availLabel = new QLabel("<b>Available Games</b>", leftWidget);
auto *detailLayout = new QVBoxLayout(m_detailWidget); availLabel->setStyleSheet("color: #555;");
detailLayout->setContentsMargins(8, 8, 8, 8); 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); leftLayout->addWidget(availLabel);
m_nameLabel->setStyleSheet("font-size: 14pt; font-weight: bold;"); leftLayout->addWidget(m_availableList, 1);
m_codeLabel = new QLabel(m_detailWidget); leftLayout->addWidget(m_addBtn);
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);
detailLayout->addWidget(m_nameLabel); // ---- Right pane: Installed games ----
detailLayout->addWidget(m_codeLabel); auto *rightWidget = new QWidget(splitter);
detailLayout->addWidget(m_hintLabel); auto *rightLayout = new QVBoxLayout(rightWidget);
detailLayout->addStretch(1); rightLayout->setContentsMargins(4, 4, 4, 4);
rightLayout->setSpacing(4);
splitter->addWidget(m_gameList); auto *instLabel = new QLabel("<b>Installed Games</b>", rightWidget);
splitter->addWidget(m_detailWidget); instLabel->setStyleSheet("color: #555;");
splitter->setStretchFactor(0, 0); 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->setStretchFactor(1, 1);
splitter->setSizes({200, 600}); splitter->setSizes({420, 420});
layout->addWidget(splitter); layout->addWidget(splitter);
connect(m_gameList, &QListWidget::currentTextChanged, // Enable Add button only when something is selected in available list
this, &GamesPanel::onGameSelected); 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) void GamesPanel::loadFromResponse(const QString &response)
{ {
// Format: "GAM list LEADVision ARCHArchitect ..." m_installedGames = parseGamResponse(response, "list");
// Each token: 4 uppercase letters = code, rest = name (_S = space) m_installedList->clear();
const QStringList tokens = response.split(' ', Qt::SkipEmptyParts); for (const QString &name : m_installedGames.keys())
if (tokens.size() < 3 || tokens[0] != "GAM" || tokens[1] != "list") return; m_installedList->addItem(name);
rebuildAvailable();
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);
}
} }
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_allGames = parseGamResponse(response, "listall");
m_nameLabel->clear(); rebuildAvailable();
m_codeLabel->clear(); }
m_hintLabel->setText("Select a game to view details.");
return; // ---------------------------------------------------------------------------
} // Rebuild available list = allGames minus installedGames (by code)
m_nameLabel->setText(name); // ---------------------------------------------------------------------------
m_codeLabel->setText(QString("Code: %1").arg(m_games[name])); void GamesPanel::rebuildAvailable()
m_hintLabel->setText("Game selected. Future controls will appear here."); {
const QSet<QString> 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 <code>
// ---------------------------------------------------------------------------
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]));
} }

View File

@@ -1,26 +1,33 @@
#pragma once #pragma once
#include <QWidget>
#include <QMap> #include <QMap>
#include <QString> #include <QString>
#include <QWidget>
class QLabel; class QLabel;
class QListWidget; class QListWidget;
class QPushButton;
class GamesPanel : public QWidget class GamesPanel : public QWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit GamesPanel(QWidget *parent = nullptr); 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: private slots:
void onGameSelected(const QString &name); void onAddClicked();
private: private:
QListWidget *m_gameList = nullptr; void rebuildAvailable();
QWidget *m_detailWidget = nullptr;
QLabel *m_nameLabel = nullptr; QListWidget *m_availableList = nullptr;
QLabel *m_codeLabel = nullptr; QListWidget *m_installedList = nullptr;
QLabel *m_hintLabel = nullptr; QPushButton *m_addBtn = nullptr;
QMap<QString, QString> m_games; // display name -> 4-letter code
QMap<QString,QString> m_allGames; // name -> code (GAM listall)
QMap<QString,QString> m_installedGames; // name -> code (GAM list)
}; };

View File

@@ -8,7 +8,7 @@
#include <QLabel> #include <QLabel>
#include <QLineEdit> #include <QLineEdit>
#include <QTextEdit> #include <QTextEdit>
#include <QUrl> #include <QAbstractSocket>
WebSocketController::WebSocketController(QLineEdit *urlEdit, WebSocketController::WebSocketController(QLineEdit *urlEdit,
QLabel *statusLabel, QLabel *statusLabel,
@@ -27,10 +27,10 @@ void WebSocketController::setSettingsTree(SettingsTree *tree)
this, &WebSocketController::onValueEdited); this, &WebSocketController::onValueEdited);
} }
void WebSocketController::setGamesPanel(GamesPanel *panel) { m_gamesPanel = panel; } void WebSocketController::setGamesPanel(GamesPanel *panel) { m_gamesPanel = panel; }
void WebSocketController::setVersionsPanel(VersionsPanel *panel) { m_versionsPanel = panel; } void WebSocketController::setVersionsPanel(VersionsPanel *p) { m_versionsPanel = p; }
void WebSocketController::setPowerPanel(PowerPanel *panel) { m_powerPanel = panel; } void WebSocketController::setPowerPanel(PowerPanel *panel) { m_powerPanel = panel; }
void WebSocketController::setLogPanel(LogPanel *panel) { m_logPanel = panel; } void WebSocketController::setLogPanel(LogPanel *panel) { m_logPanel = panel; }
bool WebSocketController::isConnected() const bool WebSocketController::isConnected() const
{ {
@@ -71,6 +71,7 @@ void WebSocketController::onConnected()
sendCommand(QStringLiteral("GBL List")); sendCommand(QStringLiteral("GBL List"));
sendCommand(QStringLiteral("GAM list")); sendCommand(QStringLiteral("GAM list"));
sendCommand(QStringLiteral("GAM listall")); // <-- fetch full catalogue
sendCommand(QStringLiteral("NAM")); sendCommand(QStringLiteral("NAM"));
sendCommand(QStringLiteral("VER")); sendCommand(QStringLiteral("VER"));
sendCommand(QStringLiteral("UID")); sendCommand(QStringLiteral("UID"));
@@ -123,38 +124,35 @@ void WebSocketController::handleProtocol(const QString &msg)
} }
// ---- LOG channel list ---- // ---- LOG channel list ----
// Response: LOG name1 name2* name3 ... (* = enabled)
if (cmd == "LOG" && tokens.size() > 1 && m_logPanel) { if (cmd == "LOG" && tokens.size() > 1 && m_logPanel) {
// Only handle the full list response (no '=' in any token)
bool isList = true; 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 (tokens[i].contains('=')) { isList = false; break; }
}
if (isList) m_logPanel->applyLogResponse(msg); if (isList) m_logPanel->applyLogResponse(msg);
return; return;
} }
// NAM // ---- NAM ----
if (cmd == "NAM" && tokens.size() >= 2) { if (cmd == "NAM" && tokens.size() >= 2) {
if (m_versionsPanel) m_versionsPanel->setDeviceName(tokens[1]); if (m_versionsPanel) m_versionsPanel->setDeviceName(tokens[1]);
return; return;
} }
// VER // ---- VER ----
if (cmd == "VER" && tokens.size() >= 3) { if (cmd == "VER" && tokens.size() >= 3) {
if (m_versionsPanel) if (m_versionsPanel)
m_versionsPanel->setVersion(tokens[1], tokens.mid(2).join(' ')); m_versionsPanel->setVersion(tokens[1], tokens.mid(2).join(' '));
return; return;
} }
// UID // ---- UID ----
if (cmd == "UID" && tokens.size() >= 3) { if (cmd == "UID" && tokens.size() >= 3) {
if (m_versionsPanel) if (m_versionsPanel)
m_versionsPanel->setUid(tokens[1], tokens.mid(2).join(' ')); m_versionsPanel->setUid(tokens[1], tokens.mid(2).join(' '));
return; return;
} }
// RNP // ---- RNP ----
if (cmd == "RNP" && tokens.size() >= 2) { if (cmd == "RNP" && tokens.size() >= 2) {
bool ok = false; bool ok = false;
const int count = tokens[1].toInt(&ok); const int count = tokens[1].toInt(&ok);
@@ -169,13 +167,19 @@ void WebSocketController::handleProtocol(const QString &msg)
return; 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 (cmd == "GAM" && tokens.size() >= 2 && tokens[1] == "list") {
if (m_gamesPanel) m_gamesPanel->loadFromResponse(msg); if (m_gamesPanel) m_gamesPanel->loadFromResponse(msg);
return; return;
} }
// GBL List key1 key2 ... // ---- GBL List key1 key2 ... ----
if (cmd == "GBL" && tokens.size() > 2 && tokens[1] == "List") { if (cmd == "GBL" && tokens.size() > 2 && tokens[1] == "List") {
m_settingsKeys = tokens.mid(2); m_settingsKeys = tokens.mid(2);
if (m_settingsTree) { if (m_settingsTree) {
@@ -186,7 +190,7 @@ void WebSocketController::handleProtocol(const QString &msg)
return; return;
} }
// GBL key=value // ---- GBL key=value ----
if (cmd == "GBL" && tokens.size() >= 2 && m_settingsTree) { if (cmd == "GBL" && tokens.size() >= 2 && m_settingsTree) {
const QString payload = tokens[1]; const QString payload = tokens[1];
const int eqIdx = payload.indexOf('='); const int eqIdx = payload.indexOf('=');

View File

@@ -27,6 +27,9 @@ static QWidget *makeGamesTab(WebSocketController *ctrl, QWidget *parent)
{ {
auto *panel = new GamesPanel(parent); auto *panel = new GamesPanel(parent);
ctrl->setGamesPanel(panel); ctrl->setGamesPanel(panel);
// Route GAM add commands back through the controller
QObject::connect(panel, &GamesPanel::commandRequested,
ctrl, &WebSocketController::sendCommand);
return panel; return panel;
} }