// Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "studiowelcomeplugin.h" #include "examplecheckout.h" #include "qdsnewdialog.h" #include #include #include #include #include #include #include #include "projectexplorer/target.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ProjectExplorer; using namespace Utils; namespace StudioWelcome { namespace Internal { static void openOpenProjectDialog() { const FilePath path = Core::DocumentManager::useProjectsDirectory() ? Core::DocumentManager::projectsDirectory() : FilePath(); const FilePaths files = Core::DocumentManager::getOpenFileNames("*.qmlproject", path); if (!files.isEmpty()) Core::ICore::openFiles(files, Core::ICore::None); } const char DO_NOT_SHOW_SPLASHSCREEN_AGAIN_KEY[] = "StudioSplashScreen"; const char DETAILED_USAGE_STATISTICS[] = "DetailedUsageStatistics"; const char STATISTICS_COLLECTION_MODE[] = "StatisticsCollectionMode"; const char NO_TELEMETRY[] = "NoTelemetry"; const char CRASH_REPORTER_SETTING[] = "CrashReportingEnabled"; QPointer s_viewWindow = nullptr; QPointer s_viewWidget = nullptr; static StudioWelcomePlugin *s_pluginInstance = nullptr; static Utils::FilePath getMainUiFileWithFallback() { const auto project = ProjectExplorer::ProjectManager::startupProject(); if (!project) return {}; if (!project->activeTarget()) return {}; const auto qmlBuildSystem = qobject_cast( project->activeTarget()->buildSystem()); if (!qmlBuildSystem) return {}; return qmlBuildSystem->getStartupQmlFileWithFallback(); } std::unique_ptr makeUserFeedbackSettings() { QStringList domain = QCoreApplication::organizationDomain().split(QLatin1Char('.')); std::reverse(domain.begin(), domain.end()); QString productId = domain.join('.'); if (!productId.isEmpty()) productId += "."; productId += QCoreApplication::applicationName(); QString organization; if (Utils::HostOsInfo::isMacHost()) { organization = QCoreApplication::organizationDomain().isEmpty() ? QCoreApplication::organizationName() : QCoreApplication::organizationDomain(); } else { organization = QCoreApplication::organizationName().isEmpty() ? QCoreApplication::organizationDomain() : QCoreApplication::organizationName(); } std::unique_ptr settings(new QSettings(organization, "UserFeedback." + productId)); settings->beginGroup("UserFeedback"); return settings; } class UsageStatisticPluginModel : public QObject { Q_OBJECT Q_PROPERTY(bool usageStatisticEnabled MEMBER m_usageStatisticEnabled NOTIFY usageStatisticChanged) Q_PROPERTY(bool crashReporterEnabled MEMBER m_crashReporterEnabled NOTIFY crashReporterEnabledChanged) Q_PROPERTY(QString version MEMBER m_versionString CONSTANT) public: explicit UsageStatisticPluginModel(QObject *parent = nullptr) : QObject(parent) { m_versionString = Utils::appInfo().displayVersion; setupModel(); } void setupModel() { auto settings = makeUserFeedbackSettings(); QVariant value = settings->value(STATISTICS_COLLECTION_MODE); m_usageStatisticEnabled = value.isValid() && value.toString() == DETAILED_USAGE_STATISTICS; m_crashReporterEnabled = Core::ICore::settings()->value(CRASH_REPORTER_SETTING, false).toBool(); emit usageStatisticChanged(); emit crashReporterEnabledChanged(); } Q_INVOKABLE void setCrashReporterEnabled(bool b) { if (m_crashReporterEnabled == b) return; Core::ICore::settings()->setValue(CRASH_REPORTER_SETTING, b); Core::ICore::askForRestart(tr("The change will take effect after restart.")); setupModel(); } Q_INVOKABLE void setTelemetryEnabled(bool b) { if (m_usageStatisticEnabled == b) return; auto settings = makeUserFeedbackSettings(); settings->setValue(STATISTICS_COLLECTION_MODE, b ? DETAILED_USAGE_STATISTICS : NO_TELEMETRY); Core::ICore::askForRestart(tr("The change will take effect after restart.")); setupModel(); } signals: void usageStatisticChanged(); void crashReporterEnabledChanged(); private: bool m_usageStatisticEnabled = false; bool m_crashReporterEnabled = false; QString m_versionString; }; class ProjectModel : public QAbstractListModel { Q_OBJECT public: enum { FilePathRole = Qt::UserRole + 1, PrettyFilePathRole, PreviewUrl, TagData, Description }; Q_PROPERTY(bool communityVersion MEMBER m_communityVersion NOTIFY communityVersionChanged) Q_PROPERTY(bool enterpriseVersion MEMBER m_enterpriseVersion NOTIFY enterpriseVersionChanged) Q_PROPERTY(int count READ count NOTIFY countChanged) explicit ProjectModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; int count() { return ProjectExplorer::ProjectExplorerPlugin::recentProjects().count(); } Q_INVOKABLE void createProject() { QTimer::singleShot(0, this, []() { ProjectExplorer::ProjectExplorerPlugin::openNewProjectDialog(); }); } Q_INVOKABLE void openProject() { QTimer::singleShot(0, this, []() { openOpenProjectDialog(); }); } Q_INVOKABLE void openProjectAt(int row) { if (m_blockOpenRecent) return; m_blockOpenRecent = true; const FilePath projectFile = FilePath::fromVariant( data(index(row, 0), ProjectModel::FilePathRole)); if (projectFile.exists()) { const OpenProjectResult result = ProjectExplorer::ProjectExplorerPlugin::openProject(projectFile); if (!result && !result.alreadyOpen().isEmpty()) { const auto fileToOpen = getMainUiFileWithFallback(); if (!fileToOpen.isEmpty() && fileToOpen.exists() && !fileToOpen.isDir()) { Core::EditorManager::openEditor(fileToOpen, Utils::Id()); } }; } delayedResetProjects(); } Q_INVOKABLE void removeFromRecentProjects(int row) { if (m_blockOpenRecent) return; m_blockOpenRecent = true; const FilePath projectFile = FilePath::fromVariant( data(index(row, 0), ProjectModel::FilePathRole)); if (projectFile.exists()) ProjectExplorer::ProjectExplorerPlugin::removeFromRecentProjects(projectFile); resetProjects(); } Q_INVOKABLE void clearRecentProjects() { if (m_blockOpenRecent) return; m_blockOpenRecent = true; ProjectExplorer::ProjectExplorerPlugin::clearRecentProjects(); resetProjects(); } Q_INVOKABLE int get(int) { return -1; } Q_INVOKABLE void showHelp() { QDesktopServices::openUrl( QUrl("qthelp://org.qt-project.qtdesignstudio/doc/studio-getting-started.html")); } Q_INVOKABLE void openExample(const QString &examplePath, const QString &exampleName, const QString &formFile, const QString &explicitQmlproject) { QTC_ASSERT(!exampleName.isEmpty(), return ); QmlDesigner::QmlDesignerPlugin::emitUsageStatistics("exampleOpened:" + exampleName); const QString exampleFolder = examplePath + "/" + exampleName + "/"; QString projectFile = exampleFolder + exampleName + ".qmlproject"; if (!explicitQmlproject.isEmpty()) projectFile = exampleFolder + explicitQmlproject; ProjectExplorer::ProjectExplorerPlugin::openProjectWelcomePage(FilePath::fromString(projectFile)); const QString qmlFile = QFileInfo(projectFile).dir().absolutePath() + "/" + formFile; // This timer should be replaced with a signal send from project loading QTimer::singleShot(1000, this, [qmlFile]() { Core::EditorManager::openEditor(Utils::FilePath::fromString(qmlFile)); }); } Q_INVOKABLE void openExample(const QString &example, const QString &formFile, const QString &url, const QString &explicitQmlproject, const QString &tempFile, const QString &completeBaseName) { Q_UNUSED(url) Q_UNUSED(explicitQmlproject) Q_UNUSED(tempFile) Q_UNUSED(completeBaseName) const FilePath projectFile = Core::ICore::resourcePath("examples") / example / (example + ".qmlproject"); ProjectExplorer::ProjectExplorerPlugin::openProjectWelcomePage(projectFile); const FilePath qmlFile = Core::ICore::resourcePath("examples") / example / formFile; Core::EditorManager::openEditor(qmlFile); } Q_INVOKABLE bool exampleVersionOk(const QString &exampleVersion) { if (exampleVersion.isEmpty()) return true; // Split versions into parts (major, minor, patch) QStringList qdsVersionParts = QCoreApplication::applicationVersion().split('.'); QStringList exampleVersionParts = exampleVersion.split('.'); // Fill missing parts with zeros while (qdsVersionParts.size() < 3) qdsVersionParts.append("0"); while (exampleVersionParts.size() < 3) exampleVersionParts.append("0"); int qdsMajor = qdsVersionParts.at(0).toInt(); int qdsMinor = qdsVersionParts.at(1).toInt(); int qdsPatch = qdsVersionParts.at(2).toInt(); int exMajor = exampleVersionParts.at(0).toInt(); int exMinor = exampleVersionParts.at(1).toInt(); int exPatch = exampleVersionParts.at(2).toInt(); return QT_VERSION_CHECK(exMajor, exMinor, exPatch) <= QT_VERSION_CHECK(qdsMajor, qdsMinor, qdsPatch); } public slots: void resetProjects(); void delayedResetProjects(); signals: void communityVersionChanged(); void enterpriseVersionChanged(); void countChanged(); private: void setupVersion(); bool m_communityVersion = true; bool m_enterpriseVersion = false; bool m_blockOpenRecent = false; }; void ProjectModel::setupVersion() { QmlDesigner::FoundLicense license = QmlDesigner::checkLicense(); m_communityVersion = license == QmlDesigner::FoundLicense::community; m_enterpriseVersion = license == QmlDesigner::FoundLicense::enterprise; } ProjectModel::ProjectModel(QObject *parent) : QAbstractListModel(parent) { connect(ProjectExplorer::ProjectExplorerPlugin::instance(), &ProjectExplorer::ProjectExplorerPlugin::recentProjectsChanged, this, &ProjectModel::delayedResetProjects); connect(this, &QAbstractListModel::modelReset, this, &ProjectModel::countChanged); setupVersion(); } int ProjectModel::rowCount(const QModelIndex &) const { return ProjectExplorer::ProjectExplorerPlugin::recentProjects().count(); } static QString getQDSVersion(const FilePath &projectFilePath) { const QString qdsVersion = QmlProjectManager::ProjectFileContentTools::qdsVersion( projectFilePath); return ProjectModel::tr("Created with Qt Design Studio version: %1").arg(qdsVersion); } static QString fromCamelCase(const QString &s) { const QRegularExpression regExp1 {"(.)([A-Z][a-z]+)"}; const QRegularExpression regExp2 {"([a-z0-9])([A-Z])"}; QString result = s; result.replace(regExp1, "\\1 \\2"); result.replace(regExp2, "\\1 \\2"); result = result.left(1).toUpper() + result.mid(1); return result; } static QString resolutionFromConstants(const FilePath &projectFilePath) { QmlProjectManager::ProjectFileContentTools::Resolution res = QmlProjectManager::ProjectFileContentTools::resolutionFromConstants( projectFilePath); if (res.width > 0 && res.height > 0) return ProjectModel::tr("Resolution: %1x%2").arg(res.width).arg(res.height); return {}; } static QString description(const FilePath &projectFilePath) { const QString created = ProjectModel::tr("Created: %1").arg( projectFilePath.toFileInfo().fileTime(QFileDevice::FileBirthTime).toString()); const QString lastEdited = ProjectModel::tr("Last Edited: %1").arg( projectFilePath.toFileInfo().fileTime(QFileDevice::FileModificationTime).toString()); return fromCamelCase(projectFilePath.baseName()) + "\n\n" + created + "\n" + lastEdited + "\n" + resolutionFromConstants(projectFilePath) + "\n" + getQDSVersion(projectFilePath); } static QString tags(const FilePath &projectFilePath) { QStringList ret; const QString defaultReturn = "content/App.qml"; Utils::FileReader reader; if (!reader.fetch(projectFilePath)) return defaultReturn; const QByteArray data = reader.data(); const bool isQt6 = data.contains("qt6Project: true"); const bool isMcu = data.contains("qtForMCUs: true"); if (isMcu) ret.append("Qt For MCU"); else if (isQt6) ret.append("Qt 6"); else ret.append("Qt 5"); return ret.join(","); } QVariant ProjectModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= ProjectExplorer::ProjectExplorerPlugin::recentProjects().count()) { return {}; } const ProjectExplorer::RecentProjectsEntry data = ProjectExplorer::ProjectExplorerPlugin::recentProjects().at(index.row()); switch (role) { case Qt::DisplayRole: return data.displayName; break; case FilePathRole: return data.filePath.toVariant(); case PrettyFilePathRole: return data.filePath.absolutePath().withTildeHomePath(); case PreviewUrl: return QVariant( QStringLiteral("image://project_preview/") + QmlProjectManager::ProjectFileContentTools::appQmlFile(data.filePath)); case TagData: return tags(data.filePath); case Description: return description(data.filePath); default: return QVariant(); } return QVariant(); } QHash ProjectModel::roleNames() const { QHash roleNames; roleNames[Qt::DisplayRole] = "displayName"; roleNames[FilePathRole] = "filePath"; roleNames[PrettyFilePathRole] = "prettyFilePath"; roleNames[PreviewUrl] = "previewUrl"; roleNames[TagData] = "tagData"; roleNames[Description] = "description"; return roleNames; } void ProjectModel::resetProjects() { beginResetModel(); endResetModel(); m_blockOpenRecent = false; } void ProjectModel::delayedResetProjects() { QTimer::singleShot(2000, this, [this] { beginResetModel(); endResetModel(); m_blockOpenRecent = false; }); } class WelcomeMode : public Core::IMode { Q_OBJECT public: WelcomeMode(); ~WelcomeMode() override; private: void setupQuickWidget(const QString &welcomePagePath); void createQuickWidget(); QQuickWidget *m_quickWidget = nullptr; QWidget *m_modeWidget = nullptr; DataModelDownloader *m_dataModelDownloader = nullptr; }; void StudioWelcomePlugin::closeSplashScreen() { Utils::CheckableDecider(DO_NOT_SHOW_SPLASHSCREEN_AGAIN_KEY).doNotAskAgain(); if (!s_viewWindow.isNull()) s_viewWindow->deleteLater(); if (!s_viewWidget.isNull()) s_viewWidget->deleteLater(); } StudioWelcomePlugin::StudioWelcomePlugin() { s_pluginInstance = this; } StudioWelcomePlugin::~StudioWelcomePlugin() { delete m_welcomeMode; } void StudioWelcomePlugin::initialize() { qmlRegisterType("projectmodel", 1, 0, "ProjectModel"); qmlRegisterType("usagestatistics", 1, 0, "UsageStatisticModel"); m_welcomeMode = new WelcomeMode; } static bool forceDownLoad() { const Key lastQDSVersionEntry = "QML/Designer/ForceWelcomePageDownload"; return Core::ICore::settings()->value(lastQDSVersionEntry, false).toBool(); } static bool showSplashScreen() { // some error dialog is maybe open, be silent to avoid focus problems (macOS had some) if (Core::ICore::mainWindow() != Core::ICore::dialogParent()) return false; const Key lastQDSVersionEntry = "QML/Designer/lastQDSVersion"; QtcSettings *settings = Core::ICore::settings(); const QString lastQDSVersion = settings->value(lastQDSVersionEntry).toString(); const QString currentVersion = Utils::appInfo().displayVersion; if (currentVersion != lastQDSVersion) { settings->setValue(lastQDSVersionEntry, currentVersion); return true; } return Utils::CheckableDecider(DO_NOT_SHOW_SPLASHSCREEN_AGAIN_KEY).shouldAskAgain(); } void StudioWelcomePlugin::extensionsInitialized() { Core::ModeManager::activateMode(m_welcomeMode->id()); // Enable QDS new project dialog and QDS wizards if (Core::ICore::isQtDesignStudio()) { ProjectExplorer::JsonWizardFactory::clearWizardPaths(); ProjectExplorer::JsonWizardFactory::addWizardPath( Core::ICore::resourcePath("qmldesigner/studio_templates")); Core::ICore::setNewDialogFactory([](QWidget *parent) { return new QdsNewDialog(parent); }); const QString filters = QString("Project (*.qmlproject);;UI file (*.ui.qml);;QML file " "(*.qml);;JavaScript file (*.js);;%1") .arg(Core::DocumentManager::allFilesFilterString()); Core::DocumentManager::setFileDialogFilter(filters); } if (showSplashScreen()) { connect(Core::ICore::instance(), &Core::ICore::coreOpened, this, [this] { NANOTRACE_SCOPE("StudioWelcome", "StudioWelcomePlugin::extensionsInitialized::coreOpened"); Core::ModeManager::setModeStyle(Core::ModeManager::Style::Hidden); if (Utils::HostOsInfo::isMacHost()) { s_viewWindow = new QQuickView(Core::ICore::mainWindow()->windowHandle()); s_viewWindow->setFlag(Qt::FramelessWindowHint); s_viewWindow->engine()->addImportPath("qrc:/studiofonts"); #ifdef QT_DEBUG s_viewWindow->engine()->addImportPath(QLatin1String(STUDIO_QML_PATH) + "splashscreen/imports"); s_viewWindow->setSource( QUrl::fromLocalFile(QLatin1String(STUDIO_QML_PATH) + "splashscreen/main.qml")); #else s_viewWindow->engine()->addImportPath("qrc:/qml/splashscreen/imports"); s_viewWindow->setSource(QUrl("qrc:/qml/splashscreen/main.qml")); #endif QTC_ASSERT(s_viewWindow->rootObject(), qWarning() << "The StudioWelcomePlugin has a runtime depdendency on " "qt/qtquicktimeline."; return ); connect(s_viewWindow->rootObject(), SIGNAL(closeClicked()), this, SLOT(closeSplashScreen())); auto mainWindow = Core::ICore::mainWindow()->windowHandle(); s_viewWindow->setPosition((mainWindow->width() - s_viewWindow->width()) / 2, (mainWindow->height() - s_viewWindow->height()) / 2); Core::ICore::mainWindow()->setEnabled(false); connect(s_viewWindow, &QObject::destroyed, []() { if (Core::ICore::mainWindow()) Core::ICore::mainWindow()->setEnabled(true); }); s_viewWindow->show(); s_viewWindow->requestActivate(); s_viewWindow->setObjectName(QmlDesigner::Constants::OBJECT_NAME_SPLASH_SCREEN); } else { s_viewWidget = new QQuickWidget(Core::ICore::dialogParent()); s_viewWidget->setWindowFlag(Qt::SplashScreen, true); s_viewWidget->setObjectName(QmlDesigner::Constants::OBJECT_NAME_SPLASH_SCREEN); s_viewWidget->setWindowModality(Qt::ApplicationModal); s_viewWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); s_viewWidget->engine()->addImportPath("qrc:/studiofonts"); #ifdef QT_DEBUG s_viewWidget->engine()->addImportPath(QLatin1String(STUDIO_QML_PATH) + "splashscreen/imports"); s_viewWidget->setSource( QUrl::fromLocalFile(QLatin1String(STUDIO_QML_PATH) + "splashscreen/main.qml")); #else s_viewWidget->engine()->addImportPath("qrc:/qml/splashscreen/imports"); s_viewWidget->setSource(QUrl("qrc:/qml/splashscreen/main.qml")); #endif QTC_ASSERT(s_viewWidget->rootObject(), qWarning() << "The StudioWelcomePlugin has a runtime depdendency on " "qt/qtquicktimeline."; return ); connect(s_viewWidget->rootObject(), SIGNAL(closeClicked()), this, SLOT(closeSplashScreen())); s_viewWidget->show(); s_viewWidget->raise(); s_viewWidget->setFocus(); } }); } } bool StudioWelcomePlugin::delayedInitialize() { QTimer::singleShot(2000, this, []() { auto modelManager = QmlJS::ModelManagerInterface::instance(); if (!modelManager) return; QmlJS::PathsAndLanguages importPaths; const QList kits = Utils::filtered(KitManager::kits(), [](const Kit *k) { const QtSupport::QtVersion *version = QtSupport::QtKitAspect::qtVersion(k); const bool valid = version && version->isValid(); const bool isQt6 = valid && version->qtVersion().majorVersion() == 6; const bool autoDetected = valid && version->isAutodetected(); return isQt6 && autoDetected && ProjectExplorer::DeviceTypeKitAspect::deviceTypeId(k) == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE; }); for (const Kit *kit : kits) { const QtSupport::QtVersion *version = QtSupport::QtKitAspect::qtVersion(kit); const Utils::FilePath qmlPath = version->qmlPath(); importPaths.maybeInsert(qmlPath, QmlJS::Dialect::QmlQtQuick2); QmlJS::ModelManagerInterface::importScan(QmlJS::ModelManagerInterface::workingCopy(), importPaths, modelManager, false); } }); return true; } WelcomeMode::WelcomeMode() { setDisplayName(tr("Welcome")); const QString welcomePagePath = Core::ICore::resourcePath("qmldesigner/welcomepage").toString(); m_dataModelDownloader = new DataModelDownloader(this); if (!m_dataModelDownloader->exists()) { //Fallback if data cannot be downloaded // TODO: Check result? Utils::FilePath::fromUserInput(welcomePagePath + "/dataImports") .copyRecursively(m_dataModelDownloader->targetFolder()); m_dataModelDownloader->setForceDownload(true); } Utils::FilePath readme = Utils::FilePath::fromUserInput(m_dataModelDownloader->targetFolder().toString() + "/readme.txt"); const Utils::Icon FLAT({{":/studiowelcome/images/mode_welcome_mask.png", Utils::Theme::IconsBaseColor}}); const Utils::Icon FLAT_ACTIVE({{":/studiowelcome/images/mode_welcome_mask.png", Utils::Theme::IconsModeWelcomeActiveColor}}); setIcon(Utils::Icon::modeIcon(FLAT, FLAT, FLAT_ACTIVE)); setPriority(Core::Constants::P_MODE_WELCOME); setId(Core::Constants::MODE_WELCOME); setContextHelp("Qt Design Studio Manual"); setContext(Core::Context(Core::Constants::C_WELCOME_MODE)); QFontDatabase::addApplicationFont(":/studiofonts/TitilliumWeb-Regular.ttf"); ExampleCheckout::registerTypes(); createQuickWidget(); if (forceDownLoad() || !readme.exists()) // Only downloads contain the readme m_dataModelDownloader->setForceDownload(true); connect(m_dataModelDownloader, &DataModelDownloader::progressChanged, this, [this](){ if (m_quickWidget->rootObject()) m_quickWidget->rootObject()->setProperty("loadingProgress", m_dataModelDownloader->progress()); }); connect(m_dataModelDownloader, &DataModelDownloader::finished, this, [this, welcomePagePath]() { delete m_quickWidget; createQuickWidget(); setupQuickWidget(welcomePagePath); m_modeWidget->layout()->addWidget(m_quickWidget); }); connect(m_dataModelDownloader, &DataModelDownloader::downloadFailed, this, [this] { m_quickWidget->setEnabled(true); }); if (m_dataModelDownloader->start()) m_quickWidget->setEnabled(false); /* connect(Core::ModeManager::instance(), &Core::ModeManager::currentModeChanged, this, [this](Utils::Id mode){ bool active = (mode == Core::Constants::MODE_WELCOME); m_modeWidget->rootObject()->setProperty("active", active); }); */ setupQuickWidget(welcomePagePath); QVBoxLayout *boxLayout = new QVBoxLayout(); boxLayout->setContentsMargins(0, 0, 0, 0); m_modeWidget = new QWidget; m_modeWidget->setLayout(boxLayout); boxLayout->addWidget(m_quickWidget); setWidget(m_modeWidget); QStringList designStudioQchPathes = {Core::HelpManager::documentationPath() + "/qtdesignstudio.qch", Core::HelpManager::documentationPath() + "/qtquick.qch", Core::HelpManager::documentationPath() + "/qtquickcontrols.qch", Core::HelpManager::documentationPath() + "/qtquicktimeline.qch", Core::HelpManager::documentationPath() + "/qtquick3d.qch", Core::HelpManager::documentationPath() + "/qtqml.qch"}; Core::HelpManager::registerDocumentation( Utils::filtered(designStudioQchPathes, [](const QString &path) { return QFileInfo::exists(path); })); } WelcomeMode::~WelcomeMode() { delete m_modeWidget; } void WelcomeMode::setupQuickWidget(const QString &welcomePagePath) { m_quickWidget->rootContext()->setContextProperty("$dataModel", m_dataModelDownloader); m_quickWidget->engine()->addImportPath(Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources/imports").toString()); m_quickWidget->engine()->addImportPath(welcomePagePath + "/imports"); m_quickWidget->engine()->addImportPath(m_dataModelDownloader->targetFolder().toString()); m_quickWidget->setSource(QUrl::fromLocalFile(welcomePagePath + "/main.qml")); QShortcut *updateShortcut = nullptr; if (Utils::HostOsInfo::isMacHost()) updateShortcut = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_F5), m_quickWidget); else updateShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_F5), m_quickWidget); connect(updateShortcut, &QShortcut::activated, this, [this, welcomePagePath](){ m_quickWidget->setSource(QUrl::fromLocalFile(welcomePagePath + "/main.qml")); }); } void WelcomeMode::createQuickWidget() { m_quickWidget = new QQuickWidget; m_quickWidget->setMinimumSize(640, 480); m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); m_quickWidget->setObjectName(QmlDesigner::Constants::OBJECT_NAME_WELCOME_PAGE); QmlDesigner::Theme::setupTheme(m_quickWidget->engine()); m_quickWidget->engine()->addImportPath("qrc:/studiofonts"); QmlDesigner::QmlDesignerPlugin::registerPreviewImageProvider(m_quickWidget->engine()); m_quickWidget->engine()->setOutputWarningsToStandardError(false); } } // namespace Internal } // namespace StudioWelcome #include "studiowelcomeplugin.moc"