summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/attributehashes/AttributeHashes.cpp344
-rw-r--r--tools/attributehashes/attributehashes.pro16
-rw-r--r--tools/tools.pro10
-rw-r--r--tools/viewer/Viewer.qrc14
-rw-r--r--tools/viewer/android/AndroidManifest.xml77
-rw-r--r--tools/viewer/main.cpp384
-rw-r--r--tools/viewer/qml/StyledButton.qml52
-rw-r--r--tools/viewer/qml/StyledMenu.qml95
-rw-r--r--tools/viewer/qml/StyledMenuButton.qml71
-rw-r--r--tools/viewer/qml/StyledMenuItem.qml159
-rw-r--r--tools/viewer/qml/StyledMenuSeparator.qml47
-rw-r--r--tools/viewer/qml/main.qml594
-rw-r--r--tools/viewer/remotedeploymentreceiver.cpp202
-rw-r--r--tools/viewer/remotedeploymentreceiver.h79
-rw-r--r--tools/viewer/resources/images/3D-studio-viewer.icobin0 -> 112324 bytes
-rw-r--r--tools/viewer/resources/images/3D-studio-viewer.svg38
-rw-r--r--tools/viewer/resources/images/arrow.pngbin0 -> 523 bytes
-rw-r--r--tools/viewer/resources/images/arrow@2x.pngbin0 -> 600 bytes
-rw-r--r--tools/viewer/resources/images/check.pngbin0 -> 502 bytes
-rw-r--r--tools/viewer/resources/images/check@2x.pngbin0 -> 638 bytes
-rw-r--r--tools/viewer/resources/images/viewer.icnsbin0 -> 161003 bytes
-rw-r--r--tools/viewer/viewer.cpp416
-rw-r--r--tools/viewer/viewer.h128
-rw-r--r--tools/viewer/viewer.pro49
24 files changed, 2775 insertions, 0 deletions
diff --git a/tools/attributehashes/AttributeHashes.cpp b/tools/attributehashes/AttributeHashes.cpp
new file mode 100644
index 0000000..b72b0b0
--- /dev/null
+++ b/tools/attributehashes/AttributeHashes.cpp
@@ -0,0 +1,344 @@
+/****************************************************************************
+**
+** Copyright (C) 1993-2009 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma warning(disable : 4201) // nonstandard extension used : nameless struct/union
+
+#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later.
+#define _WIN32_WINNT \
+ 0x0501 // Change this to the appropriate value to target other versions of Windows.
+#endif
+
+//==============================================================================
+// Includes
+//==============================================================================
+#include <stdio.h>
+
+#if defined(_PCPLATFORM) || defined(_TEGRAPLATFORM)
+#include <tchar.h>
+#endif
+
+#include <string.h>
+#include <ctype.h>
+#include <vector>
+#include <fstream>
+#include <string>
+
+#include "Qt3DSConfig.h"
+#include "Qt3DSTypes.h"
+#include "Qt3DSKernelTypes.h"
+#include "Qt3DSHash.h"
+
+//==============================================================================
+// Types
+//==============================================================================
+typedef std::vector<std::string> TStringList;
+
+//==============================================================================
+// Constants
+//==============================================================================
+const char g_Text[] = "Qt3DSAttributeHashes.txt";
+const char g_Include[] = "Qt3DSAttributeHashes.h";
+const char g_Source[] = "Qt3DSAttributeHashes.cpp";
+const char g_TestString[] = "qt.io";
+
+//==============================================================================
+// Globals
+//==============================================================================
+TStringList g_StringList;
+TStringList g_EnumList;
+
+//==============================================================================
+/**
+ * Convert a string to an enum entry and write it out
+ */
+void WriteEnumEntry(FILE *inDestination, const char *inEnum, const char *inString)
+{
+ // Save out the enum
+ if (inEnum) {
+ // Right justify the value by cutting a string of spaces
+ char thePadding[] = " ";
+ int theLength = (int)strlen(inEnum);
+ theLength = theLength > 30 ? 30 : theLength;
+ thePadding[30 - theLength] = '\0';
+
+ // "ATTRIBUTE_NAME = 0x04CECA21, // name"
+ fprintf(inDestination, " %s = %s0x%08X, // %s\n", inEnum, thePadding,
+ Q3DStudio::CHash::HashAttribute(inString), inString);
+ } else
+ fprintf(inDestination, "\n");
+}
+
+//==============================================================================
+/**
+ * Write out the enum section
+ */
+void WriteEnum(FILE *inDestination)
+{
+ // Enum entry
+ fprintf(inDestination, "/// Key for the CElement attribute-value pair\n"
+ "enum EAttribute {\n");
+
+ // Write all enums
+ for (TStringList::size_type theIndex = 0; theIndex < g_EnumList.size(); ++theIndex)
+ if (!g_StringList[theIndex].empty())
+ WriteEnumEntry(inDestination, g_EnumList[theIndex].c_str(),
+ g_StringList[theIndex].c_str());
+
+ // Enum Footer
+ fprintf(inDestination, "}; // enum EAttribute\n"
+ "\n"
+ "#define AK_STRING_QT_IO \"%s\"\n",
+ g_TestString);
+}
+
+//==============================================================================
+/**
+ * Convert strings to enums
+ */
+void ProcessEnums()
+{
+ // Write all enums
+ for (TStringList::iterator theAttribute = g_StringList.begin();
+ theAttribute != g_StringList.end(); ++theAttribute) {
+ std::string theEnum = std::string("ATTRIBUTE_");
+ theEnum += *theAttribute;
+
+ // Replace all '.' with '_'
+ // Make uppercase
+ for (std::string::size_type thePosition = 0; thePosition < theEnum.length();
+ ++thePosition) {
+ if (theEnum[thePosition] == '.')
+ theEnum[thePosition] = '_';
+
+ if (theEnum[thePosition] == '\n' || theEnum[thePosition] == '\r')
+ theEnum[thePosition] = '\0';
+
+ theEnum[thePosition] = static_cast<char>(toupper(theEnum[thePosition]));
+ }
+
+ g_EnumList.push_back(theEnum);
+ }
+}
+
+//==============================================================================
+/**
+ * Write out lookup function section
+ */
+void WriteLookup(FILE *inDestination)
+{
+ // Lookup function entry
+ fprintf(inDestination, "\n"
+ "\n"
+ "/// Function providing reverse hash lookup\n"
+ "const char *GetAttributeString(const EAttribute inAttribute)\n"
+ "{\n"
+ " switch (inAttribute) {\n");
+
+ // Write all enums
+ for (TStringList::size_type theIndex = 0; theIndex < g_EnumList.size(); ++theIndex)
+ if (!g_StringList[theIndex].empty())
+ fprintf(inDestination, " case %s: return \"%s\";\n", g_EnumList[theIndex].c_str(),
+ g_StringList[theIndex].c_str());
+
+ // End function
+ fprintf(inDestination,
+ " default: {\n"
+ " static char s_UnknownHash[16];\n"
+ " sprintf(s_UnknownHash, \"(0x%%08X)\", inAttribute);\n"
+ " return s_UnknownHash;\n"
+ " }\n"
+ " }\n"
+ "}\n");
+}
+
+//==============================================================================
+/**
+ * Single lookup to check new strings
+ */
+int SingleLookup(const char *inString)
+{
+ fprintf(stdout, "String: %s\n", inString);
+ fprintf(stdout, "Hash: 0x%08X\n", Q3DStudio::CHash::HashAttribute(inString));
+ return 0;
+}
+
+//==============================================================================
+/**
+ * Main function.
+ * Open two files, read lines from source and write enum entry to destination.
+ */
+int main(int argc, char *argv[])
+{
+ // Application notice
+ fprintf(
+ stdout,
+ "AttributeHashes\nConverting a string or a file of strings into enums of hashes...\n\n");
+
+ // Checking a single string?
+ if (argc > 1)
+ return SingleLookup(argv[1]);
+
+ // Standard file conversion
+ fprintf(stdout, "Input: %s\n", g_Text);
+ fprintf(stdout, "Output: %s, %s\n\n", g_Include, g_Source);
+
+ // Open source
+ std::ifstream theText(g_Text);
+ if (!theText) {
+ fprintf(stderr, "Failed: Could not input text file, set working directory to "
+ "src\\Runtime\\ogl-runtime\\src\\runtime and try again.\n");
+ return -1;
+ }
+
+ // Add all the strings in text file
+ while (theText) {
+ std::string theLine;
+ theText >> theLine;
+ g_StringList.push_back(theLine);
+ }
+ theText.close();
+
+ // Add unit test string
+ g_StringList.push_back(g_TestString);
+
+ // Process all string to enums
+ ProcessEnums();
+
+ // Open include file
+ FILE *theInclude = NULL;
+ fopen_s(&theInclude, g_Include, "w");
+ if (!theInclude) {
+ fprintf(stderr, "Failed: Could not open output include file\n");
+ return -1;
+ }
+
+ const char* theFileHeader =
+ "/****************************************************************************\n"
+ "**\n"
+ "** Copyright (C) 1993-2009 NVIDIA Corporation.\n"
+ "** Copyright (C) 2019 The Qt Company Ltd.\n"
+ "** Contact: https://www.qt.io/licensing/\n"
+ "**\n"
+ "** This file is part of Qt 3D Studio.\n"
+ "**\n"
+ "** $QT_BEGIN_LICENSE:GPL$\n"
+ "** Commercial License Usage\n"
+ "** Licensees holding valid commercial Qt licenses may use this file in\n"
+ "** accordance with the commercial license agreement provided with the\n"
+ "** Software or, alternatively, in accordance with the terms contained in\n"
+ "** a written agreement between you and The Qt Company. For licensing terms\n"
+ "** and conditions see https://www.qt.io/terms-conditions. For further\n"
+ "** information use the contact form at https://www.qt.io/contact-us.\n"
+ "**\n"
+ "** GNU General Public License Usage\n"
+ "** Alternatively, this file may be used under the terms of the GNU\n"
+ "** General Public License version 3 or (at your option) any later version\n"
+ "** approved by the KDE Free Qt Foundation. The licenses are as published by\n"
+ "** the Free Software Foundation and appearing in the file LICENSE.GPL3\n"
+ "** included in the packaging of this file. Please review the following\n"
+ "** information to ensure the GNU General Public License requirements will\n"
+ "** be met: https://www.gnu.org/licenses/gpl-3.0.html.\n"
+ "**\n"
+ "** $QT_END_LICENSE$\n"
+ "**\n"
+ "****************************************************************************/\n\n";
+
+ // Header
+ fprintf(theInclude, theFileHeader);
+ fprintf(
+ theInclude,
+ "#pragma once\n"
+ "\n"
+ "//==============================================================================\n"
+ "// Namespace\n"
+ "//==============================================================================\n"
+ "namespace Q3DStudio {\n"
+ "\n"
+ "// !!!!! AUTOGENERATED CODE - DO NOT MODIFY MANUALLY !!!!!\n"
+ "\n"
+ "// Run the AttributeHashes project to regenerate this file from Attributehashes.txt list\n"
+ "\n");
+
+ WriteEnum(theInclude);
+
+ // Footer
+ fprintf(theInclude, "\n"
+ "/// Function providing reverse hash lookup\n"
+ "const char *GetAttributeString(const EAttribute inAttribute);\n"
+ "\n"
+ "} // namespace Q3DStudio\n"
+ "\n");
+
+ // Close include
+ fclose(theInclude);
+
+ // Open source file
+ FILE *theSource = NULL;
+ fopen_s(&theSource, g_Source, "w");
+ if (!theSource) {
+ fprintf(stderr, "Failed: Could not open output source file\n");
+ return -1;
+ }
+
+ fprintf(theSource, theFileHeader);
+
+ // Source
+ fprintf(
+ theSource,
+ "#include \"RuntimePrefix.h\"\n"
+ "\n"
+ "//==============================================================================\n"
+ "// Includes\n"
+ "//==============================================================================\n"
+ "#include \"Qt3DSAttributeHashes.h\"\n"
+ "\n"
+ "//==============================================================================\n"
+ "// Namespace\n"
+ "//==============================================================================\n"
+ "namespace Q3DStudio {\n"
+ "\n"
+ "// !!!!! AUTOGENERATED CODE - DO NOT MODIFY MANUALLY !!!!!\n"
+ "\n"
+ "// Run the AttributeHashes project to regenerate this file from Attributehashes.txt list\n"
+ "\n");
+
+ WriteLookup(theSource);
+
+ // Footer
+ fprintf(theSource, "\n"
+ "} // namespace Q3DStudio\n"
+ "\n");
+
+ // Close include
+ fclose(theSource);
+
+ // Completed
+ fprintf(stdout, "Completed converting %d string to hashes.\n", int(g_StringList.size()));
+ return 0;
+}
diff --git a/tools/attributehashes/attributehashes.pro b/tools/attributehashes/attributehashes.pro
new file mode 100644
index 0000000..986084e
--- /dev/null
+++ b/tools/attributehashes/attributehashes.pro
@@ -0,0 +1,16 @@
+TEMPLATE = app
+TARGET = AttributeHashes
+CONFIG += console
+
+win32 {
+LIBS += \
+ -lws2_32
+}
+
+include(../../commoninclude.pri)
+
+SOURCES += AttributeHashes.cpp
+
+load(qt_tool)
+
+INSTALLS -= target
diff --git a/tools/tools.pro b/tools/tools.pro
new file mode 100644
index 0000000..7c62eae
--- /dev/null
+++ b/tools/tools.pro
@@ -0,0 +1,10 @@
+TEMPLATE = subdirs
+CONFIG += ordered
+
+!boot2qt:!integrity:!qnx {
+ SUBDIRS += viewer
+}
+
+win32 {
+ SUBDIRS += attributehashes
+}
diff --git a/tools/viewer/Viewer.qrc b/tools/viewer/Viewer.qrc
new file mode 100644
index 0000000..c808dca
--- /dev/null
+++ b/tools/viewer/Viewer.qrc
@@ -0,0 +1,14 @@
+<RCC>
+ <qresource prefix="/">
+ <file alias="images/arrow.png">resources/images/arrow.png</file>
+ <file alias="images/arrow@2x.png">resources/images/arrow@2x.png</file>
+ <file alias="images/check.png">resources/images/check.png</file>
+ <file alias="images/check@2x.png">resources/images/check@2x.png</file>
+ <file>qml/main.qml</file>
+ <file>qml/StyledMenu.qml</file>
+ <file>qml/StyledMenuButton.qml</file>
+ <file>qml/StyledMenuItem.qml</file>
+ <file>qml/StyledMenuSeparator.qml</file>
+ <file>qml/StyledButton.qml</file>
+ </qresource>
+</RCC>
diff --git a/tools/viewer/android/AndroidManifest.xml b/tools/viewer/android/AndroidManifest.xml
new file mode 100644
index 0000000..3697a9a
--- /dev/null
+++ b/tools/viewer/android/AndroidManifest.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0"?>
+<manifest package="org.qtproject.qt3dviewer" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.1" android:versionCode="1" android:installLocation="auto">
+ <application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="-- %%INSERT_APP_NAME%% --" android:icon="@drawable/icon">
+ <activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="-- %%INSERT_APP_NAME%% --" android:screenOrientation="unspecified" android:launchMode="singleTop">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+
+ <!-- Application arguments -->
+ <!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
+ <!-- Application arguments -->
+
+ <meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
+ <meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
+ <meta-data android:name="android.app.repository" android:value="default"/>
+ <meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
+ <meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
+ <!-- Deploy Qt libs as part of package -->
+ <meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
+ <meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
+ <meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
+ <!-- Run with local libs -->
+ <meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
+ <meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
+ <meta-data android:name="android.app.load_local_libs" android:value="-- %%INSERT_LOCAL_LIBS%% --"/>
+ <meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
+ <meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
+ <!-- Messages maps -->
+ <meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
+ <meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
+ <meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
+ <!-- Messages maps -->
+
+ <!-- Splash screen -->
+ <!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ -->
+ <!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ -->
+ <!-- Splash screen -->
+
+ <!-- Background running -->
+ <!-- Warning: changing this value to true may cause unexpected crashes if the
+ application still try to draw after
+ "applicationStateChanged(Qt::ApplicationSuspended)"
+ signal is sent! -->
+ <meta-data android:name="android.app.background_running" android:value="false"/>
+ <!-- Background running -->
+
+ <!-- auto screen scale factor -->
+ <meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
+ <!-- auto screen scale factor -->
+
+ <!-- extract android style -->
+ <!-- available android:values :
+ * full - useful QWidget & Quick Controls 1 apps
+ * minimal - useful for Quick Controls 2 apps, it is much faster than "full"
+ * none - useful for apps that don't use any of the above Qt modules
+ -->
+ <meta-data android:name="android.app.extract_android_style" android:value="full"/>
+ <!-- extract android style -->
+ </activity>
+
+ <!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
+
+ </application>
+
+ <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="16"/>
+ <supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
+
+ <!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
+ Remove the comment if you do not require these default permissions. -->
+ <!-- %%INSERT_PERMISSIONS -->
+
+ <!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
+ Remove the comment if you do not require these default features. -->
+ <!-- %%INSERT_FEATURES -->
+
+</manifest>
diff --git a/tools/viewer/main.cpp b/tools/viewer/main.cpp
new file mode 100644
index 0000000..12abd3b
--- /dev/null
+++ b/tools/viewer/main.cpp
@@ -0,0 +1,384 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 - 2016 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** 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 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "viewer.h"
+
+#include <QtGui/qguiapplication.h>
+#include <QtGui/qtouchdevice.h>
+#include <QtGui/qscreen.h>
+#include <QtCore/qcommandlineparser.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qtimer.h>
+#include <QtGui/qopenglcontext.h>
+#include <QtStudio3D/q3dssurfaceviewer.h>
+#include <QtStudio3D/private/q3dsimagesequencegenerator_p.h>
+#include <QtQml/qqmlapplicationengine.h>
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlcontext.h>
+
+static QSurfaceFormat findIdealGLVersion()
+{
+ QSurfaceFormat fmt;
+ fmt.setProfile(QSurfaceFormat::CoreProfile);
+
+ // Advanced: Try 4.3 core (so we get compute shaders for instance)
+ fmt.setVersion(4, 3);
+ QOpenGLContext ctx;
+ ctx.setFormat(fmt);
+ if (ctx.create() && ctx.format().version() >= qMakePair(4, 3))
+ return fmt;
+
+ // Basic: Stick with 3.3 for now to keep less fortunate,
+ // Mesa-based systems happy
+ fmt.setVersion(3, 3);
+ ctx.setFormat(fmt);
+ if (ctx.create())
+ return fmt;
+
+ // We tried...
+ return QSurfaceFormat::defaultFormat();
+}
+
+static QSurfaceFormat findIdealGLESVersion()
+{
+ QSurfaceFormat fmt;
+
+ // Advanced: Try 3.1 (so we get compute shaders for instance)
+ fmt.setVersion(3, 1);
+ QOpenGLContext ctx;
+ ctx.setFormat(fmt);
+ if (ctx.create())
+ return fmt;
+
+ // Basic: OpenGL ES 3.0 is a hard requirement at the moment since we can
+ // only generate 300 es shaders, uniform buffers are mandatory.
+ fmt.setVersion(3, 0);
+ ctx.setFormat(fmt);
+ if (ctx.create())
+ return fmt;
+
+ // We tried...
+ return QSurfaceFormat::defaultFormat();
+}
+
+int main(int argc, char *argv[])
+{
+#if defined(Q_OS_MACOS)
+ QSurfaceFormat openGLFormat;
+ openGLFormat.setRenderableType(QSurfaceFormat::OpenGL);
+ openGLFormat.setProfile(QSurfaceFormat::CoreProfile);
+ openGLFormat.setMajorVersion(4);
+ openGLFormat.setMinorVersion(1);
+ openGLFormat.setStencilBufferSize(8);
+ QSurfaceFormat::setDefaultFormat(openGLFormat);
+#endif
+
+ QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+ QCoreApplication::setOrganizationName("The Qt Company");
+ QCoreApplication::setOrganizationDomain("qt.io");
+ QCoreApplication::setApplicationName("Qt 3D Viewer");
+
+ QGuiApplication a(argc, argv);
+
+#if !defined(Q_OS_MACOS)
+ QSurfaceFormat fmt;
+ if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL)
+ fmt = findIdealGLVersion();
+ else
+ fmt = findIdealGLESVersion();
+ fmt.setDepthBufferSize(24);
+ fmt.setStencilBufferSize(8);
+ QSurfaceFormat::setDefaultFormat(fmt);
+#endif
+
+ QCommandLineParser parser;
+ parser.addHelpOption();
+ parser.addPositionalArgument(
+ "file",
+ QCoreApplication::translate("main", "The presentation file to open."),
+ QCoreApplication::translate("main", "[file]"));
+
+ parser.addOption({"sequence",
+ QCoreApplication::translate("main",
+ "Generates an image sequence.\n"
+ "The file argument must be specified.\n"""
+ "Specifying any of the seq-* arguments\n"
+ "implies setting this option.")});
+ parser.addOption({"seq-start",
+ QCoreApplication::translate("main",
+ "Start time of the sequence in\n"
+ "milliseconds.\n"
+ "The default value is 0."),
+ QCoreApplication::translate("main", "ms"), QString::number(0)});
+ parser.addOption({"seq-end",
+ QCoreApplication::translate("main",
+ "End time of the sequence in\n"
+ "milliseconds.\n"
+ "The default value is 1000."),
+ QCoreApplication::translate("main", "ms"), QString::number(1000)});
+ parser.addOption({"seq-fps",
+ QCoreApplication::translate("main",
+ "Frames per second for the sequence.\n"
+ "The default value is 60."),
+ QCoreApplication::translate("main", "fps"), QString::number(60)});
+ parser.addOption({"seq-interval",
+ QCoreApplication::translate("main",
+ "Time interval between frames in\n"
+ "the sequence in milliseconds. The seq-fps argument is ignored"
+ "if this argument is used."),
+ QCoreApplication::translate("main", "ms"), QString::number(0)});
+ parser.addOption({"seq-width",
+ QCoreApplication::translate("main",
+ "Width of the image sequence.\n"
+ "The default value is 1920."),
+ QCoreApplication::translate("main", "pixels"), QString::number(1920)});
+ parser.addOption({"seq-height",
+ QCoreApplication::translate("main",
+ "Height of the image sequence.\n"
+ "The default value is 1080."),
+ QCoreApplication::translate("main", "pixels"), QString::number(1080)});
+ parser.addOption({"seq-outpath",
+ QCoreApplication::translate("main",
+ "Output path of the image sequence.\n"
+ "The default value is the current directory."),
+ QCoreApplication::translate("main", "path"), QStringLiteral(".")});
+ parser.addOption({"seq-outfile",
+ QCoreApplication::translate("main",
+ "Output filename base for the image\n"
+ "sequence.\n"
+ "The default value is derived from the presentation filename."),
+ QCoreApplication::translate("main", "file"), QStringLiteral("")});
+ parser.addOption({"connect",
+ QCoreApplication::translate("main",
+ "If this parameter is specified, the viewer\n"
+ "is started in connection mode.\n"
+ "The default value is 36000."),
+ QCoreApplication::translate("main", "port"), QString::number(36000)});
+ parser.addOption({"fullscreen",
+ QCoreApplication::translate("main",
+ "Starts the viewer in fullscreen mode.\n")});
+ parser.addOption({"maximized",
+ QCoreApplication::translate("main",
+ "Starts the viewer in maximized mode.")});
+ parser.addOption({"windowgeometry",
+ QCoreApplication::translate("main",
+ "Specifies the initial\n"
+ "window geometry using the X11-syntax.\n"
+ "For example: 1000x800+50+50"),
+ QCoreApplication::translate("main", "geometry"), QStringLiteral("")});
+ parser.addOption({"mattecolor",
+ QCoreApplication::translate("main",
+ "Specifies custom matte color\n"
+ "using #000000 syntax.\n"
+ "For example, white matte: #ffffff"),
+ QCoreApplication::translate("main", "color"), QStringLiteral("#333333")});
+ parser.addOption({"showstats",
+ QCoreApplication::translate("main",
+ "Show render statistics on screen.")});
+ parser.addOption({"scalemode",
+ QCoreApplication::translate("main",
+ "Specifies scaling mode.\n"
+ "The default value is 'center'."),
+ QCoreApplication::translate("main", "center|fit|fill"),
+ QStringLiteral("center")});
+ QCommandLineOption variantListOption({QStringLiteral("v"),
+ QStringLiteral("variants")},
+ QObject::tr("Gives list of variant groups and variants\n"
+ "to be loaded from the presentation.\n"
+ "For example VarGroupA:var1,VarGroupB:var4"),
+ QStringLiteral("variants"));
+ parser.addOption(variantListOption);
+
+ parser.process(a);
+
+ const QStringList files = parser.positionalArguments();
+ if (files.count() > 1) {
+ qWarning() << "Only one presentation file can be given.";
+ parser.showHelp(-1);
+ }
+
+ bool generateSequence = parser.isSet("sequence") || parser.isSet("seq-start")
+ || parser.isSet("seq-end") || parser.isSet("seq-fps")
+ || parser.isSet("seq-interval") || parser.isSet("seq-width")
+ || parser.isSet("seq-height") || parser.isSet("seq-outpath")
+ || parser.isSet("seq-outfile");
+
+ QStringList variantList;
+ if (parser.isSet(variantListOption)) {
+ QString variantOption = parser.value(variantListOption);
+ variantList = variantOption.split(QLatin1Char(','),
+ QString::SkipEmptyParts);
+ }
+
+#ifndef Q_OS_ANDROID
+ Q3DSImageSequenceGenerator *generator = nullptr;
+#endif
+ Viewer viewer(generateSequence);
+
+ // Figure out control size multiplier for devices using touch screens to ensure all controls
+ // have minimum usable size.
+ qreal sizeMultiplier = 1.0;
+ const auto touchDevices = QTouchDevice::devices();
+ if (touchDevices.size() > 0) {
+ // Find out the actual screen logical pixel size. Typically touch devices we care about
+ // only have a single screen, so we just check primary screen.
+ const auto screens = QGuiApplication::screens();
+ if (screens.size() > 0) {
+ QScreen *screen = screens.at(0);
+ qreal dpi = screen->physicalDotsPerInch() / screen->devicePixelRatio();
+ sizeMultiplier = dpi / 40.0; // divider chosen empirically
+ }
+ }
+
+ QQmlApplicationEngine engine;
+ // Set import paths so that standalone installation works
+ QString extraImportPath1(QStringLiteral("%1/qml"));
+ engine.addImportPath(extraImportPath1.arg(QGuiApplication::applicationDirPath()));
+#ifdef Q_OS_MACOS
+ QString extraImportPath2(QStringLiteral("%1/../../../../qml"));
+ engine.addImportPath(extraImportPath2.arg(QGuiApplication::applicationDirPath()));
+#endif
+
+ QQmlContext *ctx = engine.rootContext();
+ ctx->setContextProperty(QStringLiteral("_menuBackgroundColor"), QColor("#404244"));
+ ctx->setContextProperty(QStringLiteral("_menuSelectionColor"), QColor("#46a2da"));
+ ctx->setContextProperty(QStringLiteral("_menuBorderColor"), QColor("#727476"));
+ ctx->setContextProperty(QStringLiteral("_dialogBorderColor"), QColor("#404244"));
+ ctx->setContextProperty(QStringLiteral("_dialogBackgroundColor"), QColor("#2e2f30"));
+ ctx->setContextProperty(QStringLiteral("_dialogFieldColor"), QColor("#404244"));
+ ctx->setContextProperty(QStringLiteral("_dialogFieldBorderColor"), QColor("#262829"));
+ ctx->setContextProperty(QStringLiteral("_textColor"), QColor("#ffffff"));
+ ctx->setContextProperty(QStringLiteral("_disabledColor"), QColor("#727476"));
+ ctx->setContextProperty(QStringLiteral("_fontSize"), int(12 * sizeMultiplier));
+ ctx->setContextProperty(QStringLiteral("_controlBaseHeight"), int(24 * sizeMultiplier));
+ ctx->setContextProperty(QStringLiteral("_controlBaseWidth"), int(80 * sizeMultiplier));
+ ctx->setContextProperty(QStringLiteral("_controlPadding"), int(12 * sizeMultiplier));
+ ctx->setContextProperty(QStringLiteral("_viewerHelper"), &viewer);
+ qmlRegisterUncreatableType<Viewer>(
+ "Qt3DStudioViewer", 1, 0, "ViewerHelper",
+ QCoreApplication::translate("main",
+ "Creation of ViewerHelper not allowed from QML"));
+ engine.load(QUrl(QLatin1String("qrc:/qml/main.qml")));
+ Q_ASSERT(engine.rootObjects().size() > 0);
+ QWindow *appWindow = qobject_cast<QWindow *>(engine.rootObjects().at(0));
+ Q_ASSERT(appWindow);
+ viewer.setQmlRootObject(appWindow);
+
+ if (parser.isSet(QStringLiteral("windowgeometry"))) {
+ int width = 1280;
+ int height = 768;
+ int x = 50;
+ int y = 50;
+ QString geometryStr = parser.value(QStringLiteral("windowgeometry"));
+ const QStringList splitPlus = geometryStr.split(QLatin1Char('+'));
+ if (splitPlus.size() > 0) {
+ const QStringList splitX = splitPlus[0].split(QLatin1Char('x'));
+ if (splitX.size() >= 2) {
+ width = splitX[0].toInt();
+ height = splitX[1].toInt();
+ }
+ if (splitPlus.size() >= 3) {
+ x = splitPlus[1].toInt();
+ y = splitPlus[2].toInt();
+ }
+ }
+ appWindow->setGeometry(x, y, width, height);
+ }
+ if (parser.isSet(QStringLiteral("fullscreen")))
+ appWindow->setVisibility(QWindow::FullScreen);
+ else if (parser.isSet(QStringLiteral("maximized")))
+ appWindow->setVisibility(QWindow::Maximized);
+
+ if (parser.isSet(QStringLiteral("mattecolor"))) {
+ QColor matteColor(parser.value("mattecolor"));
+ if (matteColor != Qt::black) {
+ appWindow->setProperty("showMatteColor", QVariant::fromValue<QColor>(matteColor));
+ appWindow->setProperty("matteColor", QVariant::fromValue<QColor>(matteColor));
+ }
+ }
+ if (parser.isSet(QStringLiteral("showstats")))
+ appWindow->setProperty("showRenderStats", true);
+ if (parser.isSet(QStringLiteral("scalemode"))) {
+ QString scaleStr(parser.value("scalemode"));
+ if (scaleStr == QStringLiteral("fit"))
+ appWindow->setProperty("scaleMode", Q3DSViewerSettings::ScaleModeFit);
+ else if (scaleStr == QStringLiteral("fill"))
+ appWindow->setProperty("scaleMode", Q3DSViewerSettings::ScaleModeFill);
+ else
+ appWindow->setProperty("scaleMode", Q3DSViewerSettings::ScaleModeCenter);
+ }
+
+ viewer.setVariantList(variantList);
+
+#ifndef Q_OS_ANDROID
+ if (generateSequence) {
+ if (files.count() != 1) {
+ qWarning() << "Presentation file is required for generating an image sequence.";
+ parser.showHelp(-1);
+ }
+ generator = new Q3DSImageSequenceGenerator;
+ QObject::connect(generator, &Q3DSImageSequenceGenerator::progress,
+ &viewer, &Viewer::generatorProgress);
+ QObject::connect(generator, &Q3DSImageSequenceGenerator::finished,
+ &viewer, &Viewer::generatorFinished);
+ viewer.setGeneratorDetails(files.first());
+ generator->generateImageSequence(
+ files.first(),
+ parser.value("seq-start").toDouble(),
+ parser.value("seq-end").toDouble(),
+ parser.value("seq-fps").toDouble(),
+ parser.value("seq-interval").toDouble(),
+ parser.value("seq-width").toInt(),
+ parser.value("seq-height").toInt(),
+ parser.value("seq-outpath"),
+ parser.value("seq-outfile"));
+ } else
+#endif
+ if (!files.isEmpty()) {
+ // Load the presentation after window has been exposed to give QtQuick time to construct
+ // the application window properly
+ QTimer *exposeTimer = new QTimer(appWindow);
+ QObject::connect(exposeTimer, &QTimer::timeout, [&](){
+ if (appWindow->isExposed()) {
+ exposeTimer->stop();
+ exposeTimer->deleteLater();
+ viewer.loadFile(files.first());
+ }
+ });
+ exposeTimer->start(0);
+ } else {
+ viewer.setContentView(Viewer::ConnectView);
+ if (parser.isSet(QStringLiteral("connect")))
+ viewer.setConnectPort(parser.value(QStringLiteral("connect")).toInt());
+ viewer.connectRemote();
+ }
+
+ return a.exec();
+}
diff --git a/tools/viewer/qml/StyledButton.qml b/tools/viewer/qml/StyledButton.qml
new file mode 100644
index 0000000..400179d
--- /dev/null
+++ b/tools/viewer/qml/StyledButton.qml
@@ -0,0 +1,52 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** 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 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+
+Button {
+ id: control
+ implicitWidth: _controlBaseWidth
+ implicitHeight: _controlBaseHeight
+
+ contentItem: Text {
+ width: _controlBaseWidth
+ text: control.text
+ height: _controlBaseHeight
+ font.pixelSize: _fontSize
+ color: _textColor
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+ background: Rectangle {
+ color: control.down ? _menuSelectionColor : _dialogFieldColor
+ border.color: _dialogFieldBorderColor
+ radius: 2
+ }
+}
diff --git a/tools/viewer/qml/StyledMenu.qml b/tools/viewer/qml/StyledMenu.qml
new file mode 100644
index 0000000..6275dbc
--- /dev/null
+++ b/tools/viewer/qml/StyledMenu.qml
@@ -0,0 +1,95 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** 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 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+
+Menu {
+ id: control
+
+ property alias hovered: menuArea.containsMouse
+
+ width: contentItem.width + leftPadding + rightPadding
+ height: contentItem.height + topPadding + bottomPadding
+ padding: 1 // For background border
+ x: 0
+ y: parent.height
+ closePolicy: Popup.CloseOnPressOutsideParent | Popup.CloseOnEscape
+
+ contentItem: MouseArea {
+ id: menuArea
+ hoverEnabled: true
+ height: list.height
+ width: list.width
+ ListView {
+ id: list
+ boundsBehavior: Flickable.StopAtBounds
+ clip: true
+ model: control.contentModel
+ currentIndex: control.currentIndex
+ highlightRangeMode: ListView.ApplyRange
+ highlightMoveDuration: 0
+ Component.onCompleted: {
+ var maxItemWidth = 0;
+ var maxShortcutWidth = 0;
+ var totalHeight = 0
+ var extraWidth = 0
+ var i;
+ for (i = control.contentData.length - 1; i >= 0; --i) {
+ if (control.contentData[i].itemWidth !== undefined) {
+ maxItemWidth = Math.max(maxItemWidth, control.contentData[i].itemWidth);
+ maxShortcutWidth = Math.max(maxShortcutWidth,
+ control.contentData[i].shortcutWidth);
+ }
+ totalHeight += control.contentData[i].height
+ }
+ maxItemWidth += _controlPadding // minimum item spacer
+ for (i = control.contentData.length - 1; i >= 0; --i) {
+ if (control.contentData[i].itemSpacerWidth !== undefined) {
+ control.contentData[i].itemSpacerWidth
+ = maxItemWidth - control.contentData[i].itemWidth;
+ control.contentData[i].shortcutSpacerWidth
+ = maxShortcutWidth - control.contentData[i].shortcutWidth;
+ }
+ }
+ width = maxItemWidth + maxShortcutWidth + extraWidth
+ + control.contentData[0].leftPadding + control.contentData[0].rightPadding
+ + control.contentData[0].arrowWidth + control.contentData[0].checkMarkWidth;
+ height = totalHeight
+ }
+ }
+ }
+
+ background: Rectangle {
+ width: control.width
+ height: control.height
+ color: _menuBackgroundColor
+ border.color: _menuBorderColor
+ }
+}
diff --git a/tools/viewer/qml/StyledMenuButton.qml b/tools/viewer/qml/StyledMenuButton.qml
new file mode 100644
index 0000000..b5290c3
--- /dev/null
+++ b/tools/viewer/qml/StyledMenuButton.qml
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** 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 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+
+Button {
+ id: control
+
+ property Menu menu: null
+ property ApplicationWindow window: null
+
+ onPressed: {
+ if (menu.visible)
+ menu.close();
+ else
+ menu.open();
+ }
+
+ onHoveredChanged: {
+ if (hovered && window.menuOpen) {
+ window.closeMenus();
+ menu.open();
+ }
+ }
+
+ hoverEnabled: true
+ width: contentItem.contentWidth + leftPadding + rightPadding
+ leftPadding: _controlPadding
+ rightPadding: _controlPadding
+ height: _controlBaseHeight
+ contentItem: Text {
+ text: control.text
+ font.pixelSize: _fontSize
+ opacity: enabled ? 1.0 : 0.3
+ color: _textColor
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+ background: Rectangle {
+ opacity: enabled ? 1 : 0.3
+ color: control.down || control.hovered
+ ? _menuSelectionColor : _menuBackgroundColor
+ }
+}
diff --git a/tools/viewer/qml/StyledMenuItem.qml b/tools/viewer/qml/StyledMenuItem.qml
new file mode 100644
index 0000000..79049a9
--- /dev/null
+++ b/tools/viewer/qml/StyledMenuItem.qml
@@ -0,0 +1,159 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** 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 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+
+MenuItem {
+ id: control
+
+ property alias shortcut: shortcut.sequence
+ property string shortcutText
+ property alias itemSpacerWidth: itemSpacer.width
+ property alias shortcutSpacerWidth: shortcutSpacer.width
+ property alias itemWidth: itemLabel.width
+ property alias shortcutWidth: shortcutLabel.width
+ property bool showArrow: false
+ property int arrowWidth: arrow.width
+ property int checkMarkWidth: checkMark.width
+ property bool showCheckMark: false
+ property Menu arrowMenu: null
+
+ hoverEnabled: true
+ width: contentItem.width + leftPadding + rightPadding
+ height: contentItem.height + topPadding + bottomPadding
+ padding: 0
+ leftPadding: 0
+ rightPadding: 0
+
+ MouseArea {
+ anchors.fill: parent
+ onPressed: {
+ if (showArrow) {
+ if (!arrowMenu.visible) {
+ arrowMenuDelay.stop();
+ arrowMenu.open();
+ }
+ } else {
+ mouse.accepted = false;
+ }
+ }
+ }
+
+ onHoveredChanged: {
+ if (showArrow)
+ arrowMenuDelay.start();
+ }
+
+ Timer {
+ id: arrowMenuDelay
+ interval: 500
+ repeat: false
+ onTriggered: {
+ if (arrowMenu.visible) {
+ if (!control.hovered && !arrowMenu.hovered)
+ arrowMenu.close();
+ } else {
+ if (control.hovered)
+ arrowMenu.open();
+ }
+ }
+ }
+
+ Shortcut {
+ id: shortcut
+ context: Qt.ApplicationShortcut
+ onActivated: control.triggered()
+ }
+
+ contentItem: Row {
+ width: checkMark.width + itemLabel.width + itemSpacer.width
+ + shortcutLabel.width + shortcutSpacer.width + arrow.width
+ height: _controlBaseHeight
+ Item {
+ id: checkMark
+ width: 16
+ height: _controlBaseHeight
+ Image {
+ anchors.fill: parent
+ visible: control.showCheckMark
+ fillMode: Image.Pad
+ source: "qrc:/images/check.png"
+ }
+ }
+ Label {
+ id: itemLabel
+ text: control.text
+ font.pixelSize: _fontSize
+ horizontalAlignment: Text.AlignLeft
+ color: control.enabled ? _textColor : _disabledColor
+ verticalAlignment: Text.AlignVCenter
+ clip: true
+ width: contentWidth
+ height: _controlBaseHeight
+ }
+ Item {
+ id: itemSpacer
+ width: _controlPadding
+ height: _controlBaseHeight
+ }
+ Label {
+ id: shortcutLabel
+ text: shortcut.nativeText === "" ? control.shortcutText : shortcut.nativeText
+ font.pixelSize: _fontSize
+ horizontalAlignment: Text.AlignLeft
+ color: control.enabled ? _textColor : _disabledColor
+ verticalAlignment: Text.AlignVCenter
+ clip: true
+ width: contentWidth
+ height: _controlBaseHeight
+ }
+ Item {
+ id: shortcutSpacer
+ width: 0
+ height: _controlBaseHeight
+ }
+ Item {
+ id: arrow
+ width: 16
+ height: _controlBaseHeight
+ Image {
+ anchors.fill: parent
+ visible: control.showArrow
+ fillMode: Image.Pad
+ source: "qrc:/images/arrow.png"
+ }
+ }
+ }
+ background: Rectangle {
+ width: control.width
+ height: control.height
+ color: control.hovered ? _menuSelectionColor : _menuBackgroundColor
+ }
+}
diff --git a/tools/viewer/qml/StyledMenuSeparator.qml b/tools/viewer/qml/StyledMenuSeparator.qml
new file mode 100644
index 0000000..344d667
--- /dev/null
+++ b/tools/viewer/qml/StyledMenuSeparator.qml
@@ -0,0 +1,47 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** 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 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+
+MenuSeparator {
+ id: control
+ padding: 0
+ topPadding: 0
+ bottomPadding: 0
+ leftPadding: 0
+ rightPadding: 0
+ width: parent.width
+ height: 1
+ contentItem: Rectangle {
+ width: control.width - control.leftPadding - control.rightPadding
+ height: 1
+ color: _menuBorderColor
+ }
+}
diff --git a/tools/viewer/qml/main.qml b/tools/viewer/qml/main.qml
new file mode 100644
index 0000000..1357f39
--- /dev/null
+++ b/tools/viewer/qml/main.qml
@@ -0,0 +1,594 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** 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 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+import QtQuick.Dialogs 1.2
+import Qt3DStudioViewer 1.0
+import QtStudio3D.OpenGL 2.4
+import QtQuick.Window 2.2
+
+ApplicationWindow {
+ id: window
+ width: 1280
+ height: 768
+ visible: true
+ title: qsTr("Qt 3D Studio Viewer")
+
+ property bool menuOpen: fileMenu.visible || viewMenu.visible
+ property Item loadedContent: contentLoader ? contentLoader.item : null
+ property string error
+ property int previousVisibility
+
+ property color showMatteColor: Qt.rgba(0.2, 0.2, 0.2, 1)
+ property color hideMatteColor: Qt.rgba(0, 0, 0, 1)
+ property color matteColor: hideMatteColor
+ property bool showRenderStats: false
+ property int scaleMode: ViewerSettings.ScaleModeCenter
+
+ function closeMenus() {
+ fileMenu.close();
+ scaleMenu.close();
+ viewMenu.close();
+ }
+
+ Component.onCompleted: {
+ _viewerHelper.restoreWindowState(window);
+ previousVisibility = visibility;
+ }
+
+ onClosing: {
+ _viewerHelper.storeWindowState(window);
+ }
+
+ Timer {
+ id: infoTimer
+ repeat: false
+ interval: 5000
+
+ onTriggered: {
+ infoOverlay.visible = false;
+ }
+ }
+
+ Rectangle {
+ id: infoOverlay
+ visible: false
+ color: "black"
+ border.color: _dialogBorderColor
+ x: parent.width * 0.2
+ y: parent.height * 0.4
+ width: parent.width * 0.6
+ height: parent.height * 0.2
+ z: 20
+ Label {
+ id: infoLabel
+ anchors.fill: parent
+ color: _textColor
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ font.pixelSize: window.width / 40
+ }
+ }
+
+ Connections {
+ target: _viewerHelper
+ onShowInfoOverlay: {
+ // Show a brief info overlay
+ infoLabel.text = infoStr;
+ infoOverlay.visible = true;
+ infoTimer.restart();
+ }
+ }
+
+ MouseArea {
+ property int swipeStart: 0
+
+ anchors.fill: parent
+ z: 10
+ enabled: !ipEntry.visible
+
+ onPressed: {
+ if (window.visibility === Window.FullScreen)
+ swipeStart = mouse.y;
+ _viewerHelper.handleMousePress(mouse.x, mouse.y, mouse.button, mouse.buttons,
+ mouse.modifiers);
+ mouse.accepted = true;
+ }
+ onReleased: {
+ _viewerHelper.handleMouseRelease(mouse.x, mouse.y, mouse.button, mouse.buttons,
+ mouse.modifiers);
+ mouse.accepted = true;
+ }
+ onPositionChanged: {
+ // Swipe down to exit fullscreen mode
+ if (window.visibility === Window.FullScreen && mouse.y > swipeStart + (height / 8)) {
+ window.visibility = window.previousVisibility;
+ } else {
+ _viewerHelper.handleMouseMove(mouse.x, mouse.y, mouse.button, mouse.buttons,
+ mouse.modifiers);
+ }
+ }
+ }
+
+ DropArea {
+ anchors.fill: parent
+ onEntered: {
+ if (drag.hasUrls) {
+ var filename = _viewerHelper.convertUrlListToFilename(drag.urls);
+ if (filename === "")
+ drag.accepted = false;
+ }
+ }
+ onDropped: {
+ if (drop.hasUrls) {
+ var filename = _viewerHelper.convertUrlListToFilename(drop.urls);
+ if (filename === "")
+ drag.accepted = false;
+ else
+ _viewerHelper.loadFile(filename);
+ }
+ }
+ }
+
+ Loader {
+ id: contentLoader
+ anchors.fill: parent
+ sourceComponent: {
+ switch (_viewerHelper.contentView) {
+ case ViewerHelper.StudioView:
+ return studioContent;
+ case ViewerHelper.ConnectView:
+ return connectContent;
+ case ViewerHelper.SequenceView:
+ return sequenceContent;
+ default:
+ return emptyContent;
+ }
+ }
+ Timer {
+ id: asyncContentChanger
+ repeat: false
+ interval: 0
+ property int view: ViewerHelper.DefaultView
+ function changeView(newView) {
+ view = newView;
+ start();
+ }
+
+ onTriggered: {
+ _viewerHelper.contentView = view;
+ }
+ }
+ }
+
+ Component {
+ id: emptyContent
+ Rectangle {
+ color: "black"
+ Label {
+ anchors.fill: parent
+ text: window.error
+ color: _textColor
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ font.pixelSize: width / 80
+ }
+ }
+ }
+
+ Component {
+ id: studioContent
+ Studio3D {
+ id: studio3D
+
+ property alias hiderVisible: hider.visible
+
+ focus: true
+ ViewerSettings {
+ matteColor: window.matteColor
+ showRenderStats: window.showRenderStats
+ scaleMode: window.scaleMode
+ }
+
+ // Hider item keeps the Studio3D hidden until it starts running and we reset the
+ // animation time to the start
+ Rectangle {
+ id: hider
+ color: "black"
+ anchors.fill: parent
+ }
+
+ Timer {
+ id: revealTimer
+ repeat: false
+ interval: 0
+ onTriggered: {
+ hider.visible = false;
+ }
+ }
+
+ onRunningChanged: {
+ if (running) {
+ // Successfully opened a presentation, update the open folder
+ _viewerHelper.openFolder = presentation.source.toString();
+ // Force the animation to start from the beginning, as the first frame render
+ // can take some time as shaders are compiled on-demand
+ // Localization note: "Scene" needs to be the same as the default "Scene"
+ // element name generated in the Studio application.
+ presentation.goToTime(qsTr("Scene"), 0);
+ revealTimer.start();
+ }
+ }
+ onErrorChanged: {
+ if (error.length > 0) {
+ window.error = error;
+ asyncContentChanger.changeView(ViewerHelper.DefaultView);
+ }
+ }
+ }
+ }
+
+ Component {
+ id: connectContent
+ Rectangle {
+ color: "black"
+ Label {
+ anchors.fill: parent
+ text: _viewerHelper.connectText
+ color: _textColor
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ font.pixelSize: width / 40
+ }
+ }
+ }
+
+ Component {
+ id: sequenceContent
+ Rectangle {
+ property alias mainText: mainLabel.text
+ property alias detailsText: detailsLabel.text
+ color: "black"
+ Item {
+ anchors.fill: parent
+ Label {
+ id: mainLabel
+ color: _textColor
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignBottom
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottomMargin: _controlPadding
+ height: parent.height / 2
+ font.pixelSize: width / 40
+ text: qsTr("Image sequence generation initializing...")
+ }
+ Label {
+ id: detailsLabel
+ color: _textColor
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignTop
+ anchors.top: mainLabel.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.topMargin: _controlPadding
+ height: parent.height / 2
+ font.pixelSize: width / 50
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ id: ipEntry
+ visible: false
+ color: _dialogBackgroundColor
+ border.color: _dialogBorderColor
+ y: (parent.height - height) / 2
+ x: (parent.width - width) / 2
+ z: 100
+ width: connectionEntry.width + (2 * _controlPadding)
+ height: connectionEntry.height + (2 * _controlPadding)
+
+ onVisibleChanged: {
+ if (visible) {
+ connectText.forceActiveFocus();
+ connectText.selectAll();
+ }
+ }
+
+ Grid {
+ id: connectionEntry
+ spacing: _controlPadding
+ columns: 2
+ y: _controlPadding
+ x: _controlPadding
+ Label {
+ id: ipEntryLabel
+ width: _controlBaseWidth
+ height: _controlBaseHeight
+ text: qsTr("Enter IP port:")
+ color: _textColor
+ font.pixelSize: _fontSize
+ verticalAlignment: Text.AlignVCenter
+ padding: _controlPadding / 2
+ }
+ TextField {
+ id: connectText
+ width: _controlBaseWidth
+ height: _controlBaseHeight
+ font.pixelSize: _fontSize
+ color: _textColor
+ selectByMouse: true
+ padding: _controlPadding / 6
+ enabled: ipEntry.visible
+ text: _viewerHelper.connectPort
+ validator: IntValidator {
+ bottom: 1
+ top: 65535
+ }
+
+ onAccepted: {
+ if (ipEntry.visible) {
+ _viewerHelper.contentView = ViewerHelper.ConnectView;
+ _viewerHelper.connectPort = Number(text);
+ _viewerHelper.connectRemote();
+ ipEntry.visible = false;
+ infoOverlay.visible = false;
+ }
+ }
+
+ background: Rectangle {
+ id: textBackground
+ color: _dialogFieldColor
+ border.width: 1
+ border.color: _dialogFieldBorderColor
+ radius: 2
+ }
+ }
+ StyledButton {
+ id: connectButton
+ width: _controlBaseWidth
+ text: qsTr("Connect")
+ onClicked: {
+ _viewerHelper.contentView = ViewerHelper.ConnectView;
+ _viewerHelper.connectPort = Number(connectText.text);
+ _viewerHelper.connectRemote();
+ ipEntry.visible = false;
+ infoOverlay.visible = false;
+ }
+ }
+ StyledButton {
+ id: cancelButton
+ width: _controlBaseWidth
+ text: qsTr("Cancel")
+ onClicked: {
+ ipEntry.visible = false;
+ }
+ }
+ }
+ }
+
+ Component {
+ id: fileDialogComponent
+ FileDialog {
+ id: fileDialog
+ title: qsTr("Choose Presentation or Project")
+ folder: _viewerHelper.openFolder
+ nameFilters: [qsTr("All supported formats (*.uip *.uia)"),
+ qsTr("Studio UI Presentation (*.uip)"),
+ qsTr("Application file (*.uia)")]
+ onAccepted: {
+ _viewerHelper.contentView = ViewerHelper.StudioView;
+ contentLoader.item.presentation.setSource(fileUrls[0]);
+ }
+ }
+ }
+
+ header: Rectangle {
+ height: _controlBaseHeight
+ color: _menuBackgroundColor
+ visible: window.visibility !== Window.FullScreen
+
+ Row {
+ anchors.fill: parent
+ StyledMenuButton {
+ id: fileButton
+ text: qsTr("File")
+ menu: fileMenu
+ window: window
+
+ StyledMenu {
+ id: fileMenu
+ StyledMenuItem {
+ text: qsTr("Open...")
+ shortcut: StandardKey.Open
+ enabled: _viewerHelper.contentView !== ViewerHelper.SequenceView
+ onTriggered: {
+ if (enabled) {
+ fileDialogLoader.sourceComponent = fileDialogComponent;
+ fileDialogLoader.item.open();
+ }
+ }
+ Loader {
+ id: fileDialogLoader
+ sourceComponent: Item {}
+ }
+ }
+ StyledMenuItem {
+ text: _viewerHelper.connected ? qsTr("Disconnect") : qsTr("Connect...")
+ shortcut: "F9"
+ enabled: _viewerHelper.contentView !== ViewerHelper.SequenceView
+ onTriggered: {
+ if (enabled) {
+ if (_viewerHelper.connected)
+ _viewerHelper.disconnectRemote();
+ else
+ ipEntry.visible = !ipEntry.visible;
+ }
+ }
+ }
+ StyledMenuItem {
+ text: qsTr("Reload")
+ enabled: _viewerHelper.contentView === ViewerHelper.StudioView
+ shortcut: "F5"
+ onTriggered: {
+ if (enabled) {
+ contentLoader.item.hiderVisible = true;
+ contentLoader.item.reset();
+ }
+ }
+ }
+ StyledMenuSeparator {}
+ StyledMenuItem {
+ text: qsTr("Quit")
+ shortcut: "Ctrl+Q"
+ onTriggered: {
+ window.close();
+ }
+ }
+ }
+ }
+ StyledMenuButton {
+ id: viewButton
+ text: qsTr("View")
+ menu: viewMenu
+ window: window
+
+ StyledMenu {
+ id: viewMenu
+ StyledMenuItem {
+ text: qsTr("Show Matte")
+ shortcut: "Ctrl+D"
+ enabled: _viewerHelper.contentView === ViewerHelper.StudioView
+ showCheckMark: window.matteColor !== window.hideMatteColor
+ onTriggered: {
+ if (enabled) {
+ if (window.matteColor === window.hideMatteColor)
+ window.matteColor = window.showMatteColor;
+ else
+ window.matteColor = window.hideMatteColor;
+ }
+ }
+ }
+ StyledMenuItem {
+ id: scaleMenuItem
+ text: qsTr("Scale Mode")
+ showArrow: true
+ arrowMenu: scaleMenu
+ shortcut: "Ctrl+Shift+S"
+ enabled: _viewerHelper.contentView === ViewerHelper.StudioView
+ onTriggered: {
+ if (enabled) {
+ scaleMenu.close();
+ if (window.scaleMode === ViewerSettings.ScaleModeCenter)
+ window.scaleMode = ViewerSettings.ScaleModeFit;
+ else if (window.scaleMode === ViewerSettings.ScaleModeFit)
+ window.scaleMode = ViewerSettings.ScaleModeFill;
+ else if (window.scaleMode === ViewerSettings.ScaleModeFill)
+ window.scaleMode = ViewerSettings.ScaleModeCenter;
+ }
+ }
+
+ StyledMenu {
+ id: scaleMenu
+ x: parent.width
+ y: 0
+
+ StyledMenuItem {
+ id: scaleCenter
+ text: qsTr("Center")
+ enabled: _viewerHelper.contentView === ViewerHelper.StudioView
+ showCheckMark: window.scaleMode === ViewerSettings.ScaleModeCenter
+ onTriggered: {
+ if (enabled)
+ window.scaleMode = ViewerSettings.ScaleModeCenter;
+ }
+ }
+ StyledMenuItem {
+ id: scaleFit
+ text: qsTr("Scale to Fit")
+ enabled: _viewerHelper.contentView === ViewerHelper.StudioView
+ showCheckMark: window.scaleMode === ViewerSettings.ScaleModeFit
+ onTriggered: {
+ if (enabled)
+ window.scaleMode = ViewerSettings.ScaleModeFit;
+ }
+ }
+ StyledMenuItem {
+ id: scaleFill
+ text: qsTr("Scale to Fill")
+ enabled: _viewerHelper.contentView === ViewerHelper.StudioView
+ showCheckMark: window.scaleMode === ViewerSettings.ScaleModeFill
+ onTriggered: {
+ if (enabled)
+ window.scaleMode = ViewerSettings.ScaleModeFill;
+ }
+ }
+ }
+ }
+ StyledMenuItem {
+ text: qsTr("Show Render Statistics")
+ shortcut: "F7"
+ enabled: _viewerHelper.contentView === ViewerHelper.StudioView
+ showCheckMark: window.showRenderStats
+ onTriggered: {
+ if (enabled)
+ window.showRenderStats = !window.showRenderStats;
+ }
+ }
+ StyledMenuSeparator {}
+ StyledMenuItem {
+ text: qsTr("Full Screen")
+ shortcut: "F11"
+ Shortcut {
+ sequence: "ESC"
+ context: Qt.ApplicationShortcut
+ enabled: window.visibility === Window.FullScreen
+ onActivated: {
+ window.visibility = window.previousVisibility;
+ }
+ }
+
+ onTriggered: {
+ if (window.visibility !== Window.FullScreen) {
+ window.previousVisibility = window.visibility
+ window.visibility = Window.FullScreen;
+ } else {
+ window.visibility = window.previousVisibility;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tools/viewer/remotedeploymentreceiver.cpp b/tools/viewer/remotedeploymentreceiver.cpp
new file mode 100644
index 0000000..cdfa5b0
--- /dev/null
+++ b/tools/viewer/remotedeploymentreceiver.cpp
@@ -0,0 +1,202 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** 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 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "remotedeploymentreceiver.h"
+#include "viewer.h"
+
+#include <QtNetwork>
+
+RemoteDeploymentReceiver::RemoteDeploymentReceiver(int serverPort, QObject *parent)
+ : QObject(parent)
+ , m_tcpServer(0)
+ , m_connection(0)
+ , m_temporaryDir(0)
+ , m_serverPort(serverPort)
+ , m_projectDeployed(false)
+{
+ m_incoming.setVersion(QDataStream::Qt_5_8);
+}
+
+RemoteDeploymentReceiver::~RemoteDeploymentReceiver()
+{
+ delete m_temporaryDir;
+ m_temporaryDir = 0;
+}
+
+void RemoteDeploymentReceiver::setPort(int value)
+{
+ m_serverPort = value;
+}
+
+QString RemoteDeploymentReceiver::startServer()
+{
+ if (m_tcpServer)
+ return QString();
+
+ m_tcpServer = new QTcpServer(this);
+ if (!m_tcpServer->listen(QHostAddress::Any, m_serverPort)) {
+ QString error = tr("Can't start the remote connection: '%1'")
+ .arg(m_tcpServer->errorString());
+ delete m_tcpServer;
+ m_tcpServer = 0;
+ return error;
+ }
+
+ QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();
+ // use the first non-localhost IPv4 address
+ for (int i = 0; i < ipAddressesList.size(); ++i) {
+ if (ipAddressesList.at(i) != QHostAddress::LocalHost
+ && ipAddressesList.at(i).toIPv4Address()) {
+ m_hostAddress = ipAddressesList.at(i);
+ break;
+ }
+ }
+
+ // if we did not find one, use IPv4 localhost
+ if (m_hostAddress.isNull())
+ m_hostAddress = QHostAddress(QHostAddress::LocalHost);
+
+ m_serverPort = m_tcpServer->serverPort();
+ connect(m_tcpServer, SIGNAL(newConnection()),
+ this, SLOT(acceptRemoteConnection()));
+ return QString();
+}
+
+void RemoteDeploymentReceiver::disconnectRemote()
+{
+ if (m_connection)
+ m_connection->disconnectFromHost();
+}
+
+void RemoteDeploymentReceiver::acceptRemoteConnection()
+{
+ Q_ASSERT(m_tcpServer);
+ Q_ASSERT(!m_connection);
+ m_connection = m_tcpServer->nextPendingConnection();
+ Q_ASSERT(m_connection);
+
+ m_incoming.setDevice(m_connection);
+
+ connect(m_connection, &QTcpSocket::disconnected,
+ this, &RemoteDeploymentReceiver::acceptRemoteDisconnection);
+
+ connect(m_connection, &QTcpSocket::readyRead,
+ this, &RemoteDeploymentReceiver::readProject);
+
+ Q_EMIT(remoteConnected());
+}
+
+void RemoteDeploymentReceiver::acceptRemoteDisconnection()
+{
+ Q_ASSERT(m_tcpServer);
+ Q_ASSERT(m_connection);
+ m_connection->deleteLater();
+ m_connection = 0;
+
+ m_incoming.setDevice(0);
+
+ Q_EMIT(remoteDisconnected());
+}
+
+void RemoteDeploymentReceiver::readProject()
+{
+ m_projectDeployed = false;
+ Q_EMIT(projectChanging());
+
+ m_incoming.startTransaction();
+
+ int totalBytes = 0;
+ m_incoming >> totalBytes;
+
+ Viewer *viewer = qobject_cast<Viewer *>(parent());
+ if (viewer && totalBytes != 0) {
+ viewer->updateProgress(
+ 100 * ((double)m_connection->bytesAvailable() / (double)totalBytes));
+ }
+
+ if (m_connection->bytesAvailable() < totalBytes) {
+ m_incoming.rollbackTransaction();
+ return;
+ }
+
+ int numberOfFiles = 0;
+ QString projectFile;
+ m_incoming >> numberOfFiles;
+ m_incoming >> projectFile;
+
+ QVector<QPair<QString, QByteArray> > files;
+ for (int i = 0; i < numberOfFiles; ++i) {
+ QString fileName;
+ QByteArray fileContents;
+ m_incoming >> fileName;
+ m_incoming >> fileContents;
+ files.append(qMakePair(fileName, fileContents));
+ }
+
+ if (!m_incoming.commitTransaction()) {
+ m_incoming.abortTransaction();
+ qWarning() << "Error transferring remote project in one payload";
+ return;
+ }
+
+ QFileInfo currentProject(m_projectFile);
+ if (projectFile != currentProject.fileName()) {
+ delete m_temporaryDir;
+ m_temporaryDir = 0;
+ }
+
+ if (!m_temporaryDir)
+ m_temporaryDir = new QTemporaryDir;
+
+ Q_ASSERT(m_temporaryDir->isValid());
+
+ for (const auto &file : qAsConst(files)) {
+ QString filePath = m_temporaryDir->path() + QDir::separator() + file.first;
+ QFile tmpFile(filePath);
+ QDir tmpFileDir = QFileInfo(tmpFile).absoluteDir();
+ if (!tmpFileDir.exists())
+ tmpFileDir.mkpath(".");
+ if (!tmpFile.open(QIODevice::WriteOnly)) {
+ delete m_temporaryDir;
+ m_temporaryDir = 0;
+ qWarning() << "Error opening temporary file for remote project:"
+ << filePath;
+ return;
+ }
+
+ if (file.first == projectFile)
+ m_projectFile = filePath;
+
+ tmpFile.write(file.second);
+ tmpFile.close();
+ }
+
+ m_projectDeployed = true;
+ Q_EMIT(projectChanged());
+}
diff --git a/tools/viewer/remotedeploymentreceiver.h b/tools/viewer/remotedeploymentreceiver.h
new file mode 100644
index 0000000..49f7de3
--- /dev/null
+++ b/tools/viewer/remotedeploymentreceiver.h
@@ -0,0 +1,79 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** 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 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#ifndef REMOTEDEPLOYMENTRECEIVER_H
+#define REMOTEDEPLOYMENTRECEIVER_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qdatastream.h>
+#include <QtCore/qtemporarydir.h>
+#include <QtNetwork/qtcpserver.h>
+
+class RemoteDeploymentReceiver : public QObject
+{
+ Q_OBJECT
+public:
+ explicit RemoteDeploymentReceiver(int serverPort, QObject *parent);
+ ~RemoteDeploymentReceiver();
+
+ QString startServer();
+ void disconnectRemote();
+
+ QHostAddress hostAddress() const { return m_hostAddress; }
+ int serverPort() const { return m_serverPort; }
+ void setServerPort(int port) { m_serverPort = port; }
+ bool isConnected() const { return m_connection; }
+ bool isProjectDeployed() const { return m_connection && m_projectDeployed; }
+ QString fileName() const { return m_projectFile; }
+
+Q_SIGNALS:
+ void projectChanged();
+ void projectChanging();
+ void remoteConnected();
+ void remoteDisconnected();
+
+private Q_SLOTS:
+ void acceptRemoteConnection();
+ void acceptRemoteDisconnection();
+ void readProject();
+ void setPort(int value);
+
+private:
+ QTcpServer *m_tcpServer = nullptr;
+ QTcpSocket *m_connection = nullptr;
+ QHostAddress m_hostAddress;
+ QDataStream m_incoming;
+ QTemporaryDir *m_temporaryDir = nullptr;
+ QString m_projectFile;
+ bool m_projectDeployed;
+ int m_serverPort;
+};
+
+#endif // REMOTEDEPLOYMENTRECEIVER_H
diff --git a/tools/viewer/resources/images/3D-studio-viewer.ico b/tools/viewer/resources/images/3D-studio-viewer.ico
new file mode 100644
index 0000000..c15cd68
--- /dev/null
+++ b/tools/viewer/resources/images/3D-studio-viewer.ico
Binary files differ
diff --git a/tools/viewer/resources/images/3D-studio-viewer.svg b/tools/viewer/resources/images/3D-studio-viewer.svg
new file mode 100644
index 0000000..69a14f4
--- /dev/null
+++ b/tools/viewer/resources/images/3D-studio-viewer.svg
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="512px" height="512px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
+ <title>icon_512x512</title>
+ <desc>Created with Sketch.</desc>
+ <defs>
+ <polygon id="path-1" points="64 0 0 48 0 112 64 160 128 112 128 48"></polygon>
+ <path d="M45.6534039,84.7089559 L45.6534039,97.4054197 L15.0588235,128 L0,112.941176 L30.1176471,82.8235294 L43.7426612,82.8235294 C35.3039365,74.0299707 30.1176471,62.091235 30.1176471,48.9411765 C30.1176471,21.9117111 52.0293581,0 79.0588235,0 C106.088289,0 128,21.9117111 128,48.9411765 C128,75.9706419 106.088289,97.8823529 79.0588235,97.8823529 C66.1449913,97.8823529 54.3993526,92.8807218 45.6534039,84.7089559 Z M79.0588235,82.8235294 C97.7715303,82.8235294 112.941176,67.6538833 112.941176,48.9411765 C112.941176,30.2284697 97.7715303,15.0588235 79.0588235,15.0588235 C60.3461167,15.0588235 45.1764706,30.2284697 45.1764706,48.9411765 C45.1764706,67.6538833 60.3461167,82.8235294 79.0588235,82.8235294 Z" id="path-2"></path>
+ </defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="icon_512x512">
+ <g id="Group-3" transform="translate(56.000000, 88.000000)">
+ <g id="Page-1-Copy">
+ <polygon id="Fill-1" fill="#41CD52" points="0 288.0032 328 288.0032 384 232.0032 384 0.0032 56 0.0032 0 56.0032"></polygon>
+ <g id="Group-6" transform="translate(72.000000, 48.000000)" fill="#FFFFFF">
+ <path d="M39.3792,138.3936 C44.8752,147.8176 55.4752,152.6656 71.0512,152.6656 C86.6272,152.6656 97.0992,147.9536 102.5952,138.6496 C107.9552,129.3456 110.7072,113.5216 110.7072,91.4016 C110.7072,69.1536 107.9632,53.0576 102.3312,42.9776 C96.7072,32.9056 86.3632,27.9216 71.0512,27.9216 C55.8752,27.9216 45.3952,32.8896 39.6432,42.9776 C34.0112,53.0656 31.1312,69.0256 31.1312,91.1456 C31.1312,113.1296 33.8832,128.9616 39.3792,138.3936 L39.3792,138.3936 Z M70.9232,176.2256 C45.6592,176.2256 27.9952,169.4096 18.0432,155.8016 C8.0992,142.2016 3.1232,120.5936 3.1232,91.2736 C3.1232,61.8336 8.2272,39.9696 18.3072,25.7056 C28.3872,11.4416 46.0512,4.2416 70.9232,4.2416 C95.9152,4.2416 113.4592,11.3056 123.4032,25.5696 C133.4832,39.7056 138.4512,61.5616 138.4512,91.1536 C138.4512,110.6496 136.3632,126.3536 132.3072,138.2656 C128.1152,150.2976 121.4432,159.4656 112.0192,165.7536 L132.4352,198.6016 L107.4352,210.2336 L85.8432,174.7776 C82.6992,175.8016 77.7232,176.1376 70.9232,176.1376 L70.9232,176.2256 Z" id="Fill-2"></path>
+ <path d="M198.66,71.5184 L198.66,125.0464 C198.66,134.9984 199.444,141.5344 200.884,144.8064 C202.316,148.0784 206.116,149.6544 212.004,149.6544 L231.9,148.7984 L233.076,170.0064 C222.212,172.1024 213.972,173.1424 208.212,173.1424 C194.468,173.1424 185.044,170.0064 179.94,163.7184 C174.836,157.4384 172.22,145.5344 172.22,127.9984 L172.22,71.5824 L153.764,71.5824 L153.764,48.9424 L172.348,48.9424 L172.348,13.7344 L198.66,13.7344 L198.66,48.9424 L232.164,48.9424 L232.164,71.4544 L198.66,71.5184 Z" id="Fill-4"></path>
+ </g>
+ </g>
+ <g id="Group" transform="translate(280.000000, 176.000000)">
+ <g id="Path-29">
+ <use fill="#D8D8D8" fill-rule="evenodd" xlink:href="#path-1"></use>
+ <path stroke="#FFFFFF" stroke-width="7" d="M64,-4.375 L131.5,46.25 L131.5,113.75 L64,164.375 L-3.5,113.75 L-3.5,46.25 L64,-4.375 Z"></path>
+ </g>
+ <polygon id="Path-27" fill="#81CFFF" points="0 48 64 96 128 48 64 0"></polygon>
+ <polygon id="Path-28-Copy" fill="#1F83C1" transform="translate(96.000000, 104.000000) scale(-1, 1) translate(-96.000000, -104.000000) " points="64 48 64 112 128 160 128 96"></polygon>
+ <polygon id="Path-28" fill="#2696DA" points="0 48 0 112 64 160 64 96"></polygon>
+ </g>
+ <g id="Group-2" transform="translate(200.000000, 208.000000)">
+ <g id="Combined-Shape">
+ <use fill="#707070" fill-rule="evenodd" xlink:href="#path-2"></use>
+ <path stroke="#FFFFFF" stroke-width="7" d="M49.1534039,92.0264501 L49.1534039,98.8551671 L15.0588235,132.949747 L-4.94974747,112.941176 L28.6678996,79.3235294 L36.3089911,79.3235294 C30.0728186,70.5670745 26.6176471,60.0229376 26.6176471,48.9411765 C26.6176471,19.9787144 50.0963615,-3.5 79.0588235,-3.5 C108.021286,-3.5 131.5,19.9787144 131.5,48.9411765 C131.5,77.9036385 108.021286,101.382353 79.0588235,101.382353 C68.1809425,101.382353 57.8185023,98.0537262 49.1534039,92.0264501 Z M79.0588235,79.3235294 C95.8385337,79.3235294 109.441176,65.7208867 109.441176,48.9411765 C109.441176,32.1614663 95.8385337,18.5588235 79.0588235,18.5588235 C62.2791133,18.5588235 48.6764706,32.1614663 48.6764706,48.9411765 C48.6764706,65.7208867 62.2791133,79.3235294 79.0588235,79.3235294 Z"></path>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/tools/viewer/resources/images/arrow.png b/tools/viewer/resources/images/arrow.png
new file mode 100644
index 0000000..40ebda8
--- /dev/null
+++ b/tools/viewer/resources/images/arrow.png
Binary files differ
diff --git a/tools/viewer/resources/images/arrow@2x.png b/tools/viewer/resources/images/arrow@2x.png
new file mode 100644
index 0000000..1a21ee0
--- /dev/null
+++ b/tools/viewer/resources/images/arrow@2x.png
Binary files differ
diff --git a/tools/viewer/resources/images/check.png b/tools/viewer/resources/images/check.png
new file mode 100644
index 0000000..5c1ef70
--- /dev/null
+++ b/tools/viewer/resources/images/check.png
Binary files differ
diff --git a/tools/viewer/resources/images/check@2x.png b/tools/viewer/resources/images/check@2x.png
new file mode 100644
index 0000000..ed730bf
--- /dev/null
+++ b/tools/viewer/resources/images/check@2x.png
Binary files differ
diff --git a/tools/viewer/resources/images/viewer.icns b/tools/viewer/resources/images/viewer.icns
new file mode 100644
index 0000000..b718d75
--- /dev/null
+++ b/tools/viewer/resources/images/viewer.icns
Binary files differ
diff --git a/tools/viewer/viewer.cpp b/tools/viewer/viewer.cpp
new file mode 100644
index 0000000..bb6af59
--- /dev/null
+++ b/tools/viewer/viewer.cpp
@@ -0,0 +1,416 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** 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 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qsettings.h>
+#include <QtCore/qfileinfo.h>
+#include <QtStudio3D/private/q3dsviewersettings_p.h>
+#include <QtStudio3D/private/q3dspresentation_p.h>
+#include <QtGui/qguiapplication.h>
+#include <QtGui/qscreen.h>
+#include <QtQuick/qquickwindow.h>
+
+#include "viewer.h"
+#include "q3dspresentationitem_p.h"
+
+Viewer::Viewer(bool generatorMode, QObject *parent)
+ : QObject(parent)
+ , m_generatorMode(generatorMode)
+{
+ if (m_generatorMode)
+ setContentView(SequenceView);
+
+ m_connectTextResetTimer.setInterval(5000);
+ m_connectTextResetTimer.setSingleShot(true);
+ connect(&m_connectTextResetTimer, &QTimer::timeout, this, &Viewer::resetConnectionInfoText);
+}
+
+Viewer::~Viewer()
+{
+}
+
+void Viewer::connectRemote()
+{
+ if (m_remoteDeploymentReceiver) {
+ delete m_remoteDeploymentReceiver;
+ m_remoteDeploymentReceiver = 0;
+ Q_EMIT connectedChanged();
+ }
+
+ m_remoteDeploymentReceiver = new RemoteDeploymentReceiver(m_connectPort, this);
+ QString error = m_remoteDeploymentReceiver->startServer();
+ if (!error.isEmpty()) {
+ delete m_remoteDeploymentReceiver;
+ m_remoteDeploymentReceiver = nullptr;
+ setContentView(DefaultView);
+ m_qmlRootObject->setProperty("error", QVariant(error));
+ return;
+ }
+
+ resetConnectionInfoText();
+
+ connect(m_remoteDeploymentReceiver, &RemoteDeploymentReceiver::remoteConnected,
+ this, &Viewer::remoteConnected);
+
+ connect(m_remoteDeploymentReceiver, &RemoteDeploymentReceiver::remoteDisconnected,
+ this, &Viewer::remoteDisconnected);
+
+ connect(m_remoteDeploymentReceiver, &RemoteDeploymentReceiver::projectChanging,
+ this, &Viewer::remoteProjectChanging);
+
+ connect(m_remoteDeploymentReceiver, &RemoteDeploymentReceiver::projectChanged,
+ this, &Viewer::loadRemoteDeploymentReceiver);
+
+ Q_EMIT connectedChanged();
+}
+
+void Viewer::disconnectRemote()
+{
+ m_remoteDeploymentReceiver->disconnectRemote();
+}
+
+void Viewer::setVariantList(const QStringList &variantList)
+{
+ if (m_variantList != variantList) {
+ m_variantList = variantList;
+ Q_EMIT variantListChanged();
+ }
+}
+
+QStringList Viewer::variantList() const
+{
+ return m_variantList;
+}
+
+// Used to load files via command line and when using remote deployment
+void Viewer::loadFile(const QString &filename)
+{
+ QString targetFilename = filename;
+ // Try to find the application (*.uia) file for loading instead of the presentation (*.uip)
+ // in case we are connected to remote sender.
+ if (isConnected() && targetFilename.endsWith(QStringLiteral(".uip"))) {
+ targetFilename.chop(4);
+ targetFilename.append(QStringLiteral(".uia"));
+ QFileInfo targetfileInfo(targetFilename);
+ // uia not found, revert to given uip
+ if (!targetfileInfo.exists())
+ targetFilename = filename;
+ }
+
+ QFileInfo fileInfo(targetFilename);
+ if (!fileInfo.exists()) {
+ setContentView(DefaultView);
+ m_qmlRootObject->setProperty(
+ "error", QVariant(tr("Tried to load nonexistent file %1").arg(targetFilename)));
+ return;
+ }
+
+ QUrl sourceUrl = QUrl::fromLocalFile(fileInfo.absoluteFilePath());
+
+ setContentView(StudioView);
+
+ if (qmlStudio()) {
+ qmlStudio()->presentation()->setVariantList(m_variantList);
+ qmlStudio()->presentation()->setSource(sourceUrl);
+ }
+}
+
+QString Viewer::convertUrlListToFilename(const QList<QUrl> &list)
+{
+ for (const QUrl &url : list) {
+ QString str = url.toLocalFile();
+ if (!str.isEmpty()) {
+ if (QFileInfo(str).suffix() == QStringLiteral("uip")
+ || QFileInfo(str).suffix() == QStringLiteral("uia")) {
+ return str;
+ }
+ }
+ }
+ return QString();
+}
+
+void Viewer::restoreWindowState(QWindow *window)
+{
+ QSettings settings;
+ QRect geo = settings.value(QStringLiteral("WindowGeometry"), QRect()).toRect();
+ int visibility = settings.value(QStringLiteral("WindowVisibility"),
+ QWindow::Windowed).toInt();
+
+ // Do not restore geometry if resulting geometry means the center of the window
+ // would be offscreen on the virtual desktop
+ QRect vgeo = window->screen()->availableVirtualGeometry();
+ QPoint center(geo.x() + geo.width() / 2, geo.y() + geo.height() / 2);
+ bool offscreen = center.x() > vgeo.width() || center.x() < 0
+ || center.y() > vgeo.height() || center.y() < 0;
+
+ if (!offscreen && !geo.isNull()) {
+ // The first geometry set at startup may adjust the geometry according to pixel
+ // ratio if mouse cursor is on different screen than where window goes and the
+ // two screens have different pixel ratios. Setting geometry twice seems to
+ // work around this.
+ geo.adjust(0, 0, -1, 0);
+ window->setGeometry(geo);
+ geo.adjust(0, 0, 1, 0);
+ window->setGeometry(geo);
+ }
+
+ window->setVisibility(QWindow::Visibility(visibility));
+}
+
+void Viewer::storeWindowState(QWindow *window)
+{
+ QSettings settings;
+ settings.setValue(QStringLiteral("WindowGeometry"), window->geometry());
+ settings.setValue(QStringLiteral("WindowState"), window->visibility());
+}
+
+// Since we need mouse events for handling the swipe gesture in viewer, we need to generate
+// a fake event for the presentation.
+void Viewer::handleMousePress(int x, int y, int button, int buttons, int modifiers)
+{
+ if (qmlStudio()) {
+ QMouseEvent fakeEvent(QEvent::MouseButtonPress,
+ QPointF(x, y) * qmlStudio()->window()->devicePixelRatio(),
+ Qt::MouseButton(button),
+ Qt::MouseButtons(buttons),
+ Qt::KeyboardModifiers(modifiers));
+ qmlStudio()->presentation()->mousePressEvent(&fakeEvent);
+ }
+}
+
+void Viewer::handleMouseRelease(int x, int y, int button, int buttons, int modifiers)
+{
+ if (qmlStudio()) {
+ QMouseEvent fakeEvent(QEvent::MouseButtonRelease,
+ QPointF(x, y) * qmlStudio()->window()->devicePixelRatio(),
+ Qt::MouseButton(button),
+ Qt::MouseButtons(buttons),
+ Qt::KeyboardModifiers(modifiers));
+ qmlStudio()->presentation()->mouseReleaseEvent(&fakeEvent);
+ }
+}
+
+void Viewer::handleMouseMove(int x, int y, int button, int buttons, int modifiers)
+{
+ if (qmlStudio()) {
+ QMouseEvent fakeEvent(QEvent::MouseMove,
+ QPointF(x, y) * qmlStudio()->window()->devicePixelRatio(),
+ Qt::MouseButton(button),
+ Qt::MouseButtons(buttons),
+ Qt::KeyboardModifiers(modifiers));
+ qmlStudio()->presentation()->mouseMoveEvent(&fakeEvent);
+ }
+}
+
+void Viewer::setContentView(Viewer::ContentView view)
+{
+ if (view != m_contentView) {
+ m_qmlStudio = nullptr;
+ m_contentView = view;
+ Q_EMIT contentViewChanged();
+ }
+}
+
+Viewer::ContentView Viewer::contentView() const
+{
+ return m_contentView;
+}
+
+void Viewer::setOpenFolder(const QUrl &folder)
+{
+ QString localFolder = folder.toLocalFile();
+ QFileInfo fi(localFolder);
+ QString newDir;
+ if (fi.isDir())
+ newDir = fi.absoluteFilePath();
+ else
+ newDir = fi.absolutePath();
+ if (newDir != m_openFileDir) {
+ m_openFileDir = newDir;
+ QSettings settings;
+ settings.setValue(QStringLiteral("DirectoryOfLastOpen"), m_openFileDir);
+ Q_EMIT openFolderChanged();
+ }
+}
+
+QUrl Viewer::openFolder()
+{
+ if (m_openFileDir.size() == 0) {
+ QSettings settings;
+ m_openFileDir = settings.value(QStringLiteral("DirectoryOfLastOpen"),
+ QString("")).toString();
+#ifdef Q_OS_ANDROID
+ if (m_openFileDir.isEmpty())
+ m_openFileDir = QStringLiteral("/sdcard/qt3dviewer"); // Add default folder for Android
+#endif
+ }
+ return QUrl::fromLocalFile(m_openFileDir);
+}
+
+void Viewer::setConnectPort(int port)
+{
+ if (m_connectPort != port) {
+ QSettings settings;
+ m_connectPort = port;
+ settings.setValue(QStringLiteral("ConnectPort"), m_connectPort);
+ Q_EMIT connectPortChanged();
+ }
+}
+
+int Viewer::connectPort()
+{
+ if (m_connectPort < 0) {
+ QSettings settings;
+ m_connectPort = settings.value(QStringLiteral("ConnectPort"), 36000).toInt();
+ }
+ return m_connectPort;
+}
+
+QString Viewer::connectText() const
+{
+ return m_connectText;
+}
+
+bool Viewer::isConnected() const
+{
+ return m_remoteDeploymentReceiver ? m_remoteDeploymentReceiver->isConnected() : false;
+}
+
+void Viewer::setQmlRootObject(QObject *obj)
+{
+ m_qmlRootObject = obj;
+}
+
+void Viewer::loadRemoteDeploymentReceiver()
+{
+ Q_ASSERT(m_remoteDeploymentReceiver);
+ const QString remote = m_remoteDeploymentReceiver->fileName();
+ QMetaObject::invokeMethod(this, "loadFile", Qt::QueuedConnection, Q_ARG(QString, remote));
+}
+
+void Viewer::remoteProjectChanging()
+{
+ if (m_contentView != ConnectView)
+ setContentView(ConnectView);
+ m_connectText = tr("Loading remote project...");
+ Q_EMIT connectTextChanged();
+}
+
+void Viewer::remoteConnected()
+{
+ m_connectText = tr("Remote Connected");
+ Q_EMIT connectTextChanged();
+ Q_EMIT connectedChanged();
+ if (m_contentView != ConnectView)
+ Q_EMIT showInfoOverlay(m_connectText);
+}
+
+void Viewer::remoteDisconnected()
+{
+ m_connectText = tr("Remote Disconnected");
+ Q_EMIT connectTextChanged();
+ Q_EMIT connectedChanged();
+ if (m_contentView != ConnectView) {
+ Q_EMIT showInfoOverlay(m_connectText);
+ } else {
+ // Start timer to reset connection info text
+ m_connectTextResetTimer.start();
+ }
+}
+
+void Viewer::resetConnectionInfoText()
+{
+ m_connectText.clear();
+ QTextStream stream(&m_connectText);
+ stream << tr("Use IP: %1 and Port: %2\n"
+ "in Qt 3D Studio Editor to connect to this viewer.\n\n"
+ "Use File/Open... to open a local presentation.")
+ .arg(m_remoteDeploymentReceiver->hostAddress().toString())
+ .arg(QString::number(m_connectPort));
+ Q_EMIT connectTextChanged();
+}
+
+Q3DSStudio3D *Viewer::qmlStudio()
+{
+ if (m_contentView == StudioView) {
+ if (!m_qmlStudio) {
+ QObject *loadedContent = m_qmlRootObject->property("loadedContent").value<QObject *>();
+ m_qmlStudio = static_cast<Q3DSStudio3D *>(loadedContent);
+ }
+ } else {
+ m_qmlStudio = nullptr;
+ }
+ return m_qmlStudio;
+}
+
+void Viewer::generatorProgress(int totalFrames, int frameCount)
+{
+ QString progressString;
+ if (frameCount >= totalFrames) {
+ progressString =
+ QCoreApplication::translate(
+ "main", "Image sequence generation done! (%2 frames generated)")
+ .arg(totalFrames);
+ } else {
+ progressString =
+ QCoreApplication::translate("main", "Image sequence generation progress: %1 / %2")
+ .arg(frameCount).arg(totalFrames);
+ }
+ QObject *loadedContent = m_qmlRootObject->property("loadedContent").value<QObject *>();
+ loadedContent->setProperty("mainText", progressString);
+}
+
+void Viewer::generatorFinished(bool success, const QString &details)
+{
+ QObject *loadedContent = m_qmlRootObject->property("loadedContent").value<QObject *>();
+ if (success) {
+ loadedContent->setProperty("detailsText", details);
+ } else {
+ QString mainString =
+ QCoreApplication::translate("main", "Image sequence generation failed:");
+ loadedContent->setProperty("mainText", mainString);
+ loadedContent->setProperty("detailsText", details);
+ }
+}
+
+void Viewer::updateProgress(int percent)
+{
+ // Don't wait for 100%, as it'll already start loading and text isn't updated anymore
+ if (percent >= 99)
+ m_connectText = tr("Loading remote project...");
+ else
+ m_connectText = QStringLiteral("Receiving (%1%)").arg(percent);
+ Q_EMIT connectTextChanged();
+}
+
+void Viewer::setGeneratorDetails(const QString &filename)
+{
+ QObject *loadedContent = m_qmlRootObject->property("loadedContent").value<QObject *>();
+ loadedContent->setProperty("detailsText", filename);
+}
diff --git a/tools/viewer/viewer.h b/tools/viewer/viewer.h
new file mode 100644
index 0000000..1ce514a
--- /dev/null
+++ b/tools/viewer/viewer.h
@@ -0,0 +1,128 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** 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 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef VIEWER_H
+#define VIEWER_H
+
+#include "remotedeploymentreceiver.h"
+
+#include <QtCore/qobject.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qtimer.h>
+#include <QtGui/qwindow.h>
+#include "q3dsstudio3d_p.h"
+
+class Viewer : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(ContentView contentView READ contentView WRITE setContentView NOTIFY contentViewChanged)
+ Q_PROPERTY(QUrl openFolder READ openFolder WRITE setOpenFolder NOTIFY openFolderChanged)
+ Q_PROPERTY(int connectPort READ connectPort WRITE setConnectPort NOTIFY connectPortChanged)
+ Q_PROPERTY(QString connectText READ connectText NOTIFY connectTextChanged)
+ Q_PROPERTY(bool connected READ isConnected NOTIFY connectedChanged)
+ Q_PROPERTY(QStringList variantList READ variantList WRITE setVariantList NOTIFY variantListChanged)
+
+public:
+ enum ContentView {
+ DefaultView,
+ StudioView,
+ ConnectView,
+ SequenceView
+ };
+
+ Q_ENUM(ContentView)
+
+ explicit Viewer(bool generatorMode, QObject *parent = nullptr);
+ ~Viewer();
+
+ Q_INVOKABLE void connectRemote();
+ Q_INVOKABLE void disconnectRemote();
+ Q_INVOKABLE void loadFile(const QString &filename);
+ Q_INVOKABLE QString convertUrlListToFilename(const QList<QUrl> &list);
+ Q_INVOKABLE void restoreWindowState(QWindow *window);
+ Q_INVOKABLE void storeWindowState(QWindow *window);
+
+ Q_INVOKABLE void handleMousePress(int x, int y, int button, int buttons, int modifiers);
+ Q_INVOKABLE void handleMouseRelease(int x, int y, int button, int buttons, int modifiers);
+ Q_INVOKABLE void handleMouseMove(int x, int y, int button, int buttons, int modifiers);
+
+ void setVariantList(const QStringList &variantList);
+ QStringList variantList() const;
+ void setContentView(ContentView view);
+ ContentView contentView() const;
+ void setOpenFolder(const QUrl &folder);
+ QUrl openFolder(); // not const since it potentially updates from settings
+ void setConnectPort(int port);
+ int connectPort(); // not const since it potentially updates from settings
+ QString connectText() const;
+ bool isConnected() const;
+
+ void setQmlRootObject(QObject *obj);
+
+ void loadProject(const QByteArray &data);
+ void updateProgress(int percent);
+ void setGeneratorDetails(const QString &filename);
+
+public Q_SLOTS:
+ void generatorProgress(int totalFrames, int frameCount);
+ void generatorFinished(bool success, const QString &details);
+
+private Q_SLOTS:
+ void loadRemoteDeploymentReceiver();
+ void remoteProjectChanging();
+ void remoteConnected();
+ void remoteDisconnected();
+ void resetConnectionInfoText();
+
+Q_SIGNALS:
+ void contentViewChanged();
+ void openFolderChanged();
+ void connectPortChanged();
+ void connectTextChanged();
+ void connectedChanged();
+ void variantListChanged();
+ void showInfoOverlay(const QString &infoStr);
+
+private:
+ Q3DSStudio3D *qmlStudio();
+
+ QString m_openFileDir;
+ QStringList m_variantList;
+ RemoteDeploymentReceiver *m_remoteDeploymentReceiver = nullptr;
+ bool m_generatorMode = false;
+ ContentView m_contentView = DefaultView;
+ QObject *m_qmlRootObject = nullptr;
+ int m_connectPort = -1;
+ QString m_connectText;
+ Q3DSStudio3D *m_qmlStudio = nullptr;
+ QTimer m_connectTextResetTimer;
+};
+
+#endif // VIEWER_H
diff --git a/tools/viewer/viewer.pro b/tools/viewer/viewer.pro
new file mode 100644
index 0000000..cad5664
--- /dev/null
+++ b/tools/viewer/viewer.pro
@@ -0,0 +1,49 @@
+include($$PWD/../../commoninclude.pri)
+
+TEMPLATE = app
+TARGET = Qt3DViewer
+QT += qml quickcontrols2 studio3d-private
+
+INCLUDEPATH += $$PWD/../../src/api/studio3dqml
+INCLUDEPATH += $$PWD/../../src/api/studio3d
+
+RESOURCES += Viewer.qrc
+RC_ICONS = resources/images/3D-studio-viewer.ico
+
+ICON = resources/images/viewer.icns
+
+SOURCES += \
+ $$PWD/../../src/api/studio3dqml/q3dsstudio3d.cpp \
+ $$PWD/../../src/api/studio3dqml/q3dsrenderer.cpp \
+ $$PWD/../../src/api/studio3dqml/q3dspresentationitem.cpp \
+ main.cpp \
+ viewer.cpp \
+ remotedeploymentreceiver.cpp
+
+HEADERS += \
+ $$PWD/../../src/api/studio3dqml/q3dsstudio3d_p.h \
+ $$PWD/../../src/api/studio3dqml/q3dsrenderer_p.h \
+ $$PWD/../../src/api/studio3dqml/q3dspresentationitem_p.h \
+ viewer.h \
+ remotedeploymentreceiver.h
+
+android: {
+SOURCES += \
+ $$PWD/../../src/api/studio3d/q3dsviewersettings.cpp \
+ $$PWD/../../src/api/studio3d/q3dspresentation.cpp \
+ $$PWD/../../src/api/studio3d/q3dsdatainput.cpp
+
+HEADERS += \
+ $$PWD/../../src/api/studio3d/q3dsviewersettings.h \
+ $$PWD/../../src/api/studio3d/q3dspresentation.h \
+ $$PWD/../../src/api/studio3d/q3dsdatainput.h
+}
+
+LIBS += \
+ -lqt3dsopengl$$qtPlatformTargetSuffix() \
+ -lqt3dsqmlstreamer$$qtPlatformTargetSuffix()
+
+macos:QMAKE_RPATHDIR += @executable_path/../../../../lib
+
+target.path = $$[QT_INSTALL_BINS]
+INSTALLS += target