From fa746daac651407075a0a4e906c49bef942bfdeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Han=C3=A7erli?= <burak.hancerli@qt.io> Date: Mon, 23 Sep 2024 15:46:26 +0000 Subject: [PATCH] QDS-13458 New UI with target functionalities --- LICENSE | 2 +- README.md | 26 +- cicd/stages/build.yml | 9 + cicd/stages/test.yml | 4 +- src/CMakeLists.txt | 8 +- src/android/AndroidManifest.xml.in | 2 +- src/backend/backend.cpp | 164 ++++++--- src/backend/backend.h | 38 +-- src/backend/constants.h | 12 +- src/backend/dsconnector/ds.cpp | 120 ++++--- src/backend/dsconnector/ds.h | 67 +++- src/backend/dsconnector/dsdiscovery.h | 4 +- src/backend/dsconnector/dsmanager.cpp | 198 +++++++++-- src/backend/dsconnector/dsmanager.h | 34 +- src/backend/logger.h | 95 ++++++ src/backend/main.cpp | 53 +-- src/backend/projectmanager.cpp | 11 +- src/backend/projectmanager.h | 4 +- src/backend/settings.cpp | 113 +++++++ .../tcpdatatypes.h => settings.h} | 39 ++- src/ui/HomePage.qml | 226 ++++--------- src/ui/SettingsPage.qml | 26 ++ src/ui/content/images/container.png | Bin 0 -> 1590 bytes src/ui/main.qml | 311 +++++++----------- tests/mock.h | 2 - tests/tst_designstudio.cpp | 52 +-- tests/tst_designstudio.h | 4 +- 27 files changed, 954 insertions(+), 670 deletions(-) create mode 100644 src/backend/logger.h create mode 100644 src/backend/settings.cpp rename src/backend/{dsconnector/tcpdatatypes.h => settings.h} (59%) create mode 100644 src/ui/content/images/container.png diff --git a/LICENSE b/LICENSE index abd492e..bed53b8 100644 --- a/LICENSE +++ b/LICENSE @@ -61,4 +61,4 @@ The Free Software Foundation may publish revised and/or new versions of the GNU Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version†applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. -If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. \ No newline at end of file +If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. diff --git a/README.md b/README.md index 73c517d..558d39c 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ ## About -Launch [Design Studio](https://www.qt.io/ui-design-tools) projects in your Android device. The viewer that helps you do with [Qt for Android](https://doc.qt.io/qt-6/android.html). +Launch [Qt Design Studio](https://www.qt.io/ui-design-tools) projects in your Android device. The viewer that helps you do with [Qt for Android](https://doc.qt.io/qt-6/android.html). -Qt UI Viewer works with minimum Android 33. +Qt UI Viewer works with minimum Android 11 (API level 30). ## Getting the App @@ -72,7 +72,9 @@ cmake \ -DCMAKE_TOOLCHAIN_FILE=<qt-android-path>/lib/cmake/Qt6/qt.toolchain.cmake \ -DANDROID_SDK_ROOT=<android-sdk-path> \ -DANDROID_NDK_ROOT=<android-sdk-path>/ndk/<ndk-version> \ - -DANDROID_OPENSSL_PATH=<openssl-path> + -DANDROID_OPENSSL_PATH=<openssl-path> \ + -DGOOGLE_PLAY_APP_VERSION=30 \ ## see the 'Versioning' section + -DBUILD_EXAMPLES=OFF cmake --build build ``` @@ -94,6 +96,24 @@ xunit-viewer -r output.junit.xml It'll create a `report.html` file that can be opened in a web browser. For more information about `xunit-viewer` please visit the [GitHub repo](https://github.com/lukejpreston/xunit-viewer). +## Versioning + +[android:versionCode](https://developer.android.com/guide/topics/manifest/manifest-element#vcode) is an integer value that should be incremented for each new release and it's required by Google Play Store. It's used to determine if the app should be updated or not. + +During the development this value can be set to any arbitrary integer. But for the release it should be incremented. The value can be controlled by setting the CMake variable called `GOOGLE_PLAY_APP_VERSION` during the CMake configuration step. + +It's easy for an individual developer to forget to increment it before the release. To prevent this, a GitLab pipeline is used to automatically increment this value for each new release. The pipeline is triggered by creating a new tag. The tag should be in the format of `vX.Y.Z` where `X`, `Y`, and `Z` are integers. Then the new value is determined by calculating the following formula: + +```text +android:versionCode = <total_number_of_tags> + 11 +``` + +The value `11` is coming from the times where `GOOGLE_PLAY_APP_VERSION` was increased without creating a tag, so we have an offset in between the tag count and the actual value. From now on the value should be incremented by creating a new tag. + +There's no `AndroidManifest.xml` file in the repository. `AndroidManifest.xml.in` file is used to generate the `AndroidManifest.xml` file during the configuration step. The `versionCode` value is set to the `GOOGLE_PLAY_APP_VERSION` and the `versionName` value is set to the output of the `git describe --always --tags` command. + +Both values can be seen in the `AndroidManifest.xml` file after the CMake configuration step is completed. + ## 3rd Party Libraries/Components * [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp.git) diff --git a/cicd/stages/build.yml b/cicd/stages/build.yml index a4dd1e1..0398e84 100644 --- a/cicd/stages/build.yml +++ b/cicd/stages/build.yml @@ -18,6 +18,14 @@ - popd .build-android-apps: &build-android-apps + - export GOOGLE_PLAY_APP_VERSION=$(( $(git tag --list | wc -l) +11 )) + # where is this magic "$(git tag --list | wc -l) +11" coming from? + # "git tag --list | wc -l" counts the number of tags in the repository. + # The "+11" is a magic number which is the last GOOGLE_PLAY_APP_VERSION + # that was released before starting to add tags to the repository. After + # GOOGLE_PLAY_APP_VERSION=11, the tags are added to the repository, and + # the GOOGLE_PLAY_APP_VERSION is incremented by 1 for each new tag. + - 'echo "Google Play App Version: ${GOOGLE_PLAY_APP_VERSION}"' - | cmake \ -S . \ @@ -30,6 +38,7 @@ -DQT_HOST_PATH=${DOCKER_ENV_QT_PATH_LINUX_GCC_64} \ -DCMAKE_INSTALL_PREFIX=${QDS_CI_JOB_QT_ANDROID_PATH} \ -DANDROID_OPENSSL_PATH=${QDS_CI_JOB_OPENSSL_PATH} \ + -DGOOGLE_PLAY_APP_VERSION=${GOOGLE_PLAY_APP_VERSION} \ -DBUILD_EXAMPLES=OFF - cmake --build ${QDS_CI_JOB_BUILD_PATH} --target aab diff --git a/cicd/stages/test.yml b/cicd/stages/test.yml index 9fa8a66..ceb5c5c 100644 --- a/cicd/stages/test.yml +++ b/cicd/stages/test.yml @@ -26,9 +26,9 @@ test-x86_64: counter=0 while [ -z "$(adb shell pidof -s io.qt.qtdesignviewer.test)" ]; do echo "Waiting for test to start" - sleep 0.1 + sleep 1 counter=$((counter+1)) - if [ $counter -gt 180 ]; then + if [ $counter -gt 20 ]; then echo "Test did not start in time" exit 1 fi diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5d4948f..558b1d9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,13 +9,14 @@ find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Network WebSockets) qt_add_executable(${PROJECT_NAME} backend/importdummy.qml backend/main.cpp + backend/logger.h backend/backend.cpp backend/backend.h backend/serviceconnector.cpp backend/serviceconnector.h backend/projectmanager.cpp backend/projectmanager.h + backend/settings.cpp backend/settings.h backend/dsconnector/ds.cpp backend/dsconnector/ds.h backend/dsconnector/dsdiscovery.cpp backend/dsconnector/dsdiscovery.h backend/dsconnector/dsmanager.cpp backend/dsconnector/dsmanager.h - backend/dsconnector/tcpdatatypes.h backend/qrscanner.cpp backend/qrscanner.h ui/main.qml ../3rdparty/zxing-cpp/example/ZXingQtReader.h @@ -27,6 +28,7 @@ qt_add_resources(${PROJECT_NAME} "images" ui/content/images/appicon.png ui/content/images/closed_eye.png ui/content/images/open_eye.png + ui/content/images/container.png ) qt_add_resources(${PROJECT_NAME} "qml" @@ -54,10 +56,10 @@ qt_add_library(qtuiviewerlib OBJECT EXCLUDE_FROM_ALL backend/projectmanager.cpp backend/projectmanager.h backend/serviceconnector.cpp backend/serviceconnector.h + backend/settings.cpp backend/settings.h backend/dsconnector/ds.cpp backend/dsconnector/ds.h backend/dsconnector/dsdiscovery.cpp backend/dsconnector/dsdiscovery.h backend/dsconnector/dsmanager.cpp backend/dsconnector/dsmanager.h - backend/dsconnector/tcpdatatypes.h ) target_link_libraries(qtuiviewerlib PRIVATE @@ -86,6 +88,6 @@ set_property(TARGET ${PROJECT_NAME} PROPERTY QT_ANDROID_EXTRA_LIBS ) # this needs to be increased with every new release -set(GOOGLE_PLAY_APP_VERSION 27) +set(GOOGLE_PLAY_APP_VERSION 31) # CMAKE_VAR_GIT_VERSION (coming from the top-level CMakeLists.txt) and GOOGLE_PLAY_APP_VERSION replaced in the following file configure_file(${CMAKE_CURRENT_SOURCE_DIR}/android/AndroidManifest.xml.in ${CMAKE_CURRENT_SOURCE_DIR}/android/AndroidManifest.xml) diff --git a/src/android/AndroidManifest.xml.in b/src/android/AndroidManifest.xml.in index eec6de3..1736aeb 100644 --- a/src/android/AndroidManifest.xml.in +++ b/src/android/AndroidManifest.xml.in @@ -1,5 +1,5 @@ <?xml version="1.0"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.qt.qtuiviewer" +<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.qt.qtdesignviewer" android:installLocation="auto" android:versionCode="@GOOGLE_PLAY_APP_VERSION@" android:versionName="@CMAKE_VAR_GIT_VERSION@"> <!-- %%INSERT_PERMISSIONS --> diff --git a/src/backend/backend.cpp b/src/backend/backend.cpp index e7bf5bf..7d0c602 100644 --- a/src/backend/backend.cpp +++ b/src/backend/backend.cpp @@ -24,7 +24,6 @@ ****************************************************************************/ #include "backend.h" -#include "backend/constants.h" #include <QDesktopServices> #include <QEventLoop> @@ -37,9 +36,11 @@ #include <QSysInfo> #include <QTimer> -// Q_DECLARE_JNI_CLASS(Secure, "android/provider/Settings$Secure"); -// Q_DECLARE_JNI_CLASS(String, "java/lang/String"); -// Q_DECLARE_JNI_CLASS(ContentResolver, "android/content/ContentResolver"); +#include "logger.h" + +Q_DECLARE_JNI_CLASS(Secure, "android/provider/Settings$Secure"); +Q_DECLARE_JNI_CLASS(String, "java/lang/String"); +Q_DECLARE_JNI_CLASS(ContentResolver, "android/content/ContentResolver"); Backend::Backend(QObject *parent) : QObject(parent) @@ -70,15 +71,19 @@ Backend::Backend(QObject *parent) } }); - const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry(); + connect(&Logger::instance(), &Logger::logMessage, this, [this](QtMsgType type, QString &msg) { + // if we have any active project running, then reroute + // all the logs to the dsmanager with the last project sender id + if (m_projectManager) { + QMetaObject::invokeMethod(m_designStudioManager.get(), + "sendProjectLogs", + Qt::QueuedConnection, + Q_ARG(QString, m_lastProjectSenderId), + Q_ARG(QString, msg)); + } + }); - // Get the unique device ID - auto context = QNativeInterface::QAndroidApplication::context(); - // auto contentResolver = context.callMethod<QtJniTypes::ContentResolver>("getContentResolver"); - // auto androidId = QtJniTypes::Secure::callStaticMethod<jstring>("getString", - // contentResolver, - // QStringLiteral("android_id")); - // const QByteArray serial = androidId.toString().toLatin1(); + const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry(); qDebug() << "Qt Design Viewer"; qDebug() << "System information:"; @@ -97,11 +102,34 @@ Backend::Backend(QObject *parent) qDebug() << "-- Product version: " << QSysInfo::productVersion(); qDebug() << "-- Build ABI: " << QSysInfo::buildAbi(); qDebug() << "-- Build CPU architecture: " << QSysInfo::buildCpuArchitecture(); - // qDebug() << "-- Unique device ID: " << serial; + qDebug() << "-- Device serial: " << getDeviceSerial(); + qDebug() << "-- Device unique ID: " << getDeviceUuid(); qDebug() << "Thread id backend:" << QThread::currentThreadId(); } +// this function returns the device serial number (which is really unique) +// but may not be reliable on all devices. Instead we use the device UUID. +QByteArray Backend::getDeviceSerial() +{ + auto context = QNativeInterface::QAndroidApplication::context(); + auto contentResolver = context.callMethod<QtJniTypes::ContentResolver>("getContentResolver"); + auto androidId = QtJniTypes::Secure::callStaticMethod<jstring>("getString", + contentResolver, + QStringLiteral("android_id")); + return androidId.toString().toLatin1(); +} + +QString Backend::getDeviceUuid() +{ + if (m_settings.deviceUuid().isEmpty()) { + qDebug() << "Device UUID not found. Generating a new one."; + m_settings.setDeviceUuid(QUuid::createUuid().toString(QUuid::WithoutBraces)); + } + + return m_settings.deviceUuid(); +} + QString Backend::buildInfo() const { #ifdef QT_DEBUG @@ -116,32 +144,6 @@ QString Backend::buildInfo() const + "\nOpenSSL support: " + QVariant(QSslSocket::supportsSsl()).toString()}; } -void Backend::setAutoScaleProject(const bool &enabled) -{ - QSettings().setValue(Constants::Settings::ProjectManager::AutoScale, enabled); - if (enabled) { - qDebug() << "Auto scale project is enabled"; - } else { - qDebug() << "Auto scale project is disabled"; - } -} - -void Backend::setUserHash(const QString &userHash) -{ - QSettings().setValue(Constants::Settings::Backend::UserHash, userHash); - emit userHashChanged(); -} - -bool Backend::autoScaleProject() -{ - return QSettings().value(Constants::Settings::ProjectManager::AutoScale, true).toBool(); -} - -QString Backend::userHash() -{ - return QSettings().value(Constants::Settings::Backend::UserHash).toString(); -} - void Backend::updatePopup(const QString &text, bool indeterminate) { emit popupTextChanged(text); @@ -151,22 +153,31 @@ void Backend::updatePopup(const QString &text, bool indeterminate) void Backend::initializeProjectManager() { - m_projectManager.reset(new ProjectManager); - connect(m_projectManager.get(), &ProjectManager::closingProject, this, [&] { - emit popupClose(); - m_projectManager.reset(); - }); + m_projectManager.reset(new ProjectManager(this, m_settings.autoScaleProject())); + connect( + m_projectManager.get(), + &ProjectManager::closingProject, + this, + [&] { + emit popupClose(); + m_projectManager.reset(); + QMetaObject::invokeMethod(m_designStudioManager.get(), + "sendProjectStopped", + Qt::QueuedConnection, + Q_ARG(QString, m_lastProjectSenderId)); + }, + Qt::QueuedConnection); } void Backend::initDesignStudioManager() { connect(&m_dsConnectorThread, &QThread::started, [this] { qDebug() << "Design Studio Manager thread started"; - m_designStudioManager.reset(new DesignStudioManager); + m_designStudioManager.reset(new DesignStudioManager(nullptr, false, getDeviceUuid())); // signals goes from DS Manager to UI connect(m_designStudioManager.get(), - &DesignStudioManager::registrationPinRequested, + &DesignStudioManager::pairingPinRequested, this, &Backend::pinRequested); @@ -175,6 +186,31 @@ void Backend::initDesignStudioManager() this, &Backend::runDsProject); + connect(m_designStudioManager.get(), + &DesignStudioManager::designStudioConnected, + this, + [this](const QString &id, const QString &ipAddr) { + emit connectedChanged(true, ipAddr); + }); + connect(m_designStudioManager.get(), + &DesignStudioManager::designStudioDisconnected, + this, + [this](const QString &id, const QString &ipAddr) { + if (id == m_lastProjectSenderId && m_projectManager) { + m_projectManager->stopProject(); + } + emit connectedChanged(false, ipAddr); + }); + + connect(m_designStudioManager.get(), + &DesignStudioManager::projectStopRequested, + this, + [this](const QString &id) { + if (m_projectManager) { + m_projectManager->stopProject(); + } + }); + // signals goes from UI to DS Manager connect(this, &Backend::enterPin, @@ -193,17 +229,39 @@ void Backend::initDesignStudioManager() &Backend::downloadProgress); } -void Backend::runDsProject(const QByteArray &projectData) +void Backend::connectDesignStudio(const QString &ipAddr) +{ + QMetaObject::invokeMethod(m_designStudioManager.get(), + "designStudioFound", + Qt::QueuedConnection, + Q_ARG(QString, ipAddr), + Q_ARG(QString, ""), + Q_ARG(bool, true)); + emit popupOpen("Connecting in the background...", 1500); +} + +void Backend::runDsProject(const QString &id, const QByteArray &projectData) { + // we'll use this to notify the correct DS when the project started/stopped + m_lastProjectSenderId = id; + QMetaObject::invokeMethod(m_designStudioManager.get(), + "sendProjectRunning", + Qt::QueuedConnection, + Q_ARG(QString, id)); + initializeProjectManager(); emit popupOpen(); updatePopup("Unpacking project..."); QString projectPath = m_projectManager->unpackProject(projectData); updatePopup("Running project..."); - if (!m_projectManager->runProject(projectPath)) + if (!m_projectManager->runProject(projectPath)) { qCritical() << "Could not run project. Please check the logs for more information."; - else { + QMetaObject::invokeMethod(m_designStudioManager.get(), + "sendProjectStopped", + Qt::QueuedConnection, + Q_ARG(QString, id)); + } else { m_projectManager->showAppWindow(); } @@ -290,7 +348,7 @@ void Backend::runUserProject(const QString &projectName, const QString &password // fetch the project list to check if the project is cached const std::optional<QJsonArray> projectList = m_serviceConnector.fetchUserProjectList( - userHash()); + m_settings.userHash()); if (projectList == std::nullopt) { qCritical() @@ -314,7 +372,7 @@ void Backend::runUserProject(const QString &projectName, const QString &password qDebug("Project is not cached. Downloading..."); updatePopup("Project is not cached. Downloading...", false); const std::optional<QByteArray> projectData - = m_serviceConnector.fetchUserProject(userHash(), projectName, password); + = m_serviceConnector.fetchUserProject(m_settings.userHash(), projectName, password); if (projectData == std::nullopt) { qCritical() @@ -371,7 +429,7 @@ void Backend::runOnlineProject(const QString &url) void Backend::updateUserProjectList() { - const QString userHash = Backend::userHash(); + const QString userHash = m_settings.userHash(); if (userHash.isEmpty()) { return; @@ -432,7 +490,7 @@ void Backend::parseDesignViewerUrl(const QUrl &url) emit urlUpdated(url.toString()); } else if (url.scheme() == "qtdesignviewer") { qDebug() << "Registering user from QR code"; - setUserHash(url.host()); + m_settings.setUserHash(url.host()); updateUserProjectList(); } else if (url.scheme() == "qtdesignstudio") { qDebug() << "Connecting to Design Studio from QR code"; diff --git a/src/backend/backend.h b/src/backend/backend.h index 38cc89c..1351df8 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -29,11 +29,13 @@ #include <QThread> #include <QTimer> +#include <memory> + #include "dsconnector/dsmanager.h" #include "projectmanager.h" #include "qrscanner.h" #include "serviceconnector.h" -#include <memory> +#include "settings.h" class Backend : public QObject { @@ -43,31 +45,30 @@ class Backend : public QObject public: explicit Backend(QObject *parent = nullptr); - void setLogs(const QString &logs) - { - m_logs = logs; - emit logsChanged(m_logs); - } - QJsonArray projectList() const { return m_projectList; } private: // UI data - QString m_logs; QJsonArray m_projectList; // Other members ServiceConnector m_serviceConnector; - QThread m_dsConnectorThread; std::unique_ptr<ProjectManager> m_projectManager; - std::unique_ptr<DesignStudioManager> m_designStudioManager; std::unique_ptr<QrScanner> m_qrScanner; + // DS Connector + QThread m_dsConnectorThread; + std::unique_ptr<DesignStudioManager> m_designStudioManager; + QString m_lastProjectSenderId; + // Settings QTimer m_projectListUpdateTimer; + Settings m_settings; // member functions void updatePopup(const QString &text, bool indeterminate = true); + QByteArray getDeviceSerial(); + QString getDeviceUuid(); signals: // UI signals - Home page @@ -75,19 +76,17 @@ signals: void urlUpdated(QString); void userHashChanged(); - // UI signals - Logs page - void logsChanged(QString); - // UI signals - Popup void downloadProgress(float); void popupProgressIndeterminateChanged(bool indeterminate); void popupTextChanged(QString text); - void popupOpen(); + void popupOpen(const QString &text = {}, int timeout = 0); void popupClose(); // UI signals - from DS Manager page void pinRequested(const QString &id); void pinPopupOpen(); + void connectedChanged(bool connected, const QString &ipAddr); // UI signals - from UI void enterPin(const QString &deviceId, const QString &pin); @@ -100,21 +99,14 @@ public slots: void runOnlineProject(const QString &url); void runUserProject(const QString &projectName, const QString &password); void runDemoProject(const QString &projectName); - void runDsProject(const QByteArray &projectData); + void runDsProject(const QString &id, const QByteArray &projectData); void clearDemoCaches(); void initDesignStudioManager(); + void connectDesignStudio(const QString &ipAddr); void parseDesignViewerUrl(const QUrl &url); - // settings - setters - void setAutoScaleProject(const bool &enabled); - void setUserHash(const QString &userHash); - - // settings - getters - bool autoScaleProject(); - QString userHash(); - void popupInterrupted(); private slots: diff --git a/src/backend/constants.h b/src/backend/constants.h index 540341e..dfbf261 100644 --- a/src/backend/constants.h +++ b/src/backend/constants.h @@ -30,17 +30,6 @@ namespace Constants { -// Settings keys -namespace Settings { -namespace ProjectManager { -const static QString AutoScale = "project/autoScale"; -} // namespace ProjectManager - -namespace Backend { -const static QString UserHash = "user/hash"; -} // namespace Backend -} // namespace Settings - // Path Constants namespace Paths { const static QString WritableLocation = QStandardPaths::writableLocation( @@ -49,6 +38,7 @@ const static QString ProjectCachePath = WritableLocation + "/projectCache"; const static QString DemoProjectsPath = WritableLocation + "/demoProjects"; const static QString ConfigPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); const static QString ConfigPathDsManager = ConfigPath + "/dsmanager.json"; +const static QString ConfigPathSettings = ConfigPath + "/settings.json"; } // namespace Paths } // namespace Constants diff --git a/src/backend/dsconnector/ds.cpp b/src/backend/dsconnector/ds.cpp index a3ca3e9..5379af8 100644 --- a/src/backend/dsconnector/ds.cpp +++ b/src/backend/dsconnector/ds.cpp @@ -34,39 +34,39 @@ #include <QSysInfo> #include <QTimer> -#include "tcpdatatypes.h" - DesignStudio::DesignStudio(const QString &ipv4Addr, const quint16 port, const QString &designStudioId, const QString &deviceId, + const bool skipPairing, QObject *parent) : QObject(parent) , m_ipv4Addr(ipv4Addr) , m_port(port) , m_url("ws://" + m_ipv4Addr + ":" + QString::number(m_port)) - , m_designStudioId(designStudioId) - , m_deviceId(deviceId) + , m_id(designStudioId) + , m_deviceUuid(deviceId) + , m_skipPairing(skipPairing) { initPingPong(); initSocket(); + qDebug() << "Design Studio" << m_id << "skip pairing" << m_skipPairing; } void DesignStudio::initPingPong() { connect(&m_pingTimer, &QTimer::timeout, this, [this]() { m_socket.ping(); - m_pongTimer.start(5000); + m_pongTimer.start(15000); }); connect(&m_socket, &QWebSocket::pong, this, [this](quint64 elapsedTime, const QByteArray &) { - qDebug() << "Pong received from Design Studio" << m_designStudioId << "in" << elapsedTime - << "ms"; + qDebug() << "Pong received from Design Studio" << m_id << "in" << elapsedTime << "ms"; m_pongTimer.stop(); }); connect(&m_pongTimer, &QTimer::timeout, this, [this]() { - qDebug() << "Design Studio" << m_designStudioId << "is not responding. Reconnecting."; + qDebug() << "Design Studio" << m_id << "is not responding. Reconnecting."; m_socket.close(); }); } @@ -84,15 +84,16 @@ void DesignStudio::initSocket() connect(&m_socket, &QWebSocket::stateChanged, this, [this](QAbstractSocket::SocketState state) { if (state == QAbstractSocket::ConnectedState) { - qDebug() << "Connected to Design Studio" << m_designStudioId; + qDebug() << "Connected to Design Studio" << m_id; m_socketWasConnected = true; - m_pingTimer.start(10000); + m_pingTimer.start(15000); m_pongTimer.stop(); } else if (state == QAbstractSocket::UnconnectedState && m_socketWasConnected) { - qDebug() << "Disconnected from Design Studio" << m_designStudioId; + qDebug() << "Disconnected from Design Studio" << m_id; m_socketWasConnected = false; m_pingTimer.stop(); m_pongTimer.stop(); + emit disconnected(m_id); } }); @@ -104,6 +105,11 @@ QString DesignStudio::ipv4Addr() const return m_ipv4Addr; } +bool DesignStudio::skipPairing() const +{ + return m_skipPairing; +} + quint16 DesignStudio::port() const { return m_port; @@ -114,19 +120,14 @@ QUrl DesignStudio::url() const return m_url; } -QString DesignStudio::designStudioId() const -{ - return m_designStudioId; -} - -QString DesignStudio::deviceId() const +QString DesignStudio::id() const { - return m_deviceId; + return m_id; } -bool DesignStudio::isRegistered() const +bool DesignStudio::isPaired() const { - return !m_deviceId.isEmpty(); + return m_skipPairing || m_paired; } bool DesignStudio::connected() const @@ -142,6 +143,16 @@ void DesignStudio::setIpAddress(const QString &ipv4Addr) m_socket.open(m_url); } +void DesignStudio::setSkipPairing(const bool skipPairing) +{ + m_skipPairing = skipPairing; +} + +void DesignStudio::setDesignStudioId(const QString &designStudioId) +{ + m_id = designStudioId; +} + void DesignStudio::sendDeviceInfo() { const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry(); @@ -152,18 +163,19 @@ void DesignStudio::sendDeviceInfo() deviceInfo["os"] = QSysInfo::prettyProductName(); deviceInfo["osVersion"] = QSysInfo::productVersion(); deviceInfo["architecture"] = QSysInfo::currentCpuArchitecture(); - deviceInfo["deviceId"] = m_deviceId; + deviceInfo["deviceId"] = m_deviceUuid; deviceInfo["appVersion"] = QString(CMAKE_VAR_GIT_VERSION); + deviceInfo["skipPairing"] = m_skipPairing; qDebug() << "Sending device info to Design Studio" << deviceInfo; sendData(PackageToDesignStudio::deviceInfo, deviceInfo); } -void DesignStudio::sendRegistrationPin(const QString &pin) +void DesignStudio::sendPairingPin(const QString &pin) { - qDebug() << "Sending registration PIN to Design Studio with pin" << pin; - sendData(PackageToDesignStudio::registrationPin, pin); + qDebug() << "Sending pairing PIN to Design Studio with pin" << pin; + sendData(PackageToDesignStudio::pairingPin, pin); } void DesignStudio::sendData(const QLatin1String &dataType, const QJsonValue &data) @@ -194,35 +206,36 @@ void DesignStudio::processTextMessage(const QString &message) const QString dataType = jsonObj.value("dataType").toString(); if (dataType == PackageFromDesignStudio::designStudioReady) { - m_designStudioId = jsonObj.value("data").toString(); - qDebug() << "Design Studio" << m_designStudioId << "is waiting for the info."; - sendDeviceInfo(); - } else if (dataType == PackageFromDesignStudio::pinVerificationRequested) { + const QString newDesignStudioId = jsonObj.value("data").toString(); + if (m_id != newDesignStudioId) { + qDebug() << "Design Studio" << m_id << "changed ID to" << newDesignStudioId + << "with IP address" << m_ipv4Addr << "Notifying ds manager."; + emit idChanged(newDesignStudioId, m_ipv4Addr); + } else { + qDebug() << "Design Studio" << m_id << "is waiting for the info."; + sendDeviceInfo(); + } + } else if (dataType == PackageFromDesignStudio::pinRequested) { qDebug() << "Registration PIN requested by Design Studio"; - emit registrationPinRequested(m_designStudioId); - } else if (dataType == PackageFromDesignStudio::pinVerificationFailed) { + emit pinRequested(m_id); + } else if (dataType == PackageFromDesignStudio::pinFailed) { qDebug() << "Registration PIN verification failed"; - emit pinVerificationFailed(m_designStudioId); - } else if (dataType == PackageFromDesignStudio::deviceIdReceived) { - if (!m_deviceId.isEmpty()) { - qDebug() << "Registration ID already set. Ignoring new registration ID"; - return; - } - - m_deviceId = jsonObj.value("data").toString(); - qDebug() << "Registration successful. Received ID:" << m_deviceId; - emit registrationSucceeded(m_designStudioId); - emit dsOnline(m_designStudioId, m_ipv4Addr); + emit pinFailed(m_id); } else if (dataType == PackageFromDesignStudio::deviceIdDeclined) { - qDebug() << "Design Studio unregistered"; - m_deviceId.clear(); - emit unregistered(m_designStudioId); + qDebug() << "Design Studio unpaired the device"; + m_paired = false; + emit unpaired(m_id); } else if (dataType == PackageFromDesignStudio::deviceIdAccepted) { qDebug() << "Design Studio accepted the device ID. Connection alive."; - emit dsOnline(m_designStudioId, m_ipv4Addr); + m_paired = true; + m_skipPairing = false; + emit paired(m_id); } else if (dataType == PackageFromDesignStudio::projectData) { qDebug() << "Project is expected"; emit projectIncoming(); + } else if (dataType == PackageFromDesignStudio::stopRunningProject) { + qDebug() << "Stop running project requested"; + emit projectStopRequested(m_id); } else { qDebug() << "Unkown JSON message"; } @@ -232,5 +245,20 @@ void DesignStudio::processBinaryMessage(const QByteArray &data) { // this slot is only triggered when we're receiving a qmlrc project qDebug() << "Binary message received (most probably a project)"; - emit projectReceived(data); + emit projectReceived(m_id, data); +} + +void DesignStudio::sendProjectRunning() +{ + sendData(PackageToDesignStudio::projectRunning); +} + +void DesignStudio::sendProjectStopped() +{ + sendData(PackageToDesignStudio::projectStopped); +} + +void DesignStudio::sendProjectLogs(const QString &logs) +{ + sendData(PackageToDesignStudio::projectLogs, logs); } diff --git a/src/backend/dsconnector/ds.h b/src/backend/dsconnector/ds.h index 6c39361..bb46137 100644 --- a/src/backend/dsconnector/ds.h +++ b/src/backend/dsconnector/ds.h @@ -31,30 +31,59 @@ #include <qjsonvalue.h> #include <qlatin1stringview.h> +#include <QLatin1String> + +namespace PackageFromDesignStudio { +using namespace Qt::Literals; +constexpr auto designStudioReady = "designStudioReady"_L1; +constexpr auto pinRequested = "pinRequested"_L1; +constexpr auto pinFailed = "pinVerificationFailed"_L1; +constexpr auto deviceIdDeclined = "deviceIdDeclined"_L1; +constexpr auto deviceIdAccepted = "deviceIdAccepted"_L1; +constexpr auto projectData = "projectData"_L1; +constexpr auto stopRunningProject = "stopRunningProject"_L1; +}; // namespace PackageFromDesignStudio + +namespace PackageToDesignStudio { +using namespace Qt::Literals; +constexpr auto deviceInfo = "deviceInfo"_L1; +constexpr auto pairingPin = "registrationPinResponse"_L1; +constexpr auto projectRunning = "projectRunning"_L1; +constexpr auto projectStopped = "projectStopped"_L1; +constexpr auto projectLogs = "projectLogs"_L1; +}; // namespace PackageToDesignStudio + class DesignStudio : public QObject { Q_OBJECT public: DesignStudio(const QString &ipv4Addr, const quint16 port, - const QString &designStudioId = QString(), - const QString &deviceId = QString(), + const QString &id = QString(), + const QString &deviceUuid = QString(), + const bool skipPairing = false, QObject *parent = nullptr); // Getters QString ipv4Addr() const; - QString designStudioId() const; quint16 port() const; + QString id() const; QUrl url() const; - QString deviceId() const; - bool isRegistered() const; + bool skipPairing() const; + bool isPaired() const; bool connected() const; // Setters void setIpAddress(const QString &ipv4Addr); + void setDesignStudioId(const QString &designStudioId); + void setSkipPairing(const bool skipPairing); // Send data - void sendRegistrationPin(const QString &pin); + void sendPairingPin(const QString &pin); + void sendDeviceInfo(); + void sendProjectRunning(); + void sendProjectStopped(); + void sendProjectLogs(const QString &logs); private: // Network @@ -67,14 +96,17 @@ private: QTimer m_pongTimer; // DS data - QString m_designStudioId; - QString m_deviceId; + QString m_id; + QString m_deviceUuid; + + // Pairing + bool m_paired = false; + bool m_skipPairing = false; // Settings constexpr static int m_reconnectTimeout = 5000; // DS comm - void sendDeviceInfo(); void sendData(const QLatin1String &dataType, const QJsonValue &data = QJsonValue()); // Internal @@ -86,14 +118,17 @@ private slots: void processBinaryMessage(const QByteArray &data); signals: - // DS signals - void registrationPinRequested(const QString &id); - void pinVerificationFailed(const QString &id); - void registrationSucceeded(const QString &id); - void unregistered(const QString &id); + // socket signals + void disconnected(const QString &id); - void dsOnline(const QString &id, const QString &ipv4Addr); + // DS signals + void idChanged(const QString &id, const QString &ipv4Addr); + void pinRequested(const QString &id); + void pinFailed(const QString &id); + void paired(const QString &id); + void unpaired(const QString &id); void projectIncoming(); - void projectReceived(const QByteArray &data); + void projectReceived(const QString &id, const QByteArray &data); + void projectStopRequested(const QString &id); }; diff --git a/src/backend/dsconnector/dsdiscovery.h b/src/backend/dsconnector/dsdiscovery.h index 369e924..9ddedac 100644 --- a/src/backend/dsconnector/dsdiscovery.h +++ b/src/backend/dsconnector/dsdiscovery.h @@ -38,7 +38,9 @@ private slots: void onReadyRead(); signals: - void designStudioFound(const QString &ipv4Addr, const QString &id); + void designStudioFound(const QString &ipv4Addr, + const QString &id, + const bool skipPairing = false); private: QUdpSocket m_udpSocket; diff --git a/src/backend/dsconnector/dsmanager.cpp b/src/backend/dsconnector/dsmanager.cpp index ebbfe21..9f753b2 100644 --- a/src/backend/dsconnector/dsmanager.cpp +++ b/src/backend/dsconnector/dsmanager.cpp @@ -38,22 +38,29 @@ #include <qjsonobject.h> #include <qobject.h> -DesignStudioManager::DesignStudioManager(QObject *parent) +DesignStudioManager::DesignStudioManager(QObject *parent, + const bool enableDiscovery, + const QString &deviceUuid) : QObject(parent) + , m_deviceUuid(deviceUuid) { - initializeRegisteredDesignStudios(); + qDebug() << ">>> THREAD ID _ dsmanger:" << QThread::currentThreadId(); + initializePairedDesignStudios(); - m_discovery = std::make_unique<DesignStudioDiscovery>(); - connect(m_discovery.get(), - &DesignStudioDiscovery::designStudioFound, - this, - &DesignStudioManager::designStudioFound); + if (enableDiscovery) { + qDebug() << "Starting Design Studio discovery"; + m_discovery = std::make_unique<DesignStudioDiscovery>(); + connect(m_discovery.get(), + &DesignStudioDiscovery::designStudioFound, + this, + &DesignStudioManager::designStudioFound); + } qDebug() << "Design Studio Manager initialized"; qDebug() << "Thread id dsmanger:" << QThread::currentThreadId(); } -void DesignStudioManager::initializeRegisteredDesignStudios() +void DesignStudioManager::initializePairedDesignStudios() { QFile file(Constants::Paths::ConfigPathDsManager); @@ -77,56 +84,71 @@ void DesignStudioManager::initializeRegisteredDesignStudios() QJsonObject dsObj = ds.toObject(); initDesignStudio(dsObj["ipv4Addr"].toString(), dsObj["designStudioId"].toString(), - dsObj["deviceId"].toString()); + dsObj["skipPairing"].toBool()); } } void DesignStudioManager::initDesignStudio(const QString &ipv4Addr, const QString &designStudioId, - const QString &deviceId) + const bool skipPairing) { qDebug() << "Initializing Design Studio" << designStudioId << "with IPv4 address" << ipv4Addr; - auto ds = std::make_unique<DesignStudio>(ipv4Addr, 40000, designStudioId, deviceId); + auto ds + = std::make_unique<DesignStudio>(ipv4Addr, 40000, designStudioId, m_deviceUuid, skipPairing); - connect(ds.get(), - &DesignStudio::registrationSucceeded, - this, - &DesignStudioManager::designStudioRegistered); + connect(ds.get(), &DesignStudio::paired, this, &DesignStudioManager::designStudioPaired); - connect(ds.get(), - &DesignStudio::unregistered, - this, - &DesignStudioManager::designStudioUnregistered); + connect(ds.get(), &DesignStudio::unpaired, this, &DesignStudioManager::designStudioUnpaired); - connect(ds.get(), - &DesignStudio::registrationPinRequested, - this, - &DesignStudioManager::registrationPinRequested); + connect(ds.get(), &DesignStudio::pinRequested, this, &DesignStudioManager::pairingPinRequested); + + connect(ds.get(), &DesignStudio::idChanged, this, &DesignStudioManager::dsIdChanged); connect(ds.get(), &DesignStudio::projectReceived, this, &DesignStudioManager::projectReceived); + connect(ds.get(), &DesignStudio::disconnected, this, &DesignStudioManager::dsDisconnected); + + connect(ds.get(), + &DesignStudio::projectStopRequested, + this, + &DesignStudioManager::projectStopRequested); + m_designStudios.push_back(std::move(ds)); } -void DesignStudioManager::designStudioFound(const QString &ipv4Addr, const QString &id) +void DesignStudioManager::designStudioFound(const QString &ipv4Addr, + const QString &id, + const bool skipPairing) { qDebug() << "Design Studio found with IPv4 address" << ipv4Addr << "and ID" << id; // check if the Design Studio is already in the list for (const auto &ds : m_designStudios) { - if (ds->designStudioId() == id) { + if (ds->id() == id) { if (ds->ipv4Addr() != ipv4Addr) { qDebug() << "Design Studio" << id << "changed IP address from" << ds->ipv4Addr() << "to" << ipv4Addr; ds->setIpAddress(ipv4Addr); + updateConfigFile(); } else { qDebug() << "Design Studio" << id << "already in the list"; } return; } + + if (ds->ipv4Addr() == ipv4Addr && ds->connected()) { + qDebug() << "Design Studio with IP address" << ipv4Addr << "already in the list with ID" + << ds->id() << "and connected"; + if (skipPairing) { + qDebug() << "Forcing pairing with Design Studio" << id; + ds->setSkipPairing(skipPairing); + ds->sendDeviceInfo(); + } + return; + } } - initDesignStudio(ipv4Addr, id); + initDesignStudio(ipv4Addr, id, skipPairing); } void DesignStudioManager::updateConfigFile() @@ -141,14 +163,14 @@ void DesignStudioManager::updateConfigFile() QJsonArray dsArray; for (const auto &d : m_designStudios) { - if (!d->isRegistered()) { + if (!d->isPaired()) { continue; } QJsonObject dsObj; dsObj["ipv4Addr"] = d->ipv4Addr(); - dsObj["designStudioId"] = d->designStudioId(); - dsObj["deviceId"] = d->deviceId(); + dsObj["id"] = d->id(); + dsObj["skipPairing"] = d->skipPairing(); dsArray.append(dsObj); } @@ -156,16 +178,79 @@ void DesignStudioManager::updateConfigFile() file.write(QJsonDocument(rootObj).toJson()); } -void DesignStudioManager::designStudioRegistered(const QString &designStudioId) +void DesignStudioManager::dsIdChanged(const QString &id, const QString &ipv4Addr) { - qDebug() << "Design Studio" << designStudioId << "registered. Updating settings."; + /* that can happen because of the following reasons; + 1. The IP address is pointing a different Design Studio (can happen because of DHCP or manual change) + 2. The Design Studio changed its ID (can happen because of a reinstall) + 3. Design Studio is initialized only with IP address and ID is received later + */ + + for (const auto &ds : m_designStudios) { + // case 1 and 3 + if (ds->ipv4Addr() == ipv4Addr && ds->id() != id) { + qDebug() << "Design Studio" << id << "changed ID to" << id << "with IP address" + << ipv4Addr; + ds->setDesignStudioId(id); + ds->sendDeviceInfo(); + break; + } + + // case 2 + if (ds->id() == id) { + qDebug() << "Design Studio" << id << "changed IP address from" << ds->ipv4Addr() << "to" + << ipv4Addr; + + // if there's another DS with the same IP address, remove it + m_designStudios.erase(std::remove_if(m_designStudios.begin(), + m_designStudios.end(), + [&](const auto &d) { + return d->ipv4Addr() == ipv4Addr; + }), + m_designStudios.end()); + ds->setIpAddress(ipv4Addr); + ds->sendDeviceInfo(); + break; + } + } + updateConfigFile(); } -void DesignStudioManager::designStudioUnregistered(const QString &designStudioId) +void DesignStudioManager::designStudioPaired(const QString &id) { - qDebug() << "Design Studio" << designStudioId << "unregistered. Removing from settings."; + qDebug() << "Design Studio" << id << "paired. Updating settings."; updateConfigFile(); + auto ds = std::find_if(m_designStudios.begin(), m_designStudios.end(), [&](const auto &d) { + return d->id() == id; + }); + if (ds == m_designStudios.end()) { + qCritical() << "Design Studio" << id << "not found in the list. Something went wrong."; + return; + } + + if (!(*ds)->connected()) { + qWarning() << "Design Studio" << id + << "is not connected. Not notifying UI about the connection update."; + return; + } + + emit designStudioConnected(id, (*ds)->ipv4Addr()); +} + +void DesignStudioManager::designStudioUnpaired(const QString &id) +{ + qDebug() << "Design Studio" << id << "unpaired. Removing from settings."; + updateConfigFile(); + auto ds = std::find_if(m_designStudios.begin(), m_designStudios.end(), [&](const auto &d) { + return d->id() == id; + }); + if (ds == m_designStudios.end()) { + qCritical() << "Design Studio" << id << "not found in the list. Something went wrong."; + return; + } + + emit designStudioDisconnected(id, (*ds)->ipv4Addr()); } void DesignStudioManager::enterPin(const QString &id, const QString &pin) @@ -173,7 +258,7 @@ void DesignStudioManager::enterPin(const QString &id, const QString &pin) DesignStudio *ds = nullptr; for (const auto &d : m_designStudios) { - if (d->designStudioId() == id) { + if (d->id() == id) { ds = d.get(); break; } @@ -183,5 +268,48 @@ void DesignStudioManager::enterPin(const QString &id, const QString &pin) return; } - ds->sendRegistrationPin(pin); + ds->sendPairingPin(pin); +} + +void DesignStudioManager::dsDisconnected(const QString &id) +{ + qDebug() << "Design Studio" << id << "disconnected"; + auto ds = std::find_if(m_designStudios.begin(), m_designStudios.end(), [&](const auto &d) { + return d->id() == id; + }); + + if (!(*ds)->isPaired()) { + qDebug() << "Design Studio" << id + << "is not paired. Not notifying UI about the disconnection."; + return; + } + + emit designStudioDisconnected(id, (*ds)->ipv4Addr()); +} + +void DesignStudioManager::sendProjectRunning(const QString &id) +{ + for (const auto &ds : m_designStudios) { + if (ds->id() == id) { + ds->sendProjectRunning(); + } + } +} + +void DesignStudioManager::sendProjectStopped(const QString &id) +{ + for (const auto &ds : m_designStudios) { + if (ds->id() == id) { + ds->sendProjectStopped(); + } + } +} + +void DesignStudioManager::sendProjectLogs(const QString &id, const QString &logs) +{ + for (const auto &ds : m_designStudios) { + if (ds->id() == id) { + ds->sendProjectLogs(logs); + } + } } diff --git a/src/backend/dsconnector/dsmanager.h b/src/backend/dsconnector/dsmanager.h index 824996d..48bc17c 100644 --- a/src/backend/dsconnector/dsmanager.h +++ b/src/backend/dsconnector/dsmanager.h @@ -37,15 +37,23 @@ class DesignStudioManager : public QObject { Q_OBJECT public: - explicit DesignStudioManager(QObject *parent = nullptr); + explicit DesignStudioManager(QObject *parent = nullptr, + const bool enableDiscovery = false, + const QString &deviceUuid = {}); public slots: + void sendProjectRunning(const QString &id); + void sendProjectStopped(const QString &id); + void sendProjectLogs(const QString &id, const QString &logs); + void enterPin(const QString &id, const QString &pin); - void designStudioFound(const QString &ipv4Addr, const QString &id); + void designStudioFound(const QString &ipv4Addr, + const QString &id = {}, + const bool skipPairing = false); private slots: - void designStudioRegistered(const QString &id); - void designStudioUnregistered(const QString &id); + void designStudioPaired(const QString &id); + void designStudioUnpaired(const QString &id); private: // Discovery object @@ -54,16 +62,24 @@ private: // Discovered Design Studio instances std::vector<std::unique_ptr<DesignStudio>> m_designStudios; - void initializeRegisteredDesignStudios(); + // other members + const QString m_deviceUuid; + + void dsIdChanged(const QString &id, const QString &ipv4Addr); + void initializePairedDesignStudios(); void initDesignStudio(const QString &ipv4Addr, - const QString &designStudioId, - const QString &deviceId = QString()); + const QString &designStudioId = {}, + const bool skipPairing = false); + void dsDisconnected(const QString &id); void updateConfigFile(); signals: - void registrationPinRequested(const QString &id); - void projectReceived(const QByteArray &project); + void pairingPinRequested(const QString &id); + void projectReceived(const QString &id, const QByteArray &project); + void designStudioConnected(const QString &id, const QString &ipAddr); + void designStudioDisconnected(const QString &id, const QString &ipAddr); + void projectStopRequested(const QString &id); }; #endif // DSCONNECTOR_H diff --git a/src/backend/logger.h b/src/backend/logger.h new file mode 100644 index 0000000..23efa64 --- /dev/null +++ b/src/backend/logger.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2024 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Design Viewer of the Qt Toolkit. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <QMessageBox> +#include <QObject> + +#include <android/log.h> + +class Logger : public QObject +{ + Q_OBJECT +public: + Logger(const Logger &) = delete; + Logger &operator=(const Logger &) = delete; + + static Logger &instance() + { + static Logger m_instance; + return m_instance; + } + + void log(QtMsgType type, const QMessageLogContext &context, const QString &msg) + { + QString logPrefix, logSuffix, newLog; + + QByteArray localMsg = msg.toLocal8Bit(); + const char *file = context.file ? context.file : ""; + const char *function = context.function ? context.function : ""; + + switch (type) { + case QtDebugMsg: + logPrefix = QStringLiteral("Debug: "); + break; + case QtInfoMsg: + logPrefix = QStringLiteral("Info: "); + break; + case QtWarningMsg: + logPrefix = QStringLiteral("Warning: "); + break; + case QtCriticalMsg: + logPrefix = QStringLiteral("Critical: "); + break; + case QtFatalMsg: + logPrefix = QStringLiteral("Fatal: "); + logSuffix = QStringLiteral(" (%1:%2, %3)").arg(file).arg(context.line).arg(function); + break; + } + + newLog += logPrefix + localMsg + logSuffix + "\n"; + __android_log_print(ANDROID_LOG_DEBUG, "Qt_UI_Viewer", "%s", qPrintable(newLog)); + + if (type == QtCriticalMsg || type == QtFatalMsg) { + QMessageBox msgBox{QMessageBox::Critical, "Critical:", msg, QMessageBox::Ok}; + msgBox.exec(); + } + + emit logMessage(type, newLog); + } + +private: + Logger() + { + qInstallMessageHandler( + [](QtMsgType type, const QMessageLogContext &context, const QString &msg) { + Logger::instance().log(type, context, msg); + }); + } + +signals: + void logMessage(QtMsgType type, QString &msg); +}; diff --git a/src/backend/main.cpp b/src/backend/main.cpp index 7619e75..1ea9953 100644 --- a/src/backend/main.cpp +++ b/src/backend/main.cpp @@ -26,58 +26,15 @@ #include <android/log.h> #include <QApplication> -#include <QMessageBox> #include <QQmlContext> #include "backend.h" - -static Backend *backend; -static QString appLogs; - -void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) -{ - QString logPrefix, logSuffix, newLog; - - QByteArray localMsg = msg.toLocal8Bit(); - const char *file = context.file ? context.file : ""; - const char *function = context.function ? context.function : ""; - - switch (type) { - case QtDebugMsg: - logPrefix = QStringLiteral("Debug: "); - break; - case QtInfoMsg: - logPrefix = QStringLiteral("Info: "); - break; - case QtWarningMsg: - logPrefix = QStringLiteral("Warning: "); - break; - case QtCriticalMsg: - logPrefix = QStringLiteral("Critical: "); - break; - case QtFatalMsg: - logPrefix = QStringLiteral("Fatal: "); - logSuffix = QStringLiteral(" (%1:%2, %3)").arg(file).arg(context.line).arg(function); - break; - } - - newLog += logPrefix + localMsg + logSuffix + "\n"; - __android_log_print(ANDROID_LOG_DEBUG, "Qt_UI_Viewer", "%s", qPrintable(newLog)); - - if (type != QtDebugMsg) { - if (backend) - backend->setLogs(appLogs += newLog); - - if (type == QtCriticalMsg || type == QtFatalMsg) { - QMessageBox msgBox{QMessageBox::Critical, "Critical:", msg, QMessageBox::Ok}; - msgBox.exec(); - } - } -} +#include "logger.h" int main(int argc, char *argv[]) { - qInstallMessageHandler(messageHandler); + // qInstallMessageHandler(messageHandler); + Logger::instance(); qDebug() << "Starting Qt Design Viewer"; @@ -86,10 +43,10 @@ int main(int argc, char *argv[]) QApplication::setApplicationName(QStringLiteral("Qt Design Viewer")); QApplication::setApplicationVersion(QString("Built on %1 %2").arg(__DATE__, __TIME__)); - backend = new Backend(); + Backend backend; QQuickView view; - view.engine()->rootContext()->setContextProperty("backend", backend); + view.engine()->rootContext()->setContextProperty("backend", &backend); view.setSource(QUrl(QStringLiteral("qrc:/ui/main.qml"))); view.setResizeMode(QQuickView::SizeRootObjectToView); view.showMaximized(); diff --git a/src/backend/projectmanager.cpp b/src/backend/projectmanager.cpp index dbae903..a70c903 100644 --- a/src/backend/projectmanager.cpp +++ b/src/backend/projectmanager.cpp @@ -49,10 +49,9 @@ #include "constants.h" -ProjectManager::ProjectManager(QObject *parent) +ProjectManager::ProjectManager(QObject *parent, bool autoScaleProject) : QObject(parent) - , m_autoScaleProject( - QSettings().value(Constants::Settings::ProjectManager::AutoScale, true).toBool()) + , m_autoScaleProject(autoScaleProject) { qDebug() << "ProjectManager created."; qDebug() << "Project cache path: " << Constants::Paths::ProjectCachePath; @@ -592,3 +591,9 @@ void ProjectManager::hideAppWindow() qDebug("Hiding the QML app window"); m_quickWindow->hide(); } + +void ProjectManager::stopProject() +{ + qDebug("Stopping the QML app window"); + m_quickWindow->close(); +} diff --git a/src/backend/projectmanager.h b/src/backend/projectmanager.h index 354ba59..4767ce4 100644 --- a/src/backend/projectmanager.h +++ b/src/backend/projectmanager.h @@ -37,11 +37,12 @@ class ProjectManager : public QObject { Q_OBJECT public: - explicit ProjectManager(QObject *parent = nullptr); + explicit ProjectManager(QObject *parent = nullptr, bool autoScaleProject = false); ~ProjectManager(); QString unpackProject(const QByteArray &project, bool extractZip = false); bool runProject(const QString &projectPath); + void stopProject(); bool cacheProject(const QByteArray &projectData, const QJsonObject &projectInfo); bool isProjectCached(const QJsonObject &projectInfo); @@ -69,6 +70,7 @@ private: QScopedPointer<QQmlEngine> m_qmlEngine; QScopedPointer<QQmlComponent> m_qmlComponent; QScopedPointer<QQuickWindow> m_quickWindow; + QJSValue m_consoleObject; // Member functions QString findFile(const QString &dir, const QString &filter); diff --git a/src/backend/settings.cpp b/src/backend/settings.cpp new file mode 100644 index 0000000..73299cd --- /dev/null +++ b/src/backend/settings.cpp @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2024 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Design Viewer of the Qt Toolkit. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "settings.h" + +#include <QFile> +#include <QJsonDocument> +#include <QStandardPaths> + +#include "constants.h" + +/* + Example of a settings.json file: + { + "autoScale": true + "userHash": "1234567890", + "deviceUuid": "1234567890" + } +*/ + +Settings::Settings() + : m_settingsPath(Constants::Paths::ConfigPathSettings) +{ + qDebug() << "Settings path:" << m_settingsPath; + loadSettings(); +} + +void Settings::loadSettings() +{ + QFile file(m_settingsPath); + if (file.open(QIODevice::ReadOnly)) { + QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); + m_settings = doc.object(); + return; + } + + qWarning() << "Failed to load settings from" << m_settingsPath; + m_settings.insert("projectManager", QJsonObject{{"autoScale", true}}); + m_settings.insert("backend", QJsonObject{{"userHash", ""}, {"deviceUuid", ""}}); + + if (!file.exists()) { + qDebug() << "Settings file not found. Creating a new one."; + saveSettings(); + } +} + +bool Settings::saveSettings() +{ + QFile file(m_settingsPath); + if (file.open(QIODevice::WriteOnly)) { + QJsonDocument doc(m_settings); + file.write(doc.toJson()); + return true; + } + + qWarning() << "Failed to save settings to" << m_settingsPath; + return false; +} + +void Settings::setAutoScaleProject(const bool &enabled) +{ + m_settings["autoScale"] = enabled; + saveSettings(); +} + +void Settings::setUserHash(const QString &userHash) +{ + m_settings["userHash"] = userHash; + saveSettings(); +} + +void Settings::setDeviceUuid(const QString &deviceUuid) +{ + m_settings["deviceUuid"] = deviceUuid; + saveSettings(); +} + +QString Settings::userHash() +{ + return m_settings["userHash"].toString(); +} + +QString Settings::deviceUuid() +{ + return m_settings["deviceUuid"].toString(); +} + +bool Settings::autoScaleProject() +{ + return m_settings["autoScale"].toBool(); +} diff --git a/src/backend/dsconnector/tcpdatatypes.h b/src/backend/settings.h similarity index 59% rename from src/backend/dsconnector/tcpdatatypes.h rename to src/backend/settings.h index 5001db5..1a122b0 100644 --- a/src/backend/dsconnector/tcpdatatypes.h +++ b/src/backend/settings.h @@ -3,7 +3,7 @@ ** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** -** This file is part of the Qt Design Tooling +** This file is part of the Qt Design Viewer of the Qt Toolkit. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in @@ -22,24 +22,27 @@ ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ - #pragma once -#include <QLatin1String> +#include <QJsonObject> + +class Settings +{ +public: + Settings(); + + void setAutoScaleProject(const bool &enabled); + void setUserHash(const QString &userHash); + void setDeviceUuid(const QString &deviceUuid); + + bool autoScaleProject(); + QString userHash(); + QString deviceUuid(); -namespace PackageFromDesignStudio { -using namespace Qt::Literals; -constexpr auto designStudioReady = "designStudioReady"_L1; -constexpr auto pinVerificationRequested = "pinRequested"_L1; -constexpr auto pinVerificationFailed = "pinVerificationFailed"_L1; -constexpr auto deviceIdReceived = "deviceIdReceived"_L1; -constexpr auto deviceIdDeclined = "deviceIdDeclined"_L1; -constexpr auto deviceIdAccepted = "deviceIdAccepted"_L1; -constexpr auto projectData = "projectData"_L1; -}; // namespace PackageFromDesignStudio +private: + QJsonObject m_settings; + const QString m_settingsPath; -namespace PackageToDesignStudio { -using namespace Qt::Literals; -constexpr auto deviceInfo = "deviceInfo"_L1; -constexpr auto registrationPin = "registrationPinResponse"_L1; -}; // namespace PackageToDesignStudio + void loadSettings(); + bool saveSettings(); +}; diff --git a/src/ui/HomePage.qml b/src/ui/HomePage.qml index 9e15965..c7c8e88 100644 --- a/src/ui/HomePage.qml +++ b/src/ui/HomePage.qml @@ -9,207 +9,117 @@ Item { ColumnLayout { anchors.fill: parent - GridLayout{ - id: gridLayout + Item { + id: item2 + Layout.preferredWidth: 10 + Layout.preferredHeight: 3 + Layout.fillWidth: true + Layout.fillHeight: true + } - Item { - id: item2 - Layout.preferredWidth: 10 - Layout.preferredHeight: 3 - Layout.fillWidth: true - Layout.fillHeight: true - } + Text { + id: qrCodeStatus + text: qsTr("Scan the QR code to start pairing with Qt Design Studio") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + Layout.fillWidth: true + } - Text { - id: qrCodeStatus - text: { - if (backend.userHash() === '') { - return qsTr("No user is registered. To access your shared projects:\n\n1. Open the project with Design Studio (4.4 or later).\n2. Select File -> Share Application Online.\n3. Scan the QR code.") - } + Item { + Layout.preferredWidth: 10 + Layout.preferredHeight: 10 + Layout.fillWidth: true + Layout.fillHeight: true + } - return qsTr("User registration is completed.\nScan a new QR code to access the shared project from a different user."); - } - font.pixelSize: 12 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - Layout.fillWidth: true - Connections { - target: backend - function onUserHashChanged(){ - qrCodeStatus.text = qsTr("User registration is completed.\nScan a new QR code to access the shared project from a different user."); - } - } - } + Rectangle { + id: sep1 + height: 2 + color: 'black' + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } - Button { - id: scanQrCode - text: qsTr("Scan QR code") - onClicked: backend.scanQrCode() - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - } - Rectangle { - id: sep1 - height: 2 - color: 'black' - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - } + Button { + id: scanQrCode + implicitHeight: 122 + implicitWidth: 120 + topInset: 0 + bottomInset: 0 + text: qsTr("Scan QR code") + leftPadding: 0 + rightPadding: 0 + icon.color: "#00a31d1d" + icon.width: scanQrCode.implicitHeight + icon.height: scanQrCode.implicitWidth + icon.cache: false + icon.source: "content/images/container.png" + display: AbstractButton.IconOnly + onClicked: backend.scanQrCode() + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } - Rectangle { - id: sep2 - height: 2 - color: 'black' - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - } - Item { - id: item4 - Layout.preferredWidth: 10 - Layout.preferredHeight: 10 - Layout.fillWidth: true - Layout.fillHeight: true - } + Rectangle { + id: sep2 + height: 2 + color: 'black' + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter } Item { - id: item6 + id: item4 Layout.preferredWidth: 10 Layout.preferredHeight: 10 Layout.fillWidth: true Layout.fillHeight: true } + ColumnLayout { - id: column2 Layout.fillWidth: true - ComboBox { - id: projectList + Text { + text: qsTr("Or enter the Qt Design Studio IP address manually") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap Layout.fillWidth: true - enabled: true - textRole: "appName" - model: backend.projectList - onModelChanged: { - if (backend.projectList.length > 0) { - projectList.displayText = backend.projectList[0].appName; - projectList.enabled = true; - downloadUserProject.enabled = true; - } else { - projectList.displayText = qsTr("No project is available"); - projectList.enabled = false; - downloadUserProject.enabled = false; - } - } - onCurrentIndexChanged: { - displayText = model[currentIndex].appName; - } - displayText: qsTr("Scan QR code to access your projects"); - currentIndex: -1 - Layout.preferredHeight: 50 } TextField { - id: password - property bool showText: false + id: ipAddress Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - placeholderText: qsTr("Project password") - echoMode: showText ? TextInput.Normal : TextInput.Password - - - MouseArea { - id: mouseArea - anchors.right: parent.right - height: parent.height - width: height - - Image { - id: eyeIcon - anchors.fill: parent - anchors.margins: 15 - anchors.leftMargin: 10 - anchors.rightMargin: 10 - source: "content/images/closed_eye.png" - } - - onClicked: { - if(password.showText) { - eyeIcon.source = "content/images/closed_eye.png" - } else { - eyeIcon.source = "content/images/open_eye.png" - } - - password.showText = !password.showText; - } - } + placeholderText: qsTr("IP Address") + text: "10.0.2.2" + validator: RegularExpressionValidator { regularExpression: /^(\d{1,3}\.){3}\d{1,3}$/ } } Button { id: downloadUserProject - text: qsTr("Run Project") + text: qsTr("Connect Design Studio") onClicked: { - backend.runUserProject(projectList.displayText, password.text); + backend.connectDesignStudio(ipAddress.text) } - enabled: false + enabled: true Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter } } Item { - id: item3 Layout.fillHeight: true Layout.fillWidth: true Layout.preferredHeight: 10 Layout.preferredWidth: 10 } - ColumnLayout { - id: column - Layout.fillWidth: true - visible: false - - Text { - id: downloadInstructions - text: qsTr("Enter project URL and then click the button below") - font.pixelSize: 12 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - - TextField { - id: urlTextField - horizontalAlignment: Text.AlignHCenter - placeholderText: qsTr("Enter URL") - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - } - - RowLayout { - id: rowLayout - Button { - id: downloadButton - text: qsTr("Download and Run Project") - onClicked: backend.runOnlineProject(urlTextField.text) - enabled: urlTextField.text !== '' - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - } - } - - Connections { - target: backend - function onUrlUpdated(newUrl){ - urlTextField.text = newUrl; - } - } - } } states: [ State { diff --git a/src/ui/SettingsPage.qml b/src/ui/SettingsPage.qml index 79cc624..3c40707 100644 --- a/src/ui/SettingsPage.qml +++ b/src/ui/SettingsPage.qml @@ -5,6 +5,30 @@ import QtQuick.Layouts Item { id: settingsPage + ColumnLayout { + anchors.fill: parent + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + Item { + width: 200 + height: 200 + Layout.preferredHeight: 10 + Layout.fillWidth: true + } + + Label { + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + horizontalAlignment: "AlignHCenter" + text: backend.buildInfo() + } + + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + } + ColumnLayout { anchors.fill: parent @@ -43,5 +67,7 @@ Item { Layout.preferredHeight: 10 Layout.preferredWidth: 10 } + + } } diff --git a/src/ui/content/images/container.png b/src/ui/content/images/container.png new file mode 100644 index 0000000000000000000000000000000000000000..a650d4461ee5151f7ba9d4cd99573de071046496 GIT binary patch literal 1590 zcmV-62Fdw}P)<h;3K|Lk000e1NJLTq002Ay002D*1^@s6@t_LV00009a7bBm000XU z000XU0RWnu7ytkO0drDELIAGL9O(c600d`2O+f$vv5yP<VFdsH1<Of9K~#7F<(oZB z<46?8pCgM8fpm0G`UE|>8l(*sbh}qV0c=gNECrOyM+F6U6x2r=I&^m(RF{*WKGTCX zC3LLBxA?gC_s!%cj$_Z*laOS8k`*(a1pocsym>R8ai~eWzP=9J-`@}LsTaCb?&;~d zavbN1nt($M0OB4W9}6tv-OI~Mo{Mk{Bzz5m{ah~R^Y{Pm>gp;r;vrZQEG+neZ*q?G z1nN-Y-!1m`_U@4-G7A>g{KTJ&WJhEaWqyz)_LemkU#1MC7ThGEHL&nicK4mM?w(5U z-Q6A9=WkRSfSN^_AM{!pSI?PL7VY#G7W)^yomk-Ef#Y6qPbOc~wlG`OYXPBAz}geK zZClI2*Voti78MHuLvTFF>r_s(FvPm93$aZLOld$6<70vYnjc#)?xJwxf2hU9N_0fm zb!nx|wuM-L0LFdw@3{ARb?RD}Bj8tR%{H3|8_MtQ?*1w2TBL;^9v;3>n~DW&={&lu zsB1MA7Owo2IsnmKmDI8t!Md(|dU{GnM@Mz9&(6-$(9jU2;r#rZ4h|0LUZ0$tq|wn) zN+Qa&UG)#P?mTI_^2Nmk9UdN1+)bqw%w1vZZ$qq(VBy+}Jl_6QmQ}T3ej;C}!+<6= z{j6Bn#JXYdw5O(~s>%ij2PrMX!^5<)vQkwBi=!Et0<j!~=lL|Saz$_!>%w*6!nWw| z@2~ki-}h4q7Q81_F!E6DuszDi($Z3JV{y#Q%>@T27K_0VM?RmY?d@&KNC87yMC_mq zKRrFoQ-{MI`~jbVg?ZxaJy`_q7;e*>o0~yoj{+{dv9VEAhBh15?WF)37hREedSnPh z)oZtC(>mRT1cJl$!%YK0EF8!T@t~EBo6{Ocb_j*s^Ye3oz7x0|=Uuk2zVB&*OzHCS z(&#(k`uGjopeI7mvOeBL9vqhohe0r#o}Q*g1LLAw^5{Fl;pF<2EOB;rMmIM%v2~Dg z%0UGzo8<b4;7B!XVO4NDU^Efm2_R_U9gUU1wY$3;4Bq2V6BcArIN-MH>+3<;*49?w z>T!up7OV<}*rdUOno&U*>Z=n4_#5RWnVJD*DcC9_BO}3~a(sMT)3>6I%(-RWjM0$< zWe9R=Sw_k(50FohfsjSU#>Of`KnW}t7L0m0V9}YGnV@Wce?PD|hK(3-K&&mnoOpbL zU|+noZ#sAY=*G}BNw>y1-%oIkvcA4PvIQp|B^NV$^hO{XgC!vhmIbghEDjHmWwwBE zU7peNdCtc;a3RS!IXMZ=kr<4Rk5eN-43Iqlnn5^5=ztjySon&Q4_LWi@@4#UIX#2! zLlul*6P|yH8vNsXvVCHD2*9$iINChQ@Gut^7J`Q>0~@ZJ3g-6vCq7;I2m5z{ECF%# zW;axD<2YpZ1eTYVD}s}{I1|{gf8a9^jI|QteNv|WSm%vE)O92tGOFWT)|1w@0XN<M z1;edxZ*Pli<xiA>w3(4ASYop~VtspZ)sH$ajVF*wFxr>wM<qxV(*zcP_K!YTCCh{W z)_2MCieN-A4qhcP3|{hP!Z(yOOiV&22Iv6mvOAaM-&La}?x#LdhoM>*M7<wg)p_kA zv!r?;Ly`d~4RI3#y1y+JudS+OPDCZVe8NH&kJ=k7Oa3ujSTg~DXvVKzex^1DZ~vpp zBWFC=UC`ryrZz>mayjZ+-6<ZtHRlGuLv2P>yH!bCiN`@~@2!T9rijJl^Ko^w7MHez zXdSUYYt|A!1XrOJMY!?2PF<q~;#d*GADIUk7D0ZzrmoUS!gxh|3$2^R29S@YdmxM_ z;_(kehmLNmOw9*Y{ml0;(VJq!N2XmZV)ejklLFcmTXYBRn`J+fm3>P>ctPag9@zy; z{EJzIO04fp6FXk8V&)zFw_z&{*qw$ss8V=wXjU4s6;C2|!Q`>#k!Mp7?0(>Luj&RI o2NwLsSsgZG`Itp@8n_SfUt+-2c?cMTX#fBK07*qoM6N<$g2QF!Gynhq literal 0 HcmV?d00001 diff --git a/src/ui/main.qml b/src/ui/main.qml index 5d00d42..e847d0a 100644 --- a/src/ui/main.qml +++ b/src/ui/main.qml @@ -15,117 +15,121 @@ Rectangle { M.Material.accent: M.Material.Blue M.Material.primary: M.Material.Blue - Image { - id: qdsicon1 - width: 204 - height: 173 - anchors.top: parent.top - source: "content/images/appicon.png" - anchors.horizontalCenter: parent.horizontalCenter - fillMode: Image.PreserveAspectFit + ColumnLayout{ + anchors.fill: parent + spacing: 4 + Layout.alignment: Qt.AlignCenter - Text { - id: qdvLabel - text: qsTr("Qt UI Viewer") - anchors.top: parent.bottom - font.pixelSize: 12 - anchors.topMargin: -22 - anchors.horizontalCenter: parent.horizontalCenter + Rectangle{ + id: statusBar + Layout.fillWidth: true + height: 20 + color: "#f4a71d" + property bool connected: false + Text { + id: statusText + text: qsTr("Qt Design Studio is disconnected.") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + anchors.fill: parent + height: 20 + } + Connections{ + target: backend + function onConnectedChanged(isConnected, ip){ + if(!isConnected){ + statusBar.color = "#f4a71d" + statusText.text = "Qt Design Studio is disconnected."; + } else { + statusBar.color = "#05c940" + statusText.text = "Qt Design Studio is connected to " + ip; + } + } + } } - } - Popup { - property bool popupCloseReceived : false - id: popup - anchors.centerIn: parent - width: 300 - height: 100 - modal: true - focus: true - closePolicy: Popup.CloseOnEscape + Image { + id: qdsicon1 + source: "content/images/appicon.png" + Layout.fillWidth: true + Layout.preferredHeight: 173 + fillMode: Image.PreserveAspectFit - onClosed: { - if (!popupCloseReceived) { - backend.popupInterrupted(); + Text { + id: qdvLabel + text: qsTr("Qt UI Viewer") + anchors.top: parent.bottom + font.pixelSize: 12 + anchors.topMargin: -22 + anchors.horizontalCenter: parent.horizontalCenter } } - ColumnLayout { - anchors.fill: parent - Text { - id: popupText - } - Item { - id: name - } - ProgressBar { - id: popupProgressBar + StackLayout { + id: stackLayout + Layout.margins: 15 + + HomePage { + id: homePage Layout.fillWidth: true - to: 100 } - } - Connections { - target: backend - function onPopupOpen() { - popup.open() - popup.popupCloseReceived = false - } - function onPopupClose() { - popup.popupCloseReceived = true - popup.close() - } - function onPopupTextChanged(text) { - popupText.text = text - } - function onPopupProgressIndeterminateChanged(status) { - popupProgressBar.indeterminate = status - } - function onDownloadProgress(progress) { - popupProgressBar.value = progress + ExamplesPage { + id: examplesPage + Layout.fillWidth: true } - } - } - StackLayout { - id: stackLayout - anchors.left: parent.left - anchors.right: parent.right - anchors.bottomMargin: 10 - anchors.top: qdsicon1.bottom - anchors.bottom: root.bottom - anchors.rightMargin: 20 - anchors.leftMargin: 20 - anchors.topMargin: 10 + Logs { + id: logsPage + Layout.fillWidth: true + } - HomePage { - id: homePage - Layout.fillWidth: true - } + DSManagement { + id: dsManagementPage + Layout.fillWidth: true + } - ExamplesPage { - id: examplesPage - Layout.fillWidth: true - } + SettingsPage { + id: settingsPage + Layout.fillWidth: true + } - Logs { - id: logsPage - Layout.fillWidth: true + AboutHeader { + id: headerPage + Layout.fillWidth: true + } } - DSManagement { - id: dsManagementPage + TabBar { + id: tabBar Layout.fillWidth: true - } - SettingsPage { - id: settingsPage - Layout.fillWidth: true - } + TabButton { + id: home + text: qsTr("Home") + Layout.fillWidth: true + checked: true + checkable: true + autoExclusive: true + onClicked: { + stackLayout.currentIndex = 0 + drawer.close() + } + } - AboutHeader { - id: headerPage - Layout.fillWidth: true + TabButton { + id: settings + text: qsTr("Settings") + Layout.fillWidth: true + checkable: true + autoExclusive: true + onClicked: { + stackLayout.currentIndex = 4 + drawer.close() + } + } } } @@ -157,112 +161,6 @@ Rectangle { } ] - - Drawer { - id: drawer - width: 150 - height: root.height - - ScrollView { - id: scrollview - anchors.fill: parent - padding: 0 - - ColumnLayout { - id: column - width: Math.max(implicitWidth, drawer.availableWidth) - height: Math.max(implicitHeight, drawer.availableHeight) - - TabButton { - id: home - text: qsTr("Home") - Layout.fillWidth: true - checked: true - checkable: true - autoExclusive: true - onClicked: { - stackLayout.currentIndex = 0 - drawer.close() - } - } - - TabButton { - id: examples - text: qsTr("Examples") - Layout.fillWidth: true - checkable: true - autoExclusive: true - onClicked: { - stackLayout.currentIndex = 1 - drawer.close() - } - } - - TabButton { - id: logs - text: qsTr("Logs") - Layout.fillWidth: true - checkable: true - autoExclusive: true - onClicked: { - stackLayout.currentIndex = 2 - drawer.close() - } - } - - TabButton { - id: dsManagement - text: qsTr("Design Studio") - Layout.fillWidth: true - checkable: true - autoExclusive: true - visible: false - onClicked: { - stackLayout.currentIndex = 3 - drawer.close() - } - } - - TabButton { - id: settings - text: qsTr("Settings") - Layout.fillWidth: true - checkable: true - autoExclusive: true - onClicked: { - stackLayout.currentIndex = 4 - drawer.close() - } - } - - TabButton { - id: about - text: qsTr("About") - Layout.fillWidth: true - checkable: true - autoExclusive: true - onClicked: { - stackLayout.currentIndex = 5 - drawer.close() - } - } - } - } - } - - Button { - id: menuButon - x: 8 - y: 8 - text: qsTr("Menu") - Connections { - target: menuButon - function onClicked(){ - drawer.open(); - } - } - } - Popup { property bool popupCloseReceived : false id: popup @@ -283,6 +181,7 @@ Rectangle { anchors.fill: parent Text { id: popupText + wrapMode: Text.WordWrap } Item { id: name @@ -294,10 +193,26 @@ Rectangle { } } + Timer { + id: closeTimer + interval: 500 + running: false + repeat: false + onTriggered: { + popup.close() + } + } + Connections { target: backend - function onPopupOpen() { + function onPopupOpen(text, timeout) { popup.open() + popupText.text = text + popupProgressBar.value = 0 + closeTimer.interval = timeout + if (timeout > 0) { + closeTimer.start() + } popup.popupCloseReceived = false } function onPopupClose() { diff --git a/tests/mock.h b/tests/mock.h index f7eb895..428bd4f 100644 --- a/tests/mock.h +++ b/tests/mock.h @@ -34,8 +34,6 @@ #include <QWebSocketServer> #include <qtypes.h> -#include "backend/dsconnector/tcpdatatypes.h" - #define UDP_PORT 53452 class DesignStudioMock : public QObject diff --git a/tests/tst_designstudio.cpp b/tests/tst_designstudio.cpp index b22b4e6..e36bfc7 100644 --- a/tests/tst_designstudio.cpp +++ b/tests/tst_designstudio.cpp @@ -34,7 +34,6 @@ #include <QWebSocketServer> #include "backend/dsconnector/ds.h" -#include "backend/dsconnector/tcpdatatypes.h" QJsonObject createPackage(const QLatin1String &type, const QString &data) { @@ -47,7 +46,7 @@ QJsonObject createPackage(const QLatin1String &type, const QString &data) void TestDesignStudio::initTestCase() { qDebug() << "Initialize TestDesignStudio"; - ds = new DesignStudio("localhost", 40000, "id", "deviceId"); + ds = new DesignStudio("localhost", 40000, "id"); QVERIFY(ds != nullptr); } @@ -58,7 +57,7 @@ void TestDesignStudio::testIpv4Addr() void TestDesignStudio::testId() { - QCOMPARE(ds->designStudioId(), QString("id")); + QCOMPARE(ds->id(), QString("id")); } void TestDesignStudio::testPort() @@ -71,9 +70,11 @@ void TestDesignStudio::testUrl() QCOMPARE(ds->url(), QUrl("ws://localhost:40000")); } -void TestDesignStudio::testDeviceId() +void TestDesignStudio::testSkipPairing() { - QCOMPARE(ds->deviceId(), QString("deviceId")); + QVERIFY(!ds->skipPairing()); + ds->setSkipPairing(true); + QVERIFY(ds->skipPairing()); } void TestDesignStudio::testConnected() @@ -103,14 +104,14 @@ void TestDesignStudio::testSendRegistrationPin() QJsonObject obj = QJsonDocument::fromJson(message.toUtf8()).object(); QVERIFY(obj.contains("dataType")); QVERIFY(obj.contains("data")); - QVERIFY(obj.value("dataType").toString() == PackageToDesignStudio::registrationPin); + QVERIFY(obj.value("dataType").toString() == PackageToDesignStudio::pairingPin); QVERIFY(obj.value("data").toString() == "1234"); }); QSignalSpy spy(socket, &QWebSocket::textMessageReceived); QVERIFY(spy.isValid()); - ds.sendRegistrationPin("1234"); + ds.sendPairingPin("1234"); QTRY_COMPARE(spy.count(), 1); QTest::qWait(1000); } @@ -146,7 +147,7 @@ void TestDesignStudio::testSendDeviceInfo() QVERIFY(spy.isValid()); const QJsonObject dsReadyPackage = createPackage(PackageFromDesignStudio::designStudioReady, - "data"); + ds.id()); socket->sendTextMessage(QJsonDocument(dsReadyPackage).toJson(QJsonDocument::Compact)); QTRY_COMPARE(spy.count(), 1); @@ -164,11 +165,11 @@ void TestDesignStudio::testRegistrationPinRequestedSignal() QWebSocket *socket = server.nextPendingConnection(); QVERIFY(socket != nullptr); - QSignalSpy spy(&ds, &DesignStudio::registrationPinRequested); + QSignalSpy spy(&ds, &DesignStudio::pinRequested); QVERIFY(spy.isValid()); - const QJsonObject pinRequestedPackage - = createPackage(PackageFromDesignStudio::pinVerificationRequested, "data"); + const QJsonObject pinRequestedPackage = createPackage(PackageFromDesignStudio::pinRequested, + "data"); socket->sendTextMessage(QJsonDocument(pinRequestedPackage).toJson(QJsonDocument::Compact)); QTRY_COMPARE(spy.count(), 1); @@ -185,38 +186,17 @@ void TestDesignStudio::testPinVerificationFailedSignal() QWebSocket *socket = server.nextPendingConnection(); QVERIFY(socket != nullptr); - QSignalSpy spy(&ds, &DesignStudio::pinVerificationFailed); + QSignalSpy spy(&ds, &DesignStudio::pinFailed); QVERIFY(spy.isValid()); const QJsonObject pinVerificationFailedPackage - = createPackage(PackageFromDesignStudio::pinVerificationFailed, "data"); + = createPackage(PackageFromDesignStudio::pinFailed, "data"); socket->sendTextMessage( QJsonDocument(pinVerificationFailedPackage).toJson(QJsonDocument::Compact)); QTRY_COMPARE(spy.count(), 1); } -void TestDesignStudio::testRegistrationSuccessfulSignal() -{ - QWebSocketServer server("TestDesignStudio", QWebSocketServer::NonSecureMode); - QVERIFY(server.listen(QHostAddress::LocalHost, 40007)); - - DesignStudio ds("localhost", 40007, "id"); - QTRY_VERIFY(ds.connected()); - - QWebSocket *socket = server.nextPendingConnection(); - QVERIFY(socket != nullptr); - - QSignalSpy spy(&ds, &DesignStudio::registrationSucceeded); - QVERIFY(spy.isValid()); - - const QJsonObject deviceIdReceivedPackage - = createPackage(PackageFromDesignStudio::deviceIdReceived, "data"); - socket->sendTextMessage(QJsonDocument(deviceIdReceivedPackage).toJson(QJsonDocument::Compact)); - - QTRY_COMPARE(spy.count(), 1); -} - void TestDesignStudio::testUnregisteredSignal() { QWebSocketServer server("TestDesignStudio", QWebSocketServer::NonSecureMode); @@ -228,7 +208,7 @@ void TestDesignStudio::testUnregisteredSignal() QWebSocket *socket = server.nextPendingConnection(); QVERIFY(socket != nullptr); - QSignalSpy spy(&ds, &DesignStudio::unregistered); + QSignalSpy spy(&ds, &DesignStudio::unpaired); QVERIFY(spy.isValid()); const QJsonObject deviceIdDeclinedPackage @@ -249,7 +229,7 @@ void TestDesignStudio::testConnectionAliveSignal() QWebSocket *socket = server.nextPendingConnection(); QVERIFY(socket != nullptr); - QSignalSpy spy(&ds, &DesignStudio::dsOnline); + QSignalSpy spy(&ds, &DesignStudio::paired); QVERIFY(spy.isValid()); const QJsonObject dsReadyPackage = createPackage(PackageFromDesignStudio::deviceIdAccepted, diff --git a/tests/tst_designstudio.h b/tests/tst_designstudio.h index 518d12e..7573c6b 100644 --- a/tests/tst_designstudio.h +++ b/tests/tst_designstudio.h @@ -32,6 +32,7 @@ class TestDesignStudio : public QObject Q_OBJECT private: DesignStudio *ds; + QString m_uuid; private slots: // init @@ -42,7 +43,7 @@ private slots: void testId(); void testPort(); void testUrl(); - void testDeviceId(); + void testSkipPairing(); // test status check void testConnected(); @@ -54,7 +55,6 @@ private slots: // test signals void testRegistrationPinRequestedSignal(); void testPinVerificationFailedSignal(); - void testRegistrationSuccessfulSignal(); void testUnregisteredSignal(); void testConnectionAliveSignal(); void testProjectIncomingSignal(); -- GitLab