diff options
27 files changed, 1355 insertions, 269 deletions
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/NewProjectDialog.qml b/share/qtcreator/qmldesigner/newprojectdialog/NewProjectDialog.qml index aa5b346b4e..9176af9e34 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/NewProjectDialog.qml +++ b/share/qtcreator/qmldesigner/newprojectdialog/NewProjectDialog.qml @@ -185,7 +185,7 @@ Item { anchors.fill: parent onClicked: { tabBarRow.currIndex = index - projectModel.setPage(index) + presetModel.setPage(index) projectView.currentIndex = 0 projectView.currentIndexChanged() diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/NewProjectView.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/NewProjectView.qml index 4270c0946a..e011057892 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/NewProjectView.qml +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/NewProjectView.qml @@ -49,17 +49,17 @@ GridView { } ] - model: projectModel + model: presetModel // called by onModelReset and when user clicks on an item, or when the header item is changed. onCurrentIndexChanged: { - dialogBox.selectedProject = projectView.currentIndex - var source = dialogBox.currentProjectQmlPath() + dialogBox.selectedPreset = projectView.currentIndex + var source = dialogBox.currentPresetQmlPath() loader.source = source } Connections { - target: projectModel + target: presetModel // called when data is set (setWizardFactories) function onModelReset() { @@ -76,7 +76,7 @@ GridView { background: null function fontIconCode(index) { - var code = projectModel.fontIconCode(index) + var code = presetModel.fontIconCode(index) return code ? code : StudioTheme.Constants.wizardsUnknown } diff --git a/src/plugins/projectexplorer/jsonwizard/jsonwizardfactory.cpp b/src/plugins/projectexplorer/jsonwizard/jsonwizardfactory.cpp index 5439d98d2b..946d2d92ab 100644 --- a/src/plugins/projectexplorer/jsonwizard/jsonwizardfactory.cpp +++ b/src/plugins/projectexplorer/jsonwizard/jsonwizardfactory.cpp @@ -278,6 +278,59 @@ QVariant JsonWizardFactory::getDataValue(const QLatin1String &key, const QVarian return retVal; } +std::pair<int, QStringList> JsonWizardFactory::screenSizeInfoFromPage(const QString &pageType) const +{ + /* Retrieving the ScreenFactor "trKey" values from pages[i]/data[j]/data["items"], where + * pages[i] is the page of type `pageType` and data[j] is the data item with name ScreenFactor + */ + + const Utils::Id id = Utils::Id::fromString(Constants::PAGE_ID_PREFIX + pageType); + + const auto it = std::find_if(std::cbegin(m_pages), std::cend(m_pages), [&id](const Page &page) { + return page.typeId == id; + }); + + if (it == std::cend(m_pages)) + return {}; + + const QVariant data = it->data; + if (data.type() != QVariant::List) + return {}; + + const QVariant screenFactorField = Utils::findOrDefault(data.toList(), + [](const QVariant &field) { + const QVariantMap m = field.toMap(); + return "ScreenFactor" == m["name"]; + }); + + if (screenFactorField.type() != QVariant::Map) + return {}; + + const QVariant screenFactorData = screenFactorField.toMap()["data"]; + if (screenFactorData.type() != QVariant::Map) + return {}; + + const QVariantMap screenFactorDataMap = screenFactorData.toMap(); + if (not screenFactorDataMap.contains("items")) + return {}; + + bool ok = false; + const int index = screenFactorDataMap["index"].toInt(&ok); + const QVariantList items = screenFactorDataMap["items"].toList(); + if (items.isEmpty()) + return {}; + + QStringList values = Utils::transform(items, [](const QVariant &item) { + const QVariantMap m = item.toMap(); + return m["trKey"].toString(); + }); + + if (values.isEmpty()) + return {}; + + return std::make_pair(index, values); +} + JsonWizardFactory::Page JsonWizardFactory::parsePage(const QVariant &value, QString *errorMessage) { JsonWizardFactory::Page p; diff --git a/src/plugins/projectexplorer/jsonwizard/jsonwizardfactory.h b/src/plugins/projectexplorer/jsonwizard/jsonwizardfactory.h index 2ecabe6bc3..347692bf65 100644 --- a/src/plugins/projectexplorer/jsonwizard/jsonwizardfactory.h +++ b/src/plugins/projectexplorer/jsonwizard/jsonwizardfactory.h @@ -83,6 +83,8 @@ public: bool isAvailable(Utils::Id platformId) const override; + std::pair<int, QStringList> screenSizeInfoFromPage(const QString &pageType) const; + private: Utils::Wizard *runWizardImpl(const Utils::FilePath &path, QWidget *parent, Utils::Id platform, const QVariantMap &variables, bool showWizard = true) override; diff --git a/src/plugins/studiowelcome/CMakeLists.txt b/src/plugins/studiowelcome/CMakeLists.txt index e1f899d2fd..48c07d15b9 100644 --- a/src/plugins/studiowelcome/CMakeLists.txt +++ b/src/plugins/studiowelcome/CMakeLists.txt @@ -6,14 +6,16 @@ add_qtc_plugin(StudioWelcome SOURCES studiowelcomeplugin.cpp studiowelcomeplugin.h newprojectdialogimageprovider.cpp newprojectdialogimageprovider.h - newprojectmodel.cpp newprojectmodel.h + presetmodel.cpp presetmodel.h examplecheckout.cpp examplecheckout.h studiowelcome_global.h qdsnewdialog.cpp qdsnewdialog.h wizardfactories.cpp wizardfactories.h createproject.cpp createproject.h wizardhandler.cpp wizardhandler.h + recentpresets.cpp recentpresets.h screensizemodel.h + algorithm.h stylemodel.h stylemodel.cpp studiowelcome.qrc "${PROJECT_SOURCE_DIR}/src/share/3rdparty/studiofonts/studiofonts.qrc" diff --git a/src/plugins/studiowelcome/algorithm.h b/src/plugins/studiowelcome/algorithm.h new file mode 100644 index 0000000000..b7e53da5f1 --- /dev/null +++ b/src/plugins/studiowelcome/algorithm.h @@ -0,0 +1,108 @@ + +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include <utils/algorithm.h> + +namespace Utils { + +//////// FIND +template<typename C, typename F> +[[nodiscard]] typename Utils::optional<typename C::value_type> findOptional(const C &container, + F function) +{ + auto begin = std::cbegin(container); + auto end = std::cend(container); + + auto it = std::find_if(begin, end, function); + return it == end ? nullopt : make_optional(*it); +} + +///////// FILTER +template<typename C, typename T = typename C::value_type> +[[nodiscard]] C filterOut(const C &container, const T &value = T()) +{ + C out; + std::copy_if(std::begin(container), std::end(container), inserter(out), [&](const auto &item) { + return item != value; + }); + return out; +} + +template<typename C> +[[nodiscard]] C filtered(const C &container) +{ + return filterOut(container, typename C::value_type{}); +} + +/////// MODIFY +template<typename SC, typename C> +void concat(C &out, const SC &container) +{ + std::copy(std::begin(container), std::end(container), inserter(out)); +} + +template<typename C, typename T> +void erase_one(C &container, const T &value) +{ + typename C::const_iterator i = std::find(std::cbegin(container), std::cend(container), value); + if (i == std::cend(container)) + return; + + container.erase(i); +} + +template<typename C, typename T> +void prepend(C &container, const T &value) +{ + container.insert(std::cbegin(container), value); +} + +/////// OTHER +template<typename RC, typename SC> +[[nodiscard]] RC flatten(const SC &container) +{ + RC result; + + for (const auto &innerContainer : container) + concat(result, innerContainer); + + return result; +} + +template<template<typename, typename...> class C, + typename T, + typename... TArgs, + typename RT = typename std::decay_t<T>::value_type, + typename RC = C<RT>> +[[nodiscard]] auto flatten(const C<T, TArgs...> &container) +{ + using SC = C<T, TArgs...>; + return flatten<RC, SC>(container); +} + +} // namespace Utils diff --git a/src/plugins/studiowelcome/newprojectmodel.cpp b/src/plugins/studiowelcome/newprojectmodel.cpp deleted file mode 100644 index 3d5d9246d2..0000000000 --- a/src/plugins/studiowelcome/newprojectmodel.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -****************************************************************************/ - -#include "newprojectmodel.h" - -using namespace StudioWelcome; - -/****************** BaseNewProjectModel ******************/ - -BaseNewProjectModel::BaseNewProjectModel(QObject *parent) - : QAbstractListModel(parent) -{} - -QHash<int, QByteArray> BaseNewProjectModel::roleNames() const -{ - QHash<int, QByteArray> roleNames; - roleNames[Qt::UserRole] = "name"; - return roleNames; -} - -void BaseNewProjectModel::setProjects(const ProjectsByCategory &projectsByCategory) -{ - beginResetModel(); - - for (auto &[id, category] : projectsByCategory) { - m_categories.push_back(category.name); - m_projects.push_back(category.items); - } - - endResetModel(); -} - -/****************** NewProjectCategoryModel ******************/ - -NewProjectCategoryModel::NewProjectCategoryModel(QObject *parent) - : BaseNewProjectModel(parent) -{} - -int NewProjectCategoryModel::rowCount(const QModelIndex &) const -{ - return static_cast<int>(categories().size()); -} - -QVariant NewProjectCategoryModel::data(const QModelIndex &index, int role) const -{ - Q_UNUSED(role) - return categories().at(index.row()); -} - -/****************** NewProjectModel ******************/ - -NewProjectModel::NewProjectModel(QObject *parent) - : BaseNewProjectModel(parent) -{} - -int NewProjectModel::rowCount(const QModelIndex &) const -{ - if (projects().empty()) - return 0; - - return static_cast<int>(projectsOfCurrentCategory().size()); -} - -QVariant NewProjectModel::data(const QModelIndex &index, int role) const -{ - Q_UNUSED(role) - return projectsOfCurrentCategory().at(index.row()).name; -} - -void NewProjectModel::setPage(int index) -{ - beginResetModel(); - - m_page = static_cast<size_t>(index); - - endResetModel(); -} - -QString NewProjectModel::fontIconCode(int index) const -{ - Utils::optional<ProjectItem> projectItem = project(index); - if (!projectItem) - return ""; - - return projectItem->fontIconCode; -} diff --git a/src/plugins/studiowelcome/presetmodel.cpp b/src/plugins/studiowelcome/presetmodel.cpp new file mode 100644 index 0000000000..b8db0503f2 --- /dev/null +++ b/src/plugins/studiowelcome/presetmodel.cpp @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "presetmodel.h" +#include <utils/optional.h> +#include <utils/qtcassert.h> + +#include "algorithm.h" + +using namespace StudioWelcome; + +/****************** PresetData ******************/ + +void PresetData::setData(const PresetsByCategory &presetsByCategory, + const std::vector<RecentPreset> &loadedRecents) +{ + QTC_ASSERT(!presetsByCategory.empty(), return); + m_recents = loadedRecents; + + if (!m_recents.empty()) { + m_categories.push_back("Recents"); + m_presets.push_back({}); + } + + for (auto &[id, category] : presetsByCategory) { + m_categories.push_back(category.name); + m_presets.push_back(category.items); + } + + PresetItems presets = Utils::flatten(m_presets); + + std::vector<PresetItem> recentPresets = makeRecentPresets(presets); + + if (!m_recents.empty()) + m_presets[0] = recentPresets; +} + +std::vector<PresetItem> PresetData::makeRecentPresets(const PresetItems &wizardPresets) +{ + static const PresetItem empty; + + PresetItems result; + + for (const RecentPreset &recent : m_recents) { + auto item = Utils::findOptional(wizardPresets, [&recent](const PresetItem &item) { + return item.categoryId == std::get<0>(recent) && item.name == std::get<1>(recent); + }); + + if (item) { + item->screenSizeName = std::get<2>(recent); + result.push_back(item.value()); + } + } + + return result; +} + +/****************** BasePresetModel ******************/ + +BasePresetModel::BasePresetModel(const PresetData *data, QObject *parent) + : QAbstractListModel(parent) + , m_data{data} +{} + +QHash<int, QByteArray> BasePresetModel::roleNames() const +{ + QHash<int, QByteArray> roleNames; + roleNames[Qt::UserRole] = "name"; + return roleNames; +} + +/****************** PresetCategoryModel ******************/ + +PresetCategoryModel::PresetCategoryModel(const PresetData *data, QObject *parent) + : BasePresetModel(data, parent) +{} + +int PresetCategoryModel::rowCount(const QModelIndex &) const +{ + return static_cast<int>(m_data->categories().size()); +} + +QVariant PresetCategoryModel::data(const QModelIndex &index, int role) const +{ + Q_UNUSED(role) + return m_data->categories().at(index.row()); +} + +/****************** PresetModel ******************/ + +PresetModel::PresetModel(const PresetData *data, QObject *parent) + : BasePresetModel(data, parent) +{} + +QHash<int, QByteArray> PresetModel::roleNames() const +{ + QHash<int, QByteArray> roleNames; + roleNames[Qt::UserRole] = "name"; + roleNames[Qt::UserRole + 1] = "size"; + return roleNames; +} + +int PresetModel::rowCount(const QModelIndex &) const +{ + if (m_data->presets().empty()) + return 0; + + return static_cast<int>(presetsOfCurrentCategory().size()); +} + +QVariant PresetModel::data(const QModelIndex &index, int role) const +{ + Q_UNUSED(role) + PresetItem preset = presetsOfCurrentCategory().at(index.row()); + return QVariant::fromValue<QString>(preset.name + "\n" + preset.screenSizeName); +} + +void PresetModel::setPage(int index) +{ + beginResetModel(); + + m_page = static_cast<size_t>(index); + + endResetModel(); +} + +QString PresetModel::fontIconCode(int index) const +{ + Utils::optional<PresetItem> presetItem = preset(index); + if (!presetItem) + return {}; + + return presetItem->fontIconCode; +} diff --git a/src/plugins/studiowelcome/newprojectmodel.h b/src/plugins/studiowelcome/presetmodel.h index ec7cc005ad..a1c9b0e7d2 100644 --- a/src/plugins/studiowelcome/newprojectmodel.h +++ b/src/plugins/studiowelcome/presetmodel.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -32,38 +32,47 @@ #include <utils/filepath.h> #include <utils/optional.h> +#include "recentpresets.h" + namespace Utils { class Wizard; } namespace StudioWelcome { -struct ProjectItem +struct PresetItem { QString name; QString categoryId; + QString screenSizeName; QString description; QUrl qmlPath; QString fontIconCode; std::function<Utils::Wizard *(const Utils::FilePath &path)> create; }; -inline QDebug &operator<<(QDebug &d, const ProjectItem &item) +inline QDebug &operator<<(QDebug &d, const PresetItem &item) { d << "name=" << item.name; d << "; category = " << item.categoryId; + d << "; size = " << item.screenSizeName; return d; } -struct ProjectCategory +inline bool operator==(const PresetItem &lhs, const PresetItem &rhs) +{ + return lhs.categoryId == rhs.categoryId && lhs.name == rhs.name; +} + +struct WizardCategory { QString id; QString name; - std::vector<ProjectItem> items; + std::vector<PresetItem> items; }; -inline QDebug &operator<<(QDebug &d, const ProjectCategory &cat) +inline QDebug &operator<<(QDebug &d, const WizardCategory &cat) { d << "id=" << cat.id; d << "; name=" << cat.name; @@ -72,46 +81,66 @@ inline QDebug &operator<<(QDebug &d, const ProjectCategory &cat) return d; } -using ProjectsByCategory = std::map<QString, ProjectCategory>; +using PresetsByCategory = std::map<QString, WizardCategory>; +using PresetItems = std::vector<PresetItem>; +using Categories = std::vector<QString>; -/****************** BaseNewProjectModel ******************/ +/****************** PresetData ******************/ -class BaseNewProjectModel : public QAbstractListModel +class PresetData { - using ProjectItems = std::vector<std::vector<ProjectItem>>; - using Categories = std::vector<QString>; - public: - explicit BaseNewProjectModel(QObject *parent = nullptr); - QHash<int, QByteArray> roleNames() const override; - void setProjects(const ProjectsByCategory &projects); + void setData(const PresetsByCategory &presets, const std::vector<RecentPreset> &recents); -protected: - const ProjectItems &projects() const { return m_projects; } + const std::vector<PresetItems> &presets() const { return m_presets; } const Categories &categories() const { return m_categories; } private: - ProjectItems m_projects; + std::vector<PresetItem> makeRecentPresets(const PresetItems &wizardPresets); + +private: + std::vector<PresetItems> m_presets; Categories m_categories; + std::vector<RecentPreset> m_recents; }; -/****************** NewProjectCategoryModel ******************/ +/****************** PresetCategoryModel ******************/ -class NewProjectCategoryModel : public BaseNewProjectModel +class BasePresetModel : public QAbstractListModel { public: - explicit NewProjectCategoryModel(QObject *parent = nullptr); + BasePresetModel(const PresetData *data, QObject *parent = nullptr); + QHash<int, QByteArray> roleNames() const override; + + void reset() + { + beginResetModel(); + endResetModel(); + } + +protected: + const PresetData *m_data = nullptr; +}; + +/****************** PresetCategoryModel ******************/ + +class PresetCategoryModel : public BasePresetModel +{ +public: + PresetCategoryModel(const PresetData *data, QObject *parent = nullptr); int rowCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; }; -/****************** NewProjectModel ******************/ +/****************** PresetModel ******************/ -class NewProjectModel : public BaseNewProjectModel +class PresetModel : public BasePresetModel { Q_OBJECT + public: - explicit NewProjectModel(QObject *parent = nullptr); + PresetModel(const PresetData *data, QObject *parent = nullptr); + QHash<int, QByteArray> roleNames() const override; int rowCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; @@ -120,24 +149,29 @@ public: int page() const { return static_cast<int>(m_page); } - Utils::optional<ProjectItem> project(size_t selection) const + Utils::optional<PresetItem> preset(size_t selection) const { - if (projects().empty()) + auto presets = m_data->presets(); + if (presets.empty()) return {}; - if (m_page < projects().size()) { - const std::vector<ProjectItem> projectsOfCategory = projects().at(m_page); - if (selection < projectsOfCategory.size()) - return projects().at(m_page).at(selection); + if (m_page < presets.size()) { + const std::vector<PresetItem> presetsOfCategory = presets.at(m_page); + if (selection < presetsOfCategory.size()) + return presets.at(m_page).at(selection); } return {}; } - bool empty() const { return projects().empty(); } + bool empty() const { return m_data->presets().empty(); } private: - const std::vector<ProjectItem> projectsOfCurrentCategory() const - { return projects().at(m_page); } + const std::vector<PresetItem> presetsOfCurrentCategory() const + { + return m_data->presets().at(m_page); + } + + std::vector<PresetItems> presets() const { return m_data->presets(); } private: size_t m_page = 0; diff --git a/src/plugins/studiowelcome/qdsnewdialog.cpp b/src/plugins/studiowelcome/qdsnewdialog.cpp index 50e3a085ca..83a966caeb 100644 --- a/src/plugins/studiowelcome/qdsnewdialog.cpp +++ b/src/plugins/studiowelcome/qdsnewdialog.cpp @@ -28,6 +28,7 @@ #include <coreplugin/icore.h> #include <coreplugin/iwizardfactory.h> #include <utils/qtcassert.h> +#include <utils/algorithm.h> #include <qmldesigner/components/componentcore/theme.h> #include "createproject.h" @@ -67,16 +68,17 @@ QString uniqueProjectName(const QString &path) QdsNewDialog::QdsNewDialog(QWidget *parent) : m_dialog{new QQuickWidget(parent)} - , m_categoryModel{new NewProjectCategoryModel(this)} - , m_projectModel{new NewProjectModel(this)} + , 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{Core::ICore::settings()} { setParent(m_dialog); m_dialog->rootContext()->setContextProperties(QVector<QQmlContext::PropertyPair>{ {{"categoryModel"}, QVariant::fromValue(m_categoryModel.data())}, - {{"projectModel"}, QVariant::fromValue(m_projectModel.data())}, + {{"presetModel"}, QVariant::fromValue(m_presetModel.data())}, {{"screenSizeModel"}, QVariant::fromValue(m_screenSizeModel.data())}, {{"styleModel"}, QVariant::fromValue(m_styleModel.data())}, {{"dialogBox"}, QVariant::fromValue(this)}, @@ -94,7 +96,7 @@ QdsNewDialog::QdsNewDialog(QWidget *parent) m_dialog->setWindowModality(Qt::ApplicationModal); m_dialog->setWindowFlags(Qt::Dialog); m_dialog->setAttribute(Qt::WA_DeleteOnClose); - m_dialog->setMinimumSize(1066, 554); + m_dialog->setMinimumSize(1149, 554); QSize screenSize = m_dialog->screen()->geometry().size(); if (screenSize.height() < 1080) @@ -174,7 +176,12 @@ void QdsNewDialog::onWizardCreated(QStandardItemModel *screenSizeModel, QStandar m_styleModel->setBackendModel(styleModel); if (m_qmlDetailsLoaded) { + int index = m_wizard.screenSizeIndex(m_currentPreset->screenSizeName); + if (index > -1) + setScreenSizeIndex(index); + m_screenSizeModel->reset(); + emit haveVirtualKeyboardChanged(); emit haveTargetQtVersionChanged(); @@ -186,12 +193,12 @@ void QdsNewDialog::onWizardCreated(QStandardItemModel *screenSizeModel, QStandar m_styleModel->reset(); } -QString QdsNewDialog::currentProjectQmlPath() const +QString QdsNewDialog::currentPresetQmlPath() const { - if (!m_currentProject || m_currentProject->qmlPath.isEmpty()) - return ""; + if (!m_currentPreset || m_currentPreset->qmlPath.isEmpty()) + return {}; - return m_currentProject->qmlPath.toString(); + return m_currentPreset->qmlPath.toString(); } void QdsNewDialog::setScreenSizeIndex(int index) @@ -259,11 +266,14 @@ void QdsNewDialog::setWizardFactories(QList<Core::IWizardFactory *> factories_, WizardFactories factories{factories_, m_dialog, platform}; - m_categoryModel->setProjects(factories.projectsGroupedByCategory()); // calls model reset - m_projectModel->setProjects(factories.projectsGroupedByCategory()); // calls model reset + std::vector<RecentPreset> recents = m_recentsStore.fetchAll(); + m_presetData.setData(factories.presetsGroupedByCategory(), recents); - if (m_qmlSelectedProject > -1) - setSelectedProject(m_qmlSelectedProject); + m_categoryModel->reset(); + m_presetModel->reset(); + + if (m_qmlSelectedPreset > -1) + setSelectedPreset(m_qmlSelectedPreset); if (factories.empty()) return; // TODO: some message box? @@ -277,8 +287,13 @@ void QdsNewDialog::setWizardFactories(QList<Core::IWizardFactory *> factories_, m_qmlProjectLocation = Utils::FilePath::fromString(QDir::toNativeSeparators(projectLocation.toString())); emit projectLocationChanged(); // So that QML knows to update the field - if (m_qmlDetailsLoaded) + if (m_qmlDetailsLoaded) { + int index = m_wizard.screenSizeIndex(m_currentPreset->screenSizeName); + if (index > -1) + setScreenSizeIndex(index); + m_screenSizeModel->reset(); + } if (m_qmlStylesLoaded) m_styleModel->reset(); @@ -318,6 +333,11 @@ void QdsNewDialog::accept() .withTargetQtVersion(m_qmlTargetQtVersionIndex) .execute(); + PresetItem item = m_wizard.preset(); + QString screenSize = m_wizard.screenSizeName(m_qmlScreenSizeIndex); + + m_recentsStore.add(item.categoryId, item.name, screenSize); + m_dialog->close(); m_dialog->deleteLater(); m_dialog = nullptr; @@ -340,17 +360,17 @@ QString QdsNewDialog::chooseProjectLocation() return QDir::toNativeSeparators(newPath.toString()); } -void QdsNewDialog::setSelectedProject(int selection) +void QdsNewDialog::setSelectedPreset(int selection) { - if (m_qmlSelectedProject != selection || m_projectPage != m_projectModel->page()) { - m_qmlSelectedProject = selection; + if (m_qmlSelectedPreset != selection || m_presetPage != m_presetModel->page()) { + m_qmlSelectedPreset = selection; - m_currentProject = m_projectModel->project(m_qmlSelectedProject); - if (m_currentProject) { - setProjectDescription(m_currentProject->description); + m_currentPreset = m_presetModel->preset(m_qmlSelectedPreset); + if (m_currentPreset) { + setProjectDescription(m_currentPreset->description); - m_projectPage = m_projectModel->page(); - m_wizard.reset(m_currentProject.value(), m_qmlSelectedProject, m_qmlProjectLocation); + m_presetPage = m_presetModel->page(); + m_wizard.reset(m_currentPreset.value(), m_qmlSelectedPreset); } } } diff --git a/src/plugins/studiowelcome/qdsnewdialog.h b/src/plugins/studiowelcome/qdsnewdialog.h index 8e331bd125..476750e540 100644 --- a/src/plugins/studiowelcome/qdsnewdialog.h +++ b/src/plugins/studiowelcome/qdsnewdialog.h @@ -32,9 +32,10 @@ #include <utils/optional.h> #include "wizardhandler.h" -#include "newprojectmodel.h" +#include "presetmodel.h" #include "screensizemodel.h" #include "stylemodel.h" +#include "recentpresets.h" QT_BEGIN_NAMESPACE class QStandardItemModel; @@ -46,7 +47,7 @@ class QdsNewDialog : public QObject, public Core::NewDialog Q_OBJECT public: - Q_PROPERTY(int selectedProject MEMBER m_qmlSelectedProject WRITE setSelectedProject) + Q_PROPERTY(int selectedPreset MEMBER m_qmlSelectedPreset WRITE setSelectedPreset) Q_PROPERTY(QString projectName MEMBER m_qmlProjectName WRITE setProjectName NOTIFY projectNameChanged) Q_PROPERTY(QString projectLocation MEMBER m_qmlProjectLocation READ projectLocation WRITE setProjectLocation NOTIFY projectLocationChanged) Q_PROPERTY(QString projectDescription MEMBER m_qmlProjectDescription READ projectDescription WRITE setProjectDescription NOTIFY projectDescriptionChanged) @@ -64,7 +65,8 @@ public: Q_PROPERTY(bool detailsLoaded MEMBER m_qmlDetailsLoaded) Q_PROPERTY(bool stylesLoaded MEMBER m_qmlStylesLoaded) - Q_INVOKABLE QString currentProjectQmlPath() const; + Q_INVOKABLE QString currentPresetQmlPath() const; + // TODO: screen size index should better be a property Q_INVOKABLE void setScreenSizeIndex(int index); // called when ComboBox item is "activated" Q_INVOKABLE int screenSizeIndex() const; Q_INVOKABLE void setTargetQtVersion(int index); @@ -79,7 +81,7 @@ public: const QVariantMap &extraVariables) override; void setWindowTitle(const QString &title) override { m_dialog->setWindowTitle(title); } void showDialog() override; - void setSelectedProject(int selection); + void setSelectedPreset(int selection); void setStyleIndex(int index); int getStyleIndex() const; @@ -135,14 +137,16 @@ private slots: private: QQuickWidget *m_dialog = nullptr; - QPointer<NewProjectCategoryModel> m_categoryModel; - QPointer<NewProjectModel> m_projectModel; + + PresetData m_presetData; + QPointer<PresetCategoryModel> m_categoryModel; + QPointer<PresetModel> m_presetModel; QPointer<ScreenSizeModel> m_screenSizeModel; QPointer<StyleModel> m_styleModel; QString m_qmlProjectName; Utils::FilePath m_qmlProjectLocation; QString m_qmlProjectDescription; - int m_qmlSelectedProject = -1; + int m_qmlSelectedPreset = -1; int m_qmlScreenSizeIndex = -1; int m_qmlTargetQtVersionIndex = -1; // m_qmlStyleIndex is like a cache, so it needs to be updated on get() @@ -155,7 +159,7 @@ private: QString m_qmlStatusMessage; QString m_qmlStatusType; - int m_projectPage = -1; // i.e. the page in the Presets View + int m_presetPage = -1; // i.e. the page in the Presets View QString m_qmlCustomWidth; QString m_qmlCustomHeight; @@ -163,9 +167,10 @@ private: bool m_qmlDetailsLoaded = false; bool m_qmlStylesLoaded = false; - Utils::optional<ProjectItem> m_currentProject; + Utils::optional<PresetItem> m_currentPreset; WizardHandler m_wizard; + RecentPresetsStore m_recentsStore; }; } //namespace StudioWelcome diff --git a/src/plugins/studiowelcome/recentpresets.cpp b/src/plugins/studiowelcome/recentpresets.cpp new file mode 100644 index 0000000000..f7ea5b9d61 --- /dev/null +++ b/src/plugins/studiowelcome/recentpresets.cpp @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "recentpresets.h" +#include "algorithm.h" + +#include <QRegularExpression> + +#include <coreplugin/icore.h> +#include <utils/qtcassert.h> +#include <utils/qtcsettings.h> + +using Core::ICore; +using Utils::QtcSettings; + +using namespace StudioWelcome; + +constexpr char GROUP_NAME[] = "RecentPresets"; +constexpr char WIZARDS[] = "Wizards"; + +void RecentPresetsStore::add(const QString &categoryId, const QString &name, const QString &sizeName) +{ + std::vector<RecentPreset> existing = fetchAll(); + QStringList encodedRecents = addRecentToExisting(RecentPreset{categoryId, name, sizeName}, + existing); + + m_settings->beginGroup(GROUP_NAME); + m_settings->setValue(WIZARDS, encodedRecents); + m_settings->endGroup(); + m_settings->sync(); +} + +QStringList RecentPresetsStore::addRecentToExisting(const RecentPreset &preset, + std::vector<RecentPreset> &recents) +{ + Utils::erase_one(recents, preset); + Utils::prepend(recents, preset); + + if (recents.size() > m_max) + recents.pop_back(); + + return encodeRecentPresets(recents); +} + +std::vector<RecentPreset> RecentPresetsStore::fetchAll() const +{ + m_settings->beginGroup(GROUP_NAME); + QVariant value = m_settings->value(WIZARDS); + m_settings->endGroup(); + + std::vector<RecentPreset> result; + + if (value.type() == QVariant::String) + result.push_back(decodeOneRecentPreset(value.toString())); + else if (value.type() == QVariant::StringList) + Utils::concat(result, decodeRecentPresets(value.toList())); + + const RecentPreset empty; + return Utils::filtered(result, [&empty](const RecentPreset &recent) { return recent != empty; }); +} + +QStringList RecentPresetsStore::encodeRecentPresets(const std::vector<RecentPreset> &recents) +{ + return Utils::transform<QList>(recents, [](const RecentPreset &p) -> QString { + return std::get<0>(p) + "/" + std::get<1>(p) + ":" + std::get<2>(p); + }); +} + +RecentPreset RecentPresetsStore::decodeOneRecentPreset(const QString &encoded) +{ + QRegularExpression pattern{R"(^(\S+)/(.+):(\d+ x \d+))"}; + auto m = pattern.match(encoded); + if (!m.hasMatch()) + return RecentPreset{}; + + QString category = m.captured(1); + QString name = m.captured(2); + QString size = m.captured(3); + + return std::make_tuple(category, name, size); +} + +std::vector<RecentPreset> RecentPresetsStore::decodeRecentPresets(const QVariantList &values) +{ + return Utils::transform<std::vector>(values, [](const QVariant &value) { + return decodeOneRecentPreset(value.toString()); + }); +} diff --git a/src/plugins/studiowelcome/recentpresets.h b/src/plugins/studiowelcome/recentpresets.h new file mode 100644 index 0000000000..3c223d8df5 --- /dev/null +++ b/src/plugins/studiowelcome/recentpresets.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include <vector> +#include <QPair> +#include <QSettings> + +namespace StudioWelcome { + +// preset category, preset name, size name +using RecentPreset = std::tuple<QString, QString, QString>; + +class RecentPresetsStore +{ +public: + explicit RecentPresetsStore(QSettings *settings) + : m_settings{settings} + {} + + void setMaximum(int n) { m_max = n; } + void add(const QString &categoryId, const QString &name, const QString &sizeName); + std::vector<RecentPreset> fetchAll() const; + +private: + QStringList addRecentToExisting(const RecentPreset &preset, std::vector<RecentPreset> &recents); + static QStringList encodeRecentPresets(const std::vector<RecentPreset> &recents); + static std::vector<RecentPreset> decodeRecentPresets(const QVariantList &values); + static RecentPreset decodeOneRecentPreset(const QString &encoded); + +private: + QSettings *m_settings = nullptr; + int m_max = 10; +}; + +} // namespace StudioWelcome diff --git a/src/plugins/studiowelcome/screensizemodel.h b/src/plugins/studiowelcome/screensizemodel.h index 7ea68966b8..597811146e 100644 --- a/src/plugins/studiowelcome/screensizemodel.h +++ b/src/plugins/studiowelcome/screensizemodel.h @@ -84,7 +84,7 @@ public: return item->text(); } - return ""; + return {}; } QHash<int, QByteArray> roleNames() const override diff --git a/src/plugins/studiowelcome/studiowelcome.pro b/src/plugins/studiowelcome/studiowelcome.pro index 9b7e756a56..3fb9fbb1cc 100644 --- a/src/plugins/studiowelcome/studiowelcome.pro +++ b/src/plugins/studiowelcome/studiowelcome.pro @@ -17,9 +17,10 @@ HEADERS += \ wizardfactories.h \ wizardhandler.h \ createproject.h \ - newprojectmodel.h \ + presetmodel.h \ examplecheckout.h \ screensizemodel.h \ + recentpresets.h \ stylemodel.h SOURCES += \ @@ -29,8 +30,9 @@ SOURCES += \ wizardhandler.cpp \ createproject.cpp \ newprojectdialogimageprovider.cpp \ - newprojectmodel.cpp \ + presetmodel.cpp \ examplecheckout.cpp \ + recentpresets.cpp \ stylemodel.cpp OTHER_FILES += \ diff --git a/src/plugins/studiowelcome/studiowelcome.qbs b/src/plugins/studiowelcome/studiowelcome.qbs index a0b81b872d..efb5c64e4f 100644 --- a/src/plugins/studiowelcome/studiowelcome.qbs +++ b/src/plugins/studiowelcome/studiowelcome.qbs @@ -21,8 +21,8 @@ QtcPlugin { "examplecheckout.cpp", "newprojectdialogimageprovider.h", "newprojectdialogimageprovider.cpp", - "newprojectmodel.cpp", - "newprojectmodel.h", + "presetmodel.cpp", + "presetmodel.h", "qdsnewdialog.cpp", "qdsnewdialog.h", "screensizemodel.h", @@ -36,6 +36,8 @@ QtcPlugin { "wizardfactories.h", "wizardhandler.cpp", "wizardhandler.h", + "recentpresets.cpp", + "recentpresets.h" ] Group { diff --git a/src/plugins/studiowelcome/stylemodel.h b/src/plugins/studiowelcome/stylemodel.h index 132b7251f8..554a71c02b 100644 --- a/src/plugins/studiowelcome/stylemodel.h +++ b/src/plugins/studiowelcome/stylemodel.h @@ -58,7 +58,7 @@ public: return item->text(); } - return ""; + return {}; } QHash<int, QByteArray> roleNames() const override diff --git a/src/plugins/studiowelcome/wizardfactories.cpp b/src/plugins/studiowelcome/wizardfactories.cpp index 08479ac65b..dfd21918e8 100644 --- a/src/plugins/studiowelcome/wizardfactories.cpp +++ b/src/plugins/studiowelcome/wizardfactories.cpp @@ -23,30 +23,42 @@ ** ****************************************************************************/ +#include "wizardfactories.h" +#include "algorithm.h" + #include <coreplugin/icore.h> #include <coreplugin/iwizardfactory.h> -#include <utils/algorithm.h> -#include "wizardfactories.h" +#include <projectexplorer/jsonwizard/jsonwizardfactory.h> #include <qmldesigner/components/componentcore/theme.h> using namespace StudioWelcome; WizardFactories::GetIconUnicodeFunc WizardFactories::m_getIconUnicode = &QmlDesigner::Theme::getIconUnicode; -WizardFactories::WizardFactories(QList<Core::IWizardFactory *> &factories, QWidget *wizardParent, const Utils::Id &platform) +WizardFactories::WizardFactories(const QList<Core::IWizardFactory *> &factories, + QWidget *wizardParent, + const Utils::Id &platform) : m_wizardParent{wizardParent} , m_platform{platform} - , m_factories{factories} { + m_factories = Utils::filtered(Utils::transform(factories, [](Core::IWizardFactory *f) { + return qobject_cast<JsonWizardFactory *>(f); + })); + sortByCategoryAndId(); filter(); - m_projectItems = makeProjectItemsGroupedByCategory(); + m_presetItems = makePresetItemsGroupedByCategory(); +} + +const Core::IWizardFactory *WizardFactories::front() const +{ + return m_factories.front(); } void WizardFactories::sortByCategoryAndId() { - Utils::sort(m_factories, [](Core::IWizardFactory *lhs, Core::IWizardFactory *rhs) { + Utils::sort(m_factories, [](JsonWizardFactory *lhs, JsonWizardFactory *rhs) { if (lhs->category() == rhs->category()) return lhs->id().toString() < rhs->id().toString(); else @@ -56,34 +68,43 @@ void WizardFactories::sortByCategoryAndId() void WizardFactories::filter() { - QList<Core::IWizardFactory *> acceptedFactories = Utils::filtered(m_factories, [&](auto *wizard) { + QList<JsonWizardFactory *> acceptedFactories = Utils::filtered(m_factories, [&](auto *wizard) { return wizard->isAvailable(m_platform) - && wizard->kind() == Core::IWizardFactory::ProjectWizard - && wizard->requiredFeatures().contains("QtStudio"); + && wizard->kind() == JsonWizardFactory::ProjectWizard + && wizard->requiredFeatures().contains("QtStudio"); }); m_factories = acceptedFactories; } -ProjectItem WizardFactories::makeProjectItem(Core::IWizardFactory *f, QWidget *parent, +PresetItem WizardFactories::makePresetItem(JsonWizardFactory *f, QWidget *parent, const Utils::Id &platform) { using namespace std::placeholders; + QString sizeName; + auto [index, screenSizes] = f->screenSizeInfoFromPage("Fields"); + + if (index < 0 || index >= screenSizes.size()) + sizeName.clear(); + else + sizeName = screenSizes[index]; + return { /*.name =*/f->displayName(), /*.categoryId =*/f->category(), - /*. description =*/f->description(), + /*.screenSizeName=*/sizeName, + /*.description =*/f->description(), /*.qmlPath =*/f->detailsPageQmlPath(), /*.fontIconCode =*/m_getIconUnicode(f->fontIconName()), - /*.create =*/ std::bind(&Core::IWizardFactory::runWizard, f, _1, parent, platform, + /*.create =*/ std::bind(&JsonWizardFactory::runWizard, f, _1, parent, platform, QVariantMap(), false), }; } -std::map<QString, ProjectCategory> WizardFactories::makeProjectItemsGroupedByCategory() +std::map<QString, WizardCategory> WizardFactories::makePresetItemsGroupedByCategory() { - QMap<QString, ProjectCategory> categories; + QMap<QString, WizardCategory> categories; for (auto *f : std::as_const(m_factories)) { if (!categories.contains(f->category())) { @@ -92,12 +113,12 @@ std::map<QString, ProjectCategory> WizardFactories::makeProjectItemsGroupedByCat /*.name =*/ f->displayCategory(), /*.items = */ { - makeProjectItem(f, m_wizardParent, m_platform), + makePresetItem(f, m_wizardParent, m_platform), }, }; } else { - auto projectItem = makeProjectItem(f, m_wizardParent, m_platform); - categories[f->category()].items.push_back(projectItem); + auto presetItem = makePresetItem(f, m_wizardParent, m_platform); + categories[f->category()].items.push_back(presetItem); } } diff --git a/src/plugins/studiowelcome/wizardfactories.h b/src/plugins/studiowelcome/wizardfactories.h index ce3179e82c..aa209362de 100644 --- a/src/plugins/studiowelcome/wizardfactories.h +++ b/src/plugins/studiowelcome/wizardfactories.h @@ -25,7 +25,7 @@ #pragma once -#include "newprojectmodel.h" +#include "presetmodel.h" #include <utils/id.h> @@ -33,6 +33,12 @@ namespace Core { class IWizardFactory; } +namespace ProjectExplorer { +class JsonWizardFactory; +} + +using ProjectExplorer::JsonWizardFactory; + namespace StudioWelcome { class WizardFactories @@ -41,12 +47,12 @@ public: using GetIconUnicodeFunc = QString (*)(const QString &); public: - WizardFactories(QList<Core::IWizardFactory *> &factories, QWidget *wizardParent, + WizardFactories(const QList<Core::IWizardFactory *> &factories, QWidget *wizardParent, const Utils::Id &platform); - const Core::IWizardFactory *front() const { return m_factories.front(); } - const std::map<QString, ProjectCategory> &projectsGroupedByCategory() const - { return m_projectItems; } + const Core::IWizardFactory *front() const; + const std::map<QString, WizardCategory> &presetsGroupedByCategory() const + { return m_presetItems; } bool empty() const { return m_factories.empty(); } static GetIconUnicodeFunc setIconUnicodeCallback(GetIconUnicodeFunc cb) @@ -58,15 +64,15 @@ private: void sortByCategoryAndId(); void filter(); - ProjectItem makeProjectItem(Core::IWizardFactory *f, QWidget *parent, const Utils::Id &platform); - std::map<QString, ProjectCategory> makeProjectItemsGroupedByCategory(); + PresetItem makePresetItem(JsonWizardFactory *f, QWidget *parent, const Utils::Id &platform); + std::map<QString, WizardCategory> makePresetItemsGroupedByCategory(); private: QWidget *m_wizardParent; Utils::Id m_platform; // filter wizards to only those supported by this platform. - QList<Core::IWizardFactory *> m_factories; - std::map<QString, ProjectCategory> m_projectItems; + QList<JsonWizardFactory *> m_factories; + std::map<QString, WizardCategory> m_presetItems; static GetIconUnicodeFunc m_getIconUnicode; }; diff --git a/src/plugins/studiowelcome/wizardhandler.cpp b/src/plugins/studiowelcome/wizardhandler.cpp index bf9f0f31f7..cd176707ac 100644 --- a/src/plugins/studiowelcome/wizardhandler.cpp +++ b/src/plugins/studiowelcome/wizardhandler.cpp @@ -38,18 +38,17 @@ using namespace StudioWelcome; -void WizardHandler::reset(const ProjectItem &projectInfo, int projectSelection, const Utils::FilePath &location) +void WizardHandler::reset(const PresetItem &presetInfo, int presetSelection) { - m_projectItem = projectInfo; - m_projectLocation = location; - m_selectedProject = projectSelection; + m_preset = presetInfo; + m_selectedPreset = presetSelection; if (!m_wizard) { setupWizard(); } else { QObject::connect(m_wizard, &QObject::destroyed, this, &WizardHandler::onWizardResetting); - // DON'T SET `m_selectedProject = -1` --- we are switching now to a separate project. + // DON'T SET `m_selectedPreset = -1` --- we are switching now to a separate preset. emit deletingWizard(); m_wizard->deleteLater(); @@ -60,7 +59,7 @@ void WizardHandler::destroyWizard() { emit deletingWizard(); - m_selectedProject = -1; + m_selectedPreset = -1; m_wizard->deleteLater(); m_wizard = nullptr; m_detailsPage = nullptr; @@ -68,7 +67,7 @@ void WizardHandler::destroyWizard() void WizardHandler::setupWizard() { - m_wizard = m_projectItem.create(m_projectLocation); + m_wizard = m_preset.create(m_projectLocation); if (!m_wizard) { emit wizardCreationFailed(); return; @@ -161,8 +160,8 @@ void WizardHandler::onWizardResetting() // if have a wizard request pending => create new wizard // note: we always have a wizard request pending here, unless the dialogbox was requested to be destroyed. - // if m_selectedProject != -1 => the wizard was destroyed as a result of reset to a different project type - if (m_selectedProject > -1) + // if m_selectedPreset != -1 => the wizard was destroyed as a result of reset to a different preset type + if (m_selectedPreset > -1) setupWizard(); } @@ -175,6 +174,20 @@ void WizardHandler::setScreenSizeIndex(int index) cbfield->selectRow(index); } +QString WizardHandler::screenSizeName(int index) const +{ + auto *field = m_detailsPage->jsonField("ScreenFactor"); + auto *cbfield = dynamic_cast<ProjectExplorer::ComboBoxField *>(field); + QTC_ASSERT(cbfield, return ""); + + QStandardItemModel *model = cbfield->model(); + if (index < 0 || index >= model->rowCount()) + return {}; + + QString text = model->item(index)->text(); + return text; +} + int WizardHandler::screenSizeIndex() const { auto *field = m_detailsPage->jsonField("ScreenFactor"); @@ -184,6 +197,24 @@ int WizardHandler::screenSizeIndex() const return cbfield->selectedRow(); } +int WizardHandler::screenSizeIndex(const QString &sizeName) const +{ + auto *field = m_detailsPage->jsonField("ScreenFactor"); + auto *cbfield = dynamic_cast<ProjectExplorer::ComboBoxField *>(field); + QTC_ASSERT(cbfield, return false); + + const QStandardItemModel *model = cbfield->model(); + for (int i = 0; i < model->rowCount(); ++i) { + const QStandardItem *item = model->item(i, 0); + const QString text = item->text(); + + if (text == sizeName) + return i; + } + + return -1; +} + void WizardHandler::setTargetQtVersionIndex(int index) { auto *field = m_detailsPage->jsonField("TargetQtVersion"); @@ -250,7 +281,7 @@ void WizardHandler::run(const std::function<void(QWizardPage *)> &processPage) m_wizard->next(); } while (-1 != nextId); - m_selectedProject = -1; + m_selectedPreset = -1; // Note: don't call `emit deletingWizard()` here. diff --git a/src/plugins/studiowelcome/wizardhandler.h b/src/plugins/studiowelcome/wizardhandler.h index ad9424bc44..112accfb15 100644 --- a/src/plugins/studiowelcome/wizardhandler.h +++ b/src/plugins/studiowelcome/wizardhandler.h @@ -25,7 +25,7 @@ #pragma once -#include "newprojectmodel.h" +#include "presetmodel.h" #include <utils/filepath.h> #include <utils/infolabel.h> @@ -47,9 +47,10 @@ class WizardHandler: public QObject Q_OBJECT public: - //TODO: location should not be needed in reset() -- only when creating the project - void reset(const ProjectItem &projectInfo, int projectSelection, const Utils::FilePath &location); + void reset(const PresetItem &presetInfo, int presetSelection); void setScreenSizeIndex(int index); + int screenSizeIndex(const QString &sizeName) const; + QString screenSizeName(int index) const; int screenSizeIndex() const; void setTargetQtVersionIndex(int index); bool haveTargetQtVersion() const; @@ -65,6 +66,8 @@ public: void run(const std::function<void (QWizardPage *)> &processPage); + PresetItem preset() const { return m_preset; } + signals: void deletingWizard(); void wizardCreated(QStandardItemModel *screenSizeModel, QStandardItemModel *styleModel); @@ -88,9 +91,9 @@ private: Utils::Wizard *m_wizard = nullptr; ProjectExplorer::JsonFieldPage *m_detailsPage = nullptr; - int m_selectedProject = -1; + int m_selectedPreset = -1; - ProjectItem m_projectItem; + PresetItem m_preset; Utils::FilePath m_projectLocation; }; diff --git a/tests/auto/qml/qmldesigner/wizard/CMakeLists.txt b/tests/auto/qml/qmldesigner/wizard/CMakeLists.txt index ca3a0ac7ec..de84a6e931 100644 --- a/tests/auto/qml/qmldesigner/wizard/CMakeLists.txt +++ b/tests/auto/qml/qmldesigner/wizard/CMakeLists.txt @@ -5,7 +5,7 @@ set(WITH_TESTS ON) find_package(Googletest MODULE) add_qtc_test(tst_qml_wizard - DEPENDS Core Utils StudioWelcome QmlDesigner Googletest + DEPENDS Core Utils StudioWelcome ProjectExplorer QmlDesigner Googletest DEFINES QT_CREATOR QMLDESIGNER_TEST @@ -17,8 +17,13 @@ add_qtc_test(tst_qml_wizard SOURCES wizardfactories-test.cpp stylemodel-test.cpp + recentpresets-test.cpp + presetmodel-test.cpp test-utilities.h + test-main.cpp "${StudioWelcomeDir}/wizardfactories.cpp" "${StudioWelcomeDir}/stylemodel.cpp" + "${StudioWelcomeDir}/recentpresets.cpp" + "${StudioWelcomeDir}/presetmodel.cpp" ) diff --git a/tests/auto/qml/qmldesigner/wizard/presetmodel-test.cpp b/tests/auto/qml/qmldesigner/wizard/presetmodel-test.cpp new file mode 100644 index 0000000000..dc855b67a3 --- /dev/null +++ b/tests/auto/qml/qmldesigner/wizard/presetmodel-test.cpp @@ -0,0 +1,234 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "test-utilities.h" + +#include "presetmodel.h" + +using namespace StudioWelcome; +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::PrintToString; + +namespace StudioWelcome { +void PrintTo(const PresetItem &item, std::ostream *os) +{ + *os << "{categId: " << item.categoryId << ", " + << "name: " << item.name; + + if (!item.screenSizeName.isEmpty()) + *os << ", size: " << item.screenSizeName; + + *os << "}"; +} + +} // namespace StudioWelcome + +namespace { +std::pair<QString, WizardCategory> aCategory(const QString &categId, + const QString &categName, + const std::vector<QString> &names) +{ + std::vector<PresetItem> items = Utils::transform(names, [&categId](const QString &name) { + return PresetItem{name, categId}; + }); + return std::make_pair(categId, WizardCategory{categId, categName, items}); +} + +MATCHER_P2(PresetIs, category, name, PrintToString(PresetItem{name, category})) +{ + return arg.categoryId == category && arg.name == name; +} + +MATCHER_P3(PresetIs, category, name, size, PrintToString(PresetItem{name, category, size})) +{ + return arg.categoryId == category && arg.name == name && size == arg.screenSizeName; +} + +} // namespace + +/******************* TESTS *******************/ + +TEST(QdsPresetModel, whenHaveNoPresetsNoRecentsReturnEmpty) +{ + PresetData data; + + ASSERT_THAT(data.presets(), SizeIs(0)); + ASSERT_THAT(data.categories(), SizeIs(0)); +} + +TEST(QdsPresetModel, haveSameArraySizeForPresetsAndCategories) +{ + PresetData data; + + data.setData( + { + aCategory("A.categ", "A", {"item a", "item b"}), + aCategory("B.categ", "B", {"item c", "item d"}), + }, + {/*recents*/}); + + ASSERT_THAT(data.presets(), SizeIs(2)); + ASSERT_THAT(data.categories(), SizeIs(2)); +} + +TEST(QdsPresetModel, haveWizardPresetsNoRecents) +{ + // Given + PresetData data; + + // When + data.setData( + { + aCategory("A.categ", "A", {"item a", "item b"}), + aCategory("B.categ", "B", {"item c", "item d"}), + }, + {/*recents*/}); + + // Then + ASSERT_THAT(data.categories(), ElementsAre("A", "B")); + ASSERT_THAT(data.presets()[0], + ElementsAre(PresetIs("A.categ", "item a"), PresetIs("A.categ", "item b"))); + ASSERT_THAT(data.presets()[1], + ElementsAre(PresetIs("B.categ", "item c"), PresetIs("B.categ", "item d"))); +} + +TEST(QdsPresetModel, haveRecentsNoWizardPresets) +{ + PresetData data; + + data.setData({/*wizardPresets*/}, + { + {"A.categ", "Desktop", "640 x 480"}, + {"B.categ", "Mobile", "800 x 600"}, + }); + + ASSERT_THAT(data.categories(), IsEmpty()); + ASSERT_THAT(data.presets(), IsEmpty()); +} + +TEST(QdsPresetModel, recentsAddedBeforeWizardPresets) +{ + // Given + PresetData data; + + // When + data.setData( + /*wizard presets*/ + { + aCategory("A.categ", "A", {"Desktop", "item b"}), + aCategory("B.categ", "B", {"item c", "Mobile"}), + }, + /*recents*/ + { + {"A.categ", "Desktop", "800 x 600"}, + {"B.categ", "Mobile", "640 x 480"}, + }); + + // Then + ASSERT_THAT(data.categories(), ElementsAre("Recents", "A", "B")); + + ASSERT_THAT(data.presets(), + ElementsAreArray( + {ElementsAre(PresetIs("A.categ", "Desktop"), PresetIs("B.categ", "Mobile")), + + ElementsAre(PresetIs("A.categ", "Desktop"), PresetIs("A.categ", "item b")), + ElementsAre(PresetIs("B.categ", "item c"), PresetIs("B.categ", "Mobile"))})); +} + +TEST(QdsPresetModel, recentsShouldNotSorted) +{ + // Given + PresetData data; + + // When + data.setData( + /*wizard presets*/ + { + aCategory("A.categ", "A", {"Desktop", "item b"}), + aCategory("B.categ", "B", {"item c", "Mobile"}), + aCategory("Z.categ", "Z", {"Z.desktop"}), + }, + /*recents*/ + { + {"Z.categ", "Z.desktop", "200 x 300"}, + {"B.categ", "Mobile", "200 x 300"}, + {"A.categ", "Desktop", "200 x 300"}, + }); + + // Then + ASSERT_THAT(data.presets()[0], + ElementsAre(PresetIs("Z.categ", "Z.desktop"), + PresetIs("B.categ", "Mobile"), + PresetIs("A.categ", "Desktop"))); +} + +TEST(QdsPresetModel, recentsOfSameWizardProjectButDifferentSizesAreRecognizedAsDifferentPresets) +{ + // Given + PresetData data; + + // When + data.setData( + /*wizard presets*/ + { + aCategory("A.categ", "A", {"Desktop"}), + aCategory("B.categ", "B", {"Mobile"}), + }, + /*recents*/ + { + {"B.categ", "Mobile", "400 x 400"}, + {"B.categ", "Mobile", "200 x 300"}, + {"A.categ", "Desktop", "640 x 480"}, + }); + + // Then + ASSERT_THAT(data.presets()[0], + ElementsAre(PresetIs("B.categ", "Mobile", "400 x 400"), + PresetIs("B.categ", "Mobile", "200 x 300"), + PresetIs("A.categ", "Desktop", "640 x 480"))); +} + +TEST(QdsPresetModel, outdatedRecentsAreNotShown) +{ + // Given + PresetData data; + + // When + data.setData( + /*wizard presets*/ + { + aCategory("A.categ", "A", {"Desktop"}), + aCategory("B.categ", "B", {"Mobile"}), + }, + /*recents*/ + { + {"B.categ", "NoLongerExists", "400 x 400"}, + {"A.categ", "Desktop", "640 x 480"}, + }); + + // Then + ASSERT_THAT(data.presets()[0], ElementsAre(PresetIs("A.categ", "Desktop", "640 x 480"))); +} diff --git a/tests/auto/qml/qmldesigner/wizard/recentpresets-test.cpp b/tests/auto/qml/qmldesigner/wizard/recentpresets-test.cpp new file mode 100644 index 0000000000..b1bb0e0626 --- /dev/null +++ b/tests/auto/qml/qmldesigner/wizard/recentpresets-test.cpp @@ -0,0 +1,239 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "test-utilities.h" + +#include <QDir> +#include <QRandomGenerator> +#include <QTime> + +#include "recentpresets.h" +#include "utils/filepath.h" +#include "utils/temporarydirectory.h" + +using namespace StudioWelcome; + +constexpr char GROUP_NAME[] = "RecentPresets"; +constexpr char ITEMS[] = "Wizards"; + +class QdsRecentPresets : public ::testing::Test +{ +protected: + RecentPresetsStore aStoreWithRecents(const QStringList &items) + { + settings.beginGroup(GROUP_NAME); + settings.setValue(ITEMS, items); + settings.endGroup(); + + return RecentPresetsStore{&settings}; + } + + RecentPresetsStore aStoreWithOne(const QVariant &item) + { + settings.beginGroup(GROUP_NAME); + settings.setValue(ITEMS, item); + settings.endGroup(); + + return RecentPresetsStore{&settings}; + } + +protected: + Utils::TemporaryDirectory tempDir{"recentpresets-XXXXXX"}; + QSettings settings{tempDir.filePath("test").toString(), QSettings::IniFormat}; + +private: + QString settingsPath; +}; + +/******************* TESTS *******************/ + +TEST_F(QdsRecentPresets, readFromEmptyStore) +{ + RecentPresetsStore store{&settings}; + + std::vector<RecentPreset> recents = store.fetchAll(); + + ASSERT_THAT(recents, IsEmpty()); +} + +TEST_F(QdsRecentPresets, readEmptyRecentPresets) +{ + RecentPresetsStore store = aStoreWithOne(""); + + std::vector<RecentPreset> recents = store.fetchAll(); + + ASSERT_THAT(recents, IsEmpty()); +} + +TEST_F(QdsRecentPresets, readOneRecentPresetAsList) +{ + RecentPresetsStore store = aStoreWithRecents({"category/preset:640 x 480"}); + + std::vector<RecentPreset> recents = store.fetchAll(); + + ASSERT_THAT(recents, ElementsAre(RecentPreset("category", "preset", "640 x 480"))); +} + +TEST_F(QdsRecentPresets, readOneRecentPresetAsString) +{ + RecentPresetsStore store = aStoreWithOne("category/preset:200 x 300"); + + std::vector<RecentPreset> recents = store.fetchAll(); + + ASSERT_THAT(recents, ElementsAre(RecentPreset("category", "preset", "200 x 300"))); +} + +TEST_F(QdsRecentPresets, readBadRecentPresetAsString) +{ + RecentPresetsStore store = aStoreWithOne("no_category_only_preset"); + + std::vector<RecentPreset> recents = store.fetchAll(); + + ASSERT_THAT(recents, IsEmpty()); +} + +TEST_F(QdsRecentPresets, readBadRecentPresetAsInt) +{ + RecentPresetsStore store = aStoreWithOne(32); + + std::vector<RecentPreset> recents = store.fetchAll(); + + ASSERT_THAT(recents, IsEmpty()); +} + +TEST_F(QdsRecentPresets, readBadRecentPresetsInList) +{ + RecentPresetsStore store = aStoreWithRecents({"bad1", // no category, no size + "categ/name:800 x 600", // good + "categ/bad2", //no size + "categ/bad3:", //no size + "categ 1/bad4:200 x 300", // category has space + "categ/bad5: 400 x 300", // size starts with space + "categ/bad6:400"}); // bad size + + std::vector<RecentPreset> recents = store.fetchAll(); + + ASSERT_THAT(recents, ElementsAre(RecentPreset("categ", "name", "800 x 600"))); +} + +TEST_F(QdsRecentPresets, readTwoRecentPresets) +{ + RecentPresetsStore store = aStoreWithRecents( + {"category_1/preset 1:640 x 480", "category_2/preset 2:320 x 200"}); + + std::vector<RecentPreset> recents = store.fetchAll(); + + ASSERT_THAT(recents, + ElementsAre(RecentPreset("category_1", "preset 1", "640 x 480"), + RecentPreset("category_2", "preset 2", "320 x 200"))); +} + +TEST_F(QdsRecentPresets, addFirstRecentPreset) +{ + RecentPresetsStore store{&settings}; + + store.add("A.Category", "Normal Application", "400 x 600"); + std::vector<RecentPreset> recents = store.fetchAll(); + + ASSERT_THAT(recents, ElementsAre(RecentPreset("A.Category", "Normal Application", "400 x 600"))); +} + +TEST_F(QdsRecentPresets, addExistingFirstRecentPreset) +{ + RecentPresetsStore store = aStoreWithRecents({"category/preset"}); + + store.add("category", "preset", "200 x 300"); + std::vector<RecentPreset> recents = store.fetchAll(); + + ASSERT_THAT(recents, ElementsAre(RecentPreset("category", "preset", "200 x 300"))); +} + +TEST_F(QdsRecentPresets, addSecondRecentPreset) +{ + RecentPresetsStore store = aStoreWithRecents({"A.Category/Preset 1:800 x 600"}); + + store.add("A.Category", "Preset 2", "640 x 480"); + std::vector<RecentPreset> recents = store.fetchAll(); + + ASSERT_THAT(recents, + ElementsAre(RecentPreset("A.Category", "Preset 2", "640 x 480"), + RecentPreset("A.Category", "Preset 1", "800 x 600"))); +} + +TEST_F(QdsRecentPresets, addSecondRecentPresetSameKindButDifferentSize) +{ + RecentPresetsStore store = aStoreWithRecents({"A.Category/Preset:800 x 600"}); + + store.add("A.Category", "Preset", "640 x 480"); + std::vector<RecentPreset> recents = store.fetchAll(); + + ASSERT_THAT(recents, + ElementsAre(RecentPreset("A.Category", "Preset", "640 x 480"), + RecentPreset("A.Category", "Preset", "800 x 600"))); +} + +TEST_F(QdsRecentPresets, fetchesRecentPresetsInTheReverseOrderTheyWereAdded) +{ + RecentPresetsStore store{&settings}; + + store.add("A.Category", "Preset 1", "640 x 480"); + store.add("A.Category", "Preset 2", "640 x 480"); + store.add("A.Category", "Preset 3", "800 x 600"); + std::vector<RecentPreset> recents = store.fetchAll(); + + ASSERT_THAT(recents, + ElementsAre(RecentPreset("A.Category", "Preset 3", "800 x 600"), + RecentPreset("A.Category", "Preset 2", "640 x 480"), + RecentPreset("A.Category", "Preset 1", "640 x 480"))); +} + +TEST_F(QdsRecentPresets, addingAnExistingRecentPresetMakesItTheFirst) +{ + RecentPresetsStore store = aStoreWithRecents({"A.Category/Preset 1:200 x 300", + "A.Category/Preset 2:200 x 300", + "A.Category/Preset 3:640 x 480"}); + + store.add("A.Category", "Preset 3", "640 x 480"); + std::vector<RecentPreset> recents = store.fetchAll(); + + ASSERT_THAT(recents, + ElementsAre(RecentPreset("A.Category", "Preset 3", "640 x 480"), + RecentPreset("A.Category", "Preset 1", "200 x 300"), + RecentPreset("A.Category", "Preset 2", "200 x 300"))); +} + +TEST_F(QdsRecentPresets, addingTooManyRecentPresetsRemovesTheOldestOne) +{ + RecentPresetsStore store = aStoreWithRecents( + {"A.Category/Preset 2:200 x 300", "A.Category/Preset 1:200 x 300"}); + store.setMaximum(2); + + store.add("A.Category", "Preset 3", "200 x 300"); + std::vector<RecentPreset> recents = store.fetchAll(); + + ASSERT_THAT(recents, + ElementsAre(RecentPreset("A.Category", "Preset 3", "200 x 300"), + RecentPreset("A.Category", "Preset 2", "200 x 300"))); +} diff --git a/tests/auto/qml/qmldesigner/wizard/test-main.cpp b/tests/auto/qml/qmldesigner/wizard/test-main.cpp new file mode 100644 index 0000000000..d419674ea1 --- /dev/null +++ b/tests/auto/qml/qmldesigner/wizard/test-main.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +* Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "test-utilities.h" + +#include <utils/temporarydirectory.h> + +class Environment : public testing::Environment +{ +public: + void SetUp() override + { + const QString temporayDirectoryPath = QDir::tempPath() + "/QtCreator-UnitTests-XXXXXX"; + Utils::TemporaryDirectory::setMasterTemporaryDirectory(temporayDirectoryPath); + qputenv("TMPDIR", Utils::TemporaryDirectory::masterDirectoryPath().toUtf8()); + qputenv("TEMP", Utils::TemporaryDirectory::masterDirectoryPath().toUtf8()); + } + + void TearDown() override {} +}; + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + + auto environment = std::make_unique<Environment>(); + testing::AddGlobalTestEnvironment(environment.release()); + + return RUN_ALL_TESTS(); +} + diff --git a/tests/auto/qml/qmldesigner/wizard/test-utilities.h b/tests/auto/qml/qmldesigner/wizard/test-utilities.h index 3f708e121d..b368e532f4 100644 --- a/tests/auto/qml/qmldesigner/wizard/test-utilities.h +++ b/tests/auto/qml/qmldesigner/wizard/test-utilities.h @@ -26,6 +26,7 @@ ** ****************************************************************************/ +#include "gmock/gmock-matchers.h" #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -38,8 +39,10 @@ using ::testing::Return; using ::testing::AtLeast; using ::testing::ElementsAreArray; +using ::testing::ElementsAre; using ::testing::IsEmpty; using ::testing::Not; +using ::testing::SizeIs; QT_BEGIN_NAMESPACE diff --git a/tests/auto/qml/qmldesigner/wizard/wizardfactories-test.cpp b/tests/auto/qml/qmldesigner/wizard/wizardfactories-test.cpp index 97ae04cccc..2e6a87617d 100644 --- a/tests/auto/qml/qmldesigner/wizard/wizardfactories-test.cpp +++ b/tests/auto/qml/qmldesigner/wizard/wizardfactories-test.cpp @@ -26,15 +26,17 @@ #include "test-utilities.h" #include <coreplugin/iwizardfactory.h> +#include <projectexplorer/jsonwizard/jsonwizardfactory.h> #include "wizardfactories.h" using namespace StudioWelcome; using Core::IWizardFactory; +using ProjectExplorer::JsonWizardFactory; namespace { -class MockWizardFactory : public IWizardFactory +class MockWizardFactory : public JsonWizardFactory { public: MOCK_METHOD(Utils::Wizard *, runWizardImpl, @@ -47,6 +49,8 @@ public: ), (override)); + MOCK_METHOD((std::pair<int, QStringList>), screenSizeInfoFromPage, (const QString &), (const)); + MOCK_METHOD(bool, isAvailable, (Utils::Id), (const, override)); }; @@ -78,12 +82,14 @@ protected: // a good wizard factory is a wizard factory that is not filtered out, and which is available on // platform `this->platform` - IWizardFactory *aGoodWizardFactory(const QString &name = "", const QString &id = "", const QString &categoryId = "") + IWizardFactory *aGoodWizardFactory(const QString &name = "", const QString &id = "", + const QString &categoryId = "", const std::pair<int, QStringList> &sizes = {}) { MockWizardFactory *factory = new MockWizardFactory; m_factories.push_back(std::unique_ptr<IWizardFactory>(factory)); - configureFactory(*factory, IWizardFactory::ProjectWizard, /*req QtStudio*/true, {platform, true}); + configureFactory(*factory, IWizardFactory::ProjectWizard, /*req QtStudio*/true, + {platform, true}, sizes); if (!name.isEmpty()) factory->setDisplayName(name); @@ -97,7 +103,8 @@ protected: void configureFactory(MockWizardFactory &factory, IWizardFactory::WizardKind kind, bool requiresQtStudio = true, - const QPair<QString, bool> &availableOnPlatform = {}) + const QPair<QString, bool> &availableOnPlatform = {}, + const QPair<int, QStringList> &sizes = {}) { if (kind == IWizardFactory::ProjectWizard) { QSet<Utils::Id> supported{Utils::Id{"QmlProjectManager.QmlProject"}}; @@ -127,6 +134,14 @@ protected: .Times(AtLeast(1)) .WillRepeatedly(Return(value)); } + + auto screenSizes = (sizes == std::pair<int, QStringList>{} + ? std::make_pair(0, QStringList({"640 x 480"})) + : sizes); + + EXPECT_CALL(factory, screenSizeInfoFromPage(QString("Fields"))) + .Times(AtLeast(0)) + .WillRepeatedly(Return(screenSizes)); } WizardFactories makeWizardFactoriesHandler(QList<IWizardFactory *> source, @@ -143,15 +158,21 @@ private: WizardFactories::GetIconUnicodeFunc oldIconUnicodeFunc; }; -inline QStringList projectNames(const ProjectCategory &cat) +QStringList presetNames(const WizardCategory &cat) +{ + QStringList result = Utils::transform<QStringList>(cat.items, &PresetItem::name); + return result; +} + +QStringList screenSizes(const WizardCategory &cat) { - QStringList result = Utils::transform<QStringList>(cat.items, &ProjectItem::name); + QStringList result = Utils::transform<QStringList>(cat.items, &PresetItem::screenSizeName); return result; } -inline QStringList categoryNames(const std::map<QString, ProjectCategory> &projects) +QStringList categoryNames(const std::map<QString, WizardCategory> &presets) { - QMap<QString, ProjectCategory> qmap{projects}; + const QMap<QString, WizardCategory> qmap{presets}; return qmap.keys(); } @@ -166,9 +187,9 @@ TEST_F(QdsWizardFactories, haveEmptyListOfWizardFactories) /*get wizards supporting platform*/ "platform" ); - std::map<QString, ProjectCategory> projects = wf.projectsGroupedByCategory(); + std::map<QString, WizardCategory> presets = wf.presetsGroupedByCategory(); - ASSERT_THAT(projects, IsEmpty()); + ASSERT_THAT(presets, IsEmpty()); } TEST_F(QdsWizardFactories, filtersOutNonProjectWizardFactories) @@ -178,9 +199,9 @@ TEST_F(QdsWizardFactories, filtersOutNonProjectWizardFactories) /*get wizards supporting platform*/ platform ); - std::map<QString, ProjectCategory> projects = wf.projectsGroupedByCategory(); + std::map<QString, WizardCategory> presets = wf.presetsGroupedByCategory(); - ASSERT_THAT(projects, IsEmpty()); + ASSERT_THAT(presets, IsEmpty()); } TEST_F(QdsWizardFactories, filtersOutWizardFactoriesUnavailableForPlatform) @@ -190,9 +211,9 @@ TEST_F(QdsWizardFactories, filtersOutWizardFactoriesUnavailableForPlatform) /*get wizards supporting platform*/ "Non-Desktop" ); - std::map<QString, ProjectCategory> projects = wf.projectsGroupedByCategory(); + std::map<QString, WizardCategory> presets = wf.presetsGroupedByCategory(); - ASSERT_THAT(projects, IsEmpty()); + ASSERT_THAT(presets, IsEmpty()); } TEST_F(QdsWizardFactories, filtersOutWizardFactoriesThatDontRequireQtStudio) @@ -203,18 +224,48 @@ TEST_F(QdsWizardFactories, filtersOutWizardFactoriesThatDontRequireQtStudio) }, /*get wizards supporting platform*/ platform); - std::map<QString, ProjectCategory> projects = wf.projectsGroupedByCategory(); + std::map<QString, WizardCategory> presets = wf.presetsGroupedByCategory(); - ASSERT_THAT(projects, IsEmpty()); + ASSERT_THAT(presets, IsEmpty()); } TEST_F(QdsWizardFactories, doesNotFilterOutAGoodWizardFactory) { WizardFactories wf = makeWizardFactoriesHandler({aGoodWizardFactory()}, platform); - std::map<QString, ProjectCategory> projects = wf.projectsGroupedByCategory(); + std::map<QString, WizardCategory> presets = wf.presetsGroupedByCategory(); + + ASSERT_THAT(presets, Not(IsEmpty())); +} + +TEST_F(QdsWizardFactories, DISABLED_buildsPresetItemWithCorrectSizeName) +{ + WizardFactories wf = makeWizardFactoriesHandler( + { + aGoodWizardFactory("A", "A_id", "A.category", {1, {"size 0", "size 1"}}), + }, + platform); + + std::map<QString, WizardCategory> presets = wf.presetsGroupedByCategory(); - ASSERT_THAT(projects, Not(IsEmpty())); + ASSERT_THAT(categoryNames(presets), ElementsAreArray({"A.category"})); + ASSERT_THAT(presetNames(presets["A.category"]), ElementsAreArray({"A"})); + ASSERT_THAT(screenSizes(presets["A.category"]), ElementsAreArray({"size 1"})); +} + +TEST_F(QdsWizardFactories, whenSizeInfoIsBadBuildsPresetItemWithEmptySizeName) +{ + WizardFactories wf = makeWizardFactoriesHandler( + { + aGoodWizardFactory("A", "A_id", "A.category", {1, {/*empty*/}}), + }, + platform); + + std::map<QString, WizardCategory> presets = wf.presetsGroupedByCategory(); + + ASSERT_THAT(categoryNames(presets), ElementsAreArray({"A.category"})); + ASSERT_THAT(presetNames(presets["A.category"]), ElementsAreArray({"A"})); + ASSERT_THAT(screenSizes(presets["A.category"]), ElementsAreArray({""})); } TEST_F(QdsWizardFactories, sortsWizardFactoriesByCategory) @@ -226,11 +277,11 @@ TEST_F(QdsWizardFactories, sortsWizardFactoriesByCategory) }, platform); - std::map<QString, ProjectCategory> projects = wf.projectsGroupedByCategory(); + std::map<QString, WizardCategory> presets = wf.presetsGroupedByCategory(); - ASSERT_THAT(categoryNames(projects), ElementsAreArray({"A.category", "Z.category"})); - ASSERT_THAT(projectNames(projects["A.category"]), ElementsAreArray({"X"})); - ASSERT_THAT(projectNames(projects["Z.category"]), ElementsAreArray({"B"})); + ASSERT_THAT(categoryNames(presets), ElementsAreArray({"A.category", "Z.category"})); + ASSERT_THAT(presetNames(presets["A.category"]), ElementsAreArray({"X"})); + ASSERT_THAT(presetNames(presets["Z.category"]), ElementsAreArray({"B"})); } TEST_F(QdsWizardFactories, sortsWizardFactoriesById) @@ -242,10 +293,10 @@ TEST_F(QdsWizardFactories, sortsWizardFactoriesById) }, platform); - std::map<QString, ProjectCategory> projects = wf.projectsGroupedByCategory(); + std::map<QString, WizardCategory> presets = wf.presetsGroupedByCategory(); - ASSERT_THAT(categoryNames(projects), ElementsAreArray({"category"})); - ASSERT_THAT(projectNames(projects["category"]), ElementsAreArray({"X", "B"})); + ASSERT_THAT(categoryNames(presets), ElementsAreArray({"category"})); + ASSERT_THAT(presetNames(presets["category"]), ElementsAreArray({"X", "B"})); } TEST_F(QdsWizardFactories, groupsWizardFactoriesByCategory) @@ -258,14 +309,14 @@ TEST_F(QdsWizardFactories, groupsWizardFactoriesByCategory) }, platform); - std::map<QString, ProjectCategory> projects = wf.projectsGroupedByCategory(); + std::map<QString, WizardCategory> presets = wf.presetsGroupedByCategory(); - ASSERT_THAT(categoryNames(projects), ElementsAreArray({"A.category", "Z.category"})); - ASSERT_THAT(projectNames(projects["A.category"]), ElementsAreArray({"A", "B"})); - ASSERT_THAT(projectNames(projects["Z.category"]), ElementsAreArray({"C"})); + ASSERT_THAT(categoryNames(presets), ElementsAreArray({"A.category", "Z.category"})); + ASSERT_THAT(presetNames(presets["A.category"]), ElementsAreArray({"A", "B"})); + ASSERT_THAT(presetNames(presets["Z.category"]), ElementsAreArray({"C"})); } -TEST_F(QdsWizardFactories, createsProjectItemAndCategoryCorrectlyFromWizardFactory) +TEST_F(QdsWizardFactories, createsPresetItemAndCategoryCorrectlyFromWizardFactory) { IWizardFactory *source = aGoodWizardFactory("myName", "myId", "myCategoryId"); @@ -280,23 +331,19 @@ TEST_F(QdsWizardFactories, createsProjectItemAndCategoryCorrectlyFromWizardFacto WizardFactories wf = makeWizardFactoriesHandler({source}, platform); - std::map<QString, ProjectCategory> projects = wf.projectsGroupedByCategory(); + std::map<QString, WizardCategory> presets = wf.presetsGroupedByCategory(); - ASSERT_THAT(categoryNames(projects), ElementsAreArray({"myCategoryId"})); - ASSERT_THAT(projectNames(projects["myCategoryId"]), ElementsAreArray({"myName"})); + ASSERT_THAT(categoryNames(presets), ElementsAreArray({"myCategoryId"})); + ASSERT_THAT(presetNames(presets["myCategoryId"]), ElementsAreArray({"myName"})); - auto category = projects["myCategoryId"]; + auto category = presets["myCategoryId"]; ASSERT_EQ("myCategoryId", category.id); ASSERT_EQ("myDisplayCategory", category.name); - auto projectItem = projects["myCategoryId"].items[0]; - ASSERT_EQ("myName", projectItem.name); - ASSERT_EQ("myDescription", projectItem.description); - ASSERT_EQ("qrc:/my/qml/path", projectItem.qmlPath.toString()); - ASSERT_EQ("\uABCD", projectItem.fontIconCode); + auto presetItem = presets["myCategoryId"].items[0]; + ASSERT_EQ("myName", presetItem.name); + ASSERT_EQ("myDescription", presetItem.description); + ASSERT_EQ("qrc:/my/qml/path", presetItem.qmlPath.toString()); + ASSERT_EQ("\uABCD", presetItem.fontIconCode); } -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} |