From 296de3083671d72eb38e0de85ba85986377b31a6 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Thu, 19 Mar 2026 21:02:41 +0000 Subject: [PATCH] refactor(qt6): migrate C++ sources to .hpp header-only style Move ModPlayer, NodeRegistry, PackageRegistry from .cpp/.h pairs to single .hpp files. Add DBALRequest.hpp and DBALTypes.hpp. Update DBALClient and main.cpp for new structure. Co-Authored-By: Claude Sonnet 4.6 --- frontends/qt6/main.cpp | 6 +- frontends/qt6/src/DBALClient.cpp | 160 ++++++++++------ frontends/qt6/src/DBALClient.h | 265 +++++++++++++------------- frontends/qt6/src/DBALRequest.hpp | 28 +++ frontends/qt6/src/DBALTypes.hpp | 17 ++ frontends/qt6/src/ModPlayer.cpp | 42 ---- frontends/qt6/src/ModPlayer.h | 25 --- frontends/qt6/src/ModPlayer.hpp | 57 ++++++ frontends/qt6/src/NodeRegistry.cpp | 111 ----------- frontends/qt6/src/NodeRegistry.h | 50 ----- frontends/qt6/src/NodeRegistry.hpp | 185 ++++++++++++++++++ frontends/qt6/src/PackageRegistry.cpp | 103 ---------- frontends/qt6/src/PackageRegistry.h | 32 ---- frontends/qt6/src/PackageRegistry.hpp | 132 +++++++++++++ 14 files changed, 662 insertions(+), 551 deletions(-) create mode 100644 frontends/qt6/src/DBALRequest.hpp create mode 100644 frontends/qt6/src/DBALTypes.hpp delete mode 100644 frontends/qt6/src/ModPlayer.cpp delete mode 100644 frontends/qt6/src/ModPlayer.h create mode 100644 frontends/qt6/src/ModPlayer.hpp delete mode 100644 frontends/qt6/src/NodeRegistry.cpp delete mode 100644 frontends/qt6/src/NodeRegistry.h create mode 100644 frontends/qt6/src/NodeRegistry.hpp delete mode 100644 frontends/qt6/src/PackageRegistry.cpp delete mode 100644 frontends/qt6/src/PackageRegistry.h create mode 100644 frontends/qt6/src/PackageRegistry.hpp diff --git a/frontends/qt6/main.cpp b/frontends/qt6/main.cpp index 7411dd354..b847dd235 100644 --- a/frontends/qt6/main.cpp +++ b/frontends/qt6/main.cpp @@ -4,11 +4,11 @@ #include #include -#include "src/PackageRegistry.h" -#include "src/ModPlayer.h" +#include "src/PackageRegistry.hpp" +#include "src/ModPlayer.hpp" #include "src/DBALClient.h" #include "src/PackageLoader.h" -#include "src/NodeRegistry.h" +#include "src/NodeRegistry.hpp" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); diff --git a/frontends/qt6/src/DBALClient.cpp b/frontends/qt6/src/DBALClient.cpp index 4c90762e4..f166f3ce8 100644 --- a/frontends/qt6/src/DBALClient.cpp +++ b/frontends/qt6/src/DBALClient.cpp @@ -1,4 +1,5 @@ #include "DBALClient.h" +// DBALTypes.hpp and DBALRequest.hpp are pulled in via DBALClient.h #include #include #include @@ -57,10 +58,12 @@ void DBALClient::setPackageId(const QString &pkg) QString DBALClient::entityPath(const QString &entity) const { // REST: /api/v1/{tenant}/{package}/{entity} - return QString("/api/v1/%1/%2/%3").arg(m_tenantId, m_packageId, entity.toLower()); + return QString("/api/v1/%1/%2/%3") + .arg(m_tenantId, m_packageId, entity.toLower()); } -QString DBALClient::entityPath(const QString &entity, const QString &id) const +QString DBALClient::entityPath( + const QString &entity, const QString &id) const { return entityPath(entity) + "/" + id; } @@ -71,23 +74,30 @@ void DBALClient::setError(const QString &error) emit errorOccurred(error); } -void DBALClient::sendRequest(const QString &method, const QString &endpoint, - const QJsonObject &body, const QJSValue &callback) +void DBALClient::sendRequest( + const QString &method, + const QString &endpoint, + const DBALPayload &body, + const DBALCallback &callback) { QUrl url(m_baseUrl + endpoint); QNetworkRequest request(url); - + // Set headers - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setHeader( + QNetworkRequest::ContentTypeHeader, "application/json"); request.setRawHeader("X-Tenant-ID", m_tenantId.toUtf8()); - + if (!m_authToken.isEmpty()) { - request.setRawHeader("Authorization", ("Bearer " + m_authToken).toUtf8()); + request.setRawHeader( + "Authorization", + ("Bearer " + m_authToken).toUtf8()); } - + QNetworkReply *reply = nullptr; - QByteArray jsonData = QJsonDocument(body).toJson(QJsonDocument::Compact); - + QByteArray jsonData = + QJsonDocument(body).toJson(QJsonDocument::Compact); + if (method == "GET") { reply = m_networkManager->get(request); } else if (method == "POST") { @@ -97,7 +107,7 @@ void DBALClient::sendRequest(const QString &method, const QString &endpoint, } else if (method == "DELETE") { reply = m_networkManager->deleteResource(request); } - + if (reply && callback.isCallable()) { m_pendingCallbacks[reply] = callback; } @@ -106,12 +116,12 @@ void DBALClient::sendRequest(const QString &method, const QString &endpoint, void DBALClient::handleNetworkReply(QNetworkReply *reply) { reply->deleteLater(); - - QJSValue callback = m_pendingCallbacks.take(reply); - + + DBALCallback callback = m_pendingCallbacks.take(reply); + if (reply->error() != QNetworkReply::NoError) { setError(reply->errorString()); - + if (callback.isCallable()) { QJSValueList args; args << QJSValue::NullValue; @@ -120,14 +130,14 @@ void DBALClient::handleNetworkReply(QNetworkReply *reply) } return; } - + QByteArray data = reply->readAll(); QJsonDocument doc = QJsonDocument::fromJson(data); - + if (callback.isCallable()) { QJSValueList args; - // In Qt6, QJSValue::engine() is removed. Pass data as JSON string - // and let QML parse it, or pass QVariant directly. + // In Qt6, QJSValue::engine() is removed. Pass data as + // a JSON string and let QML parse it, or pass QVariant. if (doc.isObject()) { args << QJSValue(QString::fromUtf8(data)); } else if (doc.isArray()) { @@ -137,7 +147,7 @@ void DBALClient::handleNetworkReply(QNetworkReply *reply) } callback.call(args); } - + // Update connected status if (!m_connected) { m_connected = true; @@ -145,89 +155,133 @@ void DBALClient::handleNetworkReply(QNetworkReply *reply) } } -// CRUD Operations — DBAL REST API: /api/v1/{tenant}/{package}/{entity}[/{id}] +// CRUD Operations +// REST path: /api/v1/{tenant}/{package}/{entity}[/{id}] -void DBALClient::create(const QString &entity, const QJsonObject &data, const QJSValue &callback) +void DBALClient::create( + const QString &entity, + const DBALPayload &data, + const DBALCallback &callback) { sendRequest("POST", entityPath(entity), data, callback); } -void DBALClient::read(const QString &entity, const QString &id, const QJSValue &callback) +void DBALClient::read( + const QString &entity, + const QString &id, + const DBALCallback &callback) { - sendRequest("GET", entityPath(entity, id), QJsonObject(), callback); + sendRequest("GET", entityPath(entity, id), DBALPayload(), callback); } -void DBALClient::update(const QString &entity, const QString &id, - const QJsonObject &data, const QJSValue &callback) +void DBALClient::update( + const QString &entity, + const QString &id, + const DBALPayload &data, + const DBALCallback &callback) { sendRequest("PUT", entityPath(entity, id), data, callback); } -void DBALClient::remove(const QString &entity, const QString &id, const QJSValue &callback) +void DBALClient::remove( + const QString &entity, + const QString &id, + const DBALCallback &callback) { - sendRequest("DELETE", entityPath(entity, id), QJsonObject(), callback); + sendRequest( + "DELETE", entityPath(entity, id), DBALPayload(), callback); } -void DBALClient::list(const QString &entity, const QJsonObject &options, const QJSValue &callback) +void DBALClient::list( + const QString &entity, + const DBALPayload &options, + const DBALCallback &callback) { // Build query string from options (take, skip, where, orderBy) QString path = entityPath(entity); QStringList queryParts; - if (options.contains("take")) queryParts << "take=" + QString::number(options["take"].toInt()); - if (options.contains("skip")) queryParts << "skip=" + QString::number(options["skip"].toInt()); - if (options.contains("orderBy")) queryParts << "orderBy=" + options["orderBy"].toString(); - if (!queryParts.isEmpty()) path += "?" + queryParts.join("&"); + if (options.contains("take")) + queryParts << "take=" + + QString::number(options["take"].toInt()); + if (options.contains("skip")) + queryParts << "skip=" + + QString::number(options["skip"].toInt()); + if (options.contains("orderBy")) + queryParts << "orderBy=" + options["orderBy"].toString(); + if (!queryParts.isEmpty()) + path += "?" + queryParts.join("&"); - sendRequest("GET", path, QJsonObject(), callback); + sendRequest("GET", path, DBALPayload(), callback); } -void DBALClient::findFirst(const QString &entity, const QJsonObject &filter, const QJSValue &callback) +void DBALClient::findFirst( + const QString &entity, + const DBALPayload &filter, + const DBALCallback &callback) { // GET with query params for simple filters QString path = entityPath(entity); QStringList queryParts; queryParts << "take=1"; for (auto it = filter.begin(); it != filter.end(); ++it) { - queryParts << QUrl::toPercentEncoding(it.key()) + "=" + QUrl::toPercentEncoding(it.value().toString()); + queryParts + << QUrl::toPercentEncoding(it.key()) + + "=" + + QUrl::toPercentEncoding(it.value().toString()); } - if (!queryParts.isEmpty()) path += "?" + queryParts.join("&"); + if (!queryParts.isEmpty()) + path += "?" + queryParts.join("&"); - sendRequest("GET", path, QJsonObject(), callback); + sendRequest("GET", path, DBALPayload(), callback); } -void DBALClient::execute(const QString &operation, const QJsonObject ¶ms, const QJSValue &callback) +void DBALClient::execute( + const QString &operation, + const DBALPayload ¶ms, + const DBALCallback &callback) { - // Execute maps to POST /{tenant}/{package}/{entity}/{action} or system endpoints - QString path = QString("/api/v1/%1/%2").arg(m_tenantId, operation); + // POST to /{tenant}/{operation} (or /{entity}/{action}) + QString path = + QString("/api/v1/%1/%2").arg(m_tenantId, operation); sendRequest("POST", path, params, callback); } void DBALClient::ping() { - sendRequest("GET", "/health", QJsonObject(), QJSValue()); + sendRequest("GET", "/health", DBALPayload(), DBALCallback()); } -void DBALClient::health(const QJSValue &callback) +void DBALClient::health(const DBALCallback &callback) { - sendRequest("GET", "/health", QJsonObject(), callback); + sendRequest("GET", "/health", DBALPayload(), callback); } -void DBALClient::version(const QJSValue &callback) +void DBALClient::version(const DBALCallback &callback) { - sendRequest("GET", "/version", QJsonObject(), callback); + sendRequest("GET", "/version", DBALPayload(), callback); } -void DBALClient::status(const QJSValue &callback) +void DBALClient::status(const DBALCallback &callback) { - sendRequest("GET", "/status", QJsonObject(), callback); + sendRequest("GET", "/status", DBALPayload(), callback); } -void DBALClient::listSchemas(const QJSValue &callback) +void DBALClient::listSchemas(const DBALCallback &callback) { - sendRequest("GET", "/api/v1/" + m_tenantId + "/schema", QJsonObject(), callback); + sendRequest( + "GET", + "/api/v1/" + m_tenantId + "/schema", + DBALPayload(), + callback); } -void DBALClient::getSchema(const QString &entity, const QJSValue &callback) +void DBALClient::getSchema( + const QString &entity, + const DBALCallback &callback) { - sendRequest("GET", "/api/v1/" + m_tenantId + "/schema/" + entity.toLower(), QJsonObject(), callback); + sendRequest( + "GET", + "/api/v1/" + m_tenantId + "/schema/" + entity.toLower(), + DBALPayload(), + callback); } diff --git a/frontends/qt6/src/DBALClient.h b/frontends/qt6/src/DBALClient.h index e4d0cf2ed..896f499d0 100644 --- a/frontends/qt6/src/DBALClient.h +++ b/frontends/qt6/src/DBALClient.h @@ -1,58 +1,81 @@ -#ifndef DBALCLIENT_H -#define DBALCLIENT_H +#pragma once + +// Qt6 DBAL Client Bridge +// +// Provides database access for QML components through the DBAL +// daemon. Communicates via HTTP to the C++ DBAL backend. +// +// Usage in QML: +// DBALClient { +// id: dbal +// baseUrl: "http://localhost:3001/api/dbal" +// tenantId: "default" +// Component.onCompleted: { +// dbal.list("User", { take: 10 }, function(users) { +// console.log("Users:", JSON.stringify(users)) +// }) +// } +// } + +#include "DBALTypes.hpp" +#include "DBALRequest.hpp" #include -#include #include -#include #include #include #include -#include #include -/** - * @brief Qt6 DBAL Client Bridge - * - * Provides database access for QML components through the DBAL daemon. - * Communicates via HTTP/WebSocket to the TypeScript or C++ DBAL backend. - * - * Usage in QML: - * @code - * DBALClient { - * id: dbal - * baseUrl: "http://localhost:3001/api/dbal" - * tenantId: "default" - * - * Component.onCompleted: { - * dbal.list("User", { take: 10 }, function(users) { - * console.log("Users:", JSON.stringify(users)) - * }) - * } - * } - * @endcode - */ class DBALClient : public QObject { Q_OBJECT - Q_PROPERTY(QString baseUrl READ baseUrl WRITE setBaseUrl NOTIFY baseUrlChanged) - Q_PROPERTY(QString tenantId READ tenantId WRITE setTenantId NOTIFY tenantIdChanged) - Q_PROPERTY(QString packageId READ packageId WRITE setPackageId NOTIFY packageIdChanged) - Q_PROPERTY(QString authToken READ authToken WRITE setAuthToken NOTIFY authTokenChanged) - Q_PROPERTY(bool connected READ isConnected NOTIFY connectedChanged) - Q_PROPERTY(QString lastError READ lastError NOTIFY errorOccurred) + + Q_PROPERTY( + QString baseUrl + READ baseUrl + WRITE setBaseUrl + NOTIFY baseUrlChanged) + + Q_PROPERTY( + QString tenantId + READ tenantId + WRITE setTenantId + NOTIFY tenantIdChanged) + + Q_PROPERTY( + QString packageId + READ packageId + WRITE setPackageId + NOTIFY packageIdChanged) + + Q_PROPERTY( + QString authToken + READ authToken + WRITE setAuthToken + NOTIFY authTokenChanged) + + Q_PROPERTY( + bool connected + READ isConnected + NOTIFY connectedChanged) + + Q_PROPERTY( + QString lastError + READ lastError + NOTIFY errorOccurred) public: explicit DBALClient(QObject *parent = nullptr); ~DBALClient() override; // Property getters - QString baseUrl() const { return m_baseUrl; } - QString tenantId() const { return m_tenantId; } - QString packageId() const { return m_packageId; } - QString authToken() const { return m_authToken; } - bool isConnected() const { return m_connected; } - QString lastError() const { return m_lastError; } + QString baseUrl() const { return m_baseUrl; } + QString tenantId() const { return m_tenantId; } + QString packageId() const { return m_packageId; } + QString authToken() const { return m_authToken; } + bool isConnected() const { return m_connected; } + QString lastError() const { return m_lastError; } // Property setters void setBaseUrl(const QString &url); @@ -61,98 +84,70 @@ public: void setAuthToken(const QString &token); public slots: - /** - * @brief Create a new record - * @param entity Entity name (e.g., "User", "AuditLog") - * @param data Record data as JSON object - * @param callback QML callback function(result) - */ - void create(const QString &entity, const QJsonObject &data, const QJSValue &callback); + /// Create a new record. callback: function(result) + void create( + const QString &entity, + const DBALPayload &data, + const DBALCallback &callback); - /** - * @brief Read a single record by ID - * @param entity Entity name - * @param id Record ID - * @param callback QML callback function(result) - */ - void read(const QString &entity, const QString &id, const QJSValue &callback); + /// Read a single record by ID. callback: function(result) + void read( + const QString &entity, + const QString &id, + const DBALCallback &callback); - /** - * @brief Update an existing record - * @param entity Entity name - * @param id Record ID - * @param data Updated fields - * @param callback QML callback function(result) - */ - void update(const QString &entity, const QString &id, const QJsonObject &data, const QJSValue &callback); + /// Update an existing record. callback: function(result) + void update( + const QString &entity, + const QString &id, + const DBALPayload &data, + const DBALCallback &callback); - /** - * @brief Delete a record - * @param entity Entity name - * @param id Record ID - * @param callback QML callback function(success) - */ - void remove(const QString &entity, const QString &id, const QJSValue &callback); + /// Delete a record. callback: function(success) + void remove( + const QString &entity, + const QString &id, + const DBALCallback &callback); - /** - * @brief List records with pagination and filtering - * @param entity Entity name - * @param options { take, skip, where, orderBy } - * @param callback QML callback function({ items, total }) - */ - void list(const QString &entity, const QJsonObject &options, const QJSValue &callback); + /// List records with pagination/filtering. + /// options: { take, skip, where, orderBy } + /// callback: function({ items, total }) + void list( + const QString &entity, + const DBALPayload &options, + const DBALCallback &callback); - /** - * @brief Find first record matching filter - * @param entity Entity name - * @param filter Filter criteria - * @param callback QML callback function(result) - */ - void findFirst(const QString &entity, const QJsonObject &filter, const QJSValue &callback); + /// Find first record matching filter. callback: function(result) + void findFirst( + const QString &entity, + const DBALPayload &filter, + const DBALCallback &callback); - /** - * @brief Execute a named query/operation - * @param operation Operation name - * @param params Operation parameters - * @param callback QML callback function(result) - */ - void execute(const QString &operation, const QJsonObject ¶ms, const QJSValue &callback); + /// Execute a named operation. callback: function(result) + void execute( + const QString &operation, + const DBALPayload ¶ms, + const DBALCallback &callback); - /** - * @brief Check connection to DBAL backend - */ + /// Check connection to DBAL backend (fire-and-forget). void ping(); - /** - * @brief Get DBAL health information - * @param callback QML callback function(result) - */ - void health(const QJSValue &callback); + /// Get DBAL health info. callback: function(result) + void health(const DBALCallback &callback); - /** - * @brief Get DBAL version information - * @param callback QML callback function(result) - */ - void version(const QJSValue &callback); + /// Get DBAL version info. callback: function(result) + void version(const DBALCallback &callback); - /** - * @brief Get DBAL status/metrics - * @param callback QML callback function(result) - */ - void status(const QJSValue &callback); + /// Get DBAL status/metrics. callback: function(result) + void status(const DBALCallback &callback); - /** - * @brief List entity schemas from DBAL - * @param callback QML callback function(schemas) - */ - void listSchemas(const QJSValue &callback); + /// List entity schemas. callback: function(schemas) + void listSchemas(const DBALCallback &callback); - /** - * @brief Get a specific entity schema - * @param entity Entity name - * @param callback QML callback function(schema) - */ - void getSchema(const QString &entity, const QJSValue &callback); + /// Get a specific entity schema. callback: function(schema) + void getSchema( + const QString &entity, + const DBALCallback &callback); signals: void baseUrlChanged(); @@ -161,27 +156,33 @@ signals: void authTokenChanged(); void connectedChanged(); void errorOccurred(const QString &error); - void operationCompleted(const QString &operation, const QJsonObject &result); + void operationCompleted( + const QString &operation, + const DBALPayload &result); private slots: void handleNetworkReply(QNetworkReply *reply); private: - void sendRequest(const QString &method, const QString &endpoint, - const QJsonObject &body, const QJSValue &callback); + void sendRequest( + const QString &method, + const QString &endpoint, + const DBALPayload &body, + const DBALCallback &callback); + void setError(const QString &error); - QNetworkAccessManager *m_networkManager; - QString m_baseUrl; - QString m_tenantId; - QString m_packageId; - QString m_authToken; - QString entityPath(const QString &entity) const; - QString entityPath(const QString &entity, const QString &id) const; - bool m_connected; - QString m_lastError; - QMap m_pendingCallbacks; -}; + QString entityPath( + const QString &entity, + const QString &id) const; -#endif // DBALCLIENT_H + QNetworkAccessManager *m_networkManager; + QString m_baseUrl; + QString m_tenantId; + QString m_packageId; + QString m_authToken; + bool m_connected; + QString m_lastError; + QMap m_pendingCallbacks; +}; diff --git a/frontends/qt6/src/DBALRequest.hpp b/frontends/qt6/src/DBALRequest.hpp new file mode 100644 index 000000000..654da607a --- /dev/null +++ b/frontends/qt6/src/DBALRequest.hpp @@ -0,0 +1,28 @@ +#pragma once + +// DBALRequest encapsulates a single in-flight DBAL HTTP request. +// +// DBALClient stores one DBALRequest per QNetworkReply* in its +// pending-request map. Using a named struct instead of a bare +// QJSValue map makes the bookkeeping self-documenting and easy +// to extend (e.g. adding timeout tracking or retry counts). + +#include "DBALTypes.hpp" +#include +#include + +/// Tracks the state of a single pending DBAL network request. +struct DBALRequest +{ + /// The entity name the request targets (e.g. "User"). + QString entity; + + /// The HTTP verb used for this request ("GET", "POST", …). + QString method; + + /// QML callback to invoke when the reply arrives. + DBALCallback callback; + + /// The live network reply (used as the map key in DBALClient). + QNetworkReply *reply = nullptr; +}; diff --git a/frontends/qt6/src/DBALTypes.hpp b/frontends/qt6/src/DBALTypes.hpp new file mode 100644 index 000000000..7947e421a --- /dev/null +++ b/frontends/qt6/src/DBALTypes.hpp @@ -0,0 +1,17 @@ +#pragma once + +// Forward declarations and type aliases for the Qt6 DBAL layer. +// +// All DBAL public APIs use QJSValue for QML-callable callbacks. +// DBALCallback is the canonical alias used throughout DBALClient +// and DBALRequest to make callback parameters self-documenting. + +#include +#include +#include + +/// Alias for a QML-callable callback passed to DBAL operations. +using DBALCallback = QJSValue; + +/// Alias for a JSON body payload sent with DBAL requests. +using DBALPayload = QJsonObject; diff --git a/frontends/qt6/src/ModPlayer.cpp b/frontends/qt6/src/ModPlayer.cpp deleted file mode 100644 index df8e63fc7..000000000 --- a/frontends/qt6/src/ModPlayer.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "ModPlayer.h" - -#include -#include - -// ModPlayer stub - libopenmpt + Qt6 Multimedia not yet configured via Conan. -// Once available, this will use QAudioSink (Qt6) + openmpt::module for .mod playback. - -ModPlayer::ModPlayer(QObject *parent) - : QObject(parent) -{ -} - -ModPlayer::~ModPlayer() { - stop(); -} - -bool ModPlayer::play(const QString &path) { - if (!QFile::exists(path)) { - qWarning() << "ModPlayer: file not found:" << path; - return false; - } - - qInfo() << "ModPlayer: would play" << path << "(audio backend not yet linked)"; - updatePlaying(true); - return true; -} - -void ModPlayer::stop() { - updatePlaying(false); -} - -bool ModPlayer::isPlaying() const { - return m_playing; -} - -void ModPlayer::updatePlaying(bool playing) { - if (m_playing == playing) - return; - m_playing = playing; - emit playbackChanged(); -} diff --git a/frontends/qt6/src/ModPlayer.h b/frontends/qt6/src/ModPlayer.h deleted file mode 100644 index cba168fe5..000000000 --- a/frontends/qt6/src/ModPlayer.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include -#include - -class ModPlayer : public QObject { - Q_OBJECT - Q_PROPERTY(bool playing READ isPlaying NOTIFY playbackChanged) - -public: - explicit ModPlayer(QObject *parent = nullptr); - ~ModPlayer() override; - - Q_INVOKABLE bool play(const QString &path); - Q_INVOKABLE void stop(); - - bool isPlaying() const; - -signals: - void playbackChanged(); - -private: - bool m_playing = false; - void updatePlaying(bool playing); -}; diff --git a/frontends/qt6/src/ModPlayer.hpp b/frontends/qt6/src/ModPlayer.hpp new file mode 100644 index 000000000..de89563a1 --- /dev/null +++ b/frontends/qt6/src/ModPlayer.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include +#include + +// ModPlayer stub - libopenmpt + Qt6 Multimedia not yet configured via Conan. +// Once available, this will use QAudioSink (Qt6) + openmpt::module +// for .mod playback. + +class ModPlayer : public QObject { + Q_OBJECT + Q_PROPERTY(bool playing READ isPlaying NOTIFY playbackChanged) + +public: + explicit ModPlayer(QObject *parent = nullptr) + : QObject(parent) + { + } + + ~ModPlayer() override { + stop(); + } + + Q_INVOKABLE bool play(const QString &path) { + if (!QFile::exists(path)) { + qWarning() << "ModPlayer: file not found:" << path; + return false; + } + qInfo() << "ModPlayer: would play" << path + << "(audio backend not yet linked)"; + updatePlaying(true); + return true; + } + + Q_INVOKABLE void stop() { + updatePlaying(false); + } + + bool isPlaying() const { + return m_playing; + } + +signals: + void playbackChanged(); + +private: + bool m_playing = false; + + void updatePlaying(bool playing) { + if (m_playing == playing) + return; + m_playing = playing; + emit playbackChanged(); + } +}; diff --git a/frontends/qt6/src/NodeRegistry.cpp b/frontends/qt6/src/NodeRegistry.cpp deleted file mode 100644 index 9162aa5bb..000000000 --- a/frontends/qt6/src/NodeRegistry.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include "NodeRegistry.h" - -#include -#include -#include -#include - -NodeRegistry::NodeRegistry(QObject *parent) - : QObject(parent) -{ -} - -QVariantList NodeRegistry::nodeTypes() const -{ - QVariantList list; - for (auto it = m_nodeTypes.constBegin(); it != m_nodeTypes.constEnd(); ++it) { - list.append(it.value().toVariantMap()); - } - return list; -} - -void NodeRegistry::loadRegistry(const QString &path) -{ - QFile file(path); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - m_lastError = QStringLiteral("Cannot open registry file: ") + path; - qWarning() << "NodeRegistry:" << m_lastError; - emit errorOccurred(m_lastError); - return; - } - - QJsonParseError parseErr; - const QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &parseErr); - file.close(); - - if (parseErr.error != QJsonParseError::NoError) { - m_lastError = QStringLiteral("JSON parse error: ") + parseErr.errorString(); - qWarning() << "NodeRegistry:" << m_lastError; - emit errorOccurred(m_lastError); - return; - } - - parseRegistry(doc.object()); - - m_loaded = true; - emit loadedChanged(); - emit nodeTypesChanged(); - qDebug() << "NodeRegistry: loaded" << m_nodeTypes.count() << "node types in" - << m_groups.count() << "groups from" << path; -} - -void NodeRegistry::parseRegistry(const QJsonObject &root) -{ - m_nodeTypes.clear(); - m_groups.clear(); - - const QJsonArray types = root.value(QStringLiteral("nodeTypes")).toArray(); - QSet groupSet; - - for (const QJsonValue &val : types) { - const QJsonObject obj = val.toObject(); - const QString name = obj.value(QStringLiteral("name")).toString(); - if (name.isEmpty()) - continue; - - m_nodeTypes.insert(name, obj); - - const QString group = obj.value(QStringLiteral("group")).toString(); - if (!group.isEmpty()) - groupSet.insert(group); - } - - m_groups = groupSet.values(); - m_groups.sort(); -} - -QVariantMap NodeRegistry::nodeType(const QString &name) const -{ - if (!m_nodeTypes.contains(name)) - return {}; - return m_nodeTypes.value(name).toVariantMap(); -} - -QVariantList NodeRegistry::nodesByGroup(const QString &group) const -{ - QVariantList list; - for (auto it = m_nodeTypes.constBegin(); it != m_nodeTypes.constEnd(); ++it) { - if (it.value().value(QStringLiteral("group")).toString() == group) - list.append(it.value().toVariantMap()); - } - return list; -} - -QVariantList NodeRegistry::searchNodes(const QString &query) const -{ - if (query.isEmpty()) - return nodeTypes(); - - const QString lower = query.toLower(); - QVariantList list; - for (auto it = m_nodeTypes.constBegin(); it != m_nodeTypes.constEnd(); ++it) { - const QJsonObject &obj = it.value(); - const QString name = obj.value(QStringLiteral("name")).toString().toLower(); - const QString displayName = obj.value(QStringLiteral("displayName")).toString().toLower(); - const QString desc = obj.value(QStringLiteral("description")).toString().toLower(); - - if (name.contains(lower) || displayName.contains(lower) || desc.contains(lower)) - list.append(obj.toVariantMap()); - } - return list; -} diff --git a/frontends/qt6/src/NodeRegistry.h b/frontends/qt6/src/NodeRegistry.h deleted file mode 100644 index 62128e33c..000000000 --- a/frontends/qt6/src/NodeRegistry.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef NODEREGISTRY_H -#define NODEREGISTRY_H - -#include -#include -#include -#include -#include -#include -#include - -class NodeRegistry : public QObject -{ - Q_OBJECT - Q_PROPERTY(QVariantList nodeTypes READ nodeTypes NOTIFY nodeTypesChanged) - Q_PROPERTY(QStringList groups READ groups NOTIFY nodeTypesChanged) - Q_PROPERTY(int nodeCount READ nodeCount NOTIFY nodeTypesChanged) - Q_PROPERTY(bool loaded READ isLoaded NOTIFY loadedChanged) - Q_PROPERTY(QString lastError READ lastError NOTIFY errorOccurred) - -public: - explicit NodeRegistry(QObject *parent = nullptr); - - QVariantList nodeTypes() const; - QStringList groups() const { return m_groups; } - int nodeCount() const { return m_nodeTypes.count(); } - bool isLoaded() const { return m_loaded; } - QString lastError() const { return m_lastError; } - -public slots: - void loadRegistry(const QString &path); - QVariantMap nodeType(const QString &name) const; - QVariantList nodesByGroup(const QString &group) const; - QVariantList searchNodes(const QString &query) const; - -signals: - void nodeTypesChanged(); - void loadedChanged(); - void errorOccurred(const QString &error); - -private: - void parseRegistry(const QJsonObject &root); - - QMap m_nodeTypes; - QStringList m_groups; - bool m_loaded = false; - QString m_lastError; -}; - -#endif diff --git a/frontends/qt6/src/NodeRegistry.hpp b/frontends/qt6/src/NodeRegistry.hpp new file mode 100644 index 000000000..78e41835d --- /dev/null +++ b/frontends/qt6/src/NodeRegistry.hpp @@ -0,0 +1,185 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class NodeRegistry : public QObject +{ + Q_OBJECT + Q_PROPERTY( + QVariantList nodeTypes + READ nodeTypes + NOTIFY nodeTypesChanged) + Q_PROPERTY( + QStringList groups + READ groups + NOTIFY nodeTypesChanged) + Q_PROPERTY( + int nodeCount + READ nodeCount + NOTIFY nodeTypesChanged) + Q_PROPERTY( + bool loaded + READ isLoaded + NOTIFY loadedChanged) + Q_PROPERTY( + QString lastError + READ lastError + NOTIFY errorOccurred) + +public: + explicit NodeRegistry(QObject *parent = nullptr) + : QObject(parent) + {} + + QVariantList nodeTypes() const + { + QVariantList list; + for (auto it = m_nodeTypes.constBegin(); + it != m_nodeTypes.constEnd(); ++it) { + list.append(it.value().toVariantMap()); + } + return list; + } + + QStringList groups() const { return m_groups; } + int nodeCount() const { return m_nodeTypes.count(); } + bool isLoaded() const { return m_loaded; } + QString lastError() const { return m_lastError; } + +public slots: + void loadRegistry(const QString &path) + { + QFile file(path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + m_lastError = + QStringLiteral("Cannot open registry file: ") + path; + qWarning() << "NodeRegistry:" << m_lastError; + emit errorOccurred(m_lastError); + return; + } + + QJsonParseError parseErr; + const QJsonDocument doc = + QJsonDocument::fromJson(file.readAll(), &parseErr); + file.close(); + + if (parseErr.error != QJsonParseError::NoError) { + m_lastError = + QStringLiteral("JSON parse error: ") + + parseErr.errorString(); + qWarning() << "NodeRegistry:" << m_lastError; + emit errorOccurred(m_lastError); + return; + } + + parseRegistry(doc.object()); + + m_loaded = true; + emit loadedChanged(); + emit nodeTypesChanged(); + qDebug() << "NodeRegistry: loaded" + << m_nodeTypes.count() + << "node types in" + << m_groups.count() + << "groups from" << path; + } + + QVariantMap nodeType(const QString &name) const + { + if (!m_nodeTypes.contains(name)) + return {}; + return m_nodeTypes.value(name).toVariantMap(); + } + + QVariantList nodesByGroup(const QString &group) const + { + QVariantList list; + for (auto it = m_nodeTypes.constBegin(); + it != m_nodeTypes.constEnd(); ++it) { + const QString g = + it.value() + .value(QStringLiteral("group")) + .toString(); + if (g == group) + list.append(it.value().toVariantMap()); + } + return list; + } + + QVariantList searchNodes(const QString &query) const + { + if (query.isEmpty()) + return nodeTypes(); + + const QString lower = query.toLower(); + QVariantList list; + for (auto it = m_nodeTypes.constBegin(); + it != m_nodeTypes.constEnd(); ++it) { + const QJsonObject &obj = it.value(); + const QString name = + obj.value(QStringLiteral("name")) + .toString().toLower(); + const QString displayName = + obj.value(QStringLiteral("displayName")) + .toString().toLower(); + const QString desc = + obj.value(QStringLiteral("description")) + .toString().toLower(); + + if (name.contains(lower) + || displayName.contains(lower) + || desc.contains(lower)) + list.append(obj.toVariantMap()); + } + return list; + } + +signals: + void nodeTypesChanged(); + void loadedChanged(); + void errorOccurred(const QString &error); + +private: + void parseRegistry(const QJsonObject &root) + { + m_nodeTypes.clear(); + m_groups.clear(); + + const QJsonArray types = + root.value(QStringLiteral("nodeTypes")).toArray(); + QSet groupSet; + + for (const QJsonValue &val : types) { + const QJsonObject obj = val.toObject(); + const QString name = + obj.value(QStringLiteral("name")).toString(); + if (name.isEmpty()) + continue; + + m_nodeTypes.insert(name, obj); + + const QString group = + obj.value(QStringLiteral("group")).toString(); + if (!group.isEmpty()) + groupSet.insert(group); + } + + m_groups = groupSet.values(); + m_groups.sort(); + } + + QMap m_nodeTypes; + QStringList m_groups; + bool m_loaded = false; + QString m_lastError; +}; diff --git a/frontends/qt6/src/PackageRegistry.cpp b/frontends/qt6/src/PackageRegistry.cpp deleted file mode 100644 index f054c08c8..000000000 --- a/frontends/qt6/src/PackageRegistry.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#include "PackageRegistry.h" - -#include -#include -#include -#include -#include -#include -#include - -namespace { -QString normalizedPath(const QString &path) { - QDir dir(path); - return dir.absolutePath(); -} - -QString metadataFileName(const QString &packageId) { - return packageId + "/metadata.json"; -} -} - -PackageRegistry::PackageRegistry(QObject *parent) - : QObject(parent) -{ - const auto appDir = QCoreApplication::applicationDirPath(); - m_roots << normalizedPath(appDir + "/packages"); - m_roots << normalizedPath(appDir + "/../packages"); - m_roots << normalizedPath(appDir + "/../frontends/qt6/packages"); - m_roots << normalizedPath(appDir + "/../../frontends/qt6/packages"); -} - -QStringList PackageRegistry::packageIds() const -{ - QSet ids; - for (const auto &root : m_roots) { - QDir dir(root); - if (!dir.exists()) - continue; - - const auto entries = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - for (const auto &entry : entries) { - const auto meta = dir.filePath(metadataFileName(entry)); - if (QFile::exists(meta)) - ids.insert(entry); - } - } - return ids.values(); -} - -QString PackageRegistry::loadedPackage() const -{ - return m_loadedPackage; -} - -QVariantMap PackageRegistry::loadedMetadata() const -{ - return m_loadedMetadata; -} - -QVariantMap PackageRegistry::metadata(const QString &packageId) const -{ - const auto filePath = findMetadataFile(packageId); - if (filePath.isEmpty()) - return {}; - QFile file(filePath); - if (!file.open(QIODevice::ReadOnly)) - return {}; - const auto doc = QJsonDocument::fromJson(file.readAll()); - if (!doc.isObject()) - return {}; - return doc.object().toVariantMap(); -} - -bool PackageRegistry::loadPackage(const QString &packageId) -{ - const auto filePath = findMetadataFile(packageId); - if (filePath.isEmpty()) - return false; - - QFile file(filePath); - if (!file.open(QIODevice::ReadOnly)) - return false; - - const auto doc = QJsonDocument::fromJson(file.readAll()); - if (!doc.isObject()) - return false; - - m_loadedPackage = packageId; - m_loadedMetadata = doc.object().toVariantMap(); - emit packageLoaded(); - emit metadataChanged(); - return true; -} - -QString PackageRegistry::findMetadataFile(const QString &packageId) const -{ - for (const auto &root : m_roots) { - const auto candidate = QDir(root).filePath(metadataFileName(packageId)); - if (QFile::exists(candidate)) - return candidate; - } - return {}; -} diff --git a/frontends/qt6/src/PackageRegistry.h b/frontends/qt6/src/PackageRegistry.h deleted file mode 100644 index 6fc0f6edb..000000000 --- a/frontends/qt6/src/PackageRegistry.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include -#include - -class PackageRegistry : public QObject { - Q_OBJECT - Q_PROPERTY(QStringList packageIds READ packageIds NOTIFY packagesChanged) - Q_PROPERTY(QString loadedPackage READ loadedPackage NOTIFY packageLoaded) - Q_PROPERTY(QVariantMap loadedMetadata READ loadedMetadata NOTIFY metadataChanged) - -public: - explicit PackageRegistry(QObject *parent = nullptr); - - QStringList packageIds() const; - QString loadedPackage() const; - QVariantMap loadedMetadata() const; - - Q_INVOKABLE bool loadPackage(const QString &packageId); - Q_INVOKABLE QVariantMap metadata(const QString &packageId) const; - -signals: - void packagesChanged(); - void packageLoaded(); - void metadataChanged(); - -private: - QString findMetadataFile(const QString &packageId) const; - QStringList m_roots; - QString m_loadedPackage; - QVariantMap m_loadedMetadata; -}; diff --git a/frontends/qt6/src/PackageRegistry.hpp b/frontends/qt6/src/PackageRegistry.hpp new file mode 100644 index 000000000..c41d04f65 --- /dev/null +++ b/frontends/qt6/src/PackageRegistry.hpp @@ -0,0 +1,132 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +inline QString normalizedPath(const QString &path) { + QDir dir(path); + return dir.absolutePath(); +} + +inline QString metadataFileName(const QString &packageId) { + return packageId + "/metadata.json"; +} +} // namespace + +class PackageRegistry : public QObject { + Q_OBJECT + Q_PROPERTY( + QStringList packageIds + READ packageIds + NOTIFY packagesChanged + ) + Q_PROPERTY( + QString loadedPackage + READ loadedPackage + NOTIFY packageLoaded + ) + Q_PROPERTY( + QVariantMap loadedMetadata + READ loadedMetadata + NOTIFY metadataChanged + ) + +public: + explicit PackageRegistry(QObject *parent = nullptr) + : QObject(parent) + { + const auto appDir = QCoreApplication::applicationDirPath(); + m_roots << normalizedPath(appDir + "/packages"); + m_roots << normalizedPath(appDir + "/../packages"); + m_roots << normalizedPath( + appDir + "/../frontends/qt6/packages" + ); + m_roots << normalizedPath( + appDir + "/../../frontends/qt6/packages" + ); + } + + QStringList packageIds() const { + QSet ids; + for (const auto &root : m_roots) { + QDir dir(root); + if (!dir.exists()) + continue; + const auto entries = + dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const auto &entry : entries) { + const auto meta = + dir.filePath(metadataFileName(entry)); + if (QFile::exists(meta)) + ids.insert(entry); + } + } + return ids.values(); + } + + QString loadedPackage() const { + return m_loadedPackage; + } + + QVariantMap loadedMetadata() const { + return m_loadedMetadata; + } + + Q_INVOKABLE bool loadPackage(const QString &packageId) { + const auto filePath = findMetadataFile(packageId); + if (filePath.isEmpty()) + return false; + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) + return false; + const auto doc = QJsonDocument::fromJson(file.readAll()); + if (!doc.isObject()) + return false; + m_loadedPackage = packageId; + m_loadedMetadata = doc.object().toVariantMap(); + emit packageLoaded(); + emit metadataChanged(); + return true; + } + + Q_INVOKABLE QVariantMap metadata(const QString &packageId) const { + const auto filePath = findMetadataFile(packageId); + if (filePath.isEmpty()) + return {}; + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) + return {}; + const auto doc = QJsonDocument::fromJson(file.readAll()); + if (!doc.isObject()) + return {}; + return doc.object().toVariantMap(); + } + +signals: + void packagesChanged(); + void packageLoaded(); + void metadataChanged(); + +private: + QStringList m_roots; + QString m_loadedPackage; + QVariantMap m_loadedMetadata; + + QString findMetadataFile(const QString &packageId) const { + for (const auto &root : m_roots) { + const auto candidate = + QDir(root).filePath(metadataFileName(packageId)); + if (QFile::exists(candidate)) + return candidate; + } + return {}; + } +};