From 3935c671dd3adcdb0fad7e8b57735868c81082c5 Mon Sep 17 00:00:00 2001 From: Samuel Ghinet Date: Mon, 21 Mar 2022 23:18:59 +0200 Subject: QDS New Project Dialog fix: recents should include all project properties Previously, only the wizard category, name, and size were saved for recent presets. Solved the problem by using the same kind of store (and struct type) for Recent presets as for User/Custom presets - this way we can save all properties. Other changes introduced: * After user creates custom preset C, then creates a project from it (resulting in the creation of a Recent preset R), if the user then deletes custom preset C, then the recent preset R will remain - previously, all recents of the custom preset were deleted * Now we can have multiple recent presets with the same name and size - so, no distinguishing feature inside the Presets view. User will have to look at Details and Styles panes to view differences. * Replaced .ini format with *.json file format. Change-Id: I500e9ac9378d4b9a393c3b0833ef6a34f785585c Reviewed-by: Mahmoud Badri Reviewed-by: Qt CI Bot Reviewed-by: Miikka Heikkinen --- src/plugins/studiowelcome/CMakeLists.txt | 1 - src/plugins/studiowelcome/algorithm.h | 15 +- src/plugins/studiowelcome/presetmodel.cpp | 45 +-- src/plugins/studiowelcome/presetmodel.h | 12 +- src/plugins/studiowelcome/qdsnewdialog.cpp | 67 +++-- src/plugins/studiowelcome/qdsnewdialog.h | 4 +- src/plugins/studiowelcome/recentpresets.cpp | 152 ----------- src/plugins/studiowelcome/recentpresets.h | 101 ------- src/plugins/studiowelcome/studiowelcome.qbs | 2 - src/plugins/studiowelcome/userpresets.cpp | 151 +++++++--- src/plugins/studiowelcome/userpresets.h | 51 +++- tests/auto/qml/qmldesigner/wizard/CMakeLists.txt | 2 - .../qml/qmldesigner/wizard/presetmodel-test.cpp | 116 ++++---- .../qml/qmldesigner/wizard/recentpresets-test.cpp | 303 --------------------- .../qml/qmldesigner/wizard/userpresets-test.cpp | 174 +++++++++--- 15 files changed, 395 insertions(+), 801 deletions(-) delete mode 100644 src/plugins/studiowelcome/recentpresets.cpp delete mode 100644 src/plugins/studiowelcome/recentpresets.h delete mode 100644 tests/auto/qml/qmldesigner/wizard/recentpresets-test.cpp diff --git a/src/plugins/studiowelcome/CMakeLists.txt b/src/plugins/studiowelcome/CMakeLists.txt index 209bb80b01..82e2c78beb 100644 --- a/src/plugins/studiowelcome/CMakeLists.txt +++ b/src/plugins/studiowelcome/CMakeLists.txt @@ -13,7 +13,6 @@ add_qtc_plugin(StudioWelcome wizardfactories.cpp wizardfactories.h createproject.cpp createproject.h wizardhandler.cpp wizardhandler.h - recentpresets.cpp recentpresets.h userpresets.cpp userpresets.h screensizemodel.h algorithm.h diff --git a/src/plugins/studiowelcome/algorithm.h b/src/plugins/studiowelcome/algorithm.h index b7e53da5f1..11d3d1d85d 100644 --- a/src/plugins/studiowelcome/algorithm.h +++ b/src/plugins/studiowelcome/algorithm.h @@ -42,6 +42,16 @@ template return it == end ? nullopt : make_optional(*it); } +template +[[nodiscard]] bool containsItem(const C &container, const typename C::value_type &item) +{ + auto begin = std::cbegin(container); + auto end = std::cend(container); + + auto it = std::find(begin, end, item); + return it == end ? false : true; +} + ///////// FILTER template [[nodiscard]] C filterOut(const C &container, const T &value = T()) @@ -67,13 +77,14 @@ void concat(C &out, const SC &container) } template -void erase_one(C &container, const T &value) +bool 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; + return false; container.erase(i); + return true; } template diff --git a/src/plugins/studiowelcome/presetmodel.cpp b/src/plugins/studiowelcome/presetmodel.cpp index cc06bf59f5..6705b6ca65 100644 --- a/src/plugins/studiowelcome/presetmodel.cpp +++ b/src/plugins/studiowelcome/presetmodel.cpp @@ -47,7 +47,7 @@ QString PresetData::recentsTabName() void PresetData::setData(const PresetsByCategory &presetsByCategory, const std::vector &userPresetsData, - const std::vector &loadedRecentsData) + const std::vector &loadedRecentsData) { QTC_ASSERT(!presetsByCategory.empty(), return ); m_recents = loadedRecentsData; @@ -60,16 +60,13 @@ void PresetData::setData(const PresetsByCategory &presetsByCategory, PresetItems wizardPresets = Utils::flatten(m_presets); - PresetItems userPresetItems = makeUserPresets(wizardPresets); + PresetItems userPresetItems = makeUserPresets(wizardPresets, m_userPresets); if (!userPresetItems.empty()) { m_categories.push_back(CustomTabName); m_presets.push_back(userPresetItems); } - PresetItems allWizardPresets = std::move(wizardPresets); - Utils::concat(allWizardPresets, userPresetItems); - - PresetItems recentPresets = makeRecentPresets(allWizardPresets); + PresetItems recentPresets = makeUserPresets(wizardPresets, m_recents); if (!recentPresets.empty()) { Utils::prepend(m_categories, RecentsTabName); Utils::prepend(m_presets, recentPresets); @@ -79,7 +76,7 @@ void PresetData::setData(const PresetsByCategory &presetsByCategory, } void PresetData::reload(const std::vector &userPresetsData, - const std::vector &loadedRecentsData) + const std::vector &loadedRecentsData) { m_categories.clear(); m_presets.clear(); @@ -96,11 +93,12 @@ std::shared_ptr PresetData::findPresetItemForUserPreset(const UserPr }); } -PresetItems PresetData::makeUserPresets(const PresetItems &wizardPresets) +PresetItems PresetData::makeUserPresets(const PresetItems &wizardPresets, + const std::vector &data) { PresetItems result; - for (const UserPresetData &userPresetData : m_userPresets) { + for (const UserPresetData &userPresetData : data) { std::shared_ptr foundPreset = findPresetItemForUserPreset(userPresetData, wizardPresets); if (!foundPreset) @@ -128,35 +126,6 @@ PresetItems PresetData::makeUserPresets(const PresetItems &wizardPresets) return result; } -std::shared_ptr PresetData::findPresetItemForRecent(const RecentPresetData &recent, const PresetItems &wizardPresets) -{ - return Utils::findOrDefault(wizardPresets, [&recent](const std::shared_ptr &item) { - bool sameName = item->categoryId == recent.category - && item->displayName() == recent.presetName; - - bool sameType = (recent.isUserPreset ? item->isUserPreset() : !item->isUserPreset()); - - return sameName && sameType; - }); -} - -PresetItems PresetData::makeRecentPresets(const PresetItems &wizardPresets) -{ - PresetItems result; - - for (const RecentPresetData &recent : m_recents) { - std::shared_ptr preset = findPresetItemForRecent(recent, wizardPresets); - - if (preset) { - auto clone = std::shared_ptr{preset->clone()}; - clone->screenSizeName = recent.sizeName; - result.push_back(clone); - } - } - - return result; -} - /****************** BasePresetModel ******************/ BasePresetModel::BasePresetModel(const PresetData *data, QObject *parent) diff --git a/src/plugins/studiowelcome/presetmodel.h b/src/plugins/studiowelcome/presetmodel.h index e4c6712b81..19150e0fe9 100644 --- a/src/plugins/studiowelcome/presetmodel.h +++ b/src/plugins/studiowelcome/presetmodel.h @@ -33,7 +33,6 @@ #include #include -#include "recentpresets.h" #include "userpresets.h" namespace Utils { @@ -169,10 +168,10 @@ class PresetData { public: void reload(const std::vector &userPresets, - const std::vector &loadedRecents); + const std::vector &loadedRecents); void setData(const PresetsByCategory &presets, const std::vector &userPresets, - const std::vector &recents); + const std::vector &recents); const std::vector &presets() const { return m_presets; } const Categories &categories() const { return m_categories; } @@ -180,16 +179,13 @@ public: static QString recentsTabName(); private: - PresetItems makeRecentPresets(const PresetItems &wizardPresets); - PresetItems makeUserPresets(const PresetItems &wizardPresets); - + PresetItems makeUserPresets(const PresetItems &wizardPresets, const std::vector &data); std::shared_ptr findPresetItemForUserPreset(const UserPresetData &preset, const PresetItems &wizardPresets); - std::shared_ptr findPresetItemForRecent(const RecentPresetData &recent, const PresetItems &wizardPresets); private: std::vector m_presets; Categories m_categories; - std::vector m_recents; + std::vector m_recents; std::vector m_userPresets; PresetsByCategory m_presetsByCategory; }; diff --git a/src/plugins/studiowelcome/qdsnewdialog.cpp b/src/plugins/studiowelcome/qdsnewdialog.cpp index 943ca0be32..ea0f741e1a 100644 --- a/src/plugins/studiowelcome/qdsnewdialog.cpp +++ b/src/plugins/studiowelcome/qdsnewdialog.cpp @@ -72,10 +72,14 @@ QdsNewDialog::QdsNewDialog(QWidget *parent) , m_presetModel{new PresetModel(&m_presetData, this)} , m_screenSizeModel{new ScreenSizeModel(this)} , m_styleModel{new StyleModel(this)} - , m_recentsStore{Core::ICore::settings()} + , 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()); @@ -190,8 +194,11 @@ void QdsNewDialog::updateScreenSizes() void QdsNewDialog::onWizardCreated(QStandardItemModel *screenSizeModel, QStandardItemModel *styleModel) { - m_screenSizeModel->setBackendModel(screenSizeModel); - m_styleModel->setBackendModel(styleModel); + if (screenSizeModel) + m_screenSizeModel->setBackendModel(screenSizeModel); + + if (styleModel) + m_styleModel->setBackendModel(styleModel); auto userPreset = m_currentPreset->asUserPreset(); @@ -326,7 +333,7 @@ void QdsNewDialog::setWizardFactories(QList factories_, WizardFactories factories{factories_, m_dialog, platform}; - std::vector recents = m_recentsStore.fetchAll(); + std::vector recents = m_recentsStore.fetchAll(); std::vector userPresets = m_userPresetsStore.fetchAll(); m_presetData.setData(factories.presetsGroupedByCategory(), userPresets, recents); @@ -360,33 +367,13 @@ void QdsNewDialog::setWizardFactories(QList factories_, * sure that all events have occurred before we go ahead and configure the wizard. */ - auto userPreset = m_currentPreset->asUserPreset(); - - if (m_qmlDetailsLoaded) { - updateScreenSizes(); - - if (m_wizard.haveTargetQtVersion()) { - int index = (userPreset ? m_wizard.targetQtVersionIndex(userPreset->qtVersion) - : m_wizard.targetQtVersionIndex()); - if (index != -1) - setTargetQtVersionIndex(index); - } - - if (m_wizard.haveVirtualKeyboard() && userPreset) - setUseVirtualKeyboard(userPreset->useQtVirtualKeyboard); - - emit haveVirtualKeyboardChanged(); - emit haveTargetQtVersionChanged(); - } + /* 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. + */ - if (m_qmlStylesLoaded && m_wizard.haveStyleModel()) { - if (userPreset) { - int index = m_wizard.styleIndex(userPreset->styleName); - if (index != -1) - setStyleIndex(index); - } - m_styleModel->reset(); - } + onWizardCreated(nullptr, nullptr); } QString QdsNewDialog::recentsTabName() const @@ -431,7 +418,8 @@ void QdsNewDialog::accept() std::shared_ptr item = m_wizard.preset(); QString customSizeName = m_qmlCustomWidth + " x " + m_qmlCustomHeight; - m_recentsStore.add(item->categoryId, item->displayName(), customSizeName, item->isUserPreset()); + UserPresetData preset = currentUserPresetData(m_currentPreset->displayName()); + m_recentsStore.save(preset); m_dialog->close(); m_dialog->deleteLater(); @@ -471,7 +459,7 @@ void QdsNewDialog::setSelectedPreset(int selection) } } -void QdsNewDialog::savePresetDialogAccept() +UserPresetData QdsNewDialog::currentUserPresetData(const QString &displayName) const { QString screenSize = m_qmlCustomWidth + " x " + m_qmlCustomHeight; QString targetQtVersion = ""; @@ -489,12 +477,19 @@ void QdsNewDialog::savePresetDialogAccept() UserPresetData preset = {m_currentPreset->categoryId, m_currentPreset->wizardName, - m_qmlPresetName, + 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"), @@ -503,7 +498,7 @@ void QdsNewDialog::savePresetDialogAccept() } // reload model - std::vector recents = m_recentsStore.fetchAll(); + std::vector recents = m_recentsStore.fetchAll(); std::vector userPresets = m_userPresetsStore.fetchAll(); m_presetData.reload(userPresets, recents); @@ -520,8 +515,8 @@ void QdsNewDialog::removeCurrentPreset() } // remove preset & reload model - std::vector recents = m_recentsStore.remove(m_currentPreset->categoryId, - m_currentPreset->displayName()); + UserPresetData currentPreset = currentUserPresetData(m_qmlPresetName); + std::vector recents = m_recentsStore.remove(currentPreset); auto userPreset = m_currentPreset->asUserPreset(); m_userPresetsStore.remove(userPreset->categoryId, userPreset->displayName()); diff --git a/src/plugins/studiowelcome/qdsnewdialog.h b/src/plugins/studiowelcome/qdsnewdialog.h index 09a425c499..47779c1b31 100644 --- a/src/plugins/studiowelcome/qdsnewdialog.h +++ b/src/plugins/studiowelcome/qdsnewdialog.h @@ -34,7 +34,6 @@ #include "presetmodel.h" #include "screensizemodel.h" #include "stylemodel.h" -#include "recentpresets.h" #include "userpresets.h" QT_BEGIN_NAMESPACE @@ -153,6 +152,7 @@ private: void updateScreenSizes(); bool eventFilter(QObject *obj, QEvent *ev) override; + UserPresetData currentUserPresetData(const QString &displayName) const; private slots: void onDeletingWizard(); @@ -194,7 +194,7 @@ private: std::shared_ptr m_currentPreset; WizardHandler m_wizard; - RecentPresetsStore m_recentsStore; + UserPresetsStore m_recentsStore; UserPresetsStore m_userPresetsStore; }; diff --git a/src/plugins/studiowelcome/recentpresets.cpp b/src/plugins/studiowelcome/recentpresets.cpp deleted file mode 100644 index cad2846d75..0000000000 --- a/src/plugins/studiowelcome/recentpresets.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/**************************************************************************** -** -** 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 - -#include -#include -#include - -using namespace StudioWelcome; - -constexpr char GROUP_NAME[] = "RecentPresets"; -constexpr char WIZARDS[] = "Wizards"; - -void RecentPresetsStore::add(const QString &categoryId, - const QString &name, - const QString &sizeName, - bool isUserPreset) -{ - std::vector existing = fetchAll(); - - std::vector recents - = addRecentToExisting(RecentPresetData{categoryId, name, sizeName, isUserPreset}, existing); - - save(recents); -} - -void RecentPresetsStore::save(const std::vector &recents) -{ - QStringList encodedRecents = encodeRecentPresets(recents); - - m_settings->beginGroup(GROUP_NAME); - m_settings->setValue(WIZARDS, encodedRecents); - m_settings->endGroup(); - m_settings->sync(); -} - -std::vector RecentPresetsStore::remove(const QString &categoryId, const QString &presetName) -{ - std::vector recents = fetchAll(); - size_t countBefore = recents.size(); - - /* NOTE: when removing one preset, it may happen that there are more than one recent for that - * preset. In that case, we need to remove all associated recents, for the preset.*/ - - Utils::erase(recents, [&](const RecentPresetData &p) { - return p.category == categoryId && p.presetName == presetName; - }); - - if (recents.size() < countBefore) - save(recents); - - return recents; -} - -std::vector RecentPresetsStore::addRecentToExisting( - const RecentPresetData &preset, std::vector &recents) -{ - Utils::erase_one(recents, preset); - Utils::prepend(recents, preset); - - if (int(recents.size()) > m_max) - recents.pop_back(); - - return recents; -} - -std::vector RecentPresetsStore::fetchAll() const -{ - m_settings->beginGroup(GROUP_NAME); - QVariant value = m_settings->value(WIZARDS); - m_settings->endGroup(); - - std::vector 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 RecentPresetData empty; - return Utils::filtered(result, [&empty](const RecentPresetData &recent) { return recent != empty; }); -} - -QStringList RecentPresetsStore::encodeRecentPresets(const std::vector &recents) -{ - return Utils::transform(recents, [](const RecentPresetData &p) -> QString { - QString name = p.presetName; - if (p.isUserPreset) - name.prepend("[U]"); - - return p.category + "/" + name + ":" + p.sizeName; - }); -} - -RecentPresetData RecentPresetsStore::decodeOneRecentPreset(const QString &encoded) -{ - QRegularExpression pattern{R"(^(\S+)/(.+):(\d+ x \d+)$)"}; - auto m = pattern.match(encoded); - if (!m.hasMatch()) - return RecentPresetData{}; - - QString category = m.captured(1); - QString name = m.captured(2); - QString size = m.captured(3); - bool isUserPreset = name.startsWith("[U]"); - if (isUserPreset) - name = name.split("[U]")[1]; - - if (!QRegularExpression{R"(^\w[\w ]*$)"}.match(name).hasMatch()) - return RecentPresetData{}; - - RecentPresetData result; - result.category = category; - result.presetName = name; - result.sizeName = size; - result.isUserPreset = isUserPreset; - - return result; -} - -std::vector RecentPresetsStore::decodeRecentPresets(const QVariantList &values) -{ - return Utils::transform(values, [](const QVariant &value) { - return decodeOneRecentPreset(value.toString()); - }); -} diff --git a/src/plugins/studiowelcome/recentpresets.h b/src/plugins/studiowelcome/recentpresets.h deleted file mode 100644 index 83ab5fb8cc..0000000000 --- a/src/plugins/studiowelcome/recentpresets.h +++ /dev/null @@ -1,101 +0,0 @@ -/**************************************************************************** -** -** 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 -#include -#include - -namespace StudioWelcome { - -struct RecentPresetData -{ - RecentPresetData() = default; - RecentPresetData(const QString &category, - const QString &name, - const QString &size, - bool isUserPreset = false) - : category{category} - , presetName{name} - , sizeName{size} - , isUserPreset{isUserPreset} - {} - - QString category; - QString presetName; - QString sizeName; - bool isUserPreset = false; -}; - -inline bool operator==(const RecentPresetData &lhs, const RecentPresetData &rhs) -{ - return lhs.category == rhs.category && lhs.presetName == rhs.presetName - && lhs.sizeName == rhs.sizeName && lhs.isUserPreset == rhs.isUserPreset; -} - -inline bool operator!=(const RecentPresetData &lhs, const RecentPresetData &rhs) -{ - return !(lhs == rhs); -} - -inline QDebug &operator<<(QDebug &d, const RecentPresetData &preset) -{ - d << "RecentPreset{category=" << preset.category << "; name=" << preset.presetName - << "; size=" << preset.sizeName << "; isUserPreset=" << preset.isUserPreset << "}"; - - return d; -} - -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, - bool isUserPreset = false); - - std::vector remove(const QString &categoryId, const QString &presetName); - std::vector fetchAll() const; - -private: - std::vector addRecentToExisting(const RecentPresetData &preset, - std::vector &recents); - static QStringList encodeRecentPresets(const std::vector &recents); - static std::vector decodeRecentPresets(const QVariantList &values); - static RecentPresetData decodeOneRecentPreset(const QString &encoded); - void save(const std::vector &recents); - -private: - QSettings *m_settings = nullptr; - int m_max = 10; -}; - -} // namespace StudioWelcome diff --git a/src/plugins/studiowelcome/studiowelcome.qbs b/src/plugins/studiowelcome/studiowelcome.qbs index 9648eda14d..4c82c8112e 100644 --- a/src/plugins/studiowelcome/studiowelcome.qbs +++ b/src/plugins/studiowelcome/studiowelcome.qbs @@ -37,8 +37,6 @@ QtcPlugin { "wizardfactories.h", "wizardhandler.cpp", "wizardhandler.h", - "recentpresets.cpp", - "recentpresets.h", "userpresets.cpp", "userpresets.h" ] diff --git a/src/plugins/studiowelcome/userpresets.cpp b/src/plugins/studiowelcome/userpresets.cpp index d468c3522e..e32c010bd6 100644 --- a/src/plugins/studiowelcome/userpresets.cpp +++ b/src/plugins/studiowelcome/userpresets.cpp @@ -24,43 +24,75 @@ ****************************************************************************/ #include "userpresets.h" +#include "algorithm.h" + +#include +#include +#include +#include #include -#include +#include #include using namespace StudioWelcome; -constexpr char PREFIX[] = "UserPresets"; +FileStoreIo::FileStoreIo(const QString &fileName) + : m_file{std::make_unique(fullFilePath(fileName))} +{} + +QByteArray FileStoreIo::read() const +{ + m_file->open(QFile::ReadOnly | QFile::Text); + QByteArray data = m_file->readAll(); + m_file->close(); + + return data; +} + +void FileStoreIo::write(const QByteArray &data) +{ + m_file->open(QFile::WriteOnly | QFile::Text); + m_file->write(data); + m_file->close(); +} -UserPresetsStore::UserPresetsStore() +QString FileStoreIo::fullFilePath(const QString &fileName) const { - m_settings = std::make_unique(fullFilePath(), QSettings::IniFormat); + return Core::ICore::userResourcePath(fileName).toString(); } -UserPresetsStore::UserPresetsStore(std::unique_ptr &&settings) - : m_settings{std::move(settings)} +UserPresetsStore::UserPresetsStore(const QString &fileName, StorePolicy policy) + : m_store{std::make_unique(fileName)} + , m_policy{policy} +{} + +UserPresetsStore::UserPresetsStore(std::unique_ptr &&fileStore, + StorePolicy policy) + : m_store{std::move(fileStore)} + , m_policy{policy} {} -void UserPresetsStore::savePresets(const std::vector &presets) +void UserPresetsStore::savePresets(const std::vector &presetItems) { - m_settings->beginWriteArray(PREFIX, static_cast(presets.size())); - - for (size_t i = 0; i < presets.size(); ++i) { - m_settings->setArrayIndex(static_cast(i)); - const auto &preset = presets[i]; - - m_settings->setValue("categoryId", preset.categoryId); - m_settings->setValue("wizardName", preset.wizardName); - m_settings->setValue("name", preset.name); - m_settings->setValue("screenSize", preset.screenSize); - m_settings->setValue("useQtVirtualKeyboard", preset.useQtVirtualKeyboard); - m_settings->setValue("qtVersion", preset.qtVersion); - m_settings->setValue("styleName", preset.styleName); + QJsonArray jsonArray; + + for (const auto &preset : presetItems) { + QJsonObject obj({{"categoryId", preset.categoryId}, + {"wizardName", preset.wizardName}, + {"name", preset.name}, + {"screenSize", preset.screenSize}, + {"useQtVirtualKeyboard", preset.useQtVirtualKeyboard}, + {"qtVersion", preset.qtVersion}, + {"styleName", preset.styleName}}); + + jsonArray.append(QJsonValue{obj}); } - m_settings->endArray(); - m_settings->sync(); + QJsonDocument doc(jsonArray); + QByteArray data = doc.toJson(); + + m_store->write(data); } bool UserPresetsStore::save(const UserPresetData &newPreset) @@ -68,12 +100,30 @@ bool UserPresetsStore::save(const UserPresetData &newPreset) QTC_ASSERT(newPreset.isValid(), return false); std::vector presetItems = fetchAll(); - if (Utils::anyOf(presetItems, - [&newPreset](const UserPresetData &p) { return p.name == newPreset.name; })) { - return false; + + if (m_policy == StorePolicy::UniqueNames) { + if (Utils::anyOf(presetItems, [&newPreset](const UserPresetData &p) { + return p.name == newPreset.name; + })) { + return false; + } + } else if (m_policy == StorePolicy::UniqueValues) { + if (Utils::containsItem(presetItems, newPreset)) + return false; + } + + if (m_reverse) + Utils::prepend(presetItems, newPreset); + else + presetItems.push_back(newPreset); + + if (m_maximum > -1 && static_cast(presetItems.size()) > m_maximum) { + if (m_reverse) + presetItems.pop_back(); + else + presetItems.erase(std::cbegin(presetItems)); } - presetItems.push_back(newPreset); savePresets(presetItems); return true; @@ -92,34 +142,45 @@ void UserPresetsStore::remove(const QString &category, const QString &name) savePresets(presetItems); } +std::vector UserPresetsStore::remove(const UserPresetData &preset) +{ + std::vector presetItems = fetchAll(); + bool erased = Utils::erase_one(presetItems, preset); + if (erased) + savePresets(presetItems); + + return presetItems; +} + std::vector UserPresetsStore::fetchAll() const { + QByteArray data = m_store->read(); + + const QJsonDocument doc = QJsonDocument::fromJson(data); + if (!doc.isArray()) + return {}; + std::vector result; - int size = m_settings->beginReadArray(PREFIX); - if (size >= 0) - result.reserve(static_cast(size) + 1); + const QJsonArray jsonArray = doc.array(); - for (int i = 0; i < size; ++i) { - m_settings->setArrayIndex(i); + for (const QJsonValue &value: jsonArray) { + if (!value.isObject()) + continue; + const QJsonObject obj = value.toObject(); UserPresetData preset; - preset.categoryId = m_settings->value("categoryId").toString(); - preset.wizardName = m_settings->value("wizardName").toString(); - preset.name = m_settings->value("name").toString(); - preset.screenSize = m_settings->value("screenSize").toString(); - preset.useQtVirtualKeyboard = m_settings->value("useQtVirtualKeyboard").toBool(); - preset.qtVersion = m_settings->value("qtVersion").toString(); - preset.styleName = m_settings->value("styleName").toString(); + + preset.categoryId = obj["categoryId"].toString(); + preset.wizardName = obj["wizardName"].toString(); + preset.name = obj["name"].toString(); + preset.screenSize = obj["screenSize"].toString(); + preset.useQtVirtualKeyboard = obj["useQtVirtualKeyboard"].toBool(); + preset.qtVersion = obj["qtVersion"].toString(); + preset.styleName = obj["styleName"].toString(); if (preset.isValid()) - result.push_back(std::move(preset)); + result.push_back(preset); } - m_settings->endArray(); return result; } - -QString UserPresetsStore::fullFilePath() const -{ - return Core::ICore::userResourcePath("UserPresets.ini").toString(); -} diff --git a/src/plugins/studiowelcome/userpresets.h b/src/plugins/studiowelcome/userpresets.h index 4f6053ef26..3f614f4ea0 100644 --- a/src/plugins/studiowelcome/userpresets.h +++ b/src/plugins/studiowelcome/userpresets.h @@ -25,8 +25,10 @@ #pragma once +#include #include -#include +#include +#include namespace StudioWelcome { @@ -68,24 +70,59 @@ inline bool operator==(const UserPresetData &lhs, const UserPresetData &rhs) return lhs.categoryId == rhs.categoryId && lhs.wizardName == rhs.wizardName && lhs.name == rhs.name && lhs.screenSize == rhs.screenSize && lhs.useQtVirtualKeyboard == rhs.useQtVirtualKeyboard && lhs.qtVersion == rhs.qtVersion - && lhs.styleName == rhs.styleName; + && lhs.styleName == rhs.styleName;; } +enum class StorePolicy {UniqueNames, UniqueValues}; + +class StoreIo +{ +public: + virtual ~StoreIo() {} + + virtual QByteArray read() const = 0; + virtual void write(const QByteArray &bytes) = 0; +}; + +class FileStoreIo : public StoreIo +{ +public: + explicit FileStoreIo(const QString &fileName); + FileStoreIo(FileStoreIo &&other): m_file{std::move(other.m_file)} {} + FileStoreIo& operator=(FileStoreIo &&other) { m_file = std::move(other.m_file); return *this; } + + QByteArray read() const override; + void write(const QByteArray &data) override; + +private: + QString fullFilePath(const QString &fileName) const; + + std::unique_ptr m_file; + + Q_DISABLE_COPY(FileStoreIo) +}; + class UserPresetsStore { public: - UserPresetsStore(); - UserPresetsStore(std::unique_ptr &&settings); + UserPresetsStore(const QString &fileName, StorePolicy policy); + UserPresetsStore(std::unique_ptr &&store, StorePolicy policy); bool save(const UserPresetData &preset); - void remove(const QString &category, const QString &name); std::vector fetchAll() const; + void remove(const QString &category, const QString &name); + std::vector remove(const UserPresetData &preset); + + void setMaximum(int maximum) { m_maximum = maximum; } + void setReverseOrder() { m_reverse = true; } private: - QString fullFilePath() const; void savePresets(const std::vector &presets); - std::unique_ptr m_settings; + std::unique_ptr m_store; + StorePolicy m_policy = StorePolicy::UniqueNames; + bool m_reverse = false; + int m_maximum = -1; }; } // namespace StudioWelcome diff --git a/tests/auto/qml/qmldesigner/wizard/CMakeLists.txt b/tests/auto/qml/qmldesigner/wizard/CMakeLists.txt index c5040112ef..a2965b22f1 100644 --- a/tests/auto/qml/qmldesigner/wizard/CMakeLists.txt +++ b/tests/auto/qml/qmldesigner/wizard/CMakeLists.txt @@ -17,14 +17,12 @@ add_qtc_test(tst_qml_wizard SOURCES wizardfactories-test.cpp stylemodel-test.cpp - recentpresets-test.cpp userpresets-test.cpp presetmodel-test.cpp test-utilities.h test-main.cpp "${StudioWelcomeDir}/wizardfactories.cpp" "${StudioWelcomeDir}/stylemodel.cpp" - "${StudioWelcomeDir}/recentpresets.cpp" "${StudioWelcomeDir}/userpresets.cpp" "${StudioWelcomeDir}/presetmodel.cpp" ) diff --git a/tests/auto/qml/qmldesigner/wizard/presetmodel-test.cpp b/tests/auto/qml/qmldesigner/wizard/presetmodel-test.cpp index 3c16688458..8452ca8222 100644 --- a/tests/auto/qml/qmldesigner/wizard/presetmodel-test.cpp +++ b/tests/auto/qml/qmldesigner/wizard/presetmodel-test.cpp @@ -100,6 +100,14 @@ UserPresetData aUserPreset(const QString &categId, const QString &wizardName, co return preset; } +UserPresetData aRecentPreset(const QString &categId, const QString &wizardName, const QString &screenSizeName) +{ + UserPresetData preset = aUserPreset(categId, wizardName, wizardName); + preset.screenSize = screenSizeName; + + return preset; +} + MATCHER_P2(PresetIs, category, name, PrintToString(PresetItem{name, category})) { return arg->categoryId == category && arg->wizardName == name; @@ -139,6 +147,7 @@ TEST(QdsPresetModel, haveSameArraySizeForPresetsAndCategories) PresetData data; data.setData( + /*wizard presets*/ { aCategory("A.categ", "A", {"item a", "item b"}), aCategory("B.categ", "B", {"item c", "item d"}), @@ -157,6 +166,7 @@ TEST(QdsPresetModel, haveWizardPresetsNoRecents) // When data.setData( + /*wizard presets*/ { aCategory("A.categ", "A", {"item a", "item b"}), aCategory("B.categ", "B", {"item c", "item d"}), @@ -179,6 +189,7 @@ TEST(QdsPresetModel, whenHaveUserPresetsButNoWizardPresetsReturnEmpty) // When data.setData({/*builtin presets*/}, + /*user presets*/ { aUserPreset("A.Mobile", "Scroll", "iPhone5"), aUserPreset("B.Desktop", "Launcher", "MacBook"), @@ -196,9 +207,10 @@ TEST(QdsPresetModel, haveRecentsNoWizardPresets) data.setData({/*wizardPresets*/}, {/*user presets*/}, + /*recent presets*/ { - {"A.categ", "Desktop", "640 x 480"}, - {"B.categ", "Mobile", "800 x 600"}, + aRecentPreset("A.categ", "Desktop", "640 x 480"), + aRecentPreset("B.categ", "Mobile", "800 x 600"), }); ASSERT_THAT(data.categories(), IsEmpty()); @@ -220,8 +232,8 @@ TEST(QdsPresetModel, recentsAddedWithWizardPresets) {/*user presets*/}, /*recents*/ { - {"A.categ", "Desktop", "800 x 600"}, - {"B.categ", "Mobile", "640 x 480"}, + aRecentPreset("A.categ", "Desktop", "800 x 600"), + aRecentPreset("B.categ", "Mobile", "640 x 480"), }); // Then @@ -247,6 +259,7 @@ TEST(QdsPresetModel, userPresetsAddedWithWizardPresets) aCategory("A.categ", "A", {"Desktop", "item b"}), aCategory("B.categ", "B", {"Mobile"}), }, + /*user presets*/ { aUserPreset("A.categ", "Desktop", "Windows10"), }, @@ -272,6 +285,7 @@ TEST(QdsPresetModel, doesNotAddUserPresetsOfNonExistingCategory) { aCategory("A.categ", "A", {"Desktop"}), // Only category "A.categ" exists }, + /*user presets*/ { aUserPreset("Bad.Categ", "Desktop", "Windows8"), // Bad.Categ does not exist }, @@ -293,6 +307,7 @@ TEST(QdsPresetModel, doesNotAddUserPresetIfWizardPresetItRefersToDoesNotExist) { aCategory("A.categ", "A", {"Desktop"}), }, + /*user presets*/ { aUserPreset("B.categ", "BadWizard", "Tablet"), // BadWizard referenced does not exist }, @@ -314,6 +329,7 @@ TEST(QdsPresetModel, userPresetWithSameNameAsWizardPreset) { aCategory("A.categ", "A", {"Desktop"}), }, + /*user presets*/ { aUserPreset("A.categ", "Desktop", "Desktop"), }, @@ -326,34 +342,7 @@ TEST(QdsPresetModel, userPresetWithSameNameAsWizardPreset) ElementsAre(UserPresetIs("A.categ", "Desktop", "Desktop")))); } -TEST(QdsPresetModel, recentOfUserPresetReferringToExistingWizardPreset) -{ - // Given - PresetData data; - - // When - data.setData( - /*wizard presets*/ - { - aCategory("A.categ", "A", {"Desktop"}), - }, - { - aUserPreset("A.categ", "Desktop", "Windows 7"), - }, - /*recents*/ - { - {"A.categ", "Windows 7", "200 x 300", /*is user*/true} - }); - - // Then - ASSERT_THAT(data.categories(), ElementsAre("Recents", "A", "Custom")); - ASSERT_THAT(data.presets(), - ElementsAre(ElementsAre(UserPresetIs("A.categ", "Desktop", "Windows 7")), - ElementsAre(PresetIs("A.categ", "Desktop")), - ElementsAre(UserPresetIs("A.categ", "Desktop", "Windows 7")))); -} - -TEST(QdsPresetModel, recentOfUserPresetReferringToNonexistingWizardPreset) +TEST(QdsPresetModel, recentOfNonExistentWizardPreset) { // Given PresetData data; @@ -364,12 +353,10 @@ TEST(QdsPresetModel, recentOfUserPresetReferringToNonexistingWizardPreset) { aCategory("A.categ", "A", {"Desktop"}), }, - { - aUserPreset("A.categ", "Not-Desktop", "Windows 7"), // Non-existing Wizard Preset - }, + {/*user presets*/}, /*recents*/ { - {"A.categ", "Windows 7", "200 x 300", /*is user*/true} + aRecentPreset("A.categ", "Windows 7", "200 x 300") }); // Then @@ -377,7 +364,7 @@ TEST(QdsPresetModel, recentOfUserPresetReferringToNonexistingWizardPreset) ASSERT_THAT(data.presets(), ElementsAre(ElementsAre(PresetIs("A.categ", "Desktop")))); } -TEST(QdsPresetModel, recentOfNonExistentUserPreset) +TEST(QdsPresetModel, recentsShouldNotBeSorted) { // Given PresetData data; @@ -386,20 +373,26 @@ TEST(QdsPresetModel, recentOfNonExistentUserPreset) data.setData( /*wizard presets*/ { - aCategory("A.categ", "A", {"Desktop"}), + aCategory("A.categ", "A", {"Desktop", "item b"}), + aCategory("B.categ", "B", {"item c", "Mobile"}), + aCategory("Z.categ", "Z", {"Z.desktop"}), }, {/*user presets*/}, /*recents*/ { - {"A.categ", "Windows 7", "200 x 300", /*is user*/true} + aRecentPreset("Z.categ", "Z.desktop", "200 x 300"), + aRecentPreset("B.categ", "Mobile", "200 x 300"), + aRecentPreset("A.categ", "Desktop", "200 x 300"), }); // Then - ASSERT_THAT(data.categories(), ElementsAre("A")); - ASSERT_THAT(data.presets(), ElementsAre(ElementsAre(PresetIs("A.categ", "Desktop")))); + ASSERT_THAT(data.presets()[0], + ElementsAre(PresetIs("Z.categ", "Z.desktop"), + PresetIs("B.categ", "Mobile"), + PresetIs("A.categ", "Desktop"))); } -TEST(QdsPresetModel, recentsShouldNotBeSorted) +TEST(QdsPresetModel, recentsOfSameWizardProjectButDifferentSizesAreRecognizedAsDifferentPresets) { // Given PresetData data; @@ -408,26 +401,25 @@ TEST(QdsPresetModel, recentsShouldNotBeSorted) data.setData( /*wizard presets*/ { - aCategory("A.categ", "A", {"Desktop", "item b"}), - aCategory("B.categ", "B", {"item c", "Mobile"}), - aCategory("Z.categ", "Z", {"Z.desktop"}), + aCategory("A.categ", "A", {"Desktop"}), + aCategory("B.categ", "B", {"Mobile"}), }, {/*user presets*/}, /*recents*/ { - {"Z.categ", "Z.desktop", "200 x 300"}, - {"B.categ", "Mobile", "200 x 300"}, - {"A.categ", "Desktop", "200 x 300"}, + aRecentPreset("B.categ", "Mobile", "400 x 400"), + aRecentPreset("B.categ", "Mobile", "200 x 300"), + aRecentPreset("A.categ", "Desktop", "640 x 480"), }); // Then ASSERT_THAT(data.presets()[0], - ElementsAre(PresetIs("Z.categ", "Z.desktop"), - PresetIs("B.categ", "Mobile"), - PresetIs("A.categ", "Desktop"))); + ElementsAre(PresetIs("B.categ", "Mobile", "400 x 400"), + PresetIs("B.categ", "Mobile", "200 x 300"), + PresetIs("A.categ", "Desktop", "640 x 480"))); } -TEST(QdsPresetModel, recentsOfSameWizardProjectButDifferentSizesAreRecognizedAsDifferentPresets) +TEST(QdsPresetModel, allowRecentsWithTheSameName) { // Given PresetData data; @@ -437,21 +429,23 @@ TEST(QdsPresetModel, recentsOfSameWizardProjectButDifferentSizesAreRecognizedAsD /*wizard presets*/ { aCategory("A.categ", "A", {"Desktop"}), - aCategory("B.categ", "B", {"Mobile"}), }, {/*user presets*/}, /*recents*/ { - {"B.categ", "Mobile", "400 x 400"}, - {"B.categ", "Mobile", "200 x 300"}, - {"A.categ", "Desktop", "640 x 480"}, + /* NOTE: it is assumed recents with the same name and size have other fields that + * distinguishes them from one another. It is the responsibility of the caller, who + * calls data.setData() to make sure that the recents do not contain duplicates. */ + aRecentPreset("A.categ", "Desktop", "200 x 300"), + aRecentPreset("A.categ", "Desktop", "200 x 300"), + aRecentPreset("A.categ", "Desktop", "200 x 300"), }); // 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"))); + ElementsAre(PresetIs("A.categ", "Desktop"), + PresetIs("A.categ", "Desktop"), + PresetIs("A.categ", "Desktop"))); } TEST(QdsPresetModel, outdatedRecentsAreNotShown) @@ -469,8 +463,8 @@ TEST(QdsPresetModel, outdatedRecentsAreNotShown) {/*user presets*/}, /*recents*/ { - {"B.categ", "NoLongerExists", "400 x 400"}, - {"A.categ", "Desktop", "640 x 480"}, + aRecentPreset("B.categ", "NoLongerExists", "400 x 400"), + aRecentPreset("A.categ", "Desktop", "640 x 480"), }); // Then diff --git a/tests/auto/qml/qmldesigner/wizard/recentpresets-test.cpp b/tests/auto/qml/qmldesigner/wizard/recentpresets-test.cpp deleted file mode 100644 index 745462f5c1..0000000000 --- a/tests/auto/qml/qmldesigner/wizard/recentpresets-test.cpp +++ /dev/null @@ -1,303 +0,0 @@ -/**************************************************************************** -** -** 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 -#include -#include - -#include "recentpresets.h" -#include "utils/filepath.h" -#include "utils/temporarydirectory.h" - -using namespace StudioWelcome; - -constexpr char GROUP_NAME[] = "RecentPresets"; -constexpr char ITEMS[] = "Wizards"; - -namespace StudioWelcome { -void PrintTo(const RecentPresetData &recent, std::ostream *os) -{ - *os << "{categId: " << recent.category << ", name: " << recent.presetName - << ", size: " << recent.sizeName << ", isUser: " << recent.isUserPreset; - - *os << "}"; -} -} // namespace StudioWelcome - -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 recents = store.fetchAll(); - - ASSERT_THAT(recents, IsEmpty()); -} - -TEST_F(QdsRecentPresets, readEmptyRecentPresets) -{ - RecentPresetsStore store = aStoreWithOne(""); - - std::vector recents = store.fetchAll(); - - ASSERT_THAT(recents, IsEmpty()); -} - -TEST_F(QdsRecentPresets, readOneRecentPresetAsList) -{ - RecentPresetsStore store = aStoreWithRecents({"category/preset:640 x 480"}); - - std::vector recents = store.fetchAll(); - - ASSERT_THAT(recents, ElementsAre(RecentPresetData("category", "preset", "640 x 480"))); -} - -TEST_F(QdsRecentPresets, readOneRecentPresetAsString) -{ - RecentPresetsStore store = aStoreWithOne("category/preset:200 x 300"); - - std::vector recents = store.fetchAll(); - - ASSERT_THAT(recents, ElementsAre(RecentPresetData("category", "preset", "200 x 300"))); -} - -TEST_F(QdsRecentPresets, readOneRecentUserPresetAsString) -{ - RecentPresetsStore store = aStoreWithOne("category/[U]preset:200 x 300"); - - std::vector recents = store.fetchAll(); - - ASSERT_THAT(recents, ElementsAre(RecentPresetData("category", "preset", "200 x 300", true))); -} - -TEST_F(QdsRecentPresets, readBadRecentPresetAsString) -{ - RecentPresetsStore store = aStoreWithOne("no_category_only_preset"); - - std::vector recents = store.fetchAll(); - - ASSERT_THAT(recents, IsEmpty()); -} - -TEST_F(QdsRecentPresets, readBadRecentPresetAsInt) -{ - RecentPresetsStore store = aStoreWithOne(32); - - std::vector 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 - "categ/[U]user:300 x 200", // good - "categ/[u]user2:300 x 200", // small cap "U" - "categ/[x]user3:300 x 200", // must be letter "U" - "categ/[U] user4:300 x 200", // space - }); - - std::vector recents = store.fetchAll(); - - ASSERT_THAT(recents, - ElementsAre(RecentPresetData("categ", "name", "800 x 600", false), - RecentPresetData("categ", "user", "300 x 200", true))); -} - -TEST_F(QdsRecentPresets, readTwoRecentPresets) -{ - RecentPresetsStore store = aStoreWithRecents( - {"category_1/preset 1:640 x 480", "category_2/preset 2:320 x 200"}); - - std::vector recents = store.fetchAll(); - - ASSERT_THAT(recents, - ElementsAre(RecentPresetData("category_1", "preset 1", "640 x 480"), - RecentPresetData("category_2", "preset 2", "320 x 200"))); -} - -TEST_F(QdsRecentPresets, readRecentsToDifferentKindsOfPresets) -{ - RecentPresetsStore store = aStoreWithRecents( - {"category_1/preset 1:640 x 480", "category_2/[U]preset 2:320 x 200"}); - - std::vector recents = store.fetchAll(); - - ASSERT_THAT(recents, - ElementsAre(RecentPresetData("category_1", "preset 1", "640 x 480", false), - RecentPresetData("category_2", "preset 2", "320 x 200", true))); -} - -TEST_F(QdsRecentPresets, addFirstRecentPreset) -{ - RecentPresetsStore store{&settings}; - - store.add("A.Category", "Normal Application", "400 x 600"); - std::vector recents = store.fetchAll(); - - ASSERT_THAT(recents, ElementsAre(RecentPresetData("A.Category", "Normal Application", "400 x 600"))); -} - -TEST_F(QdsRecentPresets, addFirstRecentUserPreset) -{ - RecentPresetsStore store{&settings}; - - store.add("A.Category", "Normal Application", "400 x 600", /*user preset*/ true); - std::vector recents = store.fetchAll(); - - ASSERT_THAT(recents, - ElementsAre(RecentPresetData("A.Category", "Normal Application", "400 x 600", true))); -} - -TEST_F(QdsRecentPresets, addExistingFirstRecentPreset) -{ - RecentPresetsStore store = aStoreWithRecents({"category/preset:200 x 300"}); - ASSERT_THAT(store.fetchAll(), ElementsAre(RecentPresetData("category", "preset", "200 x 300"))); - - store.add("category", "preset", "200 x 300"); - std::vector recents = store.fetchAll(); - - ASSERT_THAT(recents, ElementsAre(RecentPresetData("category", "preset", "200 x 300"))); -} - -TEST_F(QdsRecentPresets, addRecentUserPresetWithSameNameAsExistingRecentNormalPreset) -{ - RecentPresetsStore store = aStoreWithRecents({"category/preset:200 x 300"}); - ASSERT_THAT(store.fetchAll(), ElementsAre(RecentPresetData("category", "preset", "200 x 300"))); - - store.add("category", "preset", "200 x 300", /*user preset*/ true); - std::vector recents = store.fetchAll(); - - ASSERT_THAT(recents, - ElementsAre(RecentPresetData("category", "preset", "200 x 300", true), - RecentPresetData("category", "preset", "200 x 300", false))); -} - -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 recents = store.fetchAll(); - - ASSERT_THAT(recents, - ElementsAre(RecentPresetData("A.Category", "Preset 2", "640 x 480"), - RecentPresetData("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 recents = store.fetchAll(); - - ASSERT_THAT(recents, - ElementsAre(RecentPresetData("A.Category", "Preset", "640 x 480"), - RecentPresetData("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 recents = store.fetchAll(); - - ASSERT_THAT(recents, - ElementsAre(RecentPresetData("A.Category", "Preset 3", "800 x 600"), - RecentPresetData("A.Category", "Preset 2", "640 x 480"), - RecentPresetData("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 recents = store.fetchAll(); - - ASSERT_THAT(recents, - ElementsAre(RecentPresetData("A.Category", "Preset 3", "640 x 480"), - RecentPresetData("A.Category", "Preset 1", "200 x 300"), - RecentPresetData("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 recents = store.fetchAll(); - - ASSERT_THAT(recents, - ElementsAre(RecentPresetData("A.Category", "Preset 3", "200 x 300"), - RecentPresetData("A.Category", "Preset 2", "200 x 300"))); -} diff --git a/tests/auto/qml/qmldesigner/wizard/userpresets-test.cpp b/tests/auto/qml/qmldesigner/wizard/userpresets-test.cpp index 8fa73402c0..6ed562621b 100644 --- a/tests/auto/qml/qmldesigner/wizard/userpresets-test.cpp +++ b/tests/auto/qml/qmldesigner/wizard/userpresets-test.cpp @@ -29,6 +29,10 @@ #include #include +#include +#include +#include + namespace StudioWelcome { void PrintTo(const UserPresetData &preset, std::ostream *os) @@ -64,69 +68,85 @@ using namespace StudioWelcome; constexpr char ARRAY_NAME[] = "UserPresets"; +class FakeStoreIo : public StoreIo +{ +public: + QByteArray read() const override + { + return data.toUtf8(); + } + + void write(const QByteArray &bytes) override + { + data = bytes; + } + + QString data; +}; + class QdsUserPresets : public ::testing::Test { protected: void SetUp() { - settings = std::make_unique(tempDir.filePath("test").toString(), - QSettings::IniFormat); + storeIo = std::make_unique(); } - UserPresetsStore anEmptyStore() { return UserPresetsStore{std::move(settings)}; } + UserPresetsStore anEmptyStore() + { + return UserPresetsStore{std::move(storeIo), StorePolicy::UniqueNames}; + } UserPresetsStore aStoreWithZeroItems() { - settings->beginWriteArray(ARRAY_NAME, 0); - settings->endArray(); + storeIo->data = "[]"; - return UserPresetsStore{std::move(settings)}; + return UserPresetsStore{std::move(storeIo), StorePolicy::UniqueNames}; } - UserPresetsStore aStoreWithOne(const UserPresetData &preset) + UserPresetsStore aStoreWithOne(const UserPresetData &preset, + StorePolicy policy = StorePolicy::UniqueNames) { - settings->beginWriteArray(ARRAY_NAME, 1); - settings->setArrayIndex(0); - - settings->setValue("categoryId", preset.categoryId); - settings->setValue("wizardName", preset.wizardName); - settings->setValue("name", preset.name); - settings->setValue("screenSize", preset.screenSize); - settings->setValue("useQtVirtualKeyboard", preset.useQtVirtualKeyboard); - settings->setValue("qtVersion", preset.qtVersion); - settings->setValue("styleName", preset.styleName); - - settings->endArray(); - - return UserPresetsStore{std::move(settings)}; + QJsonArray array({QJsonObject{{"categoryId", preset.categoryId}, + {"wizardName", preset.wizardName}, + {"name", preset.name}, + {"screenSize", preset.screenSize}, + {"useQtVirtualKeyboard", preset.useQtVirtualKeyboard}, + {"qtVersion", preset.qtVersion}, + {"styleName", preset.styleName}}}); + QJsonDocument doc{array}; + storeIo->data = doc.toJson(); + + return UserPresetsStore{std::move(storeIo), policy}; } - UserPresetsStore aStoreWithPresets(const std::vector &presets) + UserPresetsStore aStoreWithPresets(const std::vector &presetItems) { - settings->beginWriteArray(ARRAY_NAME, presets.size()); - - for (size_t i = 0; i < presets.size(); ++i) { - settings->setArrayIndex(i); - const auto &preset = presets[i]; - - settings->setValue("categoryId", preset.categoryId); - settings->setValue("wizardName", preset.wizardName); - settings->setValue("name", preset.name); - settings->setValue("screenSize", preset.screenSize); - settings->setValue("useQtVirtualKeyboard", preset.useQtVirtualKeyboard); - settings->setValue("qtVersion", preset.qtVersion); - settings->setValue("styleName", preset.styleName); + QJsonArray array; + + for (const auto &preset : presetItems) { + QJsonObject obj({{"categoryId", preset.categoryId}, + {"wizardName", preset.wizardName}, + {"name", preset.name}, + {"screenSize", preset.screenSize}, + {"useQtVirtualKeyboard", preset.useQtVirtualKeyboard}, + {"qtVersion", preset.qtVersion}, + {"styleName", preset.styleName}}); + + array.append(QJsonValue{obj}); } - settings->endArray(); - return UserPresetsStore{std::move(settings)}; + QJsonDocument doc{array}; + storeIo->data = doc.toJson(); + + return UserPresetsStore{std::move(storeIo), StorePolicy::UniqueNames}; } Utils::TemporaryDirectory tempDir{"userpresets-XXXXXX"}; - std::unique_ptr settings; + std::unique_ptr storeIo; private: - QString settingsPath; + QString storeIoPath; }; /******************* TESTS *******************/ @@ -234,10 +254,10 @@ TEST_F(QdsUserPresets, saveIncompletePreset) ASSERT_THAT(presets, ElementsAre(preset)); } -TEST_F(QdsUserPresets, cannotSavePresetWithSameName) +TEST_F(QdsUserPresets, cannotSavePresetWithSameNameForUniqueNamesPolicy) { UserPresetData existing{"B.categ", "3D App", "Same Name", "400 x 20", true, "Qt 5", "Material Dark"}; - auto store = aStoreWithOne(existing); + auto store = aStoreWithOne(existing, StorePolicy::UniqueNames); UserPresetData newPreset{"C.categ", "Empty", "Same Name", "100 x 30", false, "Qt 6", "Fusion"}; bool saved = store.save(newPreset); @@ -246,6 +266,30 @@ TEST_F(QdsUserPresets, cannotSavePresetWithSameName) ASSERT_THAT(store.fetchAll(), ElementsAreArray({existing})); } +TEST_F(QdsUserPresets, canSavePresetWithSameNameForUniqueValuesPolicy) +{ + UserPresetData existing{"B.categ", "3D App", "Same Name", "400 x 20", true, "Qt 5", "Material Dark"}; + auto store = aStoreWithOne(existing, StorePolicy::UniqueValues); + + // NOTE: only Style is different + UserPresetData newPreset{"B.categ", "3D App", "Same Name", "400 x 20", true, "Qt 5", "Fusion"}; + bool saved = store.save(newPreset); + + ASSERT_TRUE(saved); + ASSERT_THAT(store.fetchAll(), ElementsAreArray({existing, newPreset})); +} + +TEST_F(QdsUserPresets, cannotSaveExactCopyForUniqueValuesPolicy) +{ + UserPresetData existing{"B.categ", "3D App", "Same Name", "400 x 20", true, "Qt 5", "Material Dark"}; + auto store = aStoreWithOne(existing, StorePolicy::UniqueNames); + + bool saved = store.save(existing); + + ASSERT_FALSE(saved); + ASSERT_THAT(store.fetchAll(), ElementsAreArray({existing})); +} + TEST_F(QdsUserPresets, saveNewPreset) { UserPresetData existing{"A.categ", "3D App", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"}; @@ -258,6 +302,54 @@ TEST_F(QdsUserPresets, saveNewPreset) ASSERT_THAT(presets, ElementsAre(existing, newPreset)); } +TEST_F(QdsUserPresets, canLimitPresetsToAMaximum) +{ + std::vector existing{ + {"A.categ", "AppA", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"}, + {"B.categ", "AppB", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"}, + {"C.categ", "AppC", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"}, + }; + auto store = aStoreWithPresets(existing); + store.setMaximum(3); + + UserPresetData newPreset{"D.categ", "AppD", "Huawei", "100 x 30", true, "Qt 6", "Fusion"}; + store.save(newPreset); + + auto presets = store.fetchAll(); + ASSERT_THAT(presets, ElementsAre(existing[1], existing[2], newPreset)); +} + +TEST_F(QdsUserPresets, canLimitPresetsToAMaximumForReverseOrder) +{ + std::vector existing{ + {"A.categ", "AppA", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"}, + {"B.categ", "AppB", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"}, + {"C.categ", "AppC", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"}, + }; + auto store = aStoreWithPresets(existing); + store.setMaximum(3); + store.setReverseOrder(); + + UserPresetData newPreset{"D.categ", "AppD", "Huawei", "100 x 30", true, "Qt 6", "Fusion"}; + store.save(newPreset); + + auto presets = store.fetchAll(); + ASSERT_THAT(presets, ElementsAre(newPreset, existing[0], existing[1])); +} + +TEST_F(QdsUserPresets, canSavePresetsInReverseOrder) +{ + UserPresetData existing{"A.categ", "3D App", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"}; + auto store = aStoreWithOne(existing, StorePolicy::UniqueNames); + store.setReverseOrder(); + + UserPresetData newPreset{"A.categ", "Empty", "Huawei", "100 x 30", true, "Qt 6", "Fusion"}; + store.save(newPreset); + + auto presets = store.fetchAll(); + ASSERT_THAT(presets, ElementsAre(newPreset, existing)); +} + TEST_F(QdsUserPresets, removeUserPresetFromEmptyStore) { UserPresetData preset{"C.categ", "2D App", "Android", "", false, "", ""}; -- cgit v1.2.3