diff options
Diffstat (limited to 'src/Authoring/Qt3DStudio/Application/StudioApp.cpp')
-rw-r--r-- | src/Authoring/Qt3DStudio/Application/StudioApp.cpp | 2229 |
1 files changed, 2229 insertions, 0 deletions
diff --git a/src/Authoring/Qt3DStudio/Application/StudioApp.cpp b/src/Authoring/Qt3DStudio/Application/StudioApp.cpp new file mode 100644 index 00000000..ac9efb10 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Application/StudioApp.cpp @@ -0,0 +1,2229 @@ +/**************************************************************************** +** +** Copyright (C) 1999-2002 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$ +** +****************************************************************************/ + +#include "Qt3DSCommonPrecompile.h" + +#ifdef _WIN32 +#pragma warning(disable : 4100) // unreferenced formal parameter +#endif +#include "StudioApp.h" +#include "PlayerWnd.h" +#include "DataInputDlg.h" +#include "qtsingleapplication.h" +#include "qtlocalpeer.h" +#include "TimelineWidget.h" +#include "SlideView.h" +#include "PresentationFile.h" +#include "EditPresentationIdDlg.h" +#include "Qt3DSDMWStrOps.h" + +#include <QtGui/qsurfaceformat.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qurl.h> +#include <QtCore/qsavefile.h> +#include <QtGui/qopenglcontext.h> +#include <QtWidgets/qaction.h> +#include <QtCore/qstandardpaths.h> +#include <QtCore/qcommandlineparser.h> +#include <QtXml/qdom.h> +#include <QtQml/qqmlapplicationengine.h> +#include <QtQuick/qquickitem.h> + +#ifdef ENABLE_QT_BREAKPAD +#include <qtsystemexceptionhandler.h> +#endif + +#ifdef QT3DSTUDIO_REVISION +#define STRINGIFY_INTERNAL(x) #x +#define STRINGIFY(x) STRINGIFY_INTERNAL(x) +const char *const QT3DSTUDIO_REVISION_STR = STRINGIFY(QT3DSTUDIO_REVISION); +#endif + +const QString activePresentationQuery = QStringLiteral("activePresentation:"); + +int main(int argc, char *argv[]) +{ + bool isOpenGLES = false; + + // init runtime static resources + Q_INIT_RESOURCE(res); + + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); +#if !defined(Q_OS_MACOS) + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); +#endif + SharedTools::QtSingleApplication guiApp(QStringLiteral("Qt3DStudio"), argc, argv); + + // Fix for uia and uip file attribute random ordering (see QTBUG-8158) + qSetGlobalQHashSeed(1720419); + +#if defined(Q_OS_MACOS) + QSurfaceFormat openGL33Format; + openGL33Format.setRenderableType(QSurfaceFormat::OpenGL); + openGL33Format.setProfile(QSurfaceFormat::CoreProfile); + openGL33Format.setMajorVersion(3); + openGL33Format.setMinorVersion(3); + openGL33Format.setStencilBufferSize(8); + QSurfaceFormat::setDefaultFormat(openGL33Format); +#else + QSurfaceFormat format; + format.setDepthBufferSize(24); + format.setStencilBufferSize(8); + QScopedPointer<QOpenGLContext> context(new QOpenGLContext()); + context->setFormat(format); + context->create(); + isOpenGLES = context->isOpenGLES(); +#endif + +#ifdef ENABLE_QT_BREAKPAD + const QString libexecPath = QCoreApplication::applicationDirPath() + QStringLiteral("/."); + QtSystemExceptionHandler systemExceptionHandler(libexecPath); + systemExceptionHandler.setBuildVersion(QT3DSTUDIO_REVISION_STR); +#endif + + // Parse the command line so we know what's up + QCommandLineParser parser; + parser.addHelpOption(); + parser.addPositionalArgument("file", + QObject::tr("The presentation file."), + QObject::tr("[file]")); + parser.addPositionalArgument("folder", + QObject::tr("The folder in which to create the new\n" + "presentation."), + QObject::tr("[folder]")); + parser.addOption({"create", + QObject::tr("Creates a new presentation.\n" + "The file argument must be specified.\n" + "The folder argument is optional. In\n" + "case it is omitted, the new presentation\n" + "is created in the executable folder.")}); + parser.addOption({"silent", + QObject::tr("Allows creating a project silently.\n" + "Only has effect with create.")}); + parser.addOption({"add", + QObject::tr("Add a presentation to an existing project.\n" + "Omit to create a new project. Only has effect with create.")}); + parser.process(guiApp); + + const QStringList files = parser.positionalArguments(); + if (files.count() > 1 && !parser.isSet("create")) { + qWarning() << "Only one presentation file can be given."; + parser.showHelp(-1); + } else if (files.count() > 2 && parser.isSet("create")) { + qWarning() << "Only one presentation file and a target folder can be given."; + parser.showHelp(-1); + } else if (files.count() == 0 && parser.isSet("create")) { + qWarning() << "A presentation file is required."; + parser.showHelp(-1); + } + + QObject::connect(&guiApp, &SharedTools::QtSingleApplication::messageReceived, + &g_StudioApp, &CStudioApp::handleMessageReceived); + +#if (defined Q_OS_MACOS) + QObject::connect(&guiApp, &SharedTools::QtSingleApplication::fileOpenRequest, + &g_StudioApp, &CStudioApp::openApplication); +#endif + + // Load and apply stylesheet for the application + QFile styleFile(":/style.qss"); + styleFile.open(QFile::ReadOnly); + guiApp.setStyleSheet(styleFile.readAll()); + g_StudioApp.initInstance(parser, isOpenGLES); + return g_StudioApp.run(parser); +} + +#include "Exceptions.h" +#include "IOLibraryException.h" +#include "MainFrm.h" +#include "AboutDlg.h" +#include "Views.h" +#include "Doc.h" +#include "Dialogs.h" +#include "Dispatch.h" +#include "StartupDlg.h" +#include "RecentItems.h" +#include "StudioPreferences.h" +#include "MsgRouter.h" +#include "Views.h" +#include "Qt3DSFile.h" +#include "Qt3DSFileTools.h" +#include "ITickTock.h" +#include "IStudioRenderer.h" +#include "IDocumentEditor.h" +#include "StudioUtils.h" + +#include "ClientDataModelBridge.h" +#include "IOLibraryException.h" + +#include "Qt3DSDMErrors.h" + +#include <iostream> +#include <fstream> +#include <stdio.h> +#include <fcntl.h> +#include <string.h> + +#include <QtWidgets/qapplication.h> + +#include "Core.h" +#include "HotKeys.h" +#include "StudioTutorialWidget.h" +#include "Qt3DSDMStudioSystem.h" +#include "Qt3DSDMInspectable.h" +#include "Qt3DSDMSlides.h" +#include "Qt3DSDMAnimation.h" +#include "Qt3DSDMDataCore.h" +#include "IDirectoryWatchingSystem.h" +#include "ITickTock.h" +#include "foundation/Qt3DSLogging.h" + +CStudioApp g_StudioApp; + +using namespace Q3DStudio; + +CStudioApp::CStudioApp() + : m_core(nullptr) + , m_isSilent(false) + , m_views(nullptr) + , m_toolMode(STUDIO_TOOLMODE_MOVE) + , m_manipulationMode(StudioManipulationModes::Local) + , m_selectMode(STUDIO_SELECTMODE_GROUP) + , m_dialogs(nullptr) + , m_playbackTime(0) + , m_authorZoom(false) + , m_welcomeShownThisSession(false) + , m_goStraightToWelcomeFileDialog(false) + , m_tutorialPage(0) + , m_autosaveTimer(new QTimer(this)) +{ + connect(m_autosaveTimer, &QTimer::timeout, this, [=](){ OnSave(true); }); +} + +CStudioApp::~CStudioApp() +{ + delete m_views; + m_views = nullptr; + delete m_dialogs; + m_dialogs = nullptr; + delete m_core; + m_core = nullptr; +} + +void CStudioApp::performShutdown() +{ + // Dispatch un-registration + if (m_core) { + m_core->GetDispatch()->RemoveAppStatusListener(this); + m_core->GetDispatch()->RemoveCoreAsynchronousEventListener(this); + + // close transactions before we call destructors + if (m_core->GetDoc()->isTransactionOpened()) + m_core->GetDoc()->closeTransaction(); + + qCInfo(qt3ds::TRACE_INFO) << "Studio exiting successfully"; + } + + if (m_renderer) { + if (m_views->getMainFrame()) + m_views->getMainFrame()->GetPlayerWnd()->makeCurrent(); + m_renderer->Close(); + m_renderer = std::shared_ptr<Q3DStudio::IStudioRenderer>(); + if (m_views->getMainFrame()) + m_views->getMainFrame()->GetPlayerWnd()->doneCurrent(); + } + + delete m_views; + m_views = nullptr; + delete m_dialogs; + m_dialogs = nullptr; + delete m_core; + m_core = nullptr; + + // Get rid of the temp files + Qt3DSFile::ClearCurrentTempCache(); + + CMsgRouter::GetInstance()->blockMessages(); + + qApp->exit(); +} + +/** + * Entry location for the creation of this application. + * This creates the all the views, then returns if everything + * was successful. + */ +bool CStudioApp::initInstance(const QCommandLineParser &parser, bool isOpenGLES) +{ + QApplication::setOrganizationName("The Qt Company"); + QApplication::setOrganizationDomain("qt.io"); + QApplication::setApplicationName("Qt 3D Studio"); + QApplication::setApplicationVersion(CStudioPreferences::versionString()); + + qCInfo(qt3ds::TRACE_INFO) << "Studio: " << QApplication::applicationFilePath(); + qCInfo(qt3ds::TRACE_INFO) << "Version: " << CStudioPreferences::versionString(); + + // Silent is ignored for everything but create + m_isSilent = parser.isSet("silent") && parser.isSet("create"); + + // Initialize help file paths + m_helpFilePath = Qt3DSFile::GetApplicationDirectory() + + QStringLiteral("/../doc/qt3dstudio/qt3dstudio-index.html"); + m_gettingStartedFilePath = Qt3DSFile::GetApplicationDirectory() + + QStringLiteral("/../doc/qt3dstudio/getting-started.html"); + + CStudioPreferences::loadPreferences(); + + m_dialogs = new CDialogs(!m_isSilent); + + if (isOpenGLES) { + m_dialogs->DisplayKnownErrorDialog( + tr("Qt 3D Studio cannot be started.\n" + "OpenGL ES is not supported for Qt 3D Studio Editor.\n" + "Make sure you are not using a software renderer or\n" + "wrapper like MESA or Angle before trying again.")); + exit(0); + } + + m_views = new CViews(); + + m_core = new CCore(); + getRenderer(); + m_core->GetDoc()->SetSceneGraph(m_renderer); + + // Dispatch registration + m_core->GetDispatch()->AddAppStatusListener(this); + m_core->GetDispatch()->AddCoreAsynchronousEventListener(this); + m_core->GetDispatch()->AddFailListener(this); + + // Initialize autosave + m_autosaveTimer->setInterval(CStudioPreferences::autoSaveDelay() * 1000); + if (CStudioPreferences::isAutoSavePreference()) + m_autosaveTimer->start(); + + return true; +} + +/** + * Command handler to display the about dialog. + */ +void CStudioApp::onAppAbout() +{ + CAboutDlg aboutDlg; + aboutDlg.exec(); +} + +/** + * Main application execution loop. + * The application's main thread stays in this until the app exits. + * @return true on success; false on failure + */ +bool CStudioApp::run(const QCommandLineParser &parser) +{ + bool theRetVal = false; + try { + if (parser.isSet("create")) { + // true: add a presentation to a project, false: create a new project + bool isAdd = parser.isSet("add"); + // Create, requires file and folder + if (parser.positionalArguments().count() > 1) { + theRetVal = createAndRunApplication(parser.positionalArguments().at(0), + parser.positionalArguments().at(1), !isAdd); + } else { + theRetVal = createAndRunApplication(parser.positionalArguments().at(0), + QString(), !isAdd); + } + } else if (parser.positionalArguments().count() > 0) { + // Start given file + theRetVal = openAndRunApplication(parser.positionalArguments().at(0)); + } else { + // No arguments, normal start + theRetVal = blankRunApplication(); + } + + if (!theRetVal) + qWarning("Problem starting application"); + + performShutdown(); + } catch (qt3dsdm::Qt3DSDMError &uicdmError) { + Q_UNUSED(uicdmError) + exit(1); + } catch (...) { + throw; + } + + return theRetVal; +} + +bool CStudioApp::handleWelcomeRes(int res, bool recursive) +{ + bool theReturn = true; + bool canceled = false; + switch (res) { + case StudioTutorialWidget::createNewResult: { + if (PerformSavePrompt()) { + QString theFile(m_dialogs->GetNewDocumentChoice(getMostRecentProjectParentDir())); + if (!theFile.isEmpty()) { + if (!m_core->OnNewDocument(theFile, true)) { + // Invalid filename, show a message box and the startup dialog + showInvalidFilenameWarning(); + theReturn = showStartupDialog(); + } else { + theReturn = true; + m_welcomeShownThisSession = true; + } + } else { + canceled = true; + } + } else { + canceled = true; + } + } break; + + case StudioTutorialWidget::openSampleResult: { + if (PerformSavePrompt()) { + // Try three options: + // - open a specific example .uia + // - failing that, show the main example root dir + // - failing all previous, show default Documents dir + QFileInfo filePath; + QString theFile(QStringLiteral(".")); + +#ifndef Q_OS_MACOS + filePath.setFile(Qt3DSFile::GetApplicationDirectory() + + QStringLiteral("/../examples/studio3d/SampleProject")); + + if (!filePath.exists()) { + filePath.setFile(Qt3DSFile::GetApplicationDirectory() + + QStringLiteral("/../examples/studio3d")); +#else + filePath.setFile(Qt3DSFile::GetApplicationDirectory() + + QStringLiteral("/../../../../examples/studio3d/SampleProject")); + + if (!filePath.exists()) { + filePath.setFile(Qt3DSFile::GetApplicationDirectory() + + QStringLiteral("/../../../../examples/studio3d")); +#endif + if (!filePath.exists()) { + filePath.setFile(QStandardPaths::writableLocation( + QStandardPaths::DocumentsLocation)); + } + theFile = m_dialogs->GetFileOpenChoice(filePath.absoluteFilePath()); + } else { + theFile = filePath.absoluteFilePath() + QStringLiteral("/SampleProject.uia"); + } + + if (!theFile.isEmpty()) { + OnLoadDocument(theFile); + theReturn = true; + m_welcomeShownThisSession = true; + } else { + canceled = true; + } + } else { + canceled = true; + } + } break; + default: + // Welcome screen was simply closed + theReturn = false; + break; + } + + if (canceled) { + // User Cancels the dialog. Show the welcome screen. + if (recursive) { + m_welcomeShownThisSession = false; + m_goStraightToWelcomeFileDialog = true; + theReturn = showStartupDialog(); + } else { + theReturn = false; + } + } + return theReturn; +} + +QString CStudioApp::resolvePresentationFile(const QString &inFile) +{ + QFileInfo inFileInfo(inFile); + // a uip file, just return it + if (inFileInfo.suffix().compare(QStringLiteral("uip"), Qt::CaseInsensitive) == 0) + return inFile; + + // If opening a .uia file, open the initial presentation + if (inFileInfo.suffix().compare(QStringLiteral("uia"), Qt::CaseInsensitive) == 0 + && inFileInfo.exists()) { + QString uiaPath = inFileInfo.absoluteFilePath(); + QString initialPresentation = m_core->getProjectFile().getInitialPresentationSrc(uiaPath); + + if (!initialPresentation.isEmpty()) + return inFileInfo.path() + QStringLiteral("/") + initialPresentation; + } + + // couldn't find a uip file + return {}; +} + +/** + * Show startup dialog and perform necessary action such as create new doc or load doc. + * Return false if user requests to exit + */ +bool CStudioApp::showStartupDialog() +{ +#if (defined Q_OS_MACOS) + if (m_fileOpenEvent) + return true; +#endif + + int welcomeRes = QDialog::Rejected; + bool theReturn = true; + + if (!m_welcomeShownThisSession){ + m_welcomeShownThisSession = true; + + bool show = false; + + if (!CStudioPreferences::containsShowWelcomeScreen()) { + CStudioPreferences::setShowWelcomeScreen(true); + show = true; + } else { + // if we are returning to welcome dialog page after canceling + // file dialog, do not care about settings but always show + // welcome + show = CStudioPreferences::isShowWelcomeScreen() || m_goStraightToWelcomeFileDialog; + } + + if (show) { + StudioTutorialWidget tutorial(m_pMainWnd); + welcomeRes = tutorial.exec(); + } + } + + // show the usual startup dialog only if user rejected tutorial + // ( = did not open samples or create new project) + if (welcomeRes == QDialog::Rejected) { + CStartupDlg theStartupDlg(m_pMainWnd); + + // Populate recent items + if (m_pMainWnd) { + CRecentItems *theRecentItems = m_pMainWnd->GetRecentItems(); + for (long theIndex = 0; theIndex < theRecentItems->GetItemCount(); ++theIndex) + theStartupDlg.AddRecentItem(theRecentItems->GetItem(theIndex)); + } + + theStartupDlg.exec(); + CStartupDlg::EStartupChoice theChoice = theStartupDlg.GetChoice(); + + switch (theChoice) { + case CStartupDlg::EStartupChoice_Exit: + theReturn = true; + break; + + case CStartupDlg::EStartupChoice_NewDoc: { + QString theFile = m_dialogs->GetNewDocumentChoice(getMostRecentProjectParentDir()); + if (!theFile.isEmpty()) { + if (!m_core->OnNewDocument(theFile, true)) { + // Invalid filename, show a message box and the dialog again + showInvalidFilenameWarning(); + theReturn = showStartupDialog(); + } + } else { + // User Cancels the dialog. Show startup dialog again. + theReturn = showStartupDialog(); + } + } break; + + case CStartupDlg::EStartupChoice_OpenDoc: { + QString theFile = m_dialogs->GetFileOpenChoice(getMostRecentDirectory()); + if (!theFile.isEmpty()) { + OnLoadDocument(theFile); + theReturn = true; + } else { + // User Cancels the dialog. Show startup dialog again. + theReturn = showStartupDialog(); + } + } break; + + case CStartupDlg::EStartupChoice_OpenRecent: { + QString theFile = theStartupDlg.GetRecentDoc(); + if (!theFile.isEmpty()) { + OnLoadDocument(theFile); + theReturn = true; + } else { + // User Cancels the dialog. Show startup dialog again. + theReturn = showStartupDialog(); + } + } break; + + default: + QT3DS_ASSERT(false); // Should not reach this block. + theReturn = false; + break; + } + } else { // open sample or create new + theReturn = handleWelcomeRes(welcomeRes, true); + } + return theReturn; +} + +#if (defined Q_OS_MACOS) +void CStudioApp::openApplication(const QString &inFilename) +{ + m_fileOpenEvent = true; + QString loadFile = resolvePresentationFile(inFilename); + OnLoadDocument(loadFile, true); +} +#endif + +/** + * Start the app. + */ +bool CStudioApp::blankRunApplication() +{ + initCore(); + // Event loop must be running before we launch startup dialog, or possible error message boxes + // will cause a silent crash. +#if (defined Q_OS_MACOS) + // Give a bit of time for Finder file open, in case that's how we were started + QTimer::singleShot(250, this, &CStudioApp::showStartupDialog); +#else + QTimer::singleShot(0, this, &CStudioApp::showStartupDialog); +#endif + return runApplication(); +} + +/** + * Open the specified file and run the application. + * This will load the file then go into the standard app loop. + * On load with the -silent flag, this would force the application to exit on + * load failures. + * @return true on success; false on failure + */ +bool CStudioApp::openAndRunApplication(const QString &inFilename) +{ + // Need to resolve the actual file we want to load already to be able to check for it + QString loadFile = resolvePresentationFile(inFilename); + + // First check if the desired presentation is already open on another instance + SharedTools::QtSingleApplication *app = + static_cast<SharedTools::QtSingleApplication *>(QCoreApplication::instance()); + const auto pids = app->runningInstances(); + for (const auto pid : pids) { + app->setBlock(true); + QString query = activePresentationQuery + loadFile; + if (app->sendMessage(query, true, 5000, pid)) + return true; + } + + bool theSuccess = false; + initCore(); + // Load document. Upon failure, don't show startup dialog but exit immediately. + if (OnLoadDocument(loadFile, false)) + theSuccess = runApplication(); + return theSuccess; +} + +bool CStudioApp::createAndRunApplication(const QString &filename, const QString &folder, + bool isNewProject) +{ + bool theSuccess = false; + initCore(); + // Append .uip if it is not included in the filename + QString actualFilename = filename; + if (!actualFilename.endsWith(QLatin1String(".uip"))) + actualFilename.append(QLatin1String(".uip")); + + QString actualFolder = folder; + if (!actualFolder.endsWith(QLatin1String("/"))) + actualFilename.append(QLatin1String("/")); + + // Create presentation + QString filePath = actualFolder + actualFilename; + if (!filePath.isEmpty()) { + theSuccess = m_core->OnNewDocument(filePath, isNewProject, m_isSilent); + if (!theSuccess) + return false; + + theSuccess = m_isSilent || runApplication(); + } + return theSuccess; +} + +/** + * This is the app execution loop, the main thread loops here until the app exits. + * @return true on success; false on failure + */ +bool CStudioApp::runApplication() +{ + return qApp->exec() == 0; +} + +/** + * Initialize the core and all the views. + */ +void CStudioApp::initCore() +{ + // Initialize and cache the RenderSelector values for the first time, + // this way, subsequent attempts to instantiate a RenderSelector would circumvent the need + // for any extra (unneccesary) creation of render contexts which inadvertently cause exceptions + // to be thrown. + + m_core->Initialize(); + + if (m_views) { + m_views->createViews(m_isSilent); + m_pMainWnd = m_views->getMainFrame(); + if (!CStudioPreferences::containsWindowState(STUDIO_VERSION_NUM)) { + // On first run, save and restore geometry and state. For some reason they are both + // needed to avoid a bug with palettes resizing to their original size when window is + // resized or something in a palette is edited. + m_pMainWnd->handleGeometryAndState(true); + } + m_pMainWnd->handleGeometryAndState(false); + } + + RegisterGlobalKeyboardShortcuts(m_core->GetHotKeys(), m_pMainWnd); + m_core->GetDispatch()->AddPresentationChangeListener(this); +} + +struct SIImportFailedHandler : public Q3DStudio::IImportFailedHandler +{ + CDialogs &m_Dialogs; + SIImportFailedHandler(CDialogs &dialogs) + : m_Dialogs(dialogs) + { + } + void DisplayImportFailed(const QString &inDocumentPath, const QString &inDescription, + bool inWarningsOnly) override + { + m_Dialogs.DisplayImportFailed(QUrl(inDocumentPath), inDescription, inWarningsOnly); + } +}; + +struct SIDeletingReferencedObjectHandler : public Q3DStudio::IDeletingReferencedObjectHandler +{ + CDialogs &m_Dialogs; + + SIDeletingReferencedObjectHandler(CDialogs &dialogs) + : m_Dialogs(dialogs) + { + } + + void DisplayMessageBox(const QString &inDescription) override + { + QString theTitle = QObject::tr("Warning"); + QString theMessage = QObject::tr("The following objects have action(s) that reference this " + "object and/or its descendants:\n%1\nPlease fix the " + "action(s) accordingly.").arg(inDescription); + + m_Dialogs.DisplayMessageBox(theTitle, theMessage, Qt3DSMessageBox::ICON_WARNING, false); + } +}; + +struct SIMoveRenameHandler : public Q3DStudio::IMoveRenameHandler +{ + CDialogs &m_dialogs; + + SIMoveRenameHandler(CDialogs &dialogs) + : m_dialogs(dialogs) + { + } + + void displayMessageBox(const QString &origName, const QString &newName) override + { + g_StudioApp.GetDialogs()->DisplayObjectRenamed(origName, newName); + } +}; + +void CStudioApp::setupTimer(long inMessageId, QWidget *inWnd) +{ + m_tickTock = ITickTock::CreateTickTock(inMessageId, inWnd); + getDirectoryWatchingSystem(); + m_core->GetDoc()->SetDirectoryWatchingSystem(m_directoryWatchingSystem); + m_core->GetDoc()->SetImportFailedHandler( + std::make_shared<SIImportFailedHandler>(std::ref(*GetDialogs()))); + m_core->GetDoc()->SetDocMessageBoxHandler( + std::make_shared<SIDeletingReferencedObjectHandler>(std::ref(*GetDialogs()))); + m_core->GetDoc()->setMoveRenameHandler( + std::make_shared<SIMoveRenameHandler>(std::ref(*GetDialogs()))); +} + +ITickTock &CStudioApp::getTickTock() +{ + if (m_tickTock == nullptr) + throw std::runtime_error("Uninitialized TickTock"); + return *m_tickTock; +} + +Q3DStudio::IStudioRenderer &CStudioApp::getRenderer() +{ + if (!m_renderer) + m_renderer = Q3DStudio::IStudioRenderer::CreateStudioRenderer(); + return *m_renderer; +} + +void CStudioApp::clearGuides() +{ + SCOPED_DOCUMENT_EDITOR(*m_core->GetDoc(), QObject::tr("Clear Guides"))->ClearGuides(); +} + +void CStudioApp::handleMessageReceived(const QString &message, QObject *socket) +{ + if (message.startsWith(activePresentationQuery)) { + QLocalSocket *lsocket = qobject_cast<QLocalSocket *>(socket); + if (lsocket) { + // Another studio instance wants to know if specified presentation is open on this one + QString checkPath(message.mid(activePresentationQuery.size())); + QFileInfo checkFile(checkPath); + QString docPath; + if (m_core) + docPath = m_core->GetDoc()->GetDocumentPath(); + QFileInfo openFile(docPath); + if (!checkPath.isEmpty() && checkFile == openFile) { + lsocket->write(SharedTools::QtLocalPeer::acceptReply(), + SharedTools::QtLocalPeer::acceptReply().size()); + // Since we accept active presentation query, it means the querying instance will + // shut down and this instance must be made active window. + if (m_pMainWnd) { + m_pMainWnd->setWindowState(m_pMainWnd->windowState() & ~Qt::WindowMinimized); + m_pMainWnd->raise(); + m_pMainWnd->activateWindow(); + } + } else { + lsocket->write(SharedTools::QtLocalPeer::denyReply(), + SharedTools::QtLocalPeer::denyReply().size()); + } + lsocket->waitForBytesWritten(1000); + } + } + if (socket) + delete socket; +} + +void SendAsyncCommand(CDispatch &inDispatch, Q3DStudio::TCallbackFunc inFunc) +{ + inDispatch.FireOnAsynchronousCommand(inFunc); +} + +IDirectoryWatchingSystem &CStudioApp::getDirectoryWatchingSystem() +{ + if (m_directoryWatchingSystem == nullptr) { + Q3DStudio::TCallbackCaller theCaller = + std::bind(SendAsyncCommand, std::ref(*m_core->GetDispatch()), + std::placeholders::_1); + m_directoryWatchingSystem = + IDirectoryWatchingSystem::CreateThreadedDirectoryWatchingSystem(theCaller); + } + return *m_directoryWatchingSystem; +} + +CCore *CStudioApp::GetCore() +{ + return m_core; +} + +/** + * Get the view manager for this core to communicate to the views. + */ +CViews *CStudioApp::GetViews() +{ + return m_views; +} + +/** + * Get the dialog manager for this core for displaying dialogs. + */ +CDialogs *CStudioApp::GetDialogs() +{ + return m_dialogs; +} + +long CStudioApp::GetToolMode() +{ + return m_toolMode; +} + +void CStudioApp::SetToolMode(long inToolMode) +{ + if (m_toolMode != inToolMode) { + m_toolMode = inToolMode; + m_core->GetDispatch()->FireOnToolbarChange(); + } +} + +long CStudioApp::GetSelectMode() +{ + return m_selectMode; +} + +void CStudioApp::SetSelectMode(long inSelectMode) +{ + if (m_selectMode != inSelectMode) { + m_selectMode = inSelectMode; + m_core->GetDispatch()->FireOnToolbarChange(); + } +} + +StudioManipulationModes::Enum CStudioApp::GetManipulationMode() const +{ + return m_manipulationMode; +} +void CStudioApp::SetManipulationMode(StudioManipulationModes::Enum inManipulationMode) +{ + if (m_manipulationMode != inManipulationMode) { + m_manipulationMode = inManipulationMode; + m_core->GetDispatch()->FireOnToolbarChange(); + } +} + +bool CStudioApp::CanUndo() +{ + return m_core->GetCmdStack()->CanUndo() + && !m_views->getMainFrame()->getTimelineWidget()->dndActive(); +} + +bool CStudioApp::CanRedo() +{ + return m_core->GetCmdStack()->CanRedo(); +} + +void CStudioApp::OnCopy() +{ + m_core->GetDoc()->HandleCopy(); +} + +bool CStudioApp::CanCopy() +{ + return m_core->GetDoc()->canCopy(); +} + +/** + * Get a string describing the type of the copy operation that can be done. + * Precedence of copying is 1) Actions; 2) Keyframes; 3) Objects + */ +QString CStudioApp::GetCopyType() +{ + QString theCopyType; + + CDoc *theDoc = m_core->GetDoc(); + if (theDoc->canCopySelectedActions()) + theCopyType = tr("Action"); + else if (theDoc->canCopySelectedKeyframes()) + theCopyType = tr("Keyframes"); + else + theCopyType = tr("Object"); + + return theCopyType; +} + +QString CStudioApp::getDuplicateType() const +{ + const bool slide = qobject_cast<SlideView *>(m_lastActiveView) != nullptr; + CDoc *doc = m_core->GetDoc(); + if (slide) { + qt3dsdm::Qt3DSDMSlideHandle handle = doc->GetActiveSlide(); + if (handle != doc->GetStudioSystem()->GetSlideSystem()->GetMasterSlide(handle)) + return tr("Slide"); + } else { + qt3dsdm::Qt3DSDMInstanceHandle selectedInstance = doc->GetSelectedInstance(); + CClientDataModelBridge *bridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + if (bridge->IsDuplicateable(selectedInstance)) + return tr("Object"); + } + return {}; +} + +QString CStudioApp::getDeleteType() const +{ + // Delete priority: keyframes, slides, objects + const bool slide = qobject_cast<SlideView *>(m_lastActiveView) != nullptr; + CDoc *doc = m_core->GetDoc(); + if (m_pMainWnd->getTimelineWidget()->hasSelectedKeyframes()) { + return tr("Keyframes"); + } else if (slide) { + // Check if the slide is the last one or the master + qt3dsdm::Qt3DSDMSlideHandle slideHandle = doc->GetActiveSlide(); + qt3dsdm::ISlideSystem *slideSys = doc->GetStudioSystem()->GetSlideSystem(); + qt3dsdm::Qt3DSDMSlideHandle masterSlideHandle = slideSys->GetMasterSlide(slideHandle); + size_t slideCount = slideSys->GetSlideCount(masterSlideHandle); + if (slideHandle != masterSlideHandle && slideCount > 2) + return tr("Slide"); + } else { + qt3dsdm::TInstanceHandleList selected = doc->GetSelectedValue().GetSelectedInstances(); + CClientDataModelBridge *bridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + int deletableCount = 0; + for (size_t idx = 0, end = selected.size(); idx < end; ++idx) { + if (bridge->CanDelete(selected[idx])) + deletableCount++; + } + if (deletableCount && deletableCount == int(selected.size())) + return tr("Object"); + } + return {}; +} + +bool CStudioApp::canGroupSelectedObjects() const +{ + // Grouping is allowed for single and for multiple selected items. + qt3dsdm::TInstanceHandleList selected = m_core->GetDoc() + ->GetSelectedValue().GetSelectedInstances(); + if (selected.size() >= 1) { + // Scene objects, any direct children of scene objects (layers and behaviors), effects, + // root components, images, and materials are not groupable. Anything that can be + // multiselected can be grouped, so its enough to check the first selected object's type. + // Behavior that is not direct child of scene could technically be grouped, but since it + // cannot be multiselected, we treat it as ungroupable. + qt3dsdm::Qt3DSDMInstanceHandle first = selected[0]; + if (first.Valid()) { + auto bridge = m_core->GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); + EStudioObjectType type = bridge->GetObjectType(first); + + if (type & OBJTYPE_IS_UNGROUPABLE) + return false; + + // Components can't be grouped if they are the root of currently active time context + if (type == OBJTYPE_COMPONENT && bridge->IsActiveComponent(first)) + return false; + + // All items must either be on master slide or not be on master slide + bool isMaster = bridge->IsMaster(first); + for (size_t i = 1, end = selected.size(); i < end; ++i) { + if (isMaster != bridge->IsMaster(selected[i])) + return false; + } + + return true; + } + } + return false; +} + +bool CStudioApp::canUngroupSelectedObjects() const +{ + qt3dsdm::TInstanceHandleList selected = m_core->GetDoc() + ->GetSelectedValue().GetSelectedInstances(); + if (selected.size() == 1) { + qt3dsdm::Qt3DSDMInstanceHandle first = selected[0]; + return (first.Valid() && m_core->GetDoc()->GetStudioSystem()->GetClientDataModelBridge() + ->GetObjectType(first) == OBJTYPE_GROUP); + } + return false; +} + +bool CStudioApp::groupSelectedObjects() const +{ + if (canGroupSelectedObjects()) { + qt3dsdm::TInstanceHandleList selected = m_core->GetDoc() + ->GetSelectedValue().GetSelectedInstances(); + SCOPED_DOCUMENT_EDITOR(*m_core->GetDoc(), + QObject::tr("Group objects"))->groupObjects(selected); + return true; + } + return false; +} + +bool CStudioApp::ungroupSelectedObjects() const +{ + if (canUngroupSelectedObjects()) { + qt3dsdm::TInstanceHandleList selected = m_core->GetDoc() + ->GetSelectedValue().GetSelectedInstances(); + SCOPED_DOCUMENT_EDITOR(*m_core->GetDoc(), + QObject::tr("Ungroup objects"))->ungroupObjects(selected); + return true; + } + return false; +} + +/** + * Cuts the selected object or keys + */ +void CStudioApp::OnCut() +{ + m_core->GetDoc()->HandleCut(); +} + +bool CStudioApp::CanCut() +{ + return m_core->GetDoc()->canCut(); +} + +/** + * Paste keys from the copied list yo + */ +void CStudioApp::OnPaste() +{ + m_core->GetDoc()->HandlePaste(); +} + +bool CStudioApp::CanPaste() +{ + return m_core->GetDoc()->canPaste(); +} + +/** + * Get a string describing the type of the paste operation that can be done. + * Precedence of paste is 1) Actions; 2) Object ; 3) Keyframes + */ +QString CStudioApp::GetPasteType() +{ + QString thePasteType; + + CDoc *theDoc = m_core->GetDoc(); + if (theDoc->canPasteActions()) + thePasteType = tr("Action"); + else if (theDoc->canPasteObjects()) + thePasteType = tr("Object"); + else + thePasteType = tr("Keyframes"); + + return thePasteType; +} + +bool CStudioApp::CanChangeTimebarColor() +{ + auto theSelectedInstance = m_core->GetDoc()->GetSelectedInstance(); + if (theSelectedInstance.Valid()) { + auto bridge = m_core->GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); + return !bridge->IsActiveComponent(theSelectedInstance); + } + return false; +} + +/** + * Sets any changed keyframes on the selected object + */ +void CStudioApp::HandleSetChangedKeys() +{ + m_core->GetDoc()->SetChangedKeyframes(); +} + +/** + * Deletes all selected keys + */ +void CStudioApp::DeleteSelectedKeys() +{ + m_core->GetDoc()->deleteSelectedKeyframes(); +} + +/** + * Deletes selected object or keyframes + */ +void CStudioApp::DeleteSelectedObject() +{ + const bool slide = qobject_cast<SlideView *>(m_lastActiveView) != nullptr; + m_core->GetDoc()->DeleteSelectedItems(slide); +} + +/** + * Handles the duplicate object command + */ +void CStudioApp::HandleDuplicateCommand() +{ + const bool slide = qobject_cast<SlideView *>(m_lastActiveView) != nullptr; + m_core->GetDoc()->HandleDuplicateCommand(slide); +} + +/** + * Toggles the state of autoset keyframes. + */ +void CStudioApp::OnToggleAutosetKeyframes() +{ + SetAutosetKeyframes(!CStudioPreferences::isAutosetKeyframesOn()); + + m_core->GetDispatch()->FireOnToolbarChange(); +} + +/** + * Updates the preferences, and AnimationSystem. + */ +void CStudioApp::SetAutosetKeyframes(bool inFlag) +{ + CStudioPreferences::setAutosetKeyframesOn(inFlag); + + m_core->GetDoc()->GetStudioSystem()->GetAnimationSystem()->SetAutoKeyframe(inFlag); +} + +/** + * If the presentation is not currently playing, this function will make it + * start playing from the current position. The starting point of the playhead + * is saved so that it can be restored later. + */ +void CStudioApp::PlaybackPlay() +{ + if (!m_core->GetDoc()->getPresentationId().isEmpty()) { + // Do not start playback if user is currently interacting with scene + if (getRenderer().isMouseDown()) + return; + + CDoc *theDoc = m_core->GetDoc(); + if (!theDoc->IsPlaying()) { + m_playbackTime = theDoc->GetCurrentViewTime(); + m_playbackOriginalSlide = theDoc->GetActiveSlide(); + theDoc->SetPlayMode(PLAYMODE_PLAY); + } + } +} + +/** + * If the presentation is currently playing, it is stopped. The playhead is + * left wherever it was stopped at (hence it's not restored). + */ +void CStudioApp::PlaybackStopNoRestore() +{ + m_core->GetDoc()->SetPlayMode(PLAYMODE_STOP); +} + +/** + * Moves the playhead back to time zero. + */ +void CStudioApp::PlaybackRewind() +{ + CDoc *theDoc = m_core->GetDoc(); + if (theDoc->IsPlaying()) { + theDoc->SetPlayMode(PLAYMODE_STOP, 0); + theDoc->SetPlayMode(PLAYMODE_PLAY); + } else { + m_core->GetDoc()->NotifyTimeChanged(0); + } +} + +bool CStudioApp::IsPlaying() +{ + return m_core->GetDoc()->IsPlaying(); +} + +/** + * Performs a file revert. + * This will revert the doc to the last saved version. + */ +void CStudioApp::OnRevert() +{ + if (!m_core->GetDoc()->isModified() || m_dialogs->ConfirmRevert()) { + QString theCurrentDoc = m_core->GetDoc()->GetDocumentPath(); + OnLoadDocument(theCurrentDoc); + } +} + +/** + * Check to see if it is possible to perform a revert. + */ +bool CStudioApp::CanRevert() const +{ + return m_core->GetDoc()->isModified() && m_core->GetDoc()->isValid(); +} + +/** + * Handles the recent list. + */ +void CStudioApp::OnFileOpenRecent(const QString &inDocument) +{ + if (PerformSavePrompt()) + OnLoadDocument(inDocument); +} + +/** + * Called when closing the current doc, this prompts the user to save the doc. + * This will only prompt if the doc is modified, and if the user selects save + * then this will perform the save operation. + * @return true if the operation should continue, false if not. + */ +bool CStudioApp::PerformSavePrompt() +{ + if (m_core->GetDoc()->isModified()) { + CDialogs::ESavePromptResult theResult = m_dialogs->PromptForSave(); + if (theResult == CDialogs::SAVE_FIRST) { + bool onSaveResult = OnSave(); + if (onSaveResult) + return true; + } else if (theResult == CDialogs::CONTINUE_NO_SAVE) { + return true; + } + + return false; + } + return true; +} + +/** + * If the presentation is currently playing, it is stopped. The playhead is + * restored to the position found in m_PlaybackTime. + */ +void CStudioApp::PlaybackStop() +{ + CDoc *theDoc = m_core->GetDoc(); + // change it back to the original slide first before restoring the original time + if (m_playbackOriginalSlide.Valid()) { + if (m_playbackOriginalSlide != theDoc->GetActiveSlide()) + theDoc->NotifyActiveSlideChanged(m_playbackOriginalSlide); + theDoc->SetPlayMode(PLAYMODE_STOP, m_playbackTime); + } + // Invalidate the playback original slide so we don't inadvertently trigger this code later. + m_playbackOriginalSlide = 0; +} + +/** + * advance time by a small amount + */ +void CStudioApp::advanceTime() +{ + if (!m_core->GetDoc()->getPresentationId().isEmpty()) { + long dt = CStudioPreferences::timeAdvanceAmount(); + long time = (m_core->GetDoc()->GetCurrentViewTime() + dt) / dt * dt; + m_core->GetDoc()->NotifyTimeChanged(time); + } +} + +/** + * move back time by a small amount + */ +void CStudioApp::reduceTime() +{ + if (!m_core->GetDoc()->getPresentationId().isEmpty()) { + long dt = CStudioPreferences::timeAdvanceAmount(); + long time = (m_core->GetDoc()->GetCurrentViewTime() - 1) / dt * dt; + m_core->GetDoc()->NotifyTimeChanged(time); + } +} + +/** + * advance time by a big amount + */ +void CStudioApp::advanceTimeBig() +{ + if (!m_core->GetDoc()->getPresentationId().isEmpty()) { + long dt = CStudioPreferences::bigTimeAdvanceAmount(); + long time = (m_core->GetDoc()->GetCurrentViewTime() + dt) / dt * dt; + m_core->GetDoc()->NotifyTimeChanged(time); + } +} + +/** + * move back time by a big amount + */ +void CStudioApp::reduceTimeBig() +{ + if (!m_core->GetDoc()->getPresentationId().isEmpty()) { + long dt = CStudioPreferences::bigTimeAdvanceAmount(); + long time = (m_core->GetDoc()->GetCurrentViewTime() - 1) / dt * dt; + m_core->GetDoc()->NotifyTimeChanged(time); + } +} + +/** + * If the presentation is currently playing, it is stopped. Otherwise, the + * presetation starts playing from its current position. Called when the user + * presses the Enter key. + */ +void CStudioApp::PlaybackToggle() +{ + if (!m_core->GetDoc()->getPresentationId().isEmpty()) { + // If the presentation is playing, stop it and leave the playhead where it is + if (m_core->GetDoc()->IsPlaying()) + PlaybackStopNoRestore(); + // Otherwise, the presentation is stopped, so start it playing + else + PlaybackPlay(); + } +} + +void CStudioApp::RegisterGlobalKeyboardShortcuts(CHotKeys *inShortcutHandler, QWidget *actionParent) +{ + m_core->RegisterGlobalKeyboardShortcuts(inShortcutHandler, actionParent); + + ADD_GLOBAL_SHORTCUT(actionParent, QKeySequence(Qt::Key_Period), CStudioApp::advanceTime) + ADD_GLOBAL_SHORTCUT(actionParent, QKeySequence(Qt::Key_Comma), CStudioApp::reduceTime) + ADD_GLOBAL_SHORTCUT(actionParent, QKeySequence(Qt::ShiftModifier | Qt::Key_Period), + CStudioApp::advanceTimeBig) + ADD_GLOBAL_SHORTCUT(actionParent, QKeySequence(Qt::ShiftModifier | Qt::Key_Comma), + CStudioApp::reduceTimeBig) + ADD_GLOBAL_SHORTCUT(actionParent, QKeySequence(Qt::Key_Return), CStudioApp::PlaybackToggle) + + inShortcutHandler->RegisterKeyUpEvent( + new CDynHotKeyConsumer<CStudioApp>(this, &CStudioApp::playbackPreviewEnd), nullptr, + Qt::Key_Space); + inShortcutHandler->RegisterKeyDownEvent( + new CDynHotKeyConsumer<CStudioApp>(this, &CStudioApp::playbackPreviewStart), + nullptr, Qt::Key_Space); + + if (m_views) + m_views->registerGlobalKeyboardShortcuts(inShortcutHandler, actionParent); +} + +void CStudioApp::playbackPreviewStart() +{ + if (!m_playbackPreviewOn) { + m_playbackPreviewOn = true; + m_core->GetDoc()->setPlayBackPreviewState(true); + PlaybackPlay(); + } +} + +void CStudioApp::playbackPreviewEnd() +{ + m_core->GetDoc()->setPlayBackPreviewState(false); + m_playbackPreviewOn = false; + PlaybackStop(); +} + +bool CStudioApp::isPlaybackPreviewOn() const +{ + return m_playbackPreviewOn; +} + +/** + * Handles the Save command + * This will save the file, if the file has not been saved before this will + * do a save as operation. + * @param autosave set true if triggering an autosave. + * @return true if the file was successfully saved. + */ +bool CStudioApp::OnSave(bool autosave) +{ + Qt3DSFile theCurrentDoc = m_core->GetDoc()->GetDocumentPath(); + if (!theCurrentDoc.IsFile()) { + return false; + } else if (!theCurrentDoc.CanWrite()) { + m_dialogs->DisplaySavingPresentationFailed(); + return false; + } else { + // Compose autosave filename (insert _autosave before extension) + QString autosaveFile = theCurrentDoc.GetPath().toQString(); + int insertionPoint = autosaveFile.lastIndexOf(QLatin1String(".uip")); + autosaveFile.insert(insertionPoint, QStringLiteral("_autosave")); + + if (autosave) { + // Set the copy flag to avoid changing actual document name & history + m_core->OnSaveDocument(autosaveFile, true); + } else { + m_core->OnSaveDocument(theCurrentDoc.GetAbsolutePath().toQString()); + // Delete previous autosave file + QFile::remove(autosaveFile); + } + + return true; + } +} + +/** + * This will prompt the user for a location and name to save a copy of the project folder. + * Saving under the current project is not possible. + */ +void CStudioApp::onProjectSaveAs() +{ + if (PerformSavePrompt()) { + const QString newProj = m_dialogs->GetSaveAsChoice(QObject::tr("Save Project As"), + true, true); + if (!newProj.isEmpty()) { + // Copy the project + const QString currentProj = GetCore()->getProjectFile().getProjectPath(); + if (CFilePath::copyFolder(currentProj, newProj)) { + // Prompt whether to open the new project + QMessageBox box(m_pMainWnd); + box.setWindowTitle(QObject::tr("Project copy saved successfully")); + box.setText(QObject::tr("Continue working with the original project?")); + box.setIcon(QMessageBox::Question); + box.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + box.setButtonText(QMessageBox::No, QObject::tr("No, open the one I just saved")); + box.setDefaultButton(QMessageBox::Yes); + int choice = box.exec(); + if (choice == QMessageBox::No) { + const QString newProjDoc = newProj + + QDir::cleanPath(GetCore()->GetDoc()->GetDocumentPath()).mid( + currentProj.length()); + QTimer::singleShot(0, [this, newProjDoc]() { + OnLoadDocument(newProjDoc); + }); + } + } else { + const QString title(QObject::tr("Project save failed")); + const QString warning = QObject::tr("Failed to save the project as %1.") + .arg(newProj); + QMessageBox::warning(m_pMainWnd, title, warning); + } + } + } +} + +void CStudioApp::SetAutosaveEnabled(bool enabled) +{ + if (enabled) + m_autosaveTimer->start(); + else + m_autosaveTimer->stop(); +} + +void CStudioApp::SetAutosaveInterval(int interval) +{ + m_autosaveTimer->setInterval(interval * 1000); +} + +/** + * Call to load a new document. + * There should not be a currently active document when this is called. + * @param inDocument the path to the UIP file to be loaded. + * @param inShowStartupDialogOnError true to show startup dialog if loading document is error + * @return true if loading was successful + */ +bool CStudioApp::OnLoadDocument(const QString &inDocument, bool inShowStartupDialogOnError) +{ + bool theLoadResult = false; + QString theLoadErrorParameter; + QString theErrorText; + QString loadFile = resolvePresentationFile(inDocument); + QFileInfo loadFileInfo(loadFile); + + m_core->GetDispatch()->FireOnProgressBegin(QObject::tr("Loading "), loadFileInfo.fileName()); + + // Make sure scene is visible + if (m_views) + m_views->getMainFrame()->showScene(); + + try { + m_core->getProjectFile().initProjectFile(loadFile); + OnLoadDocumentCatcher(loadFile); + m_core->GetDispatch()->FireOnOpenDocument(loadFile, true); + // Loading was successful + theLoadResult = true; + } catch (ProjectFileNotFoundException &) { + theErrorText = tr("Project file was not found"); + // No project file (.uia) was found + } catch (CUnsupportedFileFormatException &) { + theErrorText = tr("The file could not be opened. It is either invalid or was made with an " + "old version of Studio."); + // We've encountered a file format that is older than the current, OR + // corrupt files, unsupported file formats and illegal types. + } catch (CInvalidFileFormatException &) { + theErrorText = tr("The file could not be opened. The file format is invalid."); + } catch (CLoadReferencedFileException &inError) { + // referenced files (e.g. Data Files) failed to load + theErrorText = tr("%1 failed to load due to invalid referenced file: %2.").arg( + loadFileInfo.completeBaseName(), inError.GetFilePath()); + const QString theDesc = inError.GetDescription(); + if (!theDesc.isEmpty()) { + // append any description is provided + theErrorText += QStringLiteral("\n") + inError.GetDescription(); + } + } catch (CIOException &) { // provide specific error message if possible + if (!loadFileInfo.exists()) + theLoadErrorParameter = tr(" does not exist."); + qCCritical(qt3ds::INTERNAL_ERROR) + << "Failed to load document, IO error (file may be unreadable or nonexistent)"; + } catch (...) { + qCCritical(qt3ds::INTERNAL_ERROR) << "Failed to load document, unknown error"; + // We don't know exactly what went wrong during a load, but let studio 'fail gracefully'. + } + + if (!theErrorText.isEmpty()) { + qCCritical(qt3ds::INTERNAL_ERROR) << "Failed to load document: " + << theErrorText; + } + + m_core->GetDispatch()->FireOnProgressEnd(); + + // load fail + if (!theLoadResult) { +#if (defined Q_OS_MACOS) + m_fileOpenEvent = false; +#endif + if (!theErrorText.isEmpty()) { + m_dialogs->DisplayKnownErrorDialog(theErrorText); + } else { + m_dialogs->DisplayLoadingPresentationFailed(loadFileInfo, inDocument, + theLoadErrorParameter); + } + + m_core->GetDispatch()->FireOnOpenDocument(loadFile, false); + + // Show startup dialog + if (inShowStartupDialogOnError) { + if (!showStartupDialog()) + qApp->quit(); + } + } else { + m_dialogs->ResetSettings(loadFile); + if (m_core->GetDoc()->ensureActiveCamera()) + m_dialogs->DisplayMessageBox( + tr("More than one camera active"), + tr("Layer can only have one active camera. " + "Additional cameras were set to inactive."), + Qt3DSMessageBox::ICON_WARNING, false); + + m_core->getProjectFile().updateDocPresentationId(); + m_core->getProjectFile().loadSubpresentationsAndDatainputs(m_subpresentations, + m_dataInputDialogItems); + m_core->getProjectFile().loadVariants(); + GetViews()->getMainFrame()->getSlideView()->refreshVariants(); + getRenderer().RegisterSubpresentations(m_subpresentations); + + m_authorZoom = false; + + m_core->GetDispatch()->FireAuthorZoomChanged(); + verifyDatainputBindings(); + checkDeletedDatainputs(true); + } + + return theLoadResult; +} + +void CStudioApp::saveDataInputsToProjectFile() +{ + m_core->getProjectFile().ensureProjectFile(); + + QDomDocument doc; + QSaveFile file(m_core->getProjectFile().getProjectFilePath()); + if (!StudioUtils::openDomDocumentSave(file, doc)) + return; + + QDomElement assetsNode = doc.documentElement().firstChildElement(QStringLiteral("assets")); + + if (!assetsNode.isNull()) { + // remove old dataInput nodes + for (int i = assetsNode.childNodes().count() - 1; i >= 0; --i) { + QDomNode node_i = assetsNode.childNodes().at(i); + if (node_i.nodeName() == QLatin1String("dataInput")) + assetsNode.removeChild(node_i); + } + + // add the new dataInputs + for (CDataInputDialogItem *item : qAsConst(m_dataInputDialogItems)) { + QDomElement diNode = doc.createElement(QStringLiteral("dataInput")); + diNode.setAttribute(QStringLiteral("name"), item->name); + + if (item->type == EDataType::DataTypeRangedNumber) { + diNode.setAttribute(QStringLiteral("type"), QStringLiteral("Ranged Number")); + diNode.setAttribute(QStringLiteral("min"), item->minValue); + diNode.setAttribute(QStringLiteral("max"), item->maxValue); + } else if (item->type == EDataType::DataTypeString) { + diNode.setAttribute(QStringLiteral("type"), QStringLiteral("String")); + } else if (item->type == EDataType::DataTypeFloat) { + diNode.setAttribute(QStringLiteral("type"), QStringLiteral("Float")); + } else if (item->type == EDataType::DataTypeBoolean) { + diNode.setAttribute(QStringLiteral("type"), QStringLiteral("Boolean")); + } else if (item->type == EDataType::DataTypeVector4) { + diNode.setAttribute(QStringLiteral("type"), QStringLiteral("Vector4")); + } else if (item->type == EDataType::DataTypeVector3) { + diNode.setAttribute(QStringLiteral("type"), QStringLiteral("Vector3")); + } else if (item->type == EDataType::DataTypeVector2) { + diNode.setAttribute(QStringLiteral("type"), QStringLiteral("Vector2")); + } else if (item->type == EDataType::DataTypeVariant) { + diNode.setAttribute(QStringLiteral("type"), QStringLiteral("Variant")); + } + + QHashIterator<QString, QString> it(item->metadata); + QString metadataStr; + while (it.hasNext()) { + it.next(); + metadataStr.append(it.key() + QLatin1Char('$') + it.value() + QLatin1Char('$')); + } + metadataStr.chop(1); + + diNode.setAttribute(QStringLiteral("metadata"), metadataStr.trimmed()); + + assetsNode.appendChild(diNode); + } + StudioUtils::commitDomDocumentSave(file, doc); + } +} + +QString CStudioApp::getMostRecentDirectory() const +{ + QFileInfo mostRecentDirectory = QFileInfo(QStringLiteral(".")); + if (m_views) { + CRecentItems *recentItems = m_views->getMainFrame()->GetRecentItems(); + if (recentItems->GetItemCount() > 0) { + mostRecentDirectory + = QFileInfo(recentItems->GetItem(0)).path(); + } + } + return mostRecentDirectory.absoluteFilePath(); +} + +QString CStudioApp::getMostRecentProjectParentDir() const +{ + QString parentDirectory(QStringLiteral(".")); + if (m_views) { + CRecentItems *recentItems = m_views->getMainFrame()->GetRecentItems(); + if (recentItems->GetItemCount() > 0) { + QString mostRecentPresentation = recentItems->GetItem(0); + QFileInfo projectFile(PresentationFile::findProjectFile(mostRecentPresentation)); + if (!projectFile.exists()) + projectFile.setFile(mostRecentPresentation); + if (!projectFile.exists()) { + parentDirectory = QStandardPaths::writableLocation( + QStandardPaths::DocumentsLocation); + } else { + QDir dir = projectFile.absoluteDir(); + dir.cdUp(); + parentDirectory = dir.absolutePath(); + } + } + } + return parentDirectory; +} + +bool CStudioApp::isQmlStream(const QString &fileName) +{ + bool retval = false; + if (m_qmlStreamMap.contains(fileName)) { + retval = m_qmlStreamMap[fileName]; + } else { + if (!fileName.endsWith(QLatin1String(".qml")) || !QFileInfo(fileName).exists()) { + return false; // Don't pollute the map with non-qml or nonexistent files + } else { + QQmlApplicationEngine qmlEngine(fileName); + if (qmlEngine.rootObjects().size() > 0) { + const char *rootClassName = qmlEngine.rootObjects().at(0) + ->metaObject()->superClass()->className(); + retval = strcmp(rootClassName, "Q3DStudio::Q3DSQmlBehavior") != 0; + } + } + m_qmlStreamMap.insert(fileName, retval); + } + return retval; +} + +/** + * Called by OnLoadDocument, to allow the error reporting to be inserted. + * Because of the nature of the error reporting, OnLoadDocument has to have + * a certain structure that limits it (C type variables, no object destructors). + */ +void CStudioApp::OnLoadDocumentCatcher(const QString &inDocument) +{ + { + CDispatchDataModelNotificationScope __scope(*m_core->GetDispatch()); + + // CloseDocument() clears all the OpenGL buffers so it needs the correct context + getRenderer().MakeContextCurrent(); + m_core->GetDoc()->CloseDocument(); + getRenderer().ReleaseContext(); + + m_core->GetDoc()->LoadDocument(inDocument); + } + + // Make sure the client scene is resized properly + if (m_views) + m_views->recheckMainframeSizingMode(); +} + +void CStudioApp::OnFileOpen() +{ + if (PerformSavePrompt()) { + QString theFile = m_dialogs->GetFileOpenChoice(getMostRecentDirectory()); + if (!theFile.isEmpty()) + OnLoadDocument(theFile); + } +} + +/** + * Create a new project + * this creates the project file (.uia), asset folders, and a default .uip file + */ +QString CStudioApp::OnProjectNew() +{ + if (PerformSavePrompt()) { + QString theFile = m_dialogs->GetNewDocumentChoice(getMostRecentProjectParentDir(), true); + if (!theFile.isEmpty()) { + if (!m_core->OnNewDocument(theFile, true)) + showInvalidFilenameWarning(); + } + } + return {}; +} + +/** + * Closes current project + */ +void CStudioApp::OnProjectClose() +{ + if (PerformSavePrompt()) { + // CloseDocument() clears all the OpenGL buffers so it needs the correct context + getRenderer().MakeContextCurrent(); + m_core->GetDoc()->CloseDocument(); + getRenderer().ReleaseContext(); + m_pMainWnd->setActionsEnabledStatus(false); + m_pMainWnd->setWindowTitle(QObject::tr("Qt 3D Studio")); + showStartupDialog(); + } +} + + +/** + * Create a new presentation + * this creates a .uip file + */ +void CStudioApp::OnFileNew() +{ + if (PerformSavePrompt()) { + QString theFile = m_dialogs->GetNewDocumentChoice(getMostRecentDirectory(), false); + if (!theFile.isEmpty() && !m_core->OnNewDocument(theFile, false)) + showInvalidFilenameWarning(); + } +} + +bool CStudioApp::IsAuthorZoom() const +{ + return m_authorZoom; +} + +bool CStudioApp::isOnProgress() const +{ + return m_isOnProgress; +} + +void CStudioApp::SetAuthorZoom(bool inZoom) +{ + if (m_authorZoom != inZoom) { + m_authorZoom = inZoom; + m_core->GetDispatch()->FireAuthorZoomChanged(); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// These commands come over the dispatch from inside the core. The core doesn't +// have access to the CMsgRouter at the moment, so this relays the message. +void CStudioApp::OnAsynchronousCommand(CCmd *inCmd) +{ + CMsgRouter::GetInstance()->SendCommand(inCmd, m_core); +} + +void CStudioApp::OnDisplayAppStatus(const QString &inStatusMsg) +{ + Q_UNUSED(inStatusMsg) + // Do nothing, it was used to show this in the status bar +} + +void CStudioApp::OnProgressBegin(const QString &inActionText, + const QString &inAdditionalText) +{ + m_isOnProgress = true; + m_dialogs->DisplayProgressScreen(inActionText, inAdditionalText); +} + +void CStudioApp::OnProgressEnd() +{ + m_dialogs->DestroyProgressScreen(); + QTimer::singleShot(0, [this]() { + m_isOnProgress = false; + }); +} + +void CStudioApp::OnAssetDeleteFail() +{ + m_dialogs->DisplayAssetDeleteFailed(); +} + +void CStudioApp::OnPasteFail() +{ + m_dialogs->DisplayPasteFailed(); +} + +void CStudioApp::OnBuildconfigurationFileParseFail(const QString &inMessage) +{ + m_dialogs->DisplayMessageBox(tr("Build Configurations Error"), inMessage, + Qt3DSMessageBox::ICON_ERROR, false); +} + +void CStudioApp::OnSaveFail(bool inKnownError) +{ + qCCritical(qt3ds::INTERNAL_ERROR) << "Failed to save project: " + << (inKnownError ? "KnownError" : "UnknownError"); + if (inKnownError) + m_dialogs->DisplaySavingPresentationFailed(); + else + m_dialogs->DisplayKnownErrorDialog(tr("Unknown error encountered while saving.")); +} + +void CStudioApp::OnErrorFail(const QString &inText) +{ + qCCritical(qt3ds::INTERNAL_ERROR) << inText; + m_dialogs->DisplayMessageBox(tr("Qt 3D Studio"), inText, Qt3DSMessageBox::ICON_ERROR, false); +} + +void CStudioApp::OnRefreshResourceFail(const QString &inResourceName, const QString &inDescription) +{ + qCCritical(qt3ds::INTERNAL_ERROR) << "Failed to refresh resource: " + << inResourceName; + qCCritical(qt3ds::INTERNAL_ERROR) << inDescription; + m_dialogs->DisplayRefreshResourceFailed(inResourceName, inDescription); +} + +void CStudioApp::OnNewPresentation() +{ + m_core->GetDoc()->GetStudioSystem()->GetAnimationSystem()->SetAutoKeyframe( + CStudioPreferences::isAutosetKeyframesOn()); + qCInfo(qt3ds::TRACE_INFO) << "New Presentation: " + << m_core->GetDoc()->GetDocumentPath(); +} + +void CStudioApp::OnPresentationModifiedExternally() +{ + int theUserChoice = m_dialogs->DisplayChoiceBox( + tr("Warning!"), + tr("This project has changed on disk. Do you want to reload it?"), + Qt3DSMessageBox::ICON_WARNING); + if (theUserChoice == IDYES) { + QString theCurrentDoc = m_core->GetDoc()->GetDocumentPath(); + OnLoadDocument(theCurrentDoc); + } +} + +// Converts a renderable path to the format used in the SubPresentationRecord struct +// filePath can be absolute or relative to either presentation or project +QString CStudioApp::getRenderablePath(const QString &filePath) const +{ + QString renderablePath; + QDir projectDir(m_core->getProjectFile().getProjectPath()); + const QString projectPath = QDir::cleanPath(projectDir.absolutePath()); + int index = projectPath.length() + 1; + QFileInfo fi(filePath); + if (fi.isAbsolute()) { + renderablePath = filePath.mid(index); + } else { + QFileInfo presFile(m_core->GetDoc()->GetDocumentPath()); + QDir presDir(presFile.absoluteDir()); + QString checkFile = QDir::cleanPath(presDir.absoluteFilePath(filePath)); + if (!QFileInfo(checkFile).exists()) { + checkFile = QDir::cleanPath(projectDir.absoluteFilePath(filePath)); + if (!QFileInfo(checkFile).exists()) + return {}; + } + renderablePath = checkFile.mid(index); + } + return renderablePath; +} + +// Get the presentation id, returns an empty string for qml streams +// filePath can be absolute or relative to either presentation or project +QString CStudioApp::getPresentationId(const QString &filePath) const +{ + QString renderablePath = getRenderablePath(filePath); + for (SubPresentationRecord r : qAsConst(m_subpresentations)) { + if (r.m_type == QLatin1String("presentation") && r.m_argsOrSrc == renderablePath) + return r.m_id; + } + return {}; +} + +// Get the qml stream id, returns an empty string for presentations +// filePath can be absolute or relative to either presentation or project +QString CStudioApp::getQmlId(const QString &filePath) const +{ + QString renderablePath = getRenderablePath(filePath); + for (SubPresentationRecord r : qAsConst(m_subpresentations)) { + if (r.m_type == QLatin1String("presentation-qml") && r.m_argsOrSrc == renderablePath) + return r.m_id; + } + return {}; +} + +// Get the renderable id for a file path. +// filePath can be absolute or relative to either presentation or project +QString CStudioApp::getRenderableId(const QString &filePath) const +{ + QString renderablePath = getRenderablePath(filePath); + for (SubPresentationRecord r : qAsConst(m_subpresentations)) { + if (r.m_argsOrSrc == renderablePath) + return r.m_id; + } + return {}; +} + +QString CStudioApp::getRenderableAbsolutePath(const QString &renderableId) const +{ + for (SubPresentationRecord r : qAsConst(m_subpresentations)) { + if (r.m_id == renderableId) { + QDir projectDir(m_core->getProjectFile().getProjectPath()); + return QDir::cleanPath(projectDir.absoluteFilePath(r.m_argsOrSrc)); + } + } + return {}; +} + +// Returns renderable size in pixels. +QSize CStudioApp::getRenderableSize(const QString &renderableId) +{ + for (int i = 0; i < m_subpresentations.size(); ++i) { + SubPresentationRecord &r = m_subpresentations[i]; + if (r.m_id == renderableId) { + if (!r.m_size.isValid()) { + QDir projectDir(m_core->getProjectFile().getProjectPath()); + QString path = QDir::cleanPath(projectDir.absoluteFilePath(r.m_argsOrSrc)); + QString type = r.m_type; + if (type == QLatin1String("presentation")) { + r.m_size = PresentationFile::readSize(path); + } else { // QML stream + QQmlApplicationEngine qmlEngine(path); + if (qmlEngine.rootObjects().size() > 0) { + QQuickItem *item = qobject_cast<QQuickItem *>(qmlEngine.rootObjects().at(0)); + if (item) + r.m_size = QSize(qRound(item->width()), qRound(item->height())); + } + } + } + return r.m_size; + } + } + return {}; +} + +void CStudioApp::OnUndefinedDatainputsFail( + const QMultiMap<QString, QPair<qt3dsdm::Qt3DSDMInstanceHandle, + qt3dsdm::Qt3DSDMPropertyHandle>> *map, + bool askFromUser) +{ + bool res = askFromUser ? m_dialogs->DisplayUndefinedDatainputDlg(map) : true; + + // Delete invalid datainput bindings if user prompted so, or silently if not asked. + if (res) { + m_core->GetDoc()->RemoveDatainputBindings(map); + // clear commands as we do not want to create undo point + // for automatic datainput deletion + m_core->GetCmdStack()->Clear(); + } +} + +void CStudioApp::toggleEyeball() +{ + CDoc *doc = m_core->GetDoc(); + if (doc->getSelectedInstancesCount() > 0) { + qt3dsdm::Qt3DSDMPropertyHandle property + = doc->GetStudioSystem()->GetClientDataModelBridge()->GetSceneAsset().m_Eyeball; + SCOPED_DOCUMENT_EDITOR(*doc, tr("Visibility Toggle")) + ->toggleBoolPropertyOnSelected(property); + } +} + +void CStudioApp::toggleShy() +{ + CDoc *doc = m_core->GetDoc(); + if (doc->getSelectedInstancesCount() > 0) { + qt3dsdm::Qt3DSDMPropertyHandle property + = doc->GetStudioSystem()->GetClientDataModelBridge()->GetSceneAsset().m_Shy; + SCOPED_DOCUMENT_EDITOR(*doc, tr("Shy Toggle")) + ->toggleBoolPropertyOnSelected(property); + } +} + +void CStudioApp::toggleLocked() +{ + CDoc *doc = m_core->GetDoc(); + if (doc->getSelectedInstancesCount() > 0) { + qt3dsdm::Qt3DSDMPropertyHandle property + = doc->GetStudioSystem()->GetClientDataModelBridge()->GetSceneAsset().m_Locked; + SCOPED_DOCUMENT_EDITOR(*doc, tr("Locked Toggle")) + ->toggleBoolPropertyOnSelected(property); + + // Since you are not supposed to be able to select locked objects, + // we just assume anything toggled was actually locked and deselect everything + doc->DeselectAllItems(); + } +} + +void CStudioApp::showPresentationIdUniqueWarning() +{ + m_dialogs->DisplayMessageBox(tr("Warning"), + tr("Presentation Id must be unique."), + Qt3DSMessageBox::ICON_WARNING, false); +} + +void CStudioApp::showPresentationIdEmptyWarning() +{ + m_dialogs->DisplayMessageBox(tr("Warning"), + tr("Presentation Id must not be empty."), + Qt3DSMessageBox::ICON_WARNING, false); +} + +void CStudioApp::showShaderCompileError(const QString &error) +{ + m_dialogs->DisplayMessageBox(tr("Warning"), + tr("Shader compile error.\n") + error, + Qt3DSMessageBox::ICON_WARNING, false); +} + +void CStudioApp::showInvalidFilenameWarning() +{ + m_dialogs->DisplayMessageBox(tr("Invalid filename"), + tr("The filename given was invalid."), + Qt3DSMessageBox::ICON_WARNING, false); +} + +void CStudioApp::checkDeletedDatainputs(bool askFromUser) +{ + QMultiMap<QString, QPair<qt3dsdm::Qt3DSDMInstanceHandle, qt3dsdm::Qt3DSDMPropertyHandle>> *map; + map = new QMultiMap<QString, QPair<qt3dsdm::Qt3DSDMInstanceHandle, + qt3dsdm::Qt3DSDMPropertyHandle>>; + auto doc = m_core->GetDoc(); + // Update datainputs for the currently open presentation + doc->UpdateDatainputMap(map); + + if (!map->empty()) + m_core->GetDispatch()->FireOnUndefinedDatainputsFail(map, askFromUser); + + // Update allowed property types for datainput-controlled properties + // in subpresentations. It is ok to do this once + // at the project opening, as the assumption is that subpresentation files + // do not change while we are editing currently open presentation. + + // Clear the old subpresentation binding info only. + for (auto it : qAsConst(m_dataInputDialogItems)) + it->externalPresBoundTypes.clear(); + + const QMultiHash<QString, ProjectFile::DataInputOutputBinding> spDatainputs + = GetCore()->getProjectFile().getDiBindingtypesFromSubpresentations(); + QHash<QString, QHash<QString, qt3dsdm::DataModelDataType::Value>> + customPropertyTypes; // sourcepath, <propname, proptype> + // For datainput bindings in subpresentations we do not have specific + // instance and/or property handles. Get the datatype for property using + // the generic name string and leave instance/property handle empty. + for (auto sp = spDatainputs.cbegin(); sp != spDatainputs.cend(); ++sp) { + const QString propName = sp->propertyName; + CDataInputDialogItem *item = m_dataInputDialogItems.find(sp->dioName).value(); + QPair<qt3dsdm::DataModelDataType::Value, bool> spEntry; + if (propName == QLatin1String("@timeline")) { + spEntry.first = qt3dsdm::DataModelDataType::Value::RangedNumber; + spEntry.second = true; + } else if (propName == QLatin1String("@slide")) { + spEntry.first = qt3dsdm::DataModelDataType::Value::String; + spEntry.second = true; + } else { + qt3dsdm::DataModelDataType::Value theType = qt3dsdm::DataModelDataType::Value::None; + if (!sp->propertyDefinitionFile.isEmpty()) { + // Check property type from custom property definition file + if (!customPropertyTypes.contains(sp->propertyDefinitionFile)) { + QHash<QString, qt3dsdm::DataModelDataType::Value> propTypes; + QFile file(sp->propertyDefinitionFile); + file.open(QFile::Text | QFile::ReadOnly); + if (!file.isOpen()) { + qWarning() << file.errorString(); + } else { + QString xmlContent = file.readAll(); + if (sp->propertyDefinitionFile.endsWith(QLatin1String("qml"), + Qt::CaseInsensitive)) { + // Property defs are in comments in behavior scripts, and not in well + // formed XML format, so skip until start of the property definitions + // and extract properties under a fake root tag to avoid parsing issues + int start = xmlContent.indexOf(QLatin1String("/*[[")); + if (start != -1) { + int end = xmlContent.indexOf(QLatin1String("]]*/"), start); + QString tagged = QStringLiteral("<Behavior>\n"); + tagged.append(xmlContent.mid(start, end - start).trimmed()); + tagged.append(QLatin1String("</Behavior>\n")); + tagged.replace(QLatin1String("\r\n"), QLatin1String("\n")); + xmlContent = tagged; + } + } + QDomDocument domDoc; + domDoc.setContent(xmlContent); + QDomNodeList propElems + = domDoc.elementsByTagName(QStringLiteral("Property")); + for (int i = 0; i < propElems.count(); ++i) { + QDomElement elem = propElems.at(i).toElement(); + const QString metaName = elem.attribute(QStringLiteral("name")); + const QString metaType = elem.attribute(QStringLiteral("type")); + qt3dsdm::DataModelDataType::Value dataType; + if (metaType.isEmpty()) { + dataType = qt3dsdm::DataModelDataType::Value::Float; + } else { + QByteArray buf((metaType.size() + 1) * int(sizeof(wchar_t)),'\0'); + metaType.toWCharArray(reinterpret_cast<wchar_t *>(buf.data())); + qt3dsdm::CompleteMetaDataType::Enum typeValue; + WStrOps<qt3dsdm::CompleteMetaDataType::Enum> converter; + converter.StrTo(reinterpret_cast<const wchar_t *>( + buf.constData()), typeValue); + dataType = qt3dsdm::CompleteMetaDataType::ToDataType(typeValue); + } + propTypes.insert(metaName, dataType); + } + customPropertyTypes.insert(sp->propertyDefinitionFile, propTypes); + } + } + theType = customPropertyTypes[sp->propertyDefinitionFile].value( + propName, qt3dsdm::DataModelDataType::Value::None); + } + if (theType == qt3dsdm::DataModelDataType::Value::None) { + qt3dsimp::SImportComposerTypes theTypes; + qt3dsimp::SImportAsset &theAsset( + theTypes.GetImportAssetForType( + qt3dsdm::ComposerObjectTypes::ControllableObject)); + theType = theAsset.GetPropertyDataType(propName.toStdWString().c_str()); + } + spEntry.first = theType; + spEntry.second = false; + } + item->externalPresBoundTypes.insert(sp.key(), spEntry); + } +} + +void CStudioApp::verifyDatainputBindings() +{ + m_core->GetDoc()->getSceneEditor()->BeginAggregateOperation(); + bool res = m_core->GetDoc()->VerifyControlledProperties( + m_core->GetDoc()->GetActiveRootInstance()); + m_core->GetDoc()->getSceneEditor()->EndAggregateOperation(); + + if (!res) { + // we remove invalid control bindings directly without transaction, so + // we need to explicitly fire notification in order to update UI + m_core->GetDispatch()->FireEndDataModelNotifications(); + m_core->GetDoc()->SetModifiedFlag(true); + m_dialogs->DisplayMessageBox(tr("Invalid datainput usage in UIP file"), + tr("Some objects had invalid datainput control bindings." + " Invalid entries have been removed."), + Qt3DSMessageBox::ICON_WARNING, false); + } +} + +/** +* This will prompt the user for a new name for the duplicated presentation and copy the current +* presentation with that name in the same folder as the original. The new presentation is added +* to the project and gets autogenerated id. The new presentation is made current. +*/ +void CStudioApp::duplicatePresentation(const QString &presFile) +{ + QString thePresFile = presFile; + bool qmlStream = presFile.endsWith(QLatin1String(".qml")); + if (presFile.isEmpty()) + thePresFile = GetCore()->GetDoc()->GetDocumentPath(); + + if (qmlStream || PerformSavePrompt()) { + QFileInfo fi(thePresFile); + QString relativePresPath = QDir(GetCore()->getProjectFile().getProjectPath()) + .relativeFilePath(fi.absoluteFilePath()); + + EditPresentationIdDlg dlg(relativePresPath, + qmlStream ? EditPresentationIdDlg::DuplicateQmlStream + : EditPresentationIdDlg::DuplicatePresentation, + m_pMainWnd); + dlg.exec(); + const QString newPres = dlg.getDuplicateFile(); + if (!qmlStream && !newPres.isEmpty()) + OnLoadDocument(newPres); + } +} |