Merge pull request #3 from logos-co/feat/move-to-qml

feat: move to qml
This commit is contained in:
Arnaud 2026-01-30 21:34:55 +04:00 committed by GitHub
commit e575f494e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 602 additions and 379 deletions

View File

@ -1,4 +0,0 @@
CompileFlags:
Add:
- -I./libs/include
- -I./result/include

3
.gitignore vendored
View File

@ -4,4 +4,5 @@ data/
build
libs
app/build
CMakeLists.txt.user
CMakeLists.txt.user
generated_code

View File

@ -4,6 +4,7 @@ project(StorageUIPlugin VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
########### DEPENDENCIES SECTION ###########
@ -136,11 +137,11 @@ endif()
# Source files
set(SOURCES
plugin/StorageUIPlugin.cpp
plugin/StorageUIPlugin.h
plugin/src/StorageBackend.cpp
plugin/src/StorageBackend.h
plugin/storage_resources.qrc
src/StorageUIPlugin.cpp
src/StorageUIPlugin.h
src/StorageBackend.cpp
src/StorageBackend.h
src/storage_resources.qrc
)
# Add SDK sources (only if source layout, installed layout uses the library)
@ -250,17 +251,16 @@ if(_cpp_sdk_is_source)
)
endif()
# Add custom target to run the cpp generator
add_custom_target(run_cpp_generator_storage_ui
COMMAND "${CPP_GENERATOR}" --metadata "${METADATA_JSON}" --general-only --output-dir "${PLUGINS_OUTPUT_DIR}"
WORKING_DIRECTORY "${LOGOS_CPP_SDK_ROOT}/.."
COMMENT "Running logos-cpp-generator on ${METADATA_JSON} with output-dir ${PLUGINS_OUTPUT_DIR}"
VERBATIM
add_custom_command(
OUTPUT "${PLUGINS_OUTPUT_DIR}/logos_sdk.cpp"
COMMAND ${CMAKE_COMMAND} -E make_directory "${PLUGINS_OUTPUT_DIR}"
COMMAND "${CPP_GENERATOR}" --metadata "${METADATA_JSON}" --general-only --output-dir "${PLUGINS_OUTPUT_DIR}"
WORKING_DIRECTORY "${LOGOS_CPP_SDK_ROOT}/.."
DEPENDS "${METADATA_JSON}" cpp_generator_build
VERBATIM
)
# Add dependency to ensure cpp generator is built first
add_dependencies(run_cpp_generator_storage_ui cpp_generator_build)
add_dependencies(run_cpp_generator_storage_ui run_cpp_generator_storage_module)
add_custom_target(run_cpp_generator_storage_ui DEPENDS "${PLUGINS_OUTPUT_DIR}/logos_sdk.cpp")
# Add generated logos_sdk.cpp - will be generated by run_cpp_generator_storage_ui
list(APPEND SOURCES

View File

@ -158,6 +158,14 @@ Thats it. The configuration defined in `CMakeLists.txt` should allow the proj
If you encounter any configuration issues, close Qt Creator, remove the `CMakeLists.txt.user` file, and restart Qt Creator to reconfigure the project.
### Tips
Here are some tips that may help during development:
1. If you use the `Ctrl+B` shortcut to build, make sure the correct project is selected. Right-click on it and choose `Set as Active Project`.
2. If you encounter build errors, a possible fix is to nuke the build folder and rebuild from scratch.
3. Do not call storage module functions from within a callback.
## Requirements
### Build Tools

View File

@ -1,10 +0,0 @@
import QmlProject 1.1
Project {
mainFile: "qml/Main.qml"
QmlFiles {
files: [
"qml/Main.qml"
]
}
}

8
app/.clang-format Normal file
View File

@ -0,0 +1,8 @@
BasedOnStyle: LLVM
BreakBeforeBraces: Attach
BraceWrapping:
AfterLambda: false
ContinuationIndentWidth: 4
IndentWidth: 4
ColumnLimit: 120
PointerAlignment: Left

View File

@ -79,7 +79,7 @@ int main(int argc, char* argv[]) {
qDebug() << "Application cleanup...";
window.cleanup();
window.destroy();
// Cleanup core before exit
logos_core_cleanup();

View File

@ -10,26 +10,11 @@ extern "C" {
void logos_core_cleanup();
}
void MainWindow::cleanup() {
qDebug() << "MainWindow: Cleaning up before exit...";
void MainWindow::destroy() {
qDebug() << "MainWindow: Destroying MainWindow...";
if (storageWidget) {
qDebug() << "MainWindow: Stopping Storage before destroying...";
QEventLoop loop;
QObject::connect(storageWidget, SIGNAL(storageStop()), &loop, SLOT(quit()));
QMetaObject::invokeMethod(storageWidget, "stopStorage", Qt::QueuedConnection);
loop.exec();
qDebug() << "MainWindow: Storage stopped.";
qDebug() << "MainWindow: Destroying Storage...";
QObject::connect(storageWidget, SIGNAL(storageCleanup()), &loop, SLOT(quit()));
QMetaObject::invokeMethod(storageWidget, "destroy", Qt::QueuedConnection);
loop.exec();
qDebug() << "MainWindow: Storage destroyed.";
if (plugin && storageWidget) {
QMetaObject::invokeMethod(plugin, "destroyWidget", Qt::DirectConnection, Q_ARG(QWidget*, storageWidget));
}
}
@ -40,6 +25,7 @@ MainWindow::~MainWindow() {}
void MainWindow::setupUi() {
// Determine the appropriate plugin extension based on the platform
QString pluginExtension;
#if defined(Q_OS_WIN)
pluginExtension = ".dll";
#elif defined(Q_OS_MAC)
@ -55,7 +41,7 @@ void MainWindow::setupUi() {
QWidget* widget = nullptr;
if (loader.load()) {
QObject* plugin = loader.instance();
plugin = loader.instance();
if (plugin) {
// Try to create the storage widget using the plugin's createWidget method
QMetaObject::invokeMethod(plugin, "createWidget", Qt::DirectConnection, Q_RETURN_ARG(QWidget*, widget));
@ -86,6 +72,6 @@ void MainWindow::setupUi() {
}
// Set window title and size
setWindowTitle("Logos Storage UI App !");
setWindowTitle("Logos Storage UI App");
resize(800, 600);
}

View File

@ -11,13 +11,14 @@ class MainWindow : public QMainWindow {
public:
MainWindow(QWidget* parent = nullptr);
~MainWindow();
void cleanup();
void destroy();
private:
void setupUi();
void setupMenu();
QWidget* storageWidget = nullptr;
QObject* plugin = nullptr;
};
#endif // MAINWINDOW_H

23
flake.lock generated
View File

@ -188,11 +188,11 @@
]
},
"locked": {
"lastModified": 1769522053,
"narHash": "sha256-he63yFC/k0/PYi5bjDCLuBxJvv+GvQ/2S6p9Ns14MfY=",
"lastModified": 1769784473,
"narHash": "sha256-00aXkYlP9dii4aI/YDBoGe23cEKKdvYYHPGrXSItip4=",
"owner": "logos-co",
"repo": "logos-liblogos",
"rev": "277dd84a2c9616fd337320b75ae2ce1921915a87",
"rev": "1378757dc8745941ebee79ae484abe62be2f5db4",
"type": "github"
},
"original": {
@ -232,17 +232,16 @@
"nixpkgs": "nixpkgs_7"
},
"locked": {
"lastModified": 1768225619,
"narHash": "sha256-PcHrim74Rnp6hnzWEEzOsJkBvswp/6p6x17ySb6nIjQ=",
"ref": "fix/close-discovery-store",
"rev": "8f11914e5750e2c39d0c65322249b551a7252ee9",
"revCount": 869,
"lastModified": 1769016529,
"narHash": "sha256-pzUrdFKSEraZQzO2a9Uj5oDMas+Vtpu6+p4bG+lRnsk=",
"ref": "refs/heads/master",
"rev": "3c09f008bb5266a669fd19f18368f9e8b861b664",
"revCount": 877,
"submodules": true,
"type": "git",
"url": "https://github.com/logos-storage/logos-storage-nim"
},
"original": {
"ref": "fix/close-discovery-store",
"submodules": true,
"type": "git",
"url": "https://github.com/logos-storage/logos-storage-nim"
@ -260,11 +259,11 @@
]
},
"locked": {
"lastModified": 1768839561,
"narHash": "sha256-RzXrbeZpxMxgXbfc2wCm5LBgHlP+3ELe4cDfXFum/Kc=",
"lastModified": 1769611047,
"narHash": "sha256-U8lV4hgntPTMO0wpyIDFKMN1N3jSGnxKDCGHS9WoEb8=",
"owner": "logos-co",
"repo": "logos-storage-module",
"rev": "26a10d61ad7eef3c07f8992da7b0fc1b17e98759",
"rev": "14cc30674980685442e66639c37c587263520147",
"type": "github"
},
"original": {

View File

@ -12,17 +12,15 @@
"build": {
"type": "cmake",
"files": [
"src/storage_ui_plugin.cpp",
"src/storage_ui_plugin.h",
"src/storagewindow.cpp",
"src/storagewindow.h",
"src/storagewidget.cpp",
"src/storagewidget.h",
"src/storage_ui_resources.qrc"
"src/StorageUIPlugin.cpp",
"src/StorageUIPlugin.h",
"src/StorageBackend.cpp",
"src/StorageBackend.h",
"src/storage_resources.qrc"
]
},
"capabilities": [
"ui_components",
"storage"
]
}
}

View File

@ -1,44 +0,0 @@
#include "StorageUIPlugin.h"
#include "src/StorageBackend.h"
#include <QQuickWidget>
#include <QQmlContext>
#include <QQmlEngine>
#include <QDebug>
#include <QFileInfo>
#include <QFile>
QWidget* StorageUIPlugin::createWidget(LogosAPI* logosAPI) {
qDebug() << "StorageUIPlugin::createWidget called";
QQuickWidget* quickWidget = new QQuickWidget();
quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
// Register StorageBackend type with QML engine to expose the enum
qmlRegisterType<StorageBackend>("StorageBackend", 1, 0, "StorageBackend");
// Create backend instance
StorageBackend* backend = new StorageBackend(logosAPI, quickWidget);
// Set backend as context property
quickWidget->rootContext()->setContextProperty("backend", backend);
// For development: check environment variable, otherwise use qrc
QString qmlPath = "qrc:/StorageView.qml";
QString envPath = qgetenv("STORAGE_UI_QML_PATH");
if (!envPath.isEmpty() && QFile::exists(envPath)) {
qmlPath = QUrl::fromLocalFile(QFileInfo(envPath).absoluteFilePath()).toString();
qDebug() << "Loading QML from file system:" << qmlPath;
}
quickWidget->setSource(QUrl(qmlPath));
if (quickWidget->status() == QQuickWidget::Error) {
qWarning() << "StorageUIPlugin: Failed to load QML:" << quickWidget->errors();
}
return quickWidget;
}
void StorageUIPlugin::destroyWidget(QWidget* widget) {
delete widget;
}

View File

@ -1,43 +0,0 @@
import QtQuick
import QtQuick.Controls
Item {
anchors.fill: parent
Rectangle {
anchors.fill: parent
anchors.leftMargin: 0
anchors.rightMargin: 0
anchors.topMargin: 0
anchors.bottomMargin: 0
color: "#202428"
}
Text {
text: qsTr("Storage UI")
anchors.verticalCenterOffset: 214
anchors.horizontalCenterOffset: -269
color: "white"
anchors.centerIn: parent
font.pointSize: 20
}
Text {
objectName: "status"
text: "..."
color: "white"
font.pointSize: 20
anchors.topMargin: 32
}
Button {
objectName: "startButton"
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 395
anchors.horizontalCenterOffset: -270
text: "Stop"
}
}

View File

@ -1,97 +0,0 @@
#include "StorageBackend.h"
#include <QDebug>
#include <QDateTime>
#include <QLocale>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
StorageBackend::StorageBackend(LogosAPI* logosAPI, QObject* parent)
: QObject(parent),
m_status(NotStarted),
m_logosAPI(nullptr),
m_logos(nullptr)
{
qDebug() << "Initializing StorageBackend...";
if (logosAPI) {
m_logosAPI = logosAPI;
} else {
m_logosAPI = new LogosAPI("core", this);
}
m_logos = new LogosModules(m_logosAPI);
}
StorageBackend::~StorageBackend()
{
stopStorage();
}
void StorageBackend::setStatus(StorageStatus newStatus)
{
if (m_status != newStatus) {
m_status = newStatus;
emit statusChanged();
qDebug() << "StorageBackend: Status changed to" << m_status;
}
}
void StorageBackend::startStorage()
{
setStatus(Starting);
auto& storageModule = m_logos->storage_module;
QString configStr = R"({
})";
if (!storageModule.initStorage(configStr)) {
setStatus(Error);
return;
}
// Subscribe to connectedPeersResponse events
// if (!storageModule.on("connectedPeersResponse", [this](const QVariantList& data) {
// if (data.size() < 1) {
// qWarning() << "StorageBackend: connectedPeersResponse payload missing fields";
// return;
// }
// onConnectedPeersResponse(data);
// })) {
// qWarning() << "StorageBackend: failed to subscribe to connectedPeersResponse events";
// }
if (!storageModule.startStorage()) {
setStatus(Error);
return;
}
setStatus(Running);
// Refresh peers and metrics after a delay to allow Storage to fully start
// QTimer::singleShot(1000, this, [this]() {
// refreshPeers();
// refreshMetrics();
// });
}
void StorageBackend::stopStorage()
{
if (m_status != Running && m_status != Starting) {
return;
}
setStatus(Stopping);
auto& storageModule = m_logos->storage_module;
if (!storageModule.stopStorage()) {
qWarning() << "StorageBackend::stopStorage: stopStorage() returned false";
setStatus(Error);
return;
}
setStatus(Stopped);
}

View File

@ -1,48 +0,0 @@
#pragma once
#include <QObject>
#include <QString>
#include <QStringList>
#include <QTimer>
#include "logos_api.h"
#include "logos_api_client.h"
#include "logos_sdk.h"
class StorageBackend : public QObject {
Q_OBJECT
public:
enum StorageStatus {
NotStarted = 0,
Starting,
Running,
Stopping,
Stopped,
Error
};
Q_ENUM(StorageStatus)
Q_PROPERTY(StorageStatus status READ status NOTIFY statusChanged)
explicit StorageBackend(LogosAPI* logosAPI = nullptr, QObject* parent = nullptr);
~StorageBackend();
StorageStatus status() const { return m_status; }
public slots:
Q_INVOKABLE void startStorage();
Q_INVOKABLE void stopStorage();
signals:
void statusChanged();
private slots:
// void onConnectedPeersResponse(const QVariantList& data);
private:
void setStatus(StorageStatus newStatus);
StorageStatus m_status;
LogosAPI* m_logosAPI;
LogosModules* m_logos;
};

View File

@ -1,5 +0,0 @@
<RCC>
<qresource prefix="/">
<file>StorageView.qml</file>
</qresource>
</RCC>

View File

@ -1,42 +0,0 @@
cmake_minimum_required(VERSION 3.16)
project(qml VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Quick)
qt_standard_project_setup(REQUIRES 6.8)
qt_add_executable(appqml
main.cpp
)
qt_add_qml_module(appqml
URI qml
VERSION 1.0
QML_FILES
Main.qml
)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
set_target_properties(appqml PROPERTIES
# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appqml
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
target_link_libraries(appqml
PRIVATE Qt6::Quick
)
include(GNUInstallDirs)
install(TARGETS appqml
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

View File

@ -1,8 +0,0 @@
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
}

156
src/StorageBackend.cpp Normal file
View File

@ -0,0 +1,156 @@
#include "StorageBackend.h"
#include <QDateTime>
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLocale>
StorageBackend::StorageBackend(LogosAPI* logosAPI, QObject* parent)
: QObject(parent), m_status(Destroyed), m_logosAPI(nullptr), m_logos(nullptr) {
qDebug() << "Initializing StorageBackend...";
if (logosAPI) {
m_logosAPI = logosAPI;
} else {
m_logosAPI = new LogosAPI("core", this);
}
m_logos = new LogosModules(m_logosAPI);
initStorage();
}
StorageBackend::~StorageBackend()
{
m_logosAPI = nullptr;
m_logos = nullptr;
}
void StorageBackend::initStorage() {
qDebug() << "StorageBackend::initStorage called";
bool result = m_logos->storage_module.init("{}");
qDebug() << "StorageBackend::initStorage: init result =" << result;
if (!result) {
qDebug() << "StorageBackend: Failed to initialise Storage module.";
setStatus(Destroyed, "Failed to initialise Storage module.");
return;
}
setStatus(Stopped, "Storage module ready.");
if (!m_logos->storage_module.on("storageStart", [this](const QVariantList& data) {
int code = data[0].toInt();
if (code != RET_OK) {
qDebug() << "StorageBackend: storageStart event failure with code" << code;
setStatus(Stopped, "Failed to start Storage module.");
} else {
qDebug() << "StorageBackend: storageStart event success";
setStatus(Running, "Storage module started.");
}
})) {
qWarning() << "StorageWidget: failed to subscribe to storageStart events";
}
if (!m_logos->storage_module.on("storageStop", [this](const QVariantList& data) {
int code = data[0].toInt();
if (code != RET_OK) {
qDebug() << "StorageBackend: storageStop event failure with code" << code;
setStatus(Running, "Failed to stop Storage module.");
} else {
qDebug() << "StorageBackend: storageStop event success";
setStatus(Stopped, "Storage module stopped.");
emit stopped();
}
})) {
qWarning() << "StorageWidget: failed to subscribe to storageStop events";
}
startStop();
}
void StorageBackend::setStatus(StorageStatus newStatus, QString statusText) {
if (m_status != newStatus || m_statusText != statusText) {
m_status = newStatus;
m_statusText = statusText;
emit statusChanged();
qDebug() << "StorageBackend: Status changed to" << m_status;
}
}
void StorageBackend::startStop() {
qDebug() << "StorageBackend: startStop method called";
if (m_status == Destroyed || m_status == Starting || m_status == Stopping) {
qDebug() << "StorageBackend: Cannot start/stop Storage in current state:" << m_status;
return;
}
if (m_status != Running) {
qDebug() << "StorageBackend: Starting Storage...";
setStatus(Starting, "Starting Storage module...");
bool result = m_logos->storage_module.start();
if (!result) {
qDebug() << "StorageBackend: Failed to start Storage.";
setStatus(Stopped, "Failed to start Storage module.");
return;
}
qDebug() << "StorageBackend: start command sent, waiting for events.";
} else {
stop();
}
}
void StorageBackend::stop() {
qDebug() << "StorageBackend: Stopping Storage...";
setStatus(Stopping, "Stopping Storage module...");
bool result = m_logos->storage_module.stop();
if (!result) {
qDebug() << "StorageBackend: Failed to stop Storage.";
setStatus(Running, "Failed to stop Storage module.");
return;
}
qDebug() << "StorageBackend: stop command sent, waiting for events.";
}
void StorageBackend::destroy() {
qDebug() << "StorageBackend: destroy method called";
StorageStatus status = m_status;
int result = m_logos->storage_module.destroy();
if (!result) {
qDebug() << "StorageBackend: Failed to destroy Storage module." << result;
setStatus(status, "Failed to destroy Storage module.");
return;
}
qDebug() << "StorageBackend: Storage module destroyed.";
}
QString StorageBackend::statusText() const { return m_statusText; }
QString StorageBackend::startStopText() const {
if (m_status != Running) {
return "Start";
} else {
return "Stop";
}
}
bool StorageBackend::canStartStop() const { return m_status == Running || m_status == Stopped; }
bool StorageBackend::isRunning() { return m_status == Running; }
bool StorageBackend::isInitialised() { return m_status != Destroyed; }

54
src/StorageBackend.h Normal file
View File

@ -0,0 +1,54 @@
#pragma once
#include "logos_api.h"
#include "logos_sdk.h"
#include <QObject>
#include <QString>
#include <QStringList>
#include <QTimer>
#include <QtQml/qqml.h>
static const int RET_OK = 0;
class StorageBackend : public QObject {
Q_OBJECT
QML_ELEMENT
public:
enum StorageStatus { Stopped = 0, Starting, Running, Stopping, Destroyed };
Q_ENUM(StorageStatus)
Q_PROPERTY(bool canStartStop READ canStartStop NOTIFY statusChanged)
bool canStartStop() const;
Q_PROPERTY(QString startStopText READ startStopText NOTIFY statusChanged)
QString startStopText() const;
Q_PROPERTY(QString statusText READ statusText NOTIFY statusChanged)
QString statusText() const;
explicit StorageBackend(LogosAPI* logosAPI = nullptr, QObject* parent = nullptr);
~StorageBackend();
public slots:
void startStop();
void destroy();
bool isRunning();
bool isInitialised();
void stop();
signals:
void statusChanged();
void stopped();
private slots:
private:
void setStatus(StorageStatus newStatus, QString statusText);
void initStorage();
StorageStatus m_status;
LogosAPI* m_logosAPI;
LogosModules* m_logos;
QString m_statusText;
};

115
src/StorageUIPlugin.cpp Normal file
View File

@ -0,0 +1,115 @@
#include "StorageUIPlugin.h"
#include "StorageBackend.h"
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickItem>
#include <QQuickWidget>
QWidget* StorageUIPlugin::createWidget(LogosAPI* logosAPI) {
qDebug() << "StorageUIPlugin::createWidget called";
QQuickWidget* quickWidget = new QQuickWidget();
quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
QString qmlPath = "qrc:/StorageView.qml";
quickWidget->setSource(QUrl(qmlPath));
if (quickWidget->status() == QQuickWidget::Error) {
qWarning() << "StorageUIPlugin: Failed to load QML:" << quickWidget->errors();
}
// Create backend instance
StorageBackend* backend = new StorageBackend(logosAPI, quickWidget);
// Set backend as context property
QQuickItem* root = quickWidget->rootObject();
Q_ASSERT(root);
root->setProperty("backend", QVariant::fromValue(static_cast<QObject*>(backend)));
return quickWidget;
}
// Destroy the widget and clean up the backend.
// It will block the event loop up to 2 seconds to ensure proper cleanup.
// It will try to stop the backend if it is running.`
void StorageUIPlugin::destroyWidget(QWidget* widget) {
qDebug() << "StorageUIPlugin: Destroy widget";
auto* quickWidget = qobject_cast<QQuickWidget*>(widget);
if (!quickWidget) {
delete widget;
return;
}
QQuickItem* root = quickWidget->rootObject();
if (!root) {
qWarning() << "StorageUIPlugin::destroyWidget: No rootObject, deleting widget";
quickWidget->deleteLater();
return;
}
// Disable QML to ensure that not updated are pushed in the UI
root->setEnabled(false);
// Retrieve the backend object from the root element
// as it was set in createWidget
QObject* value = root->property("backend").value<QObject*>();
auto* backend = qobject_cast<StorageBackend*>(value);
if (!backend) {
qWarning() << "StorageUIPlugin::destroyWidget: No backend found on root property 'backend'.";
quickWidget->deleteLater();
return;
}
if (!backend->isInitialised()) {
qDebug() << "StorageUIPlugin::destroyWidget: backend is not initialised so let's detroy it.";
quickWidget->deleteLater();
return;
}
if (!backend->isRunning()) {
qDebug() << "StorageUIPlugin::destroyWidget: backend is not running so let's detroy it.";
backend->destroy();
quickWidget->deleteLater();
return;
}
qDebug() << "StorageUIPlugin::destroyWidget: backend is running so let's stop it.";
// Here we create a QEventLoop to wait for the stopped signal
QEventLoop loop;
QTimer timeout;
// Single shot means that the timer will only fire once.
timeout.setSingleShot(true);
// Connect to timeout and unblock the event loop after 2 seconds
QObject::connect(&timeout, &QTimer::timeout, &loop, [&]() {
qWarning() << "StorageUIPlugin::destroyWidget: stop timeout";
loop.quit();
});
// Connect to stop signal
QObject::connect(backend, &StorageBackend::stopped, &loop, [&]() { loop.quit(); }, Qt::QueuedConnection);
// Call the stop method asynchronously
QMetaObject::invokeMethod(backend, "stop", Qt::QueuedConnection);
// Set the timeout to 2 sec
timeout.start(2000);
// Block
loop.exec();
// Try to cleanup, event if the stop method failed
backend->destroy();
delete quickWidget;
}

View File

@ -10,5 +10,5 @@ class StorageUIPlugin : public QObject, public IComponent {
public:
Q_INVOKABLE QWidget* createWidget(LogosAPI* logosAPI = nullptr) override;
void destroyWidget(QWidget* widget) override;
};
Q_INVOKABLE void destroyWidget(QWidget* widget) override;
};

View File

@ -1,17 +0,0 @@
#include <QApplication>
#include <QDebug>
#include "StorageWindow.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
qDebug() << "Starting Storage Qt Application";
// Create and show the main window
StorageWindow mainWindow;
mainWindow.setWindowTitle("Logos Storage App");
mainWindow.resize(800, 600);
mainWindow.show();
return app.exec();
}

147
src/qml/CMakeLists.txt Normal file
View File

@ -0,0 +1,147 @@
cmake_minimum_required(VERSION 3.16)
project(qml VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
########### DEPENDENCIES ###########
# This section locates the required dependencies in 3 different ways:
# 1- With NIX, the root folders (LOGOS_LIBLOGOS_ROOT, LOGOS_CPP_SDK_ROO)
# are defined and point to the correct locations in
# the Nix store.
# 2- If the root folders are fetched from source (basically using git) in the
# parent folder, this is detected and used.
# 3- If none of the above apply, the vendor folders inside this project are used,
# meaning the dependencies need to be fetched using git submodules.
# Locate logos-cpp-sdk
if(NOT DEFINED LOGOS_CPP_SDK_ROOT)
set(_parent_cpp_sdk "${CMAKE_SOURCE_DIR}/../../../logos-cpp-sdk")
set(_use_vendor ${LOGOS_STORAGE_UI_USE_VENDOR})
if(NOT _use_vendor)
if(NOT EXISTS "${_parent_cpp_sdk}/cpp/logos_api.h")
set(_use_vendor ON)
endif()
endif()
if(_use_vendor)
set(LOGOS_CPP_SDK_ROOT "${CMAKE_SOURCE_DIR}/../../vendor/logos-cpp-sdk")
else()
set(LOGOS_CPP_SDK_ROOT "${_parent_cpp_sdk}")
endif()
endif()
# Locate the logos_api header file.
# If the file is found, the sdk is considered found.
set(_cpp_sdk_found FALSE)
if(EXISTS "${LOGOS_CPP_SDK_ROOT}/cpp/logos_api.h")
set(_cpp_sdk_found TRUE)
set(_cpp_sdk_is_source TRUE)
elseif(EXISTS "${LOGOS_CPP_SDK_ROOT}/include/cpp/logos_api.h")
set(_cpp_sdk_found TRUE)
set(_cpp_sdk_is_source FALSE)
endif()
# Fails if the SDK is not found.
if(NOT _cpp_sdk_found)
message(FATAL_ERROR
"logos-cpp-sdk not found at ${LOGOS_CPP_SDK_ROOT}. "
"Set LOGOS_CPP_SDK_ROOT or run git submodule update --init --recursive."
)
endif()
########### DEPENDENCIES END ###########
########### QML DEFINITION ###########
set(PLUGIN_UI_BUILD_DIR "../../build/generated_code")
list(APPEND SOURCES
${PLUGIN_UI_BUILD_DIR}/logos_sdk.cpp
)
# Create a library with the Logos SDK and generated sources to keep them separate
add_library(storage_generated STATIC
${PLUGIN_UI_BUILD_DIR}/logos_sdk.cpp
${PLUGIN_UI_BUILD_DIR}/storage_module_api.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_client.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_provider.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/module_proxy.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/token_manager.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_consumer.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_provider.cpp
)
# Define the dependencies needed for storage_generated
target_link_libraries(storage_generated PUBLIC
Qt6::Core
Qt6::RemoteObjects
)
# Discover the required dependencies
find_package(Qt6 REQUIRED COMPONENTS Quick RemoteObjects)
# Standard project setup for Qt applications
qt_standard_project_setup(REQUIRES 6.8)
# Add the Qt Quick application executable
qt_add_executable(appqml
main.cpp
)
# Use these required dependencies.
target_link_libraries(appqml PRIVATE
Qt6::Quick
Qt6::Qml
storage_generated
)
# Define the qml module and the StorageBackend sources.
qt_add_qml_module(appqml
URI StorageBackend
VERSION 1.0
SOURCES
../StorageBackend.cpp
../StorageBackend.h
QML_FILES
StorageView.qml
)
########### QML DEFINITION END ###########
########### HEADERS ###########
target_include_directories(storage_generated PUBLIC
"${PLUGIN_UI_BUILD_DIR}"
"${PLUGIN_UI_BUILD_DIR}/include"
)
# Include directories
if(_cpp_sdk_is_source)
target_include_directories(storage_generated PUBLIC
${LOGOS_CPP_SDK_ROOT}/cpp
${LOGOS_CPP_SDK_ROOT}/cpp/generated
)
else()
target_include_directories(storage_generated PUBLIC
${LOGOS_CPP_SDK_ROOT}/include
${LOGOS_CPP_SDK_ROOT}/include/cpp
${LOGOS_CPP_SDK_ROOT}/include/core
${PLUGIN_UI_BUILD_DIR}/include
)
endif()
# Import the application headers.
target_include_directories(appqml PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/..
${PLUGINS_OUTPUT_DIR}
)
########### HEADERS END ###########
message(STATUS "Qt Quick app configured successfully")

63
src/qml/StorageView.qml Normal file
View File

@ -0,0 +1,63 @@
import QtQuick
import QtQuick.Controls
//import QtQuick.Layouts
Item {
property var backend: mockBackend
readonly property int stopped: 0
readonly property int starting: 1
readonly property int running: 2
readonly property int stopping: 3
readonly property int destroyed: 4
id: root
width: 400
height: 300
QtObject {
id: mockBackend
property var status: root.stopped
property var statusText: "Destroyed"
property var startStopText: "Start"
property var canStartStop: true
function startStop() {
if (status == root.running) {
status = root.stopped
statusText = "Stopped"
startStopText = "Start"
} else {
status = root.running
statusText = "Started"
startStopText = "Stop"
}
}
}
Rectangle {
anchors.fill: parent
anchors.leftMargin: 0
anchors.rightMargin: 0
anchors.topMargin: 0
anchors.bottomMargin: 0
color: "#202428"
}
Text {
objectName: "status"
text: root.backend.statusText
color: "white"
font.pointSize: 20
anchors.centerIn: parent
anchors.topMargin: 0
}
Button {
objectName: "startStopButton"
anchors.leftMargin: 50
text: root.backend.startStopText
enabled: root.backend.canStartStop
onClicked: root.backend.startStop()
}
}

View File

@ -1,5 +1,6 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
int main(int argc, char* argv[]) {
QGuiApplication app(argc, argv);
@ -8,7 +9,6 @@ int main(int argc, char* argv[]) {
QObject::connect(
&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.loadFromModule("qml", "Main");
return app.exec();
}

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file alias="StorageView.qml">qml/StorageView.qml</file>
</qresource>
</RCC>