diff --git a/CMakeLists.txt b/CMakeLists.txt index 568ebc51934e9497b6b5d16204f0b2d84a0acf8a..710cfb228e648c25f604c2c053b8b8c08ad3be74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,9 +29,9 @@ if(QT_VERSION VERSION_LESS QT_MINIMUM_VERSION) endif() qt_add_executable(${PROJECT_NAME} - design-viewer/importdummy_wasm.qml - design-viewer/src/main.cpp - design-viewer/src/dv_wasm.cpp design-viewer/src/dv_wasm.h + resources/importdummy_wasm.qml + src/main.cpp + src/dv_wasm.cpp src/dv_wasm.h ) target_link_libraries(${PROJECT_NAME} PRIVATE diff --git a/src/dv_android.cpp b/src/dv_android.cpp deleted file mode 100644 index 76a86795d15d6ddcae1385af42145a35756e39a0..0000000000000000000000000000000000000000 --- a/src/dv_android.cpp +++ /dev/null @@ -1,201 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2023 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 "dv_android.h" - -#if !defined(Q_OS_WASM) - -#include <QEventLoop> -#include <QFileInfo> -#include <QGuiApplication> -#include <QMessageBox> -#include <QNetworkReply> -#include <QScrollBar> -#include <QSslSocket> - -#define QSTRN QString::number - -void DvAndroid::printLog(const QString &log) -{ - qDebug() << log; - m_logs += log + "\n"; - emit logsChanged(); -} - -void DvAndroid::printWarn(const QString &warn) -{ - printLog("WARN: " + warn); -} - -void DvAndroid::printError(const QString &error, const QString &fileName, int line) -{ - printLog(QString(error) - .prepend("ERROR: ") - .append(" (") - .append(fileName) - .append(":") - .append(QSTRN(line)) - .append(")")); -} - -void DvAndroid::showWarning(const QString &message) -{ - QMessageBox msg(QMessageBox::Warning, "Warning", message, QMessageBox::Ok); - msg.exec(); -} - -QSharedPointer<QNetworkReply> DvAndroid::fetchResource(const QString &url) -{ - printLog("Fetching resource from " + url); - - QNetworkRequest request(url); - request.setRawHeader("Authorization", "test"); - QSharedPointer<QNetworkReply> reply(m_nam.get(request)); - QObject::connect(reply.data(), - &QNetworkReply::sslErrors, - this, - [&](const QList<QSslError> &errors) { - printErr(errors.first().errorString()); - }); - - QEventLoop loop; - QObject::connect(reply.data(), &QNetworkReply::finished, &loop, &QEventLoop::quit); - QObject::connect(reply.data(), - &QNetworkReply::downloadProgress, - this, - [&](qint64 bytesReceived, qint64 bytesTotal) { - float percentage = roundf((float) bytesReceived / (float) bytesTotal * 100); - printLog("Download progress " + QSTRN(percentage) + "% - " - + QSTRN(bytesReceived) + "/" + QSTRN(bytesTotal)); - }); - loop.exec(); - - if (reply->error() != QNetworkReply::NoError) { - printErr(reply->errorString()); - } else { - printLog("Resource fetched successfully"); - } - - return reply; -} - -void DvAndroid::printSysInfo() -{ - const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry(); - - printLog("Qt Design Viewer"); - printLog("System information:"); - printLog("-- Qt version: " + QString(QT_VERSION_STR)); - printLog("-- OpenSSL support: " + QVariant(QSslSocket::supportsSsl()).toString()); - printLog("-- Screen height: " + QSTRN(screenGeometry.height())); - printLog("-- Screen width: " + QSTRN(screenGeometry.width())); -} - -bool DvAndroid::initialize() -{ - printLog("Initializing Qt Design Viewer..."); - printSysInfo(); - m_buildInfo = QCoreApplication::applicationVersion() + "\n" + "Qt " + QString(QT_VERSION_STR) - + "\n" + "OpenSSL support: " + QVariant(QSslSocket::supportsSsl()).toString(); - emit buildInfoChanged(); - printLog("Initialization complete"); - return true; -} - -void DvAndroid::downloadAndRun(const QString &url) -{ - printLog("========================="); - printLog("Fetching a new project..."); - - QString projectUrl = url; - if (projectUrl.startsWith("https://designviewer.qt.io/#")) { - projectUrl = projectUrl.split("#").at(1); - projectUrl.prepend("https://designviewer.qt.io/qmlprojects/"); - } - - auto reply = fetchResource(projectUrl); - if (reply->error() != QNetworkReply::NoError) { - printErr("Could not fetch project"); - return; - } - - if (!runProject(reply->readAll(), QFileInfo(url).baseName())) { - printErr("Could not run project"); - return; - } -} - -void DvAndroid::orientateWindow(Qt::ScreenOrientation orientation) -{ - QQuickItem *contentItem = m_quickWindow->contentItem(); - QQuickItem *childItem{contentItem->childItems().at(0)}; - const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry(); - - printLog("Adapting orientation. Initial sizing:"); - printLog("-- Screen size: " + QSTRN(screenGeometry.height()) + " x " - + QSTRN(screenGeometry.width())); - printLog("-- Quick window size: " + QSTRN(m_quickWindow->height()) + " x " - + QSTRN(m_quickWindow->width())); - printLog("-- Child size: " + QSTRN(childItem->height()) + " x " + QSTRN(childItem->width())); - printLog("-- Child pos: " + QSTRN(childItem->x()) + ", " + QSTRN(childItem->y())); - printLog("-- Child scale: " + QSTRN(childItem->scale())); - - printLog("Calculating the new size and scale..."); - - const QSizeF newContentSize = childItem->size().scaled(screenGeometry.size().toSizeF(), - Qt::AspectRatioMode::KeepAspectRatio); - - const qreal newScale = newContentSize.height() / childItem->size().height(); - const qreal newX = (childItem->width() - screenGeometry.width()) / -2.0f; - const qreal newY = (childItem->height() - screenGeometry.height()) / -2.0f; - - childItem->setScale(newScale); - childItem->setPosition(QPointF(newX, newY)); - - printLog("-- Calculated item height: " + QSTRN(newContentSize.height())); - printLog("-- Calculated item width: " + QSTRN(newContentSize.width())); - printLog("-- Calculated item scale: " + QSTRN(newScale)); - printLog("-- Calculated item pos..: " + QSTRN(newX) + "," + QSTRN(newY)); - - printLog("Final Sizing:"); - printLog("-- Child height: " + QSTRN(childItem->height())); - printLog("-- Child width: " + QSTRN(childItem->width())); - printLog("-- Child scale: " + QSTRN(childItem->scale())); - printLog("-- Child pos-x: " + QSTRN(childItem->x())); - printLog("-- Child pos-y: " + QSTRN(childItem->y())); -} - -void DvAndroid::showAppWindow() -{ - QScreen *screen = QGuiApplication::primaryScreen(); - QObject::connect(screen, &QScreen::orientationChanged, this, &DvAndroid::orientateWindow); - orientateWindow(screen->orientation()); - - printLog("Initializing and showing the QML app window"); - - m_quickWindow->show(); -} - -#endif // !defined(Q_OS_WASM) diff --git a/src/dv_android.h b/src/dv_android.h deleted file mode 100644 index 4d7be49608b82e6994010189fd2753f78963812c..0000000000000000000000000000000000000000 --- a/src/dv_android.h +++ /dev/null @@ -1,81 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2023 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. -** -****************************************************************************/ - -#ifndef DV_ANDROID_H -#define DV_ANDROID_H - -#include <QLabel> -#include <QLineEdit> -#include <QNetworkAccessManager> -#include <QPushButton> -#include <QScrollArea> -#include <QStringList> -#include <QVBoxLayout> -#include <QWidget> - -#include "dv_base.h" - -class DvAndroid : public DvBase -{ - Q_OBJECT - Q_PROPERTY(QString logs READ logs NOTIFY logsChanged) - Q_PROPERTY(QString buildInfo READ buildInfo NOTIFY buildInfoChanged) - -public: - bool initialize() override; - QString logs() const { return m_logs; } - QString buildInfo() const { return m_buildInfo; } - -private: - // UI data - QString m_logs; - QString m_buildInfo; - - // Other members - QNetworkAccessManager m_nam; - - void printLog(const QString &message) override; - void printWarn(const QString &message) override; - void printError(const QString &message, const QString &fileName, int line) override; - void showAppWindow() override; - - void showWarning(const QString &message); - void showFatalMessageAndDie(const QStringList &message); - QSharedPointer<QNetworkReply> fetchResource(const QString &url); - - void printSysInfo(); - -signals: - void logsChanged(); - void buildInfoChanged(); - -public slots: - void downloadAndRun(const QString &url); - -private slots: - void orientateWindow(Qt::ScreenOrientation orientation); -}; - -#endif // DV_ANDROID_H diff --git a/src/dv_base.cpp b/src/dv_base.cpp deleted file mode 100644 index 26f9361c6c27c7501ca8b812636a9a3d55c5eeab..0000000000000000000000000000000000000000 --- a/src/dv_base.cpp +++ /dev/null @@ -1,267 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2023 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. -** -****************************************************************************/ - -#ifndef DV_UTILS_H -#define DV_UTILS_H - -#include "dv_base.h" - -#include <QtGui/private/qzipreader_p.h> - -#include <QBuffer> -#include <QCoreApplication> -#include <QDirIterator> -#include <QRandomGenerator> -#include <QRegularExpression> -#include <QResource> -#include <QTemporaryDir> -#include <QTemporaryFile> - -QString DvBase::unpackProject(const QByteArray &project, bool extractZip) -{ -#if defined(Q_OS_WASM) - QString projectLocation = "/home/web_user/"; -#else - QTemporaryDir tempDir("qmlprojector"); - QString projectLocation = tempDir.path(); -#endif - - if (extractZip) { - QDir().mkpath(projectLocation); - QBuffer buffer; - buffer.setData(project); - buffer.open(QIODevice::ReadOnly); - QZipReader reader(&buffer); - reader.extractAll(projectLocation); - } - - printLog("Initial project location: " + projectLocation); - - QDir projectLocationDir(projectLocation); - - // maybe it was not a zip file so try it as resource binary - if (projectLocationDir.isEmpty()) { - if (extractZip) - printLog("File could not be extracted. Trying to open it as a resource file."); - - const uchar *data; - if (m_projectData.size()) { - printLog("Unregistering the previous data from QRC system. Path: " + m_projectPath - + " Size: " + QString::number(m_projectData.size()) + " bytes."); - data = reinterpret_cast<const uchar *>(m_projectData.data()); - if (!QResource::unregisterResource(data, m_projectPath)) { - printErr("Cannot unregister the previous resource data."); - } - } - - m_projectData = project; - data = reinterpret_cast<const uchar *>(m_projectData.data()); - printLog("Registering resource data. Size: " + QString::number(m_projectData.size())); - - const QString resourcePath{"/" + QString::number(QRandomGenerator::global()->generate())}; - m_projectPath = resourcePath; - - if (!QDir(resourcePath).removeRecursively()) { - printLog("Could not remove resource path: " + resourcePath); - } - - if (!QResource::registerResource(data, resourcePath)) { - printErr("Can not load the resource data."); - return ""; - } - - projectLocation = ":" + resourcePath; - } - - return projectLocation; -} - -QString DvBase::findFile(const QString &dir, const QString &filter) -{ - QDirIterator it(dir, {filter}, QDir::Files, QDirIterator::Subdirectories); - return it.next(); -} - -void DvBase::parseQmlprojectFile(const QString &fileName, - QString *mainFile, - QStringList *importPaths) -{ - /* if filename comes from a resource, then qml need qrc:/ at the mainfile and importPaths. - * But all other c++ call like QFileInfo::exists do not understand that, there we - * need to keep the only ":" character at the beginning of the string - */ - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) { - printErr("Could not open Qml Project file! " + fileName + ": " + file.errorString()); - return; - } - - const QString text = QString::fromUtf8(file.readAll()); - - const QRegularExpression mainFileRegExp("mainFile:\\s*\"(.*)\""); - const QRegularExpressionMatch mainFileMatch = mainFileRegExp.match(text); - - if (!mainFileMatch.hasMatch()) { - printErr("No main file found in " + fileName); - return; - } - - printLog("Found main file: " + mainFileMatch.captured(1)); - QString basePath = QFileInfo(fileName).path() + "/"; - - *mainFile = basePath + mainFileMatch.captured(1); - if (mainFile->startsWith(QLatin1String(":/"))) - *mainFile = "qrc:" + mainFile->mid(1); - - const QRegularExpression qt6ProjectRegExp("qt6Project:\\s*true"); - const QRegularExpressionMatch qt6ProjectMatch = qt6ProjectRegExp.match(text); - if (!qt6ProjectMatch.hasMatch()) { - printWarn("This is not a Qt6 project.\nQt5 projects might work, but they are not " - "officially supported."); - } - - const QRegularExpression importPathsRegExp("importPaths:\\s*\\[\\s*(.*)\\s*\\]"); - const QRegularExpressionMatch importPathsMatch = importPathsRegExp.match(text); - - if (importPathsMatch.hasMatch()) { - for (const QString &path : importPathsMatch.captured(1).split(",")) { - QString cleanedPath = path.trimmed(); - cleanedPath = basePath + cleanedPath.mid(1, cleanedPath.length() - 2); - if (QFileInfo::exists(cleanedPath)) { - if (cleanedPath.startsWith(QLatin1String(":/"))) - cleanedPath = "qrc:" + cleanedPath.mid(1); - importPaths->append(cleanedPath); - } - } - } -} - -bool DvBase::runProject(const QByteArray &projectData, const QString &projectName) -{ - const QString projectLocation = unpackProject(projectData); - printLog("Final project location: " + projectLocation); - - QString mainQmlFilePath; - QStringList importPaths; - - printLog("Looking for qmlproject file in " + projectLocation); - const QString qmlProjectFile = findFile(projectLocation, "*.qmlproject"); - if (!qmlProjectFile.isEmpty()) { - printLog("Found qmlproject file: " + qmlProjectFile); - parseQmlprojectFile(qmlProjectFile, &mainQmlFilePath, &importPaths); - } else { - printWarn("Not found: \"*.qmlproject\". Looking for main.qml.."); - mainQmlFilePath = findFile(projectLocation, "main.qml"); - - if (mainQmlFilePath.isEmpty()) { - printWarn("Not found: \"main.qml\". Looking for \"" + projectName + ".qml\".."); - mainQmlFilePath = findFile(projectLocation, projectName + ".qml"); - } - } - - if (mainQmlFilePath.isEmpty()) { - printErr("No \"*.qmlproject\", \"main.qml\" or \"" + projectName + ".qml\" found in \"" - + projectLocation + "\"."); - return false; - } - - printLog("Found mainQmlFile: " + mainQmlFilePath); - - QUrl mainQmlUrl = QUrl::fromUserInput(mainQmlFilePath); - QFile file(mainQmlUrl.path().prepend(":")); - if (!file.open(QIODevice::ReadOnly)) { - printErr("Could not open mainQmlfile for reading! " + file.fileName() + ": " - + file.errorString()); - return false; - } - - printLog("Looking for qtquickcontrols2File in " + projectLocation); - const QString qtquickcontrols2File = findFile(projectLocation, "qtquickcontrols2.conf"); - - if (!qtquickcontrols2File.isEmpty()) { - printLog("Found qtquickcontrols2File: " + qtquickcontrols2File); - qputenv("QT_QUICK_CONTROLS_CONF", qtquickcontrols2File.toLatin1()); - } - - printLog("Adding import paths"); - for (const QString &importPath : importPaths) { - printLog("-- Import path: " + importPath); - m_qmlEngine.addImportPath(importPath); - } - - QObject::connect(&m_qmlEngine, - &QQmlEngine::warnings, - this, - [&](const QList<QQmlError> &warnings) { - for (const auto &warning : warnings) { - printWarn(warning.toString()); - } - }); - - printLog("Loading mainQmlUrl: " + mainQmlUrl.toString()); - m_qmlComponent.loadUrl(mainQmlUrl); - - printLog("Waiting for qmlComponent to load"); - while (m_qmlComponent.isLoading()) - QCoreApplication::processEvents(); - - printLog("Checking if m_qmlComponent is ready"); - if (!m_qmlComponent.isReady()) { - printErr("m_qmlComponent is not ready. Reason: " + m_qmlComponent.errorString()); - return false; - } - printLog("Creating top level object"); - QObject *topLevel = m_qmlComponent.create(); - if (!topLevel && m_qmlComponent.isError()) { - printErr("Error while creating Qml m_qmlComponent:" + m_qmlComponent.errorString()); - return false; - } - - printLog("Setting up the quickWindow"); - m_quickWindow.reset(qobject_cast<QQuickWindow *>(topLevel)); - if (m_quickWindow) { - printLog("Running with incubator controller"); - m_qmlEngine.setIncubationController(m_quickWindow->incubationController()); - } else { - printWarn("Top level object is not a QQuickWindow. Trying QQuickView..."); - - QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel); - if (!contentItem) { - printErr("Top level object cannot be casted to QQuickItem. Aborting."); - return false; - } - - printLog("Initializing QQuickView"); - QQuickView *view = new QQuickView(&m_qmlEngine, nullptr); - m_quickWindow.reset(view); - view->setContent(mainQmlUrl, &m_qmlComponent, contentItem); - view->setResizeMode(QQuickView::SizeViewToRootObject); - m_quickWindow->setBaseSize(QSize(contentItem->width(), contentItem->height())); - } - - showAppWindow(); - return true; -} -#endif // DV_UTILS_H diff --git a/src/dv_base.h b/src/dv_base.h deleted file mode 100644 index 4d9c9c4b7e819526b51af581f874dff611a477d2..0000000000000000000000000000000000000000 --- a/src/dv_base.h +++ /dev/null @@ -1,66 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2023 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. -** -****************************************************************************/ - -#ifndef DV_BASE_H -#define DV_BASE_H - -#include <QQmlComponent> -#include <QQmlEngine> -#include <QQuickItem> -#include <QQuickView> -#include <QSize> -#include <QString> -#include <QWidget> - -#define printErr(x) printError(x, __FILE_NAME__, __LINE__) - -class DvBase : public QObject -{ - Q_OBJECT -public: - virtual bool initialize() = 0; - -protected: - QScopedPointer<QQuickWindow> m_quickWindow; - - QQmlEngine m_qmlEngine; - QQmlComponent m_qmlComponent{&m_qmlEngine}; - - virtual void printLog(const QString &message) = 0; - virtual void printWarn(const QString &message) = 0; - virtual void printError(const QString &message, const QString &fileName, int line) = 0; - virtual void showAppWindow() = 0; - - QString unpackProject(const QByteArray &project, bool extractZip = false); - QString findFile(const QString &dir, const QString &filter); - void parseQmlprojectFile(const QString &fileName, QString *mainFile, QStringList *importPaths); - bool runProject(const QByteArray &projectData, const QString &projectName); - -private: - QByteArray m_projectData; - QString m_projectPath; -}; - -#endif // DV_BASE_H diff --git a/src/dv_wasm.cpp b/src/dv_wasm.cpp index ca06b9acbe6212bd0086fc74a68dace7404a1db5..21df42e231ceccc6e36623d808ce36912333c4bf 100644 --- a/src/dv_wasm.cpp +++ b/src/dv_wasm.cpp @@ -29,7 +29,18 @@ #include <emscripten.h> #include <functional> +#include <QBuffer> +#include <QCoreApplication> +#include <QDirIterator> #include <QFile> +#include <QRandomGenerator> +#include <QRegularExpression> +#include <QResource> +#include <QTemporaryDir> +#include <QTemporaryFile> +#include <QtGui/private/qzipreader_p.h> + +#define printErr(x) printError(x, __FILE_NAME__, __LINE__) std::function<void(char *, size_t, char *)> g_setFileDataCallback; extern "C" EMSCRIPTEN_KEEPALIVE void qt_callSetFileData(char *content, @@ -85,6 +96,226 @@ void DvWasm::fetchProject(QByteArray *data, QString *fileName) [heapPointer, contentSize, projectfileName]); }); } +QString DvWasm::unpackProject(const QByteArray &project, bool extractZip) +{ + QString projectLocation = "/home/web_user/"; + + if (extractZip) { + QDir().mkpath(projectLocation); + QBuffer buffer; + buffer.setData(project); + buffer.open(QIODevice::ReadOnly); + QZipReader reader(&buffer); + reader.extractAll(projectLocation); + } + + printLog("Initial project location: " + projectLocation); + + QDir projectLocationDir(projectLocation); + + // maybe it was not a zip file so try it as resource binary + if (projectLocationDir.isEmpty()) { + if (extractZip) + printLog("File could not be extracted. Trying to open it as a resource file."); + + const uchar *data; + if (m_projectData.size()) { + printLog("Unregistering the previous data from QRC system. Path: " + m_projectPath + + " Size: " + QString::number(m_projectData.size()) + " bytes."); + data = reinterpret_cast<const uchar *>(m_projectData.data()); + if (!QResource::unregisterResource(data, m_projectPath)) { + printErr("Cannot unregister the previous resource data."); + } + } + + m_projectData = project; + data = reinterpret_cast<const uchar *>(m_projectData.data()); + printLog("Registering resource data. Size: " + QString::number(m_projectData.size())); + + const QString resourcePath{"/" + QString::number(QRandomGenerator::global()->generate())}; + m_projectPath = resourcePath; + + if (!QDir(resourcePath).removeRecursively()) { + printLog("Could not remove resource path: " + resourcePath); + } + + if (!QResource::registerResource(data, resourcePath)) { + printErr("Can not load the resource data."); + return ""; + } + + projectLocation = ":" + resourcePath; + } + + return projectLocation; +} + +QString DvWasm::findFile(const QString &dir, const QString &filter) +{ + QDirIterator it(dir, {filter}, QDir::Files, QDirIterator::Subdirectories); + return it.next(); +} + +void DvWasm::parseQmlprojectFile(const QString &fileName, + QString *mainFile, + QStringList *importPaths) +{ + /* if filename comes from a resource, then qml need qrc:/ at the mainfile and importPaths. + * But all other c++ call like QFileInfo::exists do not understand that, there we + * need to keep the only ":" character at the beginning of the string + */ + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + printErr("Could not open Qml Project file! " + fileName + ": " + file.errorString()); + return; + } + + const QString text = QString::fromUtf8(file.readAll()); + + const QRegularExpression mainFileRegExp("mainFile:\\s*\"(.*)\""); + const QRegularExpressionMatch mainFileMatch = mainFileRegExp.match(text); + + if (!mainFileMatch.hasMatch()) { + printErr("No main file found in " + fileName); + return; + } + + printLog("Found main file: " + mainFileMatch.captured(1)); + QString basePath = QFileInfo(fileName).path() + "/"; + + *mainFile = basePath + mainFileMatch.captured(1); + if (mainFile->startsWith(QLatin1String(":/"))) + *mainFile = "qrc:" + mainFile->mid(1); + + const QRegularExpression qt6ProjectRegExp("qt6Project:\\s*true"); + const QRegularExpressionMatch qt6ProjectMatch = qt6ProjectRegExp.match(text); + if (!qt6ProjectMatch.hasMatch()) { + printWarn("This is not a Qt6 project.\nQt5 projects might work, but they are not " + "officially supported."); + } + + const QRegularExpression importPathsRegExp("importPaths:\\s*\\[\\s*(.*)\\s*\\]"); + const QRegularExpressionMatch importPathsMatch = importPathsRegExp.match(text); + + if (importPathsMatch.hasMatch()) { + for (const QString &path : importPathsMatch.captured(1).split(",")) { + QString cleanedPath = path.trimmed(); + cleanedPath = basePath + cleanedPath.mid(1, cleanedPath.length() - 2); + if (QFileInfo::exists(cleanedPath)) { + if (cleanedPath.startsWith(QLatin1String(":/"))) + cleanedPath = "qrc:" + cleanedPath.mid(1); + importPaths->append(cleanedPath); + } + } + } +} + +bool DvWasm::runProject(const QByteArray &projectData, const QString &projectName) +{ + const QString projectLocation = unpackProject(projectData); + printLog("Final project location: " + projectLocation); + + QString mainQmlFilePath; + QStringList importPaths; + + printLog("Looking for qmlproject file in " + projectLocation); + const QString qmlProjectFile = findFile(projectLocation, "*.qmlproject"); + if (!qmlProjectFile.isEmpty()) { + printLog("Found qmlproject file: " + qmlProjectFile); + parseQmlprojectFile(qmlProjectFile, &mainQmlFilePath, &importPaths); + } else { + printWarn("Not found: \"*.qmlproject\". Looking for main.qml.."); + mainQmlFilePath = findFile(projectLocation, "main.qml"); + + if (mainQmlFilePath.isEmpty()) { + printWarn("Not found: \"main.qml\". Looking for \"" + projectName + ".qml\".."); + mainQmlFilePath = findFile(projectLocation, projectName + ".qml"); + } + } + + if (mainQmlFilePath.isEmpty()) { + printErr("No \"*.qmlproject\", \"main.qml\" or \"" + projectName + ".qml\" found in \"" + + projectLocation + "\"."); + return false; + } + + printLog("Found mainQmlFile: " + mainQmlFilePath); + + QUrl mainQmlUrl = QUrl::fromUserInput(mainQmlFilePath); + QFile file(mainQmlUrl.path().prepend(":")); + if (!file.open(QIODevice::ReadOnly)) { + printErr("Could not open mainQmlfile for reading! " + file.fileName() + ": " + + file.errorString()); + return false; + } + + printLog("Looking for qtquickcontrols2File in " + projectLocation); + const QString qtquickcontrols2File = findFile(projectLocation, "qtquickcontrols2.conf"); + + if (!qtquickcontrols2File.isEmpty()) { + printLog("Found qtquickcontrols2File: " + qtquickcontrols2File); + qputenv("QT_QUICK_CONTROLS_CONF", qtquickcontrols2File.toLatin1()); + } + + printLog("Adding import paths"); + for (const QString &importPath : importPaths) { + printLog("-- Import path: " + importPath); + m_qmlEngine.addImportPath(importPath); + } + + QObject::connect(&m_qmlEngine, + &QQmlEngine::warnings, + this, + [&](const QList<QQmlError> &warnings) { + for (const auto &warning : warnings) { + printWarn(warning.toString()); + } + }); + + printLog("Loading mainQmlUrl: " + mainQmlUrl.toString()); + m_qmlComponent.loadUrl(mainQmlUrl); + + printLog("Waiting for qmlComponent to load"); + while (m_qmlComponent.isLoading()) + QCoreApplication::processEvents(); + + printLog("Checking if m_qmlComponent is ready"); + if (!m_qmlComponent.isReady()) { + printErr("m_qmlComponent is not ready. Reason: " + m_qmlComponent.errorString()); + return false; + } + printLog("Creating top level object"); + QObject *topLevel = m_qmlComponent.create(); + if (!topLevel && m_qmlComponent.isError()) { + printErr("Error while creating Qml m_qmlComponent:" + m_qmlComponent.errorString()); + return false; + } + + printLog("Setting up the quickWindow"); + m_quickWindow.reset(qobject_cast<QQuickWindow *>(topLevel)); + if (m_quickWindow) { + printLog("Running with incubator controller"); + m_qmlEngine.setIncubationController(m_quickWindow->incubationController()); + } else { + printWarn("Top level object is not a QQuickWindow. Trying QQuickView..."); + + QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel); + if (!contentItem) { + printErr("Top level object cannot be casted to QQuickItem. Aborting."); + return false; + } + + printLog("Initializing QQuickView"); + QQuickView *view = new QQuickView(&m_qmlEngine, nullptr); + m_quickWindow.reset(view); + view->setContent(mainQmlUrl, &m_qmlComponent, contentItem); + view->setResizeMode(QQuickView::SizeViewToRootObject); + m_quickWindow->setBaseSize(QSize(contentItem->width(), contentItem->height())); + } + + showAppWindow(); + return true; +} void DvWasm::printLog(const QString &message) { diff --git a/src/dv_wasm.h b/src/dv_wasm.h index b1c384decfd8b8403cf6d1933aaca1fac7675fd0..6e9787f81bab87e5b9d2dbe5193bbf76aabfa274 100644 --- a/src/dv_wasm.h +++ b/src/dv_wasm.h @@ -26,22 +26,37 @@ #ifndef DV_WASM_H #define DV_WASM_H -#include "dv_base.h" +#include <QQmlComponent> +#include <QQmlEngine> +#include <QQuickItem> +#include <QQuickView> +#include <QSize> +#include <QString> -class DvWasm : public DvBase +class DvWasm : public QObject { + Q_OBJECT public: - bool initialize() override; + bool initialize(); private: - QByteArray *m_projectData; + QByteArray m_projectData; + QString m_projectPath; - void printLog(const QString &message) override; - void printWarn(const QString &message) override; - void printError(const QString &message, const QString &fileName, int line) override; - void showAppWindow() override; + QScopedPointer<QQuickWindow> m_quickWindow; + QQmlEngine m_qmlEngine; + QQmlComponent m_qmlComponent{&m_qmlEngine}; + + void printLog(const QString &message); + void printWarn(const QString &message); + void printError(const QString &message, const QString &fileName, int line); + void showAppWindow(); void fetchProject(QByteArray *data, QString *fileName); + QString unpackProject(const QByteArray &project, bool extractZip = false); + QString findFile(const QString &dir, const QString &filter); + void parseQmlprojectFile(const QString &fileName, QString *mainFile, QStringList *importPaths); + bool runProject(const QByteArray &projectData, const QString &projectName); }; #endif diff --git a/src/main.cpp b/src/main.cpp index 624ad9933a8452d3aaba0965877bbb2bcf1ba211..dfb622a20b340545d3ed053e6921199f654699c6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,7 +28,6 @@ #include <QQmlContext> #include <QSurfaceFormat> -#include "dv_android.h" #include "dv_wasm.h" int main(int argc, char *argv[]) @@ -38,27 +37,12 @@ int main(int argc, char *argv[]) QSurfaceFormat format = QSurfaceFormat::defaultFormat(); format.setVersion(3, 0); QSurfaceFormat::setDefaultFormat(format); - QScopedPointer<DvBase> dv; QCoreApplication::setApplicationVersion(QString("Built on %1 %2").arg(__DATE__, __TIME__)); -#ifdef Q_OS_WASM QGuiApplication app(argc, argv); - dv.reset(new DvWasm); -#else - QApplication app(argc, argv); - QApplication::setApplicationName(QStringLiteral("Qt Design Viewer")); - - dv.reset(new DvAndroid); - QQuickView view; - view.engine()->rootContext()->setContextProperty("backend", dv.data()); - view.setSource(QUrl(QStringLiteral("qrc:/main.qml"))); - view.show(); -#endif - - if (!dv->initialize()) - return -1; + DvWasm dv; return app.exec(); }