// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "qdsnewdialog.h" #include #include #include #include #include #include #include "createproject.h" #include "newprojectdialogimageprovider.h" #include "wizardfactories.h" #include #include #include using namespace StudioWelcome; namespace { /* * NOTE: copied from projectexplorer/jsonwizard/jsonprojectpage.h */ QString uniqueProjectName(const QString &path) { const QDir pathDir(path); //: File path suggestion for a new project. If you choose //: to translate it, make sure it is a valid path name without blanks //: and using only ascii chars. const QString prefix = QObject::tr("UntitledProject"); QString name = prefix; int i = 0; while (pathDir.exists(name)) name = prefix + QString::number(++i); return name; } } /***********************/ QdsNewDialog::QdsNewDialog(QWidget *parent) : m_dialog{new QQuickWidget(parent)} , m_categoryModel{new PresetCategoryModel(&m_presetData, this)} , m_presetModel{new PresetModel(&m_presetData, this)} , m_screenSizeModel{new ScreenSizeModel(this)} , m_styleModel{new StyleModel(this)} , m_recentsStore{"RecentPresets.json", StorePolicy::UniqueValues} , m_userPresetsStore{"UserPresets.json", StorePolicy::UniqueNames} { setParent(m_dialog); m_recentsStore.setReverseOrder(); m_recentsStore.setMaximum(10); m_dialog->setResizeMode(QQuickWidget::SizeRootObjectToView); // SizeViewToRootObject m_dialog->engine()->addImageProvider(QStringLiteral("newprojectdialog_library"), new Internal::NewProjectDialogImageProvider()); QmlDesigner::Theme::setupTheme(m_dialog->engine()); qmlRegisterSingletonInstance("BackendApi", 1, 0, "BackendApi", this); m_dialog->engine()->addImportPath(Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources/imports").toString()); m_dialog->engine()->addImportPath(Core::ICore::resourcePath("qmldesigner/newprojectdialog/imports").toString()); m_dialog->setSource(QUrl::fromLocalFile(qmlPath())); m_dialog->setWindowModality(Qt::ApplicationModal); m_dialog->setWindowFlags(Qt::Dialog); m_dialog->setAttribute(Qt::WA_DeleteOnClose); m_dialog->setMinimumSize(1149, 554); QSize screenSize = m_dialog->screen()->geometry().size(); if (screenSize.height() < 1080) m_dialog->resize(parent->size()); QObject::connect(&m_wizard, &WizardHandler::deletingWizard, this, &QdsNewDialog::onDeletingWizard); QObject::connect(&m_wizard, &WizardHandler::wizardCreated, this, &QdsNewDialog::onWizardCreated); QObject::connect(&m_wizard, &WizardHandler::statusMessageChanged, this, &QdsNewDialog::onStatusMessageChanged); QObject::connect(&m_wizard, &WizardHandler::projectCanBeCreated, this, &QdsNewDialog::onProjectCanBeCreatedChanged); m_dialog->installEventFilter(this); QObject::connect(&m_wizard, &WizardHandler::wizardCreationFailed, this, [this]() { QMessageBox::critical(m_dialog, tr("New Project"), tr("Failed to initialize data.")); reject(); delete this; }); QObject::connect(m_styleModel.data(), &StyleModel::modelAboutToBeReset, this, [this]() { m_qmlStyleIndex = -1; }); } bool QdsNewDialog::eventFilter(QObject *obj, QEvent *event) { if (obj == m_dialog && event->type() == QEvent::KeyPress && static_cast(event)->key() == Qt::Key_Escape) { reject(); return true; } return false; } void QdsNewDialog::onDeletingWizard() { m_screenSizeModel->setBackendModel(nullptr); m_qmlScreenSizeIndex = -1; m_screenSizeModel->reset(); m_styleModel->setBackendModel(nullptr); m_qmlStyleIndex = -1; } void QdsNewDialog::setProjectName(const QString &name) { m_qmlProjectName = name; m_wizard.setProjectName(name); } void QdsNewDialog::setProjectLocation(const QString &location) { m_qmlProjectLocation = Utils::FilePath::fromString(QDir::toNativeSeparators(location)); m_wizard.setProjectLocation(m_qmlProjectLocation); } void QdsNewDialog::onStatusMessageChanged(Utils::InfoLabel::InfoType type, const QString &message) { switch (type) { case Utils::InfoLabel::Warning: m_qmlStatusType = "warning"; break; case Utils::InfoLabel::Error: m_qmlStatusType = "error"; break; default: m_qmlStatusType = "normal"; break; } emit statusTypeChanged(); m_qmlStatusMessage = message; emit statusMessageChanged(); } void QdsNewDialog::onProjectCanBeCreatedChanged(bool value) { if (m_qmlFieldsValid == value) return; m_qmlFieldsValid = value; emit fieldsValidChanged(); } void QdsNewDialog::updateScreenSizes() { int index = m_wizard.screenSizeIndex(m_currentPreset->screenSizeName); if (index > -1) { setScreenSizeIndex(index); } else { index = m_screenSizeModel->appendItem(m_currentPreset->screenSizeName); setScreenSizeIndex(index); } m_screenSizeModel->reset(); } void QdsNewDialog::onWizardCreated(QStandardItemModel *screenSizeModel, QStandardItemModel *styleModel) { if (screenSizeModel) m_screenSizeModel->setBackendModel(screenSizeModel); if (styleModel) m_styleModel->setBackendModel(styleModel); auto userPreset = m_currentPreset->asUserPreset(); if (m_qmlDetailsLoaded) { if (m_currentPreset->isUserPreset()) { if (m_wizard.haveVirtualKeyboard()) setUseVirtualKeyboard(userPreset->useQtVirtualKeyboard); if (m_wizard.haveTargetQtVersion()) { int index = m_wizard.targetQtVersionIndex(userPreset->qtVersion); if (index != -1) setTargetQtVersionIndex(index); } } else { if (m_wizard.haveTargetQtVersion()) { int index = m_wizard.targetQtVersionIndex(); if (index != -1) setTargetQtVersionIndex(index); } } emit haveVirtualKeyboardChanged(); emit haveTargetQtVersionChanged(); updateScreenSizes(); setProjectName(m_qmlProjectName); setProjectLocation(m_qmlProjectLocation.toString()); } if (m_qmlStylesLoaded && m_wizard.haveStyleModel()) { if (m_currentPreset->isUserPreset()) { int index = m_wizard.styleIndex(userPreset->styleName); if (index != -1) setStyleIndex(index); } else { /* NOTE: For a builtin preset, we don't need to set style index. That's because defaults * will be loaded from the backend Wizard. */ } m_styleModel->reset(); } } QString QdsNewDialog::currentPresetQmlPath() const { if (!m_currentPreset || m_currentPreset->qmlPath.isEmpty()) return {}; return m_currentPreset->qmlPath.toString(); } void QdsNewDialog::setScreenSizeIndex(int index) { m_wizard.setScreenSizeIndex(index); m_qmlScreenSizeIndex = index; } int QdsNewDialog::screenSizeIndex() const { return m_wizard.screenSizeIndex(); } void QdsNewDialog::setTargetQtVersionIndex(int index) { if (m_qmlTargetQtVersionIndex != index) { m_wizard.setTargetQtVersionIndex(index); m_qmlTargetQtVersionIndex = index; emit targetQtVersionIndexChanged(); } } int QdsNewDialog::getTargetQtVersionIndex() const { return m_qmlTargetQtVersionIndex; } void QdsNewDialog::setStyleIndex(int index) { if (!m_qmlStylesLoaded) return; if (index == -1) { m_qmlStyleIndex = index; return; } m_qmlStyleIndex = index; int actualIndex = m_styleModel->actualIndex(m_qmlStyleIndex); QTC_ASSERT(actualIndex >= 0, return); m_wizard.setStyleIndex(actualIndex); } int QdsNewDialog::getStyleIndex() const { /** * m_wizard.styleIndex property is the wizard's (backend's) value of the style index. * The initial value (saved in the wizard.json) is read from there. Any subsequent reads of * the style index should use m_styleIndex, which is the QML's style index property. Setting * the style index should update both the m_styleIndex and the backend. In this regard, the * QdsNewDialog's m_styleIndex acts as some kind of cache. */ if (!m_qmlStylesLoaded) return -1; if (m_qmlStyleIndex == -1) { int actualIndex = m_wizard.styleIndex(); // Not nice, get sets the property... m_qmlStyleIndex acts like a cache. m_qmlStyleIndex = m_styleModel->filteredIndex(actualIndex); return m_qmlStyleIndex; } return m_styleModel->actualIndex(m_qmlStyleIndex); } void QdsNewDialog::setUseVirtualKeyboard(bool value) { if (m_qmlUseVirtualKeyboard != value) { m_qmlUseVirtualKeyboard = value; emit useVirtualKeyboardChanged(); } } void QdsNewDialog::setWizardFactories(QList factories_, const Utils::FilePath &defaultLocation, const QVariantMap &) { Utils::Id platform = Utils::Id::fromSetting("Desktop"); WizardFactories factories{factories_, m_dialog, platform}; std::vector recents = m_recentsStore.fetchAll(); std::vector userPresets = m_userPresetsStore.fetchAll(); m_presetData.setData(factories.presetsGroupedByCategory(), userPresets, recents); m_categoryModel->reset(); m_presetModel->reset(); if (m_qmlSelectedPreset > -1) setSelectedPreset(m_qmlSelectedPreset); if (factories.empty()) return; // TODO: some message box? const Core::IWizardFactory *first = factories.front(); Utils::FilePath projectLocation = first->runPath(defaultLocation); m_qmlProjectName = uniqueProjectName(projectLocation.toString()); emit projectNameChanged(); // So that QML knows to update the field m_qmlProjectLocation = Utils::FilePath::fromString(QDir::toNativeSeparators(projectLocation.toString())); emit projectLocationChanged(); // So that QML knows to update the field /* NOTE: * Here we expect that details are loaded && that styles are loaded. We use the * functionality below to update the state of the first item that is selected right when * the dialog pops up. Otherwise, relying solely on onWizardCreated is not useful, since * for the dialog popup, that wizard is created before details & styles are loaded. * * It might be a better alternative to receive notifications from QML in the cpp file, that * style is loaded, and that details is loaded, and do updates from there. But, if we handle * those events, they may be called before the wizard is created - so we would need to make * sure that all events have occurred before we go ahead and configure the wizard. */ /* onWizardCreated will have been called by this time, as a result of m_presetModel->reset(), * but at that time the Details and Styles panes haven't been loaded yet - only the backend * models loaded. We call it again, cause at this point those panes are now loaded, and we can * set them up. */ onWizardCreated(nullptr, nullptr); } QString QdsNewDialog::recentsTabName() const { return PresetData::recentsTabName(); } QString QdsNewDialog::qmlPath() const { return Core::ICore::resourcePath("qmldesigner/newprojectdialog/NewProjectDialog.qml").toString(); } void QdsNewDialog::showDialog() { m_dialog->show(); } bool QdsNewDialog::getHaveVirtualKeyboard() const { return m_wizard.haveVirtualKeyboard(); } bool QdsNewDialog::getHaveTargetQtVersion() const { return m_wizard.haveTargetQtVersion(); } void QdsNewDialog::accept() { CreateProject create{m_wizard}; m_dialog->hide(); create.withName(m_qmlProjectName) .atLocation(m_qmlProjectLocation) .withScreenSizes(m_qmlScreenSizeIndex, m_qmlCustomWidth, m_qmlCustomHeight) .withStyle(m_qmlStyleIndex) .useQtVirtualKeyboard(m_qmlUseVirtualKeyboard) .saveAsDefaultLocation(m_qmlSaveAsDefaultLocation) .withTargetQtVersion(m_qmlTargetQtVersionIndex) .execute(); std::shared_ptr item = m_wizard.preset(); QString customSizeName = m_qmlCustomWidth + " x " + m_qmlCustomHeight; UserPresetData preset = currentUserPresetData(m_currentPreset->displayName()); m_recentsStore.save(preset); m_dialog->close(); m_dialog->deleteLater(); m_dialog = nullptr; } void QdsNewDialog::reject() { m_screenSizeModel->setBackendModel(nullptr); m_styleModel->setBackendModel(nullptr); m_wizard.destroyWizard(); m_dialog->close(); m_dialog = nullptr; } QString QdsNewDialog::chooseProjectLocation() { Utils::FilePath newPath = Utils::FileUtils::getExistingDirectory(m_dialog, tr("Choose Directory"), m_qmlProjectLocation); return QDir::toNativeSeparators(newPath.toString()); } void QdsNewDialog::setSelectedPreset(int selection) { if (m_qmlSelectedPreset != selection || m_presetPage != m_presetModel->page()) { m_qmlSelectedPreset = selection; m_currentPreset = m_presetModel->preset(m_qmlSelectedPreset); if (m_currentPreset) { setProjectDescription(m_currentPreset->description); m_presetPage = m_presetModel->page(); m_wizard.reset(m_currentPreset, m_qmlSelectedPreset); } } } UserPresetData QdsNewDialog::currentUserPresetData(const QString &displayName) const { QString screenSize = m_qmlCustomWidth + " x " + m_qmlCustomHeight; QString targetQtVersion = ""; QString styleName = ""; bool useVirtualKeyboard = false; if (m_wizard.haveTargetQtVersion()) targetQtVersion = m_wizard.targetQtVersionName(m_qmlTargetQtVersionIndex); if (m_wizard.haveStyleModel()) styleName = m_wizard.styleName(m_qmlStyleIndex); if (m_wizard.haveVirtualKeyboard()) useVirtualKeyboard = m_qmlUseVirtualKeyboard; UserPresetData preset = {m_currentPreset->categoryId, m_currentPreset->wizardName, displayName, screenSize, useVirtualKeyboard, targetQtVersion, styleName}; return preset; } void QdsNewDialog::savePresetDialogAccept() { UserPresetData preset = currentUserPresetData(m_qmlPresetName); if (!m_userPresetsStore.save(preset)) { QMessageBox::warning(m_dialog, tr("Save Preset"), tr("A preset with this name already exists.")); return; } // reload model std::vector recents = m_recentsStore.fetchAll(); std::vector userPresets = m_userPresetsStore.fetchAll(); m_presetData.reload(userPresets, recents); m_categoryModel->reset(); emit userPresetSaved(); } void QdsNewDialog::removeCurrentPreset() { if (!m_currentPreset->isUserPreset()) { qWarning() << "Will not attempt to remove non-user preset"; return; } // remove preset & reload model UserPresetData currentPreset = currentUserPresetData(m_qmlPresetName); std::vector recents = m_recentsStore.remove(currentPreset); auto userPreset = m_currentPreset->asUserPreset(); m_userPresetsStore.remove(userPreset->categoryId, userPreset->displayName()); std::vector userPresets = m_userPresetsStore.fetchAll(); m_presetData.reload(userPresets, recents); m_qmlSelectedPreset = -1; m_presetPage = -1; if (userPresets.size() == 0) { m_presetModel->setPage(0); emit lastUserPresetRemoved(); } m_categoryModel->reset(); m_presetModel->reset(); }