diff --git a/.gitmodules b/.gitmodules index f2a53617a074dd7c32d8d78c47a5f1d09f8769ae..0406ca94d5d4f98c432216f3072c15fea38be8b9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,9 @@ [submodule "qtquickdesigner-components"] -path = qtquickdesigner-components +path = 3rdparty/qtquickdesigner-components url = https://git.qt.io/design-studio/kit-dependencies/qt-quickdesigner-components.git -[submodule "emsdk"] -path = emsdk -url = https://github.com/emscripten-core/emsdk.git +[submodule "3rdparty/zxing-cpp"] + path = 3rdparty/zxing-cpp + url = https://github.com/zxing-cpp/zxing-cpp.git +[submodule "3rdparty/qtquickdesigner-components"] + path = 3rdparty/qtquickdesigner-components + url = https://git.qt.io/design-studio/kit-dependencies/qt-quickdesigner-components diff --git a/3rdparty/qtquickdesigner-components b/3rdparty/qtquickdesigner-components new file mode 160000 index 0000000000000000000000000000000000000000..5736952b4b0c8aab4ae56b622ea9a53ae718b60a --- /dev/null +++ b/3rdparty/qtquickdesigner-components @@ -0,0 +1 @@ +Subproject commit 5736952b4b0c8aab4ae56b622ea9a53ae718b60a diff --git a/3rdparty/zxing-cpp b/3rdparty/zxing-cpp new file mode 160000 index 0000000000000000000000000000000000000000..c97fc94e0e129feb0c3596371f86d725540322d7 --- /dev/null +++ b/3rdparty/zxing-cpp @@ -0,0 +1 @@ +Subproject commit c97fc94e0e129feb0c3596371f86d725540322d7 diff --git a/CMakeLists.txt b/CMakeLists.txt index 7978a7ea1b01cad6c410229a349a3baf30f360eb..9c2f4ad05e4d7c34fd07cdc1f411032e2c9bfe77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ find_package( find_package( Qt6 - COMPONENTS Core Widgets Quick Gui Qml + COMPONENTS Core Widgets Quick Gui Qml Multimedia MultimediaWidgets REQUIRED ) @@ -28,6 +28,9 @@ if(QT_VERSION VERSION_LESS QT_MINIMUM_VERSION) message(FATAL_ERROR "Minimum supported Qt version: ${QT_MINIMUM_VERSION}") endif() + +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/zxing-cpp) + qt_add_executable(${PROJECT_NAME} src/importdummy.qml src/main.cpp @@ -35,14 +38,16 @@ qt_add_executable(${PROJECT_NAME} src/serviceConnector.cpp src/serviceConnector.h src/projectManager.cpp src/projectManager.h src/dsConnector.cpp src/dsConnector.h + 3rdparty/zxing-cpp/example/ZXingQtReader.h ui/main.qml ui/resources.qrc ) target_link_libraries(${PROJECT_NAME} PRIVATE - Qt6::Core Qt6::Widgets - Qt6::Quick Qt6::Gui - Qt6::Qml Qt6::GuiPrivate + Qt::Core Qt::Widgets + Qt::Quick Qt::Gui + Qt::Qml Qt::GuiPrivate Qt::Multimedia Qt::MultimediaWidgets + ZXing::ZXing ) set_property(TARGET ${PROJECT_NAME} @@ -69,11 +74,13 @@ set_property(TARGET ${PROJECT_NAME} PROPERTY QT_ANDROID_TARGET_SDK_VERSION 34) qt6_import_qml_plugins(${PROJECT_NAME}) execute_process(COMMAND git describe --always --tags OUTPUT_VARIABLE GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) -execute_process(COMMAND git -C ${CMAKE_SOURCE_DIR}/qtquickdesigner-components describe --always --tags OUTPUT_VARIABLE QT_QUICK_COMPONENTS_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) +execute_process(COMMAND git -C ${CMAKE_SOURCE_DIR}/3rdparty/qtquickdesigner-components describe --always --tags OUTPUT_VARIABLE QT_QUICK_COMPONENTS_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) +execute_process(COMMAND git -C ${CMAKE_SOURCE_DIR}/3rdparty/zxing-cpp describe --always --tags OUTPUT_VARIABLE ZXING-CPP OUTPUT_STRIP_TRAILING_WHITESPACE) add_definitions( -DGIT_VERSION="${GIT_VERSION}" ) add_definitions( -DQT_QUICK_COMPONENTS_VERSION="${QT_QUICK_COMPONENTS_VERSION}" ) -message(STATUS "GIT_VERSION: ${GIT_VERSION}") -message(STATUS "QT_QUICK_COMPONENTS_VERSION: ${QT_QUICK_COMPONENTS_VERSION}") +message(STATUS "PROJECT VERSION: ${GIT_VERSION}") message(STATUS "QT_VERSION: ${QT_VERSION}") +message(STATUS "QT_QUICK_COMPONENTS_VERSION: ${QT_QUICK_COMPONENTS_VERSION}") +message(STATUS "ZXING-CPP: ${ZXING-CPP}") diff --git a/README.md b/README.md index 10c34f863b6553edd5cd9385b8adfa59133db690..171d62e41ff9dfec9c3d35150d7d38f2c5ef0ab9 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,9 @@ Qt Design Viewer works with minimum Android 33. * android: Files needed for Android build system * src: Backend source files * ui: UI source files -* qtquickdesigner-components: Required 3rd party QML components +* 3rdparty: Required 3rd party libraries + * qtquickdesigner-components: QML components + * zxing-cpp: QR code decoding/encoding ## Building @@ -36,7 +38,7 @@ Build instructions are provided for Linux and macOS hosts. Windows should work a First build and install QtQuickDesigner Components for Android: ```bash -cd qtquickdesigner-components +pushd 3rdparty/qtquickdesigner-components cmake \ -S . \ -B build \ @@ -50,7 +52,7 @@ cmake \ cmake --build build cmake --install build -cd .. +popd ``` Then build the Qt Design Viewer: @@ -71,3 +73,13 @@ cmake --build build ## Usage Upload the final APK file to your Android device and run. Then follow the instructions within the app. + +## 3rd Party Libraries/Components + +* [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp.git) + Apache License 2.0 + ZXing C++ port for QR code decoding. + +* [QtQuickDesigner Components](https://codereview.qt-project.org/gitweb?p=qt-labs%2Fqtquickdesigner-components.git;a=summary) + GNU GENERAL PUBLIC LICENSE + QtQuickDesigner Components is a collection of QML components for Qt Quick Designer. diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 991eba4fbe321927ab6067e0c9d2a49695e5be29..874d2a0c03b874f89d2885778f608a221ee90354 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,6 +1,6 @@ <?xml version="1.0"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.qt.qtdesignviewer" - android:installLocation="auto" android:versionCode="21" android:versionName="1.2"> + android:installLocation="auto" android:versionCode="22" android:versionName="1.2"> <!-- %%INSERT_PERMISSIONS --> <!-- %%INSERT_FEATURES --> <supports-screens android:anyDensity="true" android:largeScreens="true" @@ -9,6 +9,7 @@ android:extractNativeLibs="true" android:hardwareAccelerated="true" android:label="Qt UI Viewer" android:requestLegacyExternalStorage="true" android:allowNativeHeapPointerTagging="false" android:icon="@mipmap/app_icon"> + <profileable android:shell="true" android:enabled="true" /> <activity android:name="org.qtproject.qt.android.bindings.QtActivity" android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:label="Qt UI Viewer" android:launchMode="singleTask" diff --git a/cicd/gitlab-ci.yml b/cicd/gitlab-ci.yml index 276bcefd5718a81582ff856626f962aea2d11ea5..4e583bfdfb111790deac64cf5c00a3d46c6b3b98 100644 --- a/cicd/gitlab-ci.yml +++ b/cicd/gitlab-ci.yml @@ -1,9 +1,10 @@ variables: QDS_CI_QT_VERSION: - value: "652" + value: "661" options: - "643" - "652" + - "661" description: "Qt version for build" QDS_CI_ARTIFACTS_PATH: "${CI_PROJECT_DIR}/artifacts" DEBIAN_FRONTEND: non-interactive diff --git a/cicd/stages/build.yml b/cicd/stages/build.yml index 8ae57968560a8827310e12afd15c5a603793ffd5..5ea8a310ec6be6216207b3bd26330409acf3fc7b 100644 --- a/cicd/stages/build.yml +++ b/cicd/stages/build.yml @@ -20,7 +20,6 @@ build-android-multiarch: QDS_BUILD_PATH: "${CI_PROJECT_DIR}/outdir/build" QDS_CI_JOB_ARTIFACTS_PATH: ${QDS_CI_ARTIFACTS_PATH}/${QDS_CI_JOB_TARGET_PLATFORM}/${QDS_CI_JOB_TARGET_ARCH} QDS_CI_JOB_TARGET_PLATFORM: "android" - QDS_CI_QT_VERSION: "652" image: "git.qt.io:4567/design-studio/maintenance/docker-images/${QDS_CI_JOB_TARGET_PLATFORM}:${QDS_CI_QT_VERSION}-${QDS_CI_JOB_TARGET_PLATFORM}-${QDS_CI_JOB_TARGET_ARCH}" artifacts: name: design-viewer-${CI_JOB_ID}-qt${QDS_CI_QT_VERSION}-${QDS_CI_JOB_TARGET_PLATFORM}-${QDS_CI_JOB_TARGET_ARCH} @@ -31,9 +30,9 @@ build-android-multiarch: - if: $QDS_CI_QT_VERSION_ANDROID != "none" script: - mkdir -p ${QDS_CI_JOB_ARTIFACTS_PATH} - - cd qtquickdesigner-components - ls -l /opt/openssl/ssl_3/ - ls -l ${QDS_CI_JOB_OPENSSL_PATH} + - pushd 3rdparty/qtquickdesigner-components - | cmake \ -S . \ @@ -47,7 +46,7 @@ build-android-multiarch: -DANDROID_OPENSSL_PATH=${QDS_CI_JOB_OPENSSL_PATH} - cmake --build . - cmake --install . - - cd .. + - popd - | cmake \ -S . \ diff --git a/qtquickdesigner-components b/qtquickdesigner-components deleted file mode 160000 index 55dfd50975935efbe67ea8cdb77ac01e4640db88..0000000000000000000000000000000000000000 --- a/qtquickdesigner-components +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 55dfd50975935efbe67ea8cdb77ac01e4640db88 diff --git a/src/backend.cpp b/src/backend.cpp index a94691d8566e3c094f9bdc31fb09b6dfd4bab826..04315c03da66a60b25e983ed0011f50a5dd2985a 100644 --- a/src/backend.cpp +++ b/src/backend.cpp @@ -26,13 +26,19 @@ #include "backend.h" #include "qapplication.h" +#include <QCameraDevice> #include <QDesktopServices> #include <QFileInfo> #include <QJsonArray> #include <QJsonObject> - +#include <QMediaDevices> #include <QSettings> #include <QSslSocket> +#include <QVideoSink> + +#include "../3rdparty/zxing-cpp/example/ZXingQtReader.h" + +using namespace ZXingQt; Backend::Backend(QObject *parent) : QObject(parent) @@ -104,6 +110,50 @@ void Backend::updateInBackground(const bool &enabled) } } +void Backend::scanQrCode() +{ + 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; + if (i++ < 30) { + return; + } + + i = 0; + + auto results = ReadBarcodes(frame.toImage()); + for (auto &result : results) { + qDebug() << "Text: " << result.text(); + qDebug() << "Format: " << result.format(); + qDebug() << "Content:" << result.contentType(); + parseDesignViewerUrl(result.text()); + qobject_cast<CustomVideoWidget *>(m_captureSession->videoOutput())->close(); + } + }); + + 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->show(); + m_captureSession->camera()->start(); +} + void Backend::cacheDemoProjects(const bool &enabled) { QSettings().setValue("system/cacheDemoProjects", enabled); diff --git a/src/backend.h b/src/backend.h index 124c9490073c1d5ad84877948c0e3fca2bedd6a1..a061edb48278b9fee1246c9d0f0a28ed8604c5b0 100644 --- a/src/backend.h +++ b/src/backend.h @@ -26,12 +26,35 @@ #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(); +}; + class Backend : public QObject { Q_OBJECT @@ -64,6 +87,7 @@ private: QThread m_dsConnectorThread; QTimer m_backgroundTimer; + QSharedPointer<QMediaCaptureSession> m_captureSession; // member functions void updateUserProjectList(); @@ -84,6 +108,8 @@ signals: void urlUpdated(QString); public slots: + void scanQrCode(); + void runOnlineProject(const QString &url); void runUserProject(const QString &projectName); void runDemoProject(const QString &projectName); diff --git a/src/main.cpp b/src/main.cpp index 24974a06aeafbd3f0cec030801f785659fceaf6d..a4cc5408f20cb46cc3569bef758d62b11f779c79 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,8 +24,10 @@ ****************************************************************************/ #include <android/log.h> + #include <QApplication> #include <QMessageBox> +#include <QPermission> #include <QQmlContext> #include "backend.h" @@ -83,6 +85,25 @@ int main(int argc, char *argv[]) QQuickView view; backend = new Backend(); + // request permissions + QCameraPermission permission; + switch (app.checkPermission(permission)) { + case Qt::PermissionStatus::Granted: + break; + case Qt::PermissionStatus::Undetermined: + case Qt::PermissionStatus::Denied: + app.requestPermission(permission, [](const QPermission &permission) { + if (permission.status() == Qt::PermissionStatus::Denied) { + QMessageBox msgBox{QMessageBox::Critical, + "Critical:", + "Camera permission denied", + QMessageBox::Ok}; + msgBox.exec(); + } + }); + break; + } + view.engine()->rootContext()->setContextProperty("backend", backend); view.setSource(QUrl(QStringLiteral("qrc:/main.qml"))); view.setResizeMode(QQuickView::SizeRootObjectToView); diff --git a/src/projectManager.cpp b/src/projectManager.cpp index 1150c23243d2ecb02e97770fb6f86c7d7c542e8b..1b3867cd5bf8e735bb0d3ecf10561ce256762ce8 100644 --- a/src/projectManager.cpp +++ b/src/projectManager.cpp @@ -40,7 +40,11 @@ #include <QStandardPaths> #include <QTemporaryDir> #include <QTemporaryFile> +#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) +#include <QtCore/private/qzipreader_p.h> +#else #include <QtGui/private/qzipreader_p.h> +#endif ProjectManager::ProjectManager(QObject *parent) : QObject(parent) diff --git a/ui/HomePage.qml b/ui/HomePage.qml index 4ed0c0264c06913eb29206fa48367886d0433700..add017c5e392360228402655c4af2b167d92bcbd 100644 --- a/ui/HomePage.qml +++ b/ui/HomePage.qml @@ -46,7 +46,21 @@ Item { wrapMode: Text.WordWrap Layout.fillWidth: true } + Button { + id: scanQrCode + text: qsTr("Scan QR Code") + onClicked: backend.scanQrCode() + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } + Item { + id: item4 + Layout.preferredWidth: 10 + Layout.preferredHeight: 10 + Layout.fillWidth: true + Layout.fillHeight: true + } ColumnLayout { id: column2 Layout.fillWidth: true