Skip to content
Snippets Groups Projects
Commit 5d40f2f2 authored by Burak Hançerli's avatar Burak Hançerli :headphones:
Browse files

Merge branch 'QDS-10224/build-for-android' into 'master'

QDS-10224 Beginning of a new design for Android

Closes QDS-10224

See merge request design-studio/cloud-services/design-viewer-app!2
parents 1018d957 3a0cb042
No related branches found
No related tags found
1 merge request!2QDS-10224 Beginning of a new design for Android
Showing
with 1182 additions and 717 deletions
......@@ -25,12 +25,55 @@ if(QT_VERSION VERSION_LESS QT_MINIMUM_VERSION)
message(FATAL_ERROR "Minimum supported Qt version: ${QT_MINIMUM_VERSION}")
endif()
qt_add_executable(${PROJECT_NAME} design-viewer/main.cpp
design-viewer/importdummy_wasm.qml)
qt_add_executable(${PROJECT_NAME}
design-viewer/importdummy_wasm.qml
design-viewer/src/main.cpp
design-viewer/src/dv_android.cpp design-viewer/src/dv_android.h
design-viewer/src/dv_wasm.cpp design-viewer/src/dv_wasm.h
design-viewer/src/dv_base.cpp design-viewer/src/dv_base.h
)
set_property(TARGET ${PROJECT_NAME} PROPERTY QT_WASM_INITIAL_MEMORY "50MB")
target_link_libraries(${PROJECT_NAME} PRIVATE
Qt6::Core Qt6::Widgets
Qt6::Quick Qt6::Gui
Qt6::Qml Qt6::GuiPrivate
)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Core Qt6::Widgets Qt6::Quick
Qt6::Gui Qt6::Qml Qt6::GuiPrivate)
set_property(TARGET ${PROJECT_NAME}
APPEND PROPERTY QT_WASM_INITIAL_MEMORY "50MB"
)
set_property(TARGET ${PROJECT_NAME}
APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/design-viewer/android
)
if (ANDROID)
set(ANDROID_OPENSSL_PATH ${ANDROID_SDK_ROOT}/android_openssl/ssl_3/arm64-v8a)
if(EXISTS ${ANDROID_OPENSSL_PATH})
message(STATUS "Linking OpenSSL for Android. Path: ${ANDROID_OPENSSL_PATH}")
set_property(TARGET ${PROJECT_NAME} PROPERTY QT_ANDROID_EXTRA_LIBS
${ANDROID_OPENSSL_PATH}/libcrypto_3.so
${ANDROID_OPENSSL_PATH}/libssl_3.so)
else()
message(WARNING "Cannot find OpenSSL for Android.")
endif()
endif()
# --install configuration
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.html
FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.wasm
FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.js
FILES ${CMAKE_CURRENT_BINARY_DIR}/qtloader.js
DESTINATION ${CMAKE_INSTALL_PREFIX}
)
install(
DIRECTORY ${CMAKE_SOURCE_DIR}/design-viewer/www/
DESTINATION ${CMAKE_INSTALL_PREFIX}
)
set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${CMAKE_INSTALL_PREFIX})
qt6_import_qml_plugins(${PROJECT_NAME})
# .clang-format for Qt Creator
#
# This is for clang-format >= 5.0.
#
# The configuration below follows the Qt Creator Coding Rules [1] as closely as
# possible. For documentation of the options, see [2].
#
# Use ../../tests/manual/clang-format-for-qtc/test.cpp for documenting problems
# or testing changes.
#
# In case you update this configuration please also update the qtcStyle() in src\plugins\clangformat\clangformatutils.cpp
#
# [1] https://doc-snapshots.qt.io/qtcreator-extending/coding-style.html
# [2] https://clang.llvm.org/docs/ClangFormatStyleOptions.html
#
---
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: DontAlign
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterClass: true
AfterControlStatement: Never
AfterEnum: false
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: true
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakBeforeBinaryOperators: All
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeComma
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 100
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- forever # avoids { wrapped to next line
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeCategories:
- Regex: '^<Q.*'
Priority: 200
IncludeIsMainRegex: '(Test)?$'
IndentCaseLabels: false
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertBraces: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
# Do not add QT_BEGIN_NAMESPACE/QT_END_NAMESPACE as this will indent lines in between.
MacroBlockBegin: ""
MacroBlockEnd: ""
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 4
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 88
PenaltyBreakBeforeFirstCallParameter: 300
PenaltyBreakComment: 500
PenaltyBreakFirstLessLess: 400
PenaltyBreakString: 600
PenaltyExcessCharacter: 50
PenaltyReturnTypeOnItsOwnLine: 300
PointerAlignment: Right
ReflowComments: false
SortIncludes: CaseSensitive
SortUsingDeclarations: true
SpaceAfterCStyleCast: true
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: c++11
TabWidth: 4
UseTab: Never
......@@ -3,7 +3,7 @@
package="io.qt.qtdesignviewer"
android:installLocation="auto"
android:versionCode="1"
android:versionName="1.0">
android:versionName="1.2">
<!-- %%INSERT_PERMISSIONS -->
<!-- %%INSERT_FEATURES -->
<supports-screens
......@@ -24,7 +24,8 @@
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
android:label="Qt Design Viewer"
android:launchMode="singleTop"
android:screenOrientation="unspecified">
android:screenOrientation="unspecified"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
......
/****************************************************************************
**
** Copyright (C) 2019 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 <QtGui/private/qzipreader_p.h>
#include <QGuiApplication>
#include <QDebug>
#include <QQuickView>
#include <QQmlEngine>
#include <QDirIterator>
#include <QBuffer>
#include <QRegularExpression>
#include <QQmlComponent>
#include <QQuickItem>
#include <QQuickWindow>
#include <QPointer>
#include <QResource>
#include <QFileInfoList>
#include <QTemporaryFile>
#include <QTemporaryDir>
#include <QFontDatabase>
#include <QFontInfo>
#include <QDir>
#if defined(Q_OS_ANDROID)
#include <QApplication>
#include <QMessageBox>
#endif
#ifdef Q_OS_WASM
#include <emscripten.h>
std::function<void(char *, size_t, char *)> g_setFileDataCallback;
extern "C" EMSCRIPTEN_KEEPALIVE void qt_callSetFileData(char *content, size_t contentSize,
char *fileName)
{
if (g_setFileDataCallback == nullptr)
return;
g_setFileDataCallback(content, contentSize, fileName);
g_setFileDataCallback = nullptr;
}
void fetchProject(QByteArray *data, QString *fileName)
{
// Call qt_callSetFileData to make sure the emscripten linker does not
// optimize it away, which may happen if the function is called from JavaScript
// only. Set g_setFileDataCallback to null to make it a no-op.
::g_setFileDataCallback = nullptr;
::qt_callSetFileData(nullptr, 0, nullptr);
auto setFileDataCallback = [data, fileName](char *content, size_t contentSize, char *projectFilename){
*data->setRawData(content, contentSize);
*fileName = QString::fromUtf8(projectFilename);
};
g_setFileDataCallback = setFileDataCallback;
EM_ASM_({
// Copy the file file content to the C++ heap.
// Note: this could be simplified by passing the content as an
// "array" type to ccall and then let it copy to C++ memory.
// However, this built-in solution does not handle files larger
// than ~15M (Chrome). Instead, allocate memory manually and
// pass a pointer to the C++ side (which will free() it when done).
// TODO: consider slice()ing the file to read it picewise and
// then assembling it in a QByteArray on the C++ side.
const contentSize = contentArray.byteLength;
const heapPointer = _malloc(contentSize);
const heapBytes = new Uint8Array(Module.HEAPU8.buffer, heapPointer, contentSize);
heapBytes.set(contentArray);
// Null out the first data copy to enable GC
reader = null;
contentArray = null;
// Call the C++ file data ready callback
ccall("qt_callSetFileData", null,
["number", "number", "string"], [heapPointer, contentSize, projectfileName]);
});
}
void printWarning(const QString &error)
{
QString escaped = error;
escaped.replace("'", "\'");
escaped.replace("\n", "\\n");
emscripten_run_script("alert('" + escaped.toUtf8() + "');");
}
void printError(const QString &error)
{
printWarning(error);
emscripten_run_script("location.hash = ''; location.reload();");
}
void setScreenSize(const QSize &size)
{
const QString command =
QString::fromLatin1("setScreenSize(%1, %2);").arg(size.width()).arg(size.height());
emscripten_run_script(command.toUtf8());
}
#elif defined(Q_OS_ANDROID)
std::function<int(QStringList)> showFatalMessageAndDie;
void fetchProject(QByteArray *data, QString *fileName)
{
*fileName = QCoreApplication::arguments().at(1);
QFile file(*fileName);
if (file.open(QIODevice::ReadOnly))
*data = file.readAll();
}
void printError(const QString &error)
{
qDebug() << error;
}
void setScreenSize(const QSize &size)
{
qDebug() << "Screen size: " << size.width() << "x" << size.height();
}
#else
void fetchProject(QByteArray *data, QString *fileName)
{
if (QCoreApplication::arguments().count() < 2) {
qWarning() << "Pass a package file name as argument";
return;
}
*fileName = QCoreApplication::arguments().at(1);
QFile file(*fileName);
if (file.open(QIODevice::ReadOnly))
*data = file.readAll();
}
void printError(const QString &error)
{
fprintf(stderr, "%s\n", qPrintable(error));
}
void setScreenSize(const QSize &size)
{
fprintf(stderr, "Screen size: %d x %d\n", size.width(), size.height());
}
#endif // Q_OS_WASM
QString unpackProject(const QByteArray &project, const QString &targetDir, bool skipExtract = false)
{
QDir().mkpath(targetDir);
QBuffer buffer;
buffer.setData(project);
buffer.open(QIODevice::ReadOnly);
if (!skipExtract) {
QZipReader reader(&buffer);
reader.extractAll(targetDir);
}
QDir projectLocationDir(targetDir);
// maybe it was not a zip file so try it as resource binary
if (projectLocationDir.isEmpty()) {
if (!skipExtract)
qDebug() << "File could not be extracted. Trying to open it as a resource file.";
const uchar* data = reinterpret_cast<const uchar*>(project.data());
const QString resourcePath("/qtdesignviewer");
const QFileInfo sourceInfo(resourcePath);
const QDir sourceDir(sourceInfo.dir());
if (QResource::registerResource(data, resourcePath)) {
return ":" + resourcePath;
} else {
printError("Can not load the resource data.");
}
}
return targetDir;
}
QString findFile(const QString &dir, const QString &filter)
{
QDirIterator it(dir, {filter}, QDir::NoFilter, QDirIterator::Subdirectories);
return it.next();
}
void parseQmlprojectFile(const QString &fileName, QString *mainFile, QStringList *importPaths)
{
/* if filename comes from a resource 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 at the beginning
*/
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
printError("Could not open " + 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()) {
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()) {
printWarning("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);
}
}
}
} else {
return;
}
}
int main(int argc, char *argv[])
{
qDebug().noquote() << QString("Built on %1 %2\n").arg(__DATE__, __TIME__);
#if defined(Q_OS_ANDROID)
QString mainQml;
showFatalMessageAndDie = [&argc,&argv](QStringList messages) {
QApplication *app = static_cast<QApplication*>(QApplication::instance());
const bool createdLocally = (app == nullptr);
if (createdLocally)
app = new QApplication(argc, argv);
messages.append("\nThis app is supposed to be run from Qt Design Studio.");
QMessageBox msg(QMessageBox::Critical, "Fatal error", messages.join("\n"), QMessageBox::Ok);
QObject::connect(&msg, &QMessageBox::finished, [&app](int){app->exit(-1);});
msg.show();
int retVal = app->exec();
if (createdLocally) {
delete app;
}
return retVal;
};
if (argc < 2) {
return showFatalMessageAndDie({QString("Qml project to show has not been defined.")});
}
#endif
QSurfaceFormat format = QSurfaceFormat::defaultFormat();
format.setVersion(3,0);
QSurfaceFormat::setDefaultFormat(format);
#if defined(Q_OS_ANDROID)
QApplication app(argc, argv);
QApplication::setApplicationName(QStringLiteral("Qt Design Viewer"));
#else
QGuiApplication app(argc, argv);
#endif
QString projectFileName;
QByteArray projectData;
fetchProject(&projectData, &projectFileName);
#ifdef Q_OS_WASM
QString projectLocation = "/home/web_user/";
#else // Q_OS_WASM
QTemporaryDir tempDir("qmlprojector");
QString projectLocation = tempDir.path();
#endif // Q_OS_WASM
#if defined(Q_OS_ANDROID)
const bool skipZipOpenAttempt = projectFileName.endsWith(".qmlrc");
projectLocation = unpackProject(projectData, projectLocation, skipZipOpenAttempt);
#else
projectLocation = unpackProject(projectData, projectLocation);
#endif
QString mainQmlFile;
QStringList importPaths;
const QString qmlProjectFile = findFile(projectLocation, "*.qmlproject");
if (!qmlProjectFile.isEmpty()) {
parseQmlprojectFile(qmlProjectFile, &mainQmlFile, &importPaths);
} else {
mainQmlFile = findFile(projectLocation, "main.qml");
if (mainQmlFile.isEmpty())
mainQmlFile = findFile(projectLocation, QFileInfo(projectFileName).baseName() + ".qml");
}
if (mainQmlFile.isEmpty()) {
printError("No \"*.qmlproject\", \"main.qml\" or \"" + QFileInfo(projectFileName).baseName()
+ ".qml\" found in \"" + projectFileName + "\".");
return -1;
}
QUrl mainQmlUrl = QUrl::fromUserInput(mainQmlFile);
const QString qtquickcontrols2File = findFile(projectLocation, "qtquickcontrols2.conf");
if (!qtquickcontrols2File.isEmpty())
qputenv("QT_QUICK_CONTROLS_CONF", qtquickcontrols2File.toLatin1());
QQmlEngine engine;
for (const QString &importPath : importPaths)
engine.addImportPath(importPath);
QPointer<QQmlComponent> component = new QQmlComponent(&engine);
component->loadUrl(mainQmlUrl);
while (component->isLoading())
QCoreApplication::processEvents();
if (!component->isReady()) {
printError(component->errorString());
#if defined(Q_OS_ANDROID)
showFatalMessageAndDie({"Error while loading Qml component.", component->errorString()});
#endif
return -1;
}
QObject *topLevel = component->create();
if (!topLevel && component->isError()) {
printError("Create error");
#if defined(Q_OS_ANDROID)
showFatalMessageAndDie({"Error while creating Qml component.", component->errorString()});
#endif
return -1;
}
QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(topLevel));
if (window) {
engine.setIncubationController(window->incubationController());
setScreenSize(QSize()); // Full browser size. TODO: Find out initial ApplicationWindow size
} else {
QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel);
if (contentItem) {
QQuickView* view = new QQuickView(&engine, nullptr);
window.reset(view);
view->setContent(mainQmlUrl, component, contentItem);
view->setResizeMode(QQuickView::SizeViewToRootObject);
setScreenSize(QSize(contentItem->width(), contentItem->height()));
}
}
window->show();
const int exitcode = app.exec();
delete component;
return exitcode;
}
/****************************************************************************
**
** 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>
void DvAndroid::printLog(const QString &log)
{
QDateTime now = QDateTime::currentDateTime();
QString time = now.toString("hh:mm:ss");
m_logs->setText(m_logs->text() + "\n" + time + " >> " + log);
qDebug() << log;
}
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(QString::number(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) {
printLog(errors.first().errorString());
});
QEventLoop loop;
QObject::connect(reply.data(), &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
if (reply->error() != QNetworkReply::NoError) {
printLog(reply->errorString());
} else {
printLog("Resource fetched successfully");
}
return reply;
}
void DvAndroid::setupUi()
{
m_mainWindow.setLayout(m_layout);
// setup UI layout
m_layout->addWidget(m_logo);
m_layout->addWidget(m_buildInfo);
m_layout->addWidget(m_logs);
m_layout->addWidget(m_scrollArea);
m_layout->addWidget(m_lineEdit);
m_layout->addWidget(m_button);
// show build info
m_buildInfo->setText(QCoreApplication::applicationVersion());
// configure logs area
m_logs->setWordWrap(true);
// configure scrollarea for the logs
m_scrollArea->setWidget(m_logs);
m_scrollArea->setWidgetResizable(true);
QObject::connect(m_scrollArea->verticalScrollBar(), &QScrollBar::rangeChanged, this, [&]() {
m_scrollArea->verticalScrollBar()->setSliderPosition(
m_scrollArea->verticalScrollBar()->maximum());
});
// configure line edit
m_lineEdit->setText("https://designviewer.qt.io/qmlprojects/d41d8cd98f00b204e9800998ecf8427e/"
"UntitledProject18.qmlrc");
// m_lineEdit->setText(
// "https://designviewer.qt.io/qmlprojects/17e8907b3b84b8206d45be4f551f4e25/TestTwo.qmlrc");
// m_lineEdit->setText("https://designviewer.qt.io/qmlprojects/17e8907b3b84b8206d45be4f551f4e25/"
// "UntitledProject23.qmlrc");
// configure the button
m_button->setText("Download and run project");
QObject::connect(m_button, &QPushButton::clicked, this, &DvAndroid::fetchAndRunProject);
// start the show
m_mainWindow.showMaximized();
}
void DvAndroid::printSysInfo()
{
printLog("Qt Design Viewer");
printLog("System information:");
printLog("-- Qt version: " + QString(QT_VERSION_STR));
printLog("-- OpenSSL support: " + QVariant(QSslSocket::supportsSsl()).toString());
printLog("-- Window height: " + QString::number(m_mainWindow.height()));
printLog("-- Window width: " + QString::number(m_mainWindow.width()));
}
void DvAndroid::updateLogo()
{
printLog("Fetching logo...");
auto logoReply = fetchResource("https://designviewer.qt.io/qtdesignstudioviewer-256.png");
if (logoReply->error() != QNetworkReply::NoError) {
printErr("Could not fetch logo");
return;
}
QByteArray data = logoReply->readAll();
QPixmap pixmap;
pixmap.loadFromData(data);
m_logo->setPixmap(pixmap);
m_logo->setAlignment(Qt::AlignCenter);
printLog("Logo fetched successfully");
}
bool DvAndroid::initialize()
{
printLog("Initializing Qt Design Viewer...");
setupUi();
printSysInfo();
updateLogo();
printLog("Initialization complete");
return true;
}
void DvAndroid::fetchAndRunProject()
{
printLog("=========================");
printLog("Fetching a new project...");
auto reply = fetchResource(m_lineEdit->text());
if (reply->error() != QNetworkReply::NoError) {
printErr("Could not fetch project");
return;
}
if (!runProject(reply->readAll(), QFileInfo(m_lineEdit->text()).baseName())) {
printErr("Could not run project");
return;
}
}
void DvAndroid::showAppWindow()
{
QQuickItem *contentItem{m_quickWindow->contentItem()};
QQuickItem *childItem{contentItem->childItems().at(0)};
const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
printLog("Initial sizing:");
printLog("-- Primary screen height: " + QString::number(screenGeometry.height()));
printLog("-- Primary screen width: " + QString::number(screenGeometry.width()));
printLog("-- Content item height: " + QString::number(contentItem->height()));
printLog("-- Content item width: " + QString::number(contentItem->width()));
printLog("-- Content item scale: " + QString::number(contentItem->scale()));
printLog("-- Child item height: " + QString::number(childItem->height()));
printLog("-- Child item width: " + QString::number(childItem->width()));
printLog("-- Child item scale: " + QString::number(childItem->scale()));
printLog("Calculating the new size and scale...");
const QSizeF contentSize = childItem->size();
const QSizeF newContentSize = contentSize.scaled(m_mainWindow.size().toSizeF(),
Qt::AspectRatioMode::KeepAspectRatio);
const qreal newScale = newContentSize.width() / contentSize.width();
const int leftOffset = childItem->width() - screenGeometry.width();
const int newLeftOffset = (leftOffset * (-0.5)) * newScale;
printLog("Calculated item height: " + QString::number(newContentSize.height()));
printLog("Calculated item width: " + QString::number(newContentSize.width()));
printLog("Calculated item scale: " + QString::number(newScale));
printLog("Current left offset: " + QString::number(leftOffset));
printLog("Calculated left offset: " + QString::number(newLeftOffset));
contentItem->setScale(newScale);
if (leftOffset > 0) {
printLog("Left offset is bigger than 0");
printLog("Setting new left offset: " + QString::number(newLeftOffset));
contentItem->setX(newLeftOffset);
}
for (const auto &cItem : contentItem->childItems()) {
qDebug() << cItem << cItem->objectName();
}
printLog("Initializing and showing the QML app window");
m_appWindow.reset(QWidget::createWindowContainer(m_quickWindow.data()));
m_appWindow->show();
m_appWindow->raise();
printLog("Final Sizing:");
printLog("-- Main window height: " + QString::number(m_mainWindow.height()));
printLog("-- Main window width: " + QString::number(m_mainWindow.width()));
printLog("-- Quick window height: " + QString::number(m_quickWindow->height()));
printLog("-- Quick window width: " + QString::number(m_quickWindow->width()));
printLog("-- Quick window pos-x: " + QString::number(m_quickWindow->position().x()));
printLog("-- Quick window pos-y: " + QString::number(m_quickWindow->position().y()));
printLog("-- App window height: " + QString::number(m_appWindow->height()));
printLog("-- App window width: " + QString::number(m_appWindow->width()));
printLog("-- App window pos-x: " + QString::number(m_appWindow->pos().x()));
printLog("-- App window pos-y: " + QString::number(m_appWindow->pos().y()));
printLog("-- Content item height: " + QString::number(contentItem->height()));
printLog("-- Content item width: " + QString::number(contentItem->width()));
printLog("-- Content item scale: " + QString::number(contentItem->scale()));
printLog("-- Content item pos-x: " + QString::number(contentItem->x()));
printLog("-- Content item pos-y: " + QString::number(contentItem->y()));
printLog("-- Child content item height: " + QString::number(childItem->height()));
printLog("-- Child content item width: " + QString::number(childItem->width()));
printLog("-- Child content item scale: " + QString::number(childItem->scale()));
printLog("-- Child content pos-x: " + QString::number(childItem->x()));
printLog("-- Child content pos-y: " + QString::number(childItem->y()));
}
#endif // !defined(Q_OS_WASM)
/****************************************************************************
**
** 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
{
public:
bool initialize() override;
private:
// UI components
QWidget m_mainWindow;
QSharedPointer<QWidget> m_appWindow;
QVBoxLayout *m_layout{new QVBoxLayout};
QLabel *m_logo{new QLabel};
QLabel *m_buildInfo{new QLabel};
QLabel *m_logs{new QLabel};
QScrollArea *m_scrollArea{new QScrollArea};
QLineEdit *m_lineEdit{new QLineEdit};
QPushButton *m_button{new QPushButton};
// 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 setupUi();
void printSysInfo();
void updateLogo();
private slots:
void fetchAndRunProject();
};
#endif // DV_ANDROID_H
/****************************************************************************
**
** 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 = reinterpret_cast<const uchar *>(project.data());
printLog("Registering resource data. Size: " + QString::number(project.size()));
const QString resourcePath{"/" + QString::number(QRandomGenerator::global()->generate())};
// const QString resourcePath{"/qtdesignviewer"};
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
/****************************************************************************
**
** 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
{
public:
virtual bool initialize() = 0;
protected:
QSharedPointer<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);
};
#endif // DV_BASE_H
/****************************************************************************
**
** 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_wasm.h"
#if defined(Q_OS_WASM)
#include <emscripten.h>
#include <functional>
#include <QFile>
std::function<void(char *, size_t, char *)> g_setFileDataCallback;
extern "C" EMSCRIPTEN_KEEPALIVE void qt_callSetFileData(char *content,
size_t contentSize,
char *fileName)
{
if (g_setFileDataCallback == nullptr)
return;
g_setFileDataCallback(content, contentSize, fileName);
g_setFileDataCallback = nullptr;
}
void DvWasm::fetchProject(QByteArray *data, QString *fileName)
{
// Call qt_callSetFileData to make sure the emscripten linker does not
// optimize it away, which may happen if the function is called from JavaScript
// only. Set g_setFileDataCallback to null to make it a no-op.
::g_setFileDataCallback = nullptr;
::qt_callSetFileData(nullptr, 0, nullptr);
auto setFileDataCallback =
[data, fileName](char *content, size_t contentSize, char *projectFilename) {
data->setRawData(content, contentSize);
*fileName = QString::fromUtf8(projectFilename);
};
g_setFileDataCallback = setFileDataCallback;
EM_ASM_({
// Copy the file file content to the C++ heap.
// Note: this could be simplified by passing the content as an
// "array" type to ccall and then let it copy to C++ memory.
// However, this built-in solution does not handle files larger
// than ~15M (Chrome). Instead, allocate memory manually and
// pass a pointer to the C++ side (which will free() it when done).
// TODO: consider slice()ing the file to read it picewise and
// then assembling it in a QByteArray on the C++ side.
const contentSize = contentArray.byteLength;
const heapPointer = _malloc(contentSize);
const heapBytes = new Uint8Array(Module.HEAPU8.buffer, heapPointer, contentSize);
heapBytes.set(contentArray);
// Null out the first data copy to enable GC
reader = null;
contentArray = null;
// Call the C++ file data ready callback
ccall("qt_callSetFileData",
null,
["number", "number", "string"],
[heapPointer, contentSize, projectfileName]);
});
}
void DvWasm::printLog(const QString &message)
{
fprintf(stdout, "%s\n", qPrintable(message));
}
void DvWasm::printWarn(const QString &message)
{
QString escaped = message;
escaped.replace("'", "\'");
escaped.replace("\n", "\\n");
emscripten_run_script("alert('" + escaped.toUtf8() + "');");
}
void DvWasm::printError(const QString &message, const QString &fileName, int line)
{
printWarn(message);
emscripten_run_script("location.hash = ''; location.reload();");
}
void DvWasm::showAppWindow()
{
printLog("Resizing the QML app window");
const QSize size;
const QString command
= QString::fromLatin1("setScreenSize(%1, %2);").arg(size.width()).arg(size.height());
emscripten_run_script(command.toUtf8());
printLog("Showing the QML app window");
m_quickWindow->show();
}
bool DvWasm::initialize()
{
QString projectFileName;
QByteArray projectData;
fetchProject(&projectData, &projectFileName);
if (!runProject(projectData, projectFileName)) {
printError("Failed to run project", projectFileName, 0);
return false;
}
return true;
}
#endif // Q_OS_WASM
/****************************************************************************
**
** 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_WASM_H
#define DV_WASM_H
#include "dv_base.h"
class DvWasm : public DvBase
{
public:
bool initialize() override;
private:
QByteArray *m_projectData;
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 fetchProject(QByteArray *data, QString *fileName);
};
#endif
/****************************************************************************
**
** Copyright (C) 2019 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 <QApplication>
#include <QDebug>
#include <QSurfaceFormat>
#include "dv_android.h"
#include "dv_wasm.h"
int main(int argc, char *argv[])
{
qDebug().noquote() << QString("Built on %1 %2\n").arg(__DATE__, __TIME__);
QSurfaceFormat format = QSurfaceFormat::defaultFormat();
format.setVersion(3, 0);
QSurfaceFormat::setDefaultFormat(format);
QScopedPointer<DvBase> dv;
QCoreApplication::setApplicationVersion(QString("Built on %1 %2\n").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);
#endif
if (!dv->initialize())
return -1;
return app.exec();
}
<!doctype html>
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="author" content="The Qt Company">
<meta name="description" content="The online Qt Design Viewer allows users to view and share Qml-based UI designs. The designs can be either created with the Qt Design Studio or be hand coded.">
<meta name="keywords" content="Qml, Qt Quick, Qt, Ui, Design Studio, Viewer, WebAssembly">
<meta name="viewport" content="width=device-width">
<link rel="icon" href="qtdesignstudioviewer-16.png" sizes="16x16">
<link rel="icon" href="qtdesignstudioviewer-32.png" sizes="32x32">
<link rel="icon" href="qtdesignstudioviewer-48.png" sizes="48x48">
<meta charset="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="author" content="The Qt Company" />
<meta
name="description"
content="The online Qt Design Viewer allows users to view and share Qml-based UI designs. The designs can be either created with the Qt Design Studio or be hand coded."
/>
<meta
name="keywords"
content="Qml, Qt Quick, Qt, Ui, Design Studio, Viewer, WebAssembly"
/>
<meta name="viewport" content="width=device-width" />
<link
href="resources/styles/default.css"
rel="stylesheet"
type="text/css"
/>
<link
rel="icon"
href="resources/images/qtdesignstudioviewer-16.png"
sizes="16x16"
/>
<link
rel="icon"
href="resources/images/qtdesignstudioviewer-32.png"
sizes="32x32"
/>
<link
rel="icon"
href="resources/images/qtdesignstudioviewer-48.png"
sizes="48x48"
/>
<title>Qt Design Viewer</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
<style>
@font-face {
font-family: Titillium;
src: url(Titillium-Light.otf);
font-weight: 300;
}
@font-face {
font-family: Titillium;
src: url(Titillium-Thin.otf);
font-weight: 100;
}
@font-face {
font-family: Titillium;
src: url(Titillium-ThinItalic.otf);
font-weight: 100;
font-style: italic;
}
html, body { padding: 0; margin: 0; background-color: #262525; color: #e0e0e0; font-family: Titillium; font-size: 1.1em; font-weight: 100; font-style: italic; }
a {color: #e0e0e0; }
#qtcanvas { border: 0px none; height: 0px; width: 0px; margin: auto; outline: 0px solid transparent; caret-color: transparent; cursor: default; }
#qtspinner { margin: 0; }
#title { font-size: 2em; font-weight: 300; font-style: normal; font-stretch: condensed; }
#instruction { font-style: normal; }
#subtitle { padding-top: 0.4em; padding-bottom: 1.4em; }
#dropzone { border: 0.18em dashed; text-align: center; padding: 1.2em; background-color: #353535; }
#dropzone, #projectsmenu { width: 70%; margin: auto; }
#projectsmenu { display: none; padding: 0; }
#projectsmenulist { list-style-type: none; background-color: #353535; }
#projectsmenulist, #projectsmenulist > li { float: left; padding: 0; margin: 0; }
#projectsmenulist > li { margin: 1em; }
#footer { padding-top: 1.2em; clear: both; font-size: 0.8em; text-align: center; }
#launchstatus { text-align: center; width: 50%; margin: auto; display: none; }
#appheader { text-align: center; margin: auto; padding: 5px; width: 500px;
background-color: #191818; margin-top: 10px; margin-bottom: 10px;
display: none
}
#appname { width: 400px; }
#passwordDialog { width: 50%; padding: 1.2em; margin: auto; background-color: #0059ff79; display: none }
.inline-block-child { display: inline-block; }
.alert { width: 70%; padding: 1.2em; margin: auto; background-color: #f492367a; display: none }
.closebtn { margin-left: 15px; font-weight: bold; float: right;
font-size: 22px; line-height: 20px; cursor: pointer; transition: 0.3s;
}
.closebtn:hover { color: black; }
</style>
</head>
<body onload="init()">
<figure style="overflow: visible;" id="qtspinner">
<figure style="overflow: visible" id="qtspinner">
<center style="line-height: 150%">
<img src="qtdesignstudioviewer-128.png" srcset="qtdesignstudioviewer-128.png 1x, qtdesignstudioviewer-256.png 2x, qtdesignstudioviewer-384.png 3x, qtdesignstudioviewer-512.png 4x, " width="128" height="128" style="display: block"/>
<img
src="resources/images/qtdesignstudioviewer-128.png"
srcset="
resources/images/qtdesignstudioviewer-128.png 1x,
resources/images/qtdesignstudioviewer-256.png 2x,
resources/images/qtdesignstudioviewer-384.png 3x,
resources/images/qtdesignstudioviewer-512.png 4x
"
width="128"
height="128"
style="display: block"
/>
<div id="title">Qt Design Viewer</div>
<div id="subtitle" class="subtitleclass">powered by web assembly</div>
<div id="subtitle" class="subtitleclass">Powered by WebAssembly</div>
<div id="qtstatus"></div>
<noscript>JavaScript is disabled. Please enable JavaScript to use this application.</noscript>
<noscript
>JavaScript is disabled. Please enable JavaScript to use this
application.</noscript
>
</center>
</figure>
<div id="dropzone">
<div id="instruction">drop your qmlrc file or zip file here</div>
<p>(package with a pure Qml project, containing either a .qmlproject file or a main.qml)</p>
<div id="instruction">Drop your qmlrc file or zip file here</div>
<p>
(Package with a pure Qml project, containing either a .qmlproject file
or a main.qml)
</p>
<form id="uploadform">
<label><input id="fileinput" type="file" accept=".qmlrc,.zip" style="display:none;" /><img src="uploadIcon.svg" width="32" height="32"/><br />(or load by clicking here)</label>
<label
><input
id="fileinput"
type="file"
accept=".qmlrc,.zip"
style="display: none"
/><img
src="resources/images/uploadIcon.svg"
width="32"
height="32"
/><br />(or load by clicking here)</label
>
</form>
</div>
<div id="projectsmenu">
<div id="projectsmenu" style="text-align: center">
<p>EXAMPLES</p>
<ul id="projectsmenulist">
</ul>
<ul id="projectsmenulist"></ul>
</div>
<div id="launchstatus">
<p id="launchstatustext"></p>
<div class="progress">
<div id="downloadprogress" class="progress-bar" style="width: 0%" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
<div
id="downloadprogress"
class="progress-bar"
style="width: 0%"
role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
></div>
</div>
</div>
<div id="appheader">
<!--
<img src="reload.png" style.float = 'left' class='child inline-block-child' height="30px" onclick="restart()" />
<img src="screenshot.png" class='child inline-block-child' height="30px" onClicked="" />
<div id="appname" class='child inline-block-child'></div>
<img src="scalemode1.png" style.float = 'right' class='child inline-block-child' height="30px" onClicked="" />
-->
<div id="appname" class='child inline-block-child'></div>
<div id="appname" class="child inline-block-child"></div>
</div>
<div id="passwordDialog" class="alert">
<span class="closebtn" onclick="this.parentElement.style.display='none';">Cancel</span>
<span class="closebtn" onclick="this.parentElement.style.display='none';"
>Cancel</span
>
<span class="closebtn" onclick="loadFileInHash()">Ok</span>
<div>Password for the application:
<input type="password" id="passwordInput">
<div>
Password for the application:
<input type="password" id="passwordInput" />
</div>
</div>
<canvas id="qtcanvas" oncontextmenu="event.preventDefault()" contenteditable="true"></canvas>
<canvas
id="qtcanvas"
oncontextmenu="event.preventDefault()"
contenteditable="true"
></canvas>
<div id="footer">
<div id="alertBox" class="alert">
<span class="closebtn" onclick="this.parentElement.style.display='none';">&times;</span>
<span
class="closebtn"
onclick="this.parentElement.style.display='none';"
>&times;</span
>
<div id="alertText"></div>
</div>
<p>
Note: This is a static web page. The Qml application that You load runs and remains locally in Your browser, nothing gets uploaded into the cloud.
Note: This is a static web page. The Qml application that You load runs
and remains locally in Your browser, nothing gets uploaded into the
cloud.
</p>
<p>
<a href="https://github.com/qt/qtdesignviewer">"Qt Design Viewer" sources</a>
| Made with: <a href="https://doc.qt.io/qt-6/wasm.html">Qt for WebAssembly</a>
| Powered by: <a href="https://webassembly.org/">WebAssembly</a> & <a href="https://emscripten.org/">emscripten</a>
<a href="https://github.com/qt/qtdesignviewer"
>"Qt Design Viewer" sources</a
>
| Made with:
<a href="https://doc.qt.io/qt-6/wasm.html">Qt for WebAssembly</a> |
Powered by: <a href="https://webassembly.org/">WebAssembly</a> &
<a href="https://emscripten.org/">emscripten</a>
</p>
<p id="versioninfo_main">Version: 1.0.0</p>
<div>
<script type='text/javascript'>
var contentArray;
var projectfileName;
var reader;
var spinner = document.querySelector('#qtspinner');
var canvas = document.querySelector('#qtcanvas');
var uploadform = document.querySelector('#uploadform');
var fileinput = document.querySelector('#fileinput');
var dropzone = document.querySelector('#dropzone');
var projectsmenu = document.querySelector('#projectsmenu');
var footer = document.querySelector('#footer');
var launchstatus = document.querySelector('#launchstatus');
var launchstatustext = document.querySelector('#launchstatustext');
var downloadProgress = document.querySelector('#downloadprogress');
var appheader = document.querySelector('#appheader');
var appname = document.querySelector('#appname');
var alertBox = document.querySelector('#alertBox');
var passwordDialog = document.querySelector('#passwordDialog');
var alertText = document.querySelector('#alertText');
var passwordInput = document.querySelector('#passwordInput')
var qtLoader;
var versionInfo = undefined;
function showAlert(text) {
alertText.innerHTML=text
alertBox.style.display='block';
}
function hideMainPage() {
uploadform.style.display = dropzone.style.display = projectsmenu.style.display = footer.style.display = 'none';
}
function showDownloader() {
hideMainPage();
launchstatustext.innerHTML = "Downloading application";
}
function baseName(str)
{
var base = new String(str).substring(str.lastIndexOf('/') + 1);
if(base.lastIndexOf(".") != -1)
base = base.substring(0, base.lastIndexOf("."));
return base;
}
function loadFileInHash() {
var filename = location.hash.split('#')[1]
appname.innerHTML = baseName(filename);
launchstatus.style.display = 'block'
loadFromServer(filename, passwordInput.value);
}
window.addEventListener('hashchange', function() {
if (location.hash == "") {
location.reload(); // E.g. when browsing back
return;
}
loadFileInHash();
}, false);
function handleFileSelection(event) {
reader = new FileReader();
var file = fileinput.files[0];
reader.onload = function() {
contentArray = new Uint8Array(reader.result);
projectfileName = file.name;
hideMainPage();
loadProjector();
};
reader.readAsArrayBuffer(file);
}
function loadFromServer(fileName, password) {
var request = new XMLHttpRequest();
request.responseType = "arraybuffer";
request.onload = function () {
hidePasswordDialog()
if (this.status == 404) {
alert(this.status + " " + this.statusText);
location.hash = "";
return;
}
if (this.status == 403) {
showPasswordDialog()
return;
}
contentArray = new Uint8Array(request.response);
projectfileName = fileName;
loadProjector();
}
request.onprogress = (event) => {
showDownloader();
var percentage = 100*event.loaded/event.total
console.log(`Downloaded ${event.loaded} of ${event.total} bytes`, "- ", percentage, "%");
downloadProgress.style = "width: " + percentage + "%"
}
downloadProgress.style = "width: 0%"
request.open("GET", "qmlprojects/" + fileName, true);
request.setRequestHeader("authorization", password)
request.send(null);
}
function listExamples() {
var request = new XMLHttpRequest();
request.responseType = "json";
request.onload = function (oEvent) {
var json = request.response;
if (json.projects.length > 0) {
var projectsmenulist = document.querySelector('#projectsmenulist');
for (project in json.projects) {
var li = document.createElement('li');
var a = document.createElement('a');
var filename = json.projects[project].file;
a.setAttribute("href", '#' + filename);
a.innerHTML = filename;
li.appendChild(a);
projectsmenulist.appendChild(li);
}
projectsmenu.style.display = 'block';
}
}
request.open("GET", "qmlprojects/index.json", true);
request.send(null);
}
function showVersionInfo() {
var request = new XMLHttpRequest();
request.responseType = "json";
request.onload = function (oEvent) {
if (request.status == 404) {
console.log("Unable to load version info")
return;
}
var json = request.response;
versionInfo = request.response;
var version = json.version;
var buildNumber = parseInt(json.buildNumber) || 0
var versionText = "Version: " + version;
if(buildNumber !== undefined)
versionText += "." + buildNumber;
document.querySelector('#versioninfo_main').innerHTML = versionText;
}
request.open("GET", "version.json", true);
request.send(null);
}
function showPasswordDialog() {
passwordDialog.style.display = 'block';
}
function hidePasswordDialog() {
passwordDialog.style.display = 'none';
}
function verifyWebGL(){
const gl = canvas.getContext('webgl2');
if (!gl) {
if (typeof WebGL2RenderingContext !== 'undefined') {
showAlert('Your browser appears to support WebGL2 but it might be disabled. Try updating your OS and/or video card drivers.');
} else {
showAlert('Your browser has no support for WebGL2.');
}
return false;
} else {
return true;
}
}
function init() {
if(!verifyWebGL()) {
return;
}
if (location.hash == '')
listExamples();
else
loadFileInHash();
showVersionInfo();
fileinput.onchange = handleFileSelection;
dropzone.ondragover = dropzone.ondragenter = function(event) {
event.preventDefault();
};
dropzone.ondrop = function(event) {
fileinput.files = event.dataTransfer.files;
handleFileSelection(event);
event.preventDefault();
};
}
function restart() {
qtLoader.loadEmscriptenModule("qtdesignviewer");
}
function loadProjector() {
qtLoader = QtLoader({
canvasElements : [canvas],
showLoader: function(loaderStatus) {
spinner.style.display = 'block';
canvas.style.display = 'none';
if(loaderStatus === 'Downloading/Compiling')
loaderStatus = 'Starting'
launchstatustext.innerHTML = loaderStatus;
},
showError: function(errorText) {
launchstatustext.innerHTML = errorText;
spinner.style.display = 'block';
canvas.style.display = 'none';
},
showExit: function() {
launchstatustext.innerHTML = "Application exit";
if (qtLoader.exitCode !== undefined)
launchstatustext.innerHTML += " with code " + qtLoader.exitCode;
if (qtLoader.exitText !== undefined)
launchstatustext.innerHTML += " (" + qtLoader.exitText + ")";
spinner.style.display = 'block';
canvas.style.display = 'none';
},
showCanvas: function() {
spinner.style.display = 'none';
canvas.style.display = 'block';
launchstatus.style.display = 'none'
appheader.style.display = 'block'
},
});
// workaround for making sure that self.module is set up before
// running setScreenSize
self.moduleConfig.preRun = []
self.moduleConfig.preRun.push(function(module) {
self.module = module;
});
qtLoader.loadEmscriptenModule("qtdesignviewer");
}
</div>
function setScreenSize(width, height) {
if (width > 1 && height > 1) {
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
} else {
// undefined root size
canvas.style.width = canvas.style.height = "100%";
document.documentElement.style.height = document.body.style.height = "100%";
document.documentElement.style.overflow = document.body.style.overflow = "hidden";
}
qtLoader.resizeCanvasElement(canvas);
}
</script>
<script type="text/javascript" src="qtloader.js"></script>
<script src="scripts/script.js" type="text/javascript"></script>
<script src="qtloader.js" type="text/javascript"></script>
</body>
</html>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment