diff --git a/CMakeLists.txt b/CMakeLists.txt index e0277147598d42872b814222d9ec98ee9e07ae0c..de88c8171fefbfadf3a477b1910bc71058c7e4c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,9 +8,13 @@ set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + add_compile_options(-Wall -Wextra) +endif() + find_package( QT NAMES Qt6 COMPONENTS Core Widgets Quick Gui Qml Multimedia MultimediaWidgets Concurrent @@ -35,9 +39,10 @@ qt_add_executable(${PROJECT_NAME} src/importdummy.qml src/main.cpp src/backend.cpp src/backend.h - src/serviceConnector.cpp src/serviceConnector.h - src/projectManager.cpp src/projectManager.h - src/dsConnector.cpp src/dsConnector.h + src/serviceconnector.cpp src/serviceconnector.h + src/projectmanager.cpp src/projectmanager.h + src/dsconnector.cpp src/dsconnector.h + src/qrscanner.cpp src/qrscanner.h 3rdparty/zxing-cpp/example/ZXingQtReader.h ui/main.qml ui/resources.qrc diff --git a/src/backend.cpp b/src/backend.cpp index f403780e99cf6c56024566c4b2226f76762a62c3..7a5d26d7b23d95f050223d3c4c54a41831b6a60a 100644 --- a/src/backend.cpp +++ b/src/backend.cpp @@ -24,25 +24,14 @@ ****************************************************************************/ #include "backend.h" -#include "qapplication.h" -#include <QCameraDevice> #include <QDesktopServices> -#include <QElapsedTimer> +#include <QEventLoop> #include <QFileInfo> +#include <QGuiApplication> #include <QJsonArray> #include <QJsonObject> -#include <QMediaDevices> -#include <QMessageBox> -#include <QPermission> #include <QSettings> -#include <QSslSocket> -#include <QVideoSink> -#include <QtConcurrent> - -#include "../3rdparty/zxing-cpp/example/ZXingQtReader.h" - -using namespace ZXingQt; Backend::Backend(QObject *parent) : QObject(parent) @@ -147,113 +136,13 @@ void Backend::updatePopup(const QString &text, bool indeterminate) void Backend::scanQrCode() { - // request permissions - QCameraPermission permission; - QCoreApplication &app = *QCoreApplication::instance(); - switch (app.checkPermission(permission)) { - case Qt::PermissionStatus::Granted: - openCamera(); - break; - case Qt::PermissionStatus::Undetermined: - case Qt::PermissionStatus::Denied: - app.requestPermission(permission, [this](const QPermission &permission) { - if (permission.status() == Qt::PermissionStatus::Denied) { - QMessageBox msgBox{QMessageBox::Critical, - "Critical:", - "Camera permission denied", - QMessageBox::Ok}; - msgBox.exec(); - return; - } - - openCamera(); - }); - break; - } -} - -void Backend::openCamera() -{ - // start camera - m_captureSession.reset(new QMediaCaptureSession(this)); - m_captureSession->setCamera(new QCamera(this)); - m_captureSession->setVideoOutput(new CustomVideoWidget); - - m_captureSession->camera()->setFocusMode(QCamera::FocusModeAuto); - - CustomVideoWidget *videoWidget = qobject_cast<CustomVideoWidget *>( - m_captureSession->videoOutput()); - - connect(m_captureSession->videoSink(), - &QVideoSink::videoFrameChanged, - this, - [&](const QVideoFrame &frame) { - static int i = 0; - static QAtomicInt running = 0; - - if (i++ < 20 || running > 0) { - return; - } - - i = 0; - - QtConcurrent::run([=] { - // lock the thread so that only one barcode can be read at a time - running++; - - QElapsedTimer timer; - timer.start(); - QList<Result> results = ReadBarcodes(frame.toImage()); - qDebug() << "Barcode detection took" << timer.elapsed() << "ms"; - - // release the lock so that another barcode can be read - running--; - return results; - }).then([=](const QList<Result> &results) { - if (results.isEmpty() || !m_captureSession) - return; - - qDebug() << "Stopping camera"; - qobject_cast<CustomVideoWidget *>(m_captureSession->videoOutput())->close(); - qDebug() << "Camera stopped"; - - Result result = results.first(); - - qDebug() << "Text: " << result.text(); - qDebug() << "Format: " << result.format(); - qDebug() << "Content:" << result.contentType(); - - // we have to use invokeMethod because we are in a different thread - // then where the serviceConnector is created - QMetaObject::invokeMethod(this, - "parseDesignViewerUrl", - Qt::QueuedConnection, - Q_ARG(QUrl, result.text())); - }); - }); - - // stop camera when app is not active. - // i.e. when the user switches to another app or the screen is locked - QGuiApplication *app(qobject_cast<QGuiApplication *>(QCoreApplication::instance())); - connect(app, &QGuiApplication::applicationStateChanged, this, [&](Qt::ApplicationState state) { - if (state != Qt::ApplicationState::ApplicationActive && m_captureSession) { - qDebug() << "Application is not active. Stopping camera"; - qobject_cast<CustomVideoWidget *>(m_captureSession->videoOutput())->close(); - } + m_qrScanner.reset(new QrScanner); + connect(m_qrScanner.data(), &QrScanner::qrCodeScanned, this, [&](const QString &qrCode) { + qDebug() << "QR code scanned:" << qrCode; + parseDesignViewerUrl(QUrl(qrCode)); + m_qrScanner.reset(); }); - - // stop camera when video widget is closed - connect(videoWidget, &CustomVideoWidget::closed, this, [&] { - qDebug() << "Video widget closed. Clearing capture session"; - m_captureSession->camera()->stop(); - m_captureSession->camera()->deleteLater(); - m_captureSession->videoOutput()->deleteLater(); - m_captureSession.clear(); - }); - - videoWidget->setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint); - videoWidget->show(); - m_captureSession->camera()->start(); + m_qrScanner->scanQrCode(); } void Backend::initializeProjectManager() @@ -382,9 +271,7 @@ void Backend::clearDemoCaches() { emit popupOpen(); updatePopup("Clearing demo caches..."); - m_projectManager.reset(new ProjectManager); - m_projectManager->clearDemoCaches(); - m_projectManager.reset(); + ProjectManager().clearDemoCaches(); emit popupClose(); } @@ -500,6 +387,16 @@ void Backend::updateUserProjectList() const QString projectName{project.toObject().value("appName").toString()}; qDebug() << "--" << projectName; } + + // check if any project is removed on the cloud + for (const auto &project : m_projectList) { + const QString projectName{project.toObject().value("appName").toString()}; + if (!projectList.value().contains(project)) { + qDebug() << "Project removed:" << projectName << ". Removing from cache..."; + // remove the project from the cache + ProjectManager().clearCachedProject(project.toObject()); + } + } } // we need to set m_projectList even if it is empty diff --git a/src/backend.h b/src/backend.h index a5ad992dce08bc4ffba19723b70c39f8b80bd39b..5ce42d7ee7eb62112e7c31eea76a82999bfd9d48 100644 --- a/src/backend.h +++ b/src/backend.h @@ -26,34 +26,12 @@ #ifndef DV_ANDROID_H #define DV_ANDROID_H -#include <QCamera> -#include <QImageCapture> -#include <QMediaCaptureSession> #include <QThread> -#include <QVideoWidget> -#include <QWidget> -#include "dsConnector.h" -#include "projectManager.h" -#include "serviceConnector.h" - -#include <QLabel> -#include <QVBoxLayout> - -class CustomVideoWidget : public QVideoWidget -{ - Q_OBJECT -public: - explicit CustomVideoWidget(QWidget *parent = nullptr) - : QVideoWidget(parent) - {} - ~CustomVideoWidget() override {} - - void closeEvent(QCloseEvent *event) override { emit closed(); } - -signals: - void closed(); -}; +#include "dsconnector.h" +#include "projectmanager.h" +#include "qrscanner.h" +#include "serviceconnector.h" class Backend : public QObject { @@ -78,13 +56,12 @@ private: QJsonArray m_projectList; // Other members + ServiceConnector m_serviceConnector; + QThread m_dsConnectorThread; QScopedPointer<ProjectManager> m_projectManager; QScopedPointer<DesignStudioConnector> m_designStudioConnector; - QThread m_dsConnectorThread; - - // QR code scanner - QSharedPointer<QMediaCaptureSession> m_captureSession; + QScopedPointer<QrScanner> m_qrScanner; // Settings QTimer m_backgroundTimer; @@ -117,7 +94,6 @@ signals: public slots: QString buildInfo() const { return m_buildInfo; } void scanQrCode(); - void openCamera(); void runOnlineProject(const QString &url); void runUserProject(const QString &projectName, const QString &password); diff --git a/src/dsConnector.cpp b/src/dsconnector.cpp similarity index 99% rename from src/dsConnector.cpp rename to src/dsconnector.cpp index 56bdd858d891ee11767765f16d7f2874427a2ff4..d82c31dfd87eefbea6d47faaab8e52bc99f8b79b 100644 --- a/src/dsConnector.cpp +++ b/src/dsconnector.cpp @@ -23,7 +23,7 @@ ** ****************************************************************************/ -#include "dsConnector.h" +#include "dsconnector.h" #include <QNetworkInterface> diff --git a/src/dsConnector.h b/src/dsconnector.h similarity index 100% rename from src/dsConnector.h rename to src/dsconnector.h diff --git a/src/projectManager.cpp b/src/projectmanager.cpp similarity index 97% rename from src/projectManager.cpp rename to src/projectmanager.cpp index f479b48db4bf23f6246feb7fa863fd867abb1583..629536f518307fb3192788b46995106aa40f12bf 100644 --- a/src/projectManager.cpp +++ b/src/projectmanager.cpp @@ -23,7 +23,7 @@ ** ****************************************************************************/ -#include "projectManager.h" +#include "projectmanager.h" #include <QBuffer> #include <QDir> @@ -48,9 +48,9 @@ ProjectManager::ProjectManager(const bool &autoScaleProject, QObject *parent) : QObject(parent) - , m_autoScaleProject(autoScaleProject) , m_projectCachePath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)) , m_demoProjectCachePath(m_projectCachePath + "/demoProjects") + , m_autoScaleProject(autoScaleProject) { qDebug() << "ProjectManager created."; qDebug() << "Project cache path: " << m_projectCachePath; @@ -399,6 +399,20 @@ bool ProjectManager::isProjectCached(const QJsonObject &projectInfo) return true; } +void ProjectManager::clearCachedProject(const QJsonObject &projectInfo) +{ + const QString projectId = projectInfo.value("id").toString(); + qDebug() << "Clearing cache for project " << projectId; + + const QString cachePath = m_projectCachePath + "/" + projectId; + if (!QDir(cachePath).exists()) { + qDebug() << "Project " << projectId << " is not cached"; + } + + qDebug() << "Removing cache for project " << projectId; + QDir(cachePath).removeRecursively(); +} + bool ProjectManager::runCachedProject(const QJsonObject &projectInfo) { const QString projectId = projectInfo.value("id").toString(); diff --git a/src/projectManager.h b/src/projectmanager.h similarity index 98% rename from src/projectManager.h rename to src/projectmanager.h index 9cc3b952edeee2656261a2828270b5f96a3553ff..8c79fac1cf576f0f8a2e4f857ce193596257ecf2 100644 --- a/src/projectManager.h +++ b/src/projectmanager.h @@ -46,6 +46,7 @@ public: bool cacheProject(const QByteArray &projectData, const QJsonObject &projectInfo); bool isProjectCached(const QJsonObject &projectInfo); bool runCachedProject(const QJsonObject &projectInfo); + void clearCachedProject(const QJsonObject &projectInfo); bool cacheDemoProject(const QByteArray &projectData, const QJsonObject &projectInfo); bool isDemoProjectCached(const QJsonObject &projectName); diff --git a/src/qrscanner.cpp b/src/qrscanner.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7d26ae28cdd24513b47ef67f72529599dfca3efd --- /dev/null +++ b/src/qrscanner.cpp @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** 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 "qrscanner.h" + +#include <QApplication> +#include <QCamera> +#include <QCameraDevice> +#include <QElapsedTimer> +#include <QImageCapture> +#include <QMediaDevices> +#include <QMessageBox> +#include <QPermission> +#include <QVideoSink> +#include <QtConcurrent> + +#include "../3rdparty/zxing-cpp/example/ZXingQtReader.h" + +using namespace ZXingQt; + +QrScanner::~QrScanner() +{ + if (m_captureSession) { + m_captureSession->camera()->stop(); + m_captureSession->camera()->deleteLater(); + m_captureSession->videoOutput()->deleteLater(); + m_captureSession.clear(); + } +} + +void QrScanner::scanQrCode() +{ + // request permissions + QCameraPermission permission; + QCoreApplication &app = *QCoreApplication::instance(); + switch (app.checkPermission(permission)) { + case Qt::PermissionStatus::Granted: + openCamera(); + break; + case Qt::PermissionStatus::Undetermined: + case Qt::PermissionStatus::Denied: + app.requestPermission(permission, [this](const QPermission &permission) { + if (permission.status() == Qt::PermissionStatus::Denied) { + QMessageBox msgBox{QMessageBox::Critical, + "Critical:", + "Camera permission denied", + QMessageBox::Ok}; + msgBox.exec(); + return; + } + + openCamera(); + }); + break; + } +} + +void QrScanner::openCamera() +{ + // start camera + m_captureSession.reset(new QMediaCaptureSession(this)); + m_captureSession->setCamera(new QCamera(this)); + m_captureSession->setVideoOutput(new CustomVideoWidget); + + m_captureSession->camera()->setFocusMode(QCamera::FocusModeAuto); + + CustomVideoWidget *videoWidget = qobject_cast<CustomVideoWidget *>( + m_captureSession->videoOutput()); + + connect(m_captureSession->videoSink(), + &QVideoSink::videoFrameChanged, + this, + [&](const QVideoFrame &frame) { + static int i = 0; + static QAtomicInt running = 0; + + if (i++ < 20 || running > 0) { + return; + } + + i = 0; + + QtConcurrent::run([=] { + // lock the thread so that only one barcode can be read at a time + running++; + + QElapsedTimer timer; + timer.start(); + QList<Result> results = ReadBarcodes(frame.toImage()); + qDebug() << "Barcode detection took" << timer.elapsed() << "ms"; + + // release the lock so that another barcode can be read + running--; + return results; + }).then([=](const QList<Result> &results) { + if (results.isEmpty() || !m_captureSession) + return; + + qDebug() << "Stopping camera"; + qobject_cast<CustomVideoWidget *>(m_captureSession->videoOutput())->close(); + qDebug() << "Camera stopped"; + + Result result = results.first(); + + qDebug() << "Text: " << result.text(); + qDebug() << "Format: " << result.format(); + qDebug() << "Content:" << result.contentType(); + + // we have to use invokeMethod because we are in a different thread + // then where the serviceConnector is created + // QMetaObject::invokeMethod(this, + // "parseDesignViewerUrl", + // Qt::QueuedConnection, + // Q_ARG(QUrl, result.text())); + emit qrCodeScanned(result.text()); + }); + }); + + // stop camera when app is not active. + // i.e. when the user switches to another app or the screen is locked + QGuiApplication *app(qobject_cast<QGuiApplication *>(QCoreApplication::instance())); + connect(app, &QGuiApplication::applicationStateChanged, this, [&](Qt::ApplicationState state) { + if (state != Qt::ApplicationState::ApplicationActive && m_captureSession) { + qDebug() << "Application is not active. Stopping camera"; + qobject_cast<CustomVideoWidget *>(m_captureSession->videoOutput())->close(); + } + }); + + // stop camera when video widget is closed + connect(videoWidget, &CustomVideoWidget::closed, this, [&] { + qDebug() << "Video widget closed. Clearing capture session"; + m_captureSession->camera()->stop(); + m_captureSession->camera()->deleteLater(); + m_captureSession->videoOutput()->deleteLater(); + m_captureSession.clear(); + }); + + videoWidget->setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint); + videoWidget->show(); + m_captureSession->camera()->start(); +} diff --git a/src/qrscanner.h b/src/qrscanner.h new file mode 100644 index 0000000000000000000000000000000000000000..dab9787be7acad7aba79bace92fca2f0a5642d66 --- /dev/null +++ b/src/qrscanner.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** 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 <QMediaCaptureSession> +#include <QVideoWidget> + +class CustomVideoWidget : public QVideoWidget +{ + Q_OBJECT +public: + explicit CustomVideoWidget(QWidget *parent = nullptr) + : QVideoWidget(parent) + {} + ~CustomVideoWidget() override {} + + void closeEvent(QCloseEvent *) override { emit closed(); } + +signals: + void closed(); +}; + +class QrScanner : public QObject +{ + Q_OBJECT +public: + ~QrScanner(); + void scanQrCode(); + +private: + QSharedPointer<QMediaCaptureSession> m_captureSession; + + void openCamera(); + +signals: + void qrCodeScanned(const QString &qrCode); +}; diff --git a/src/serviceConnector.cpp b/src/serviceconnector.cpp similarity index 99% rename from src/serviceConnector.cpp rename to src/serviceconnector.cpp index c65b0ab64298b1d77f05acce56d97008c6504ab4..422709fe28bae79787126c96ffd89e67036c3040 100644 --- a/src/serviceConnector.cpp +++ b/src/serviceconnector.cpp @@ -23,7 +23,7 @@ ** ****************************************************************************/ -#include "serviceConnector.h" +#include "serviceconnector.h" #include <QNetworkAccessManager> #include <QNetworkReply> diff --git a/src/serviceConnector.h b/src/serviceconnector.h similarity index 100% rename from src/serviceConnector.h rename to src/serviceconnector.h diff --git a/ui/main.qml b/ui/main.qml index 0021ae03337f90925ac35172c4b6c088346ddb94..4b69f686738758ee978b3cd897588fc27422a994 100644 --- a/ui/main.qml +++ b/ui/main.qml @@ -26,7 +26,7 @@ Rectangle { Text { id: qdvLabel - text: qsTr("Qt UI Viewer2") + text: qsTr("Qt UI Viewer") anchors.top: parent.bottom font.pixelSize: 12 anchors.topMargin: -22