forked from qt-creator/qt-creator
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 <mahmoud.badri@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
@@ -13,7 +13,6 @@ add_qtc_plugin(StudioWelcome
|
|||||||
wizardfactories.cpp wizardfactories.h
|
wizardfactories.cpp wizardfactories.h
|
||||||
createproject.cpp createproject.h
|
createproject.cpp createproject.h
|
||||||
wizardhandler.cpp wizardhandler.h
|
wizardhandler.cpp wizardhandler.h
|
||||||
recentpresets.cpp recentpresets.h
|
|
||||||
userpresets.cpp userpresets.h
|
userpresets.cpp userpresets.h
|
||||||
screensizemodel.h
|
screensizemodel.h
|
||||||
algorithm.h
|
algorithm.h
|
||||||
|
@@ -42,6 +42,16 @@ template<typename C, typename F>
|
|||||||
return it == end ? nullopt : make_optional(*it);
|
return it == end ? nullopt : make_optional(*it);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename C>
|
||||||
|
[[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
|
///////// FILTER
|
||||||
template<typename C, typename T = typename C::value_type>
|
template<typename C, typename T = typename C::value_type>
|
||||||
[[nodiscard]] C filterOut(const C &container, const T &value = T())
|
[[nodiscard]] C filterOut(const C &container, const T &value = T())
|
||||||
@@ -67,13 +77,14 @@ void concat(C &out, const SC &container)
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<typename C, typename T>
|
template<typename C, typename T>
|
||||||
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);
|
typename C::const_iterator i = std::find(std::cbegin(container), std::cend(container), value);
|
||||||
if (i == std::cend(container))
|
if (i == std::cend(container))
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
container.erase(i);
|
container.erase(i);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename C, typename T>
|
template<typename C, typename T>
|
||||||
|
@@ -47,7 +47,7 @@ QString PresetData::recentsTabName()
|
|||||||
|
|
||||||
void PresetData::setData(const PresetsByCategory &presetsByCategory,
|
void PresetData::setData(const PresetsByCategory &presetsByCategory,
|
||||||
const std::vector<UserPresetData> &userPresetsData,
|
const std::vector<UserPresetData> &userPresetsData,
|
||||||
const std::vector<RecentPresetData> &loadedRecentsData)
|
const std::vector<UserPresetData> &loadedRecentsData)
|
||||||
{
|
{
|
||||||
QTC_ASSERT(!presetsByCategory.empty(), return );
|
QTC_ASSERT(!presetsByCategory.empty(), return );
|
||||||
m_recents = loadedRecentsData;
|
m_recents = loadedRecentsData;
|
||||||
@@ -60,16 +60,13 @@ void PresetData::setData(const PresetsByCategory &presetsByCategory,
|
|||||||
|
|
||||||
PresetItems wizardPresets = Utils::flatten(m_presets);
|
PresetItems wizardPresets = Utils::flatten(m_presets);
|
||||||
|
|
||||||
PresetItems userPresetItems = makeUserPresets(wizardPresets);
|
PresetItems userPresetItems = makeUserPresets(wizardPresets, m_userPresets);
|
||||||
if (!userPresetItems.empty()) {
|
if (!userPresetItems.empty()) {
|
||||||
m_categories.push_back(CustomTabName);
|
m_categories.push_back(CustomTabName);
|
||||||
m_presets.push_back(userPresetItems);
|
m_presets.push_back(userPresetItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
PresetItems allWizardPresets = std::move(wizardPresets);
|
PresetItems recentPresets = makeUserPresets(wizardPresets, m_recents);
|
||||||
Utils::concat(allWizardPresets, userPresetItems);
|
|
||||||
|
|
||||||
PresetItems recentPresets = makeRecentPresets(allWizardPresets);
|
|
||||||
if (!recentPresets.empty()) {
|
if (!recentPresets.empty()) {
|
||||||
Utils::prepend(m_categories, RecentsTabName);
|
Utils::prepend(m_categories, RecentsTabName);
|
||||||
Utils::prepend(m_presets, recentPresets);
|
Utils::prepend(m_presets, recentPresets);
|
||||||
@@ -79,7 +76,7 @@ void PresetData::setData(const PresetsByCategory &presetsByCategory,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PresetData::reload(const std::vector<UserPresetData> &userPresetsData,
|
void PresetData::reload(const std::vector<UserPresetData> &userPresetsData,
|
||||||
const std::vector<RecentPresetData> &loadedRecentsData)
|
const std::vector<UserPresetData> &loadedRecentsData)
|
||||||
{
|
{
|
||||||
m_categories.clear();
|
m_categories.clear();
|
||||||
m_presets.clear();
|
m_presets.clear();
|
||||||
@@ -96,11 +93,12 @@ std::shared_ptr<PresetItem> PresetData::findPresetItemForUserPreset(const UserPr
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
PresetItems PresetData::makeUserPresets(const PresetItems &wizardPresets)
|
PresetItems PresetData::makeUserPresets(const PresetItems &wizardPresets,
|
||||||
|
const std::vector<UserPresetData> &data)
|
||||||
{
|
{
|
||||||
PresetItems result;
|
PresetItems result;
|
||||||
|
|
||||||
for (const UserPresetData &userPresetData : m_userPresets) {
|
for (const UserPresetData &userPresetData : data) {
|
||||||
std::shared_ptr<PresetItem> foundPreset = findPresetItemForUserPreset(userPresetData,
|
std::shared_ptr<PresetItem> foundPreset = findPresetItemForUserPreset(userPresetData,
|
||||||
wizardPresets);
|
wizardPresets);
|
||||||
if (!foundPreset)
|
if (!foundPreset)
|
||||||
@@ -128,35 +126,6 @@ PresetItems PresetData::makeUserPresets(const PresetItems &wizardPresets)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<PresetItem> PresetData::findPresetItemForRecent(const RecentPresetData &recent, const PresetItems &wizardPresets)
|
|
||||||
{
|
|
||||||
return Utils::findOrDefault(wizardPresets, [&recent](const std::shared_ptr<PresetItem> &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<PresetItem> preset = findPresetItemForRecent(recent, wizardPresets);
|
|
||||||
|
|
||||||
if (preset) {
|
|
||||||
auto clone = std::shared_ptr<PresetItem>{preset->clone()};
|
|
||||||
clone->screenSizeName = recent.sizeName;
|
|
||||||
result.push_back(clone);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/****************** BasePresetModel ******************/
|
/****************** BasePresetModel ******************/
|
||||||
|
|
||||||
BasePresetModel::BasePresetModel(const PresetData *data, QObject *parent)
|
BasePresetModel::BasePresetModel(const PresetData *data, QObject *parent)
|
||||||
|
@@ -33,7 +33,6 @@
|
|||||||
#include <utils/optional.h>
|
#include <utils/optional.h>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
#include "recentpresets.h"
|
|
||||||
#include "userpresets.h"
|
#include "userpresets.h"
|
||||||
|
|
||||||
namespace Utils {
|
namespace Utils {
|
||||||
@@ -169,10 +168,10 @@ class PresetData
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void reload(const std::vector<UserPresetData> &userPresets,
|
void reload(const std::vector<UserPresetData> &userPresets,
|
||||||
const std::vector<RecentPresetData> &loadedRecents);
|
const std::vector<UserPresetData> &loadedRecents);
|
||||||
void setData(const PresetsByCategory &presets,
|
void setData(const PresetsByCategory &presets,
|
||||||
const std::vector<UserPresetData> &userPresets,
|
const std::vector<UserPresetData> &userPresets,
|
||||||
const std::vector<RecentPresetData> &recents);
|
const std::vector<UserPresetData> &recents);
|
||||||
|
|
||||||
const std::vector<PresetItems> &presets() const { return m_presets; }
|
const std::vector<PresetItems> &presets() const { return m_presets; }
|
||||||
const Categories &categories() const { return m_categories; }
|
const Categories &categories() const { return m_categories; }
|
||||||
@@ -180,16 +179,13 @@ public:
|
|||||||
static QString recentsTabName();
|
static QString recentsTabName();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PresetItems makeRecentPresets(const PresetItems &wizardPresets);
|
PresetItems makeUserPresets(const PresetItems &wizardPresets, const std::vector<UserPresetData> &data);
|
||||||
PresetItems makeUserPresets(const PresetItems &wizardPresets);
|
|
||||||
|
|
||||||
std::shared_ptr<PresetItem> findPresetItemForUserPreset(const UserPresetData &preset, const PresetItems &wizardPresets);
|
std::shared_ptr<PresetItem> findPresetItemForUserPreset(const UserPresetData &preset, const PresetItems &wizardPresets);
|
||||||
std::shared_ptr<PresetItem> findPresetItemForRecent(const RecentPresetData &recent, const PresetItems &wizardPresets);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<PresetItems> m_presets;
|
std::vector<PresetItems> m_presets;
|
||||||
Categories m_categories;
|
Categories m_categories;
|
||||||
std::vector<RecentPresetData> m_recents;
|
std::vector<UserPresetData> m_recents;
|
||||||
std::vector<UserPresetData> m_userPresets;
|
std::vector<UserPresetData> m_userPresets;
|
||||||
PresetsByCategory m_presetsByCategory;
|
PresetsByCategory m_presetsByCategory;
|
||||||
};
|
};
|
||||||
|
@@ -72,10 +72,14 @@ QdsNewDialog::QdsNewDialog(QWidget *parent)
|
|||||||
, m_presetModel{new PresetModel(&m_presetData, this)}
|
, m_presetModel{new PresetModel(&m_presetData, this)}
|
||||||
, m_screenSizeModel{new ScreenSizeModel(this)}
|
, m_screenSizeModel{new ScreenSizeModel(this)}
|
||||||
, m_styleModel{new StyleModel(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);
|
setParent(m_dialog);
|
||||||
|
|
||||||
|
m_recentsStore.setReverseOrder();
|
||||||
|
m_recentsStore.setMaximum(10);
|
||||||
|
|
||||||
m_dialog->setResizeMode(QQuickWidget::SizeRootObjectToView); // SizeViewToRootObject
|
m_dialog->setResizeMode(QQuickWidget::SizeRootObjectToView); // SizeViewToRootObject
|
||||||
m_dialog->engine()->addImageProvider(QStringLiteral("newprojectdialog_library"),
|
m_dialog->engine()->addImageProvider(QStringLiteral("newprojectdialog_library"),
|
||||||
new Internal::NewProjectDialogImageProvider());
|
new Internal::NewProjectDialogImageProvider());
|
||||||
@@ -190,7 +194,10 @@ void QdsNewDialog::updateScreenSizes()
|
|||||||
|
|
||||||
void QdsNewDialog::onWizardCreated(QStandardItemModel *screenSizeModel, QStandardItemModel *styleModel)
|
void QdsNewDialog::onWizardCreated(QStandardItemModel *screenSizeModel, QStandardItemModel *styleModel)
|
||||||
{
|
{
|
||||||
|
if (screenSizeModel)
|
||||||
m_screenSizeModel->setBackendModel(screenSizeModel);
|
m_screenSizeModel->setBackendModel(screenSizeModel);
|
||||||
|
|
||||||
|
if (styleModel)
|
||||||
m_styleModel->setBackendModel(styleModel);
|
m_styleModel->setBackendModel(styleModel);
|
||||||
|
|
||||||
auto userPreset = m_currentPreset->asUserPreset();
|
auto userPreset = m_currentPreset->asUserPreset();
|
||||||
@@ -326,7 +333,7 @@ void QdsNewDialog::setWizardFactories(QList<Core::IWizardFactory *> factories_,
|
|||||||
|
|
||||||
WizardFactories factories{factories_, m_dialog, platform};
|
WizardFactories factories{factories_, m_dialog, platform};
|
||||||
|
|
||||||
std::vector<RecentPresetData> recents = m_recentsStore.fetchAll();
|
std::vector<UserPresetData> recents = m_recentsStore.fetchAll();
|
||||||
std::vector<UserPresetData> userPresets = m_userPresetsStore.fetchAll();
|
std::vector<UserPresetData> userPresets = m_userPresetsStore.fetchAll();
|
||||||
m_presetData.setData(factories.presetsGroupedByCategory(), userPresets, recents);
|
m_presetData.setData(factories.presetsGroupedByCategory(), userPresets, recents);
|
||||||
|
|
||||||
@@ -360,33 +367,13 @@ void QdsNewDialog::setWizardFactories(QList<Core::IWizardFactory *> factories_,
|
|||||||
* sure that all events have occurred before we go ahead and configure the wizard.
|
* sure that all events have occurred before we go ahead and configure the wizard.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
auto userPreset = m_currentPreset->asUserPreset();
|
/* 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_qmlDetailsLoaded) {
|
onWizardCreated(nullptr, nullptr);
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_qmlStylesLoaded && m_wizard.haveStyleModel()) {
|
|
||||||
if (userPreset) {
|
|
||||||
int index = m_wizard.styleIndex(userPreset->styleName);
|
|
||||||
if (index != -1)
|
|
||||||
setStyleIndex(index);
|
|
||||||
}
|
|
||||||
m_styleModel->reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QdsNewDialog::recentsTabName() const
|
QString QdsNewDialog::recentsTabName() const
|
||||||
@@ -431,7 +418,8 @@ void QdsNewDialog::accept()
|
|||||||
std::shared_ptr<PresetItem> item = m_wizard.preset();
|
std::shared_ptr<PresetItem> item = m_wizard.preset();
|
||||||
QString customSizeName = m_qmlCustomWidth + " x " + m_qmlCustomHeight;
|
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->close();
|
||||||
m_dialog->deleteLater();
|
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 screenSize = m_qmlCustomWidth + " x " + m_qmlCustomHeight;
|
||||||
QString targetQtVersion = "";
|
QString targetQtVersion = "";
|
||||||
@@ -489,12 +477,19 @@ void QdsNewDialog::savePresetDialogAccept()
|
|||||||
|
|
||||||
UserPresetData preset = {m_currentPreset->categoryId,
|
UserPresetData preset = {m_currentPreset->categoryId,
|
||||||
m_currentPreset->wizardName,
|
m_currentPreset->wizardName,
|
||||||
m_qmlPresetName,
|
displayName,
|
||||||
screenSize,
|
screenSize,
|
||||||
useVirtualKeyboard,
|
useVirtualKeyboard,
|
||||||
targetQtVersion,
|
targetQtVersion,
|
||||||
styleName};
|
styleName};
|
||||||
|
|
||||||
|
return preset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QdsNewDialog::savePresetDialogAccept()
|
||||||
|
{
|
||||||
|
UserPresetData preset = currentUserPresetData(m_qmlPresetName);
|
||||||
|
|
||||||
if (!m_userPresetsStore.save(preset)) {
|
if (!m_userPresetsStore.save(preset)) {
|
||||||
QMessageBox::warning(m_dialog,
|
QMessageBox::warning(m_dialog,
|
||||||
tr("Save Preset"),
|
tr("Save Preset"),
|
||||||
@@ -503,7 +498,7 @@ void QdsNewDialog::savePresetDialogAccept()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// reload model
|
// reload model
|
||||||
std::vector<RecentPresetData> recents = m_recentsStore.fetchAll();
|
std::vector<UserPresetData> recents = m_recentsStore.fetchAll();
|
||||||
std::vector<UserPresetData> userPresets = m_userPresetsStore.fetchAll();
|
std::vector<UserPresetData> userPresets = m_userPresetsStore.fetchAll();
|
||||||
m_presetData.reload(userPresets, recents);
|
m_presetData.reload(userPresets, recents);
|
||||||
|
|
||||||
@@ -520,8 +515,8 @@ void QdsNewDialog::removeCurrentPreset()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remove preset & reload model
|
// remove preset & reload model
|
||||||
std::vector<RecentPresetData> recents = m_recentsStore.remove(m_currentPreset->categoryId,
|
UserPresetData currentPreset = currentUserPresetData(m_qmlPresetName);
|
||||||
m_currentPreset->displayName());
|
std::vector<UserPresetData> recents = m_recentsStore.remove(currentPreset);
|
||||||
|
|
||||||
auto userPreset = m_currentPreset->asUserPreset();
|
auto userPreset = m_currentPreset->asUserPreset();
|
||||||
m_userPresetsStore.remove(userPreset->categoryId, userPreset->displayName());
|
m_userPresetsStore.remove(userPreset->categoryId, userPreset->displayName());
|
||||||
|
@@ -34,7 +34,6 @@
|
|||||||
#include "presetmodel.h"
|
#include "presetmodel.h"
|
||||||
#include "screensizemodel.h"
|
#include "screensizemodel.h"
|
||||||
#include "stylemodel.h"
|
#include "stylemodel.h"
|
||||||
#include "recentpresets.h"
|
|
||||||
#include "userpresets.h"
|
#include "userpresets.h"
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
@@ -153,6 +152,7 @@ private:
|
|||||||
|
|
||||||
void updateScreenSizes();
|
void updateScreenSizes();
|
||||||
bool eventFilter(QObject *obj, QEvent *ev) override;
|
bool eventFilter(QObject *obj, QEvent *ev) override;
|
||||||
|
UserPresetData currentUserPresetData(const QString &displayName) const;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onDeletingWizard();
|
void onDeletingWizard();
|
||||||
@@ -194,7 +194,7 @@ private:
|
|||||||
std::shared_ptr<PresetItem> m_currentPreset;
|
std::shared_ptr<PresetItem> m_currentPreset;
|
||||||
|
|
||||||
WizardHandler m_wizard;
|
WizardHandler m_wizard;
|
||||||
RecentPresetsStore m_recentsStore;
|
UserPresetsStore m_recentsStore;
|
||||||
UserPresetsStore m_userPresetsStore;
|
UserPresetsStore m_userPresetsStore;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -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 <QRegularExpression>
|
|
||||||
|
|
||||||
#include <coreplugin/icore.h>
|
|
||||||
#include <utils/qtcassert.h>
|
|
||||||
#include <utils/qtcsettings.h>
|
|
||||||
|
|
||||||
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<RecentPresetData> existing = fetchAll();
|
|
||||||
|
|
||||||
std::vector<RecentPresetData> recents
|
|
||||||
= addRecentToExisting(RecentPresetData{categoryId, name, sizeName, isUserPreset}, existing);
|
|
||||||
|
|
||||||
save(recents);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RecentPresetsStore::save(const std::vector<RecentPresetData> &recents)
|
|
||||||
{
|
|
||||||
QStringList encodedRecents = encodeRecentPresets(recents);
|
|
||||||
|
|
||||||
m_settings->beginGroup(GROUP_NAME);
|
|
||||||
m_settings->setValue(WIZARDS, encodedRecents);
|
|
||||||
m_settings->endGroup();
|
|
||||||
m_settings->sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<RecentPresetData> RecentPresetsStore::remove(const QString &categoryId, const QString &presetName)
|
|
||||||
{
|
|
||||||
std::vector<RecentPresetData> 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<RecentPresetData> RecentPresetsStore::addRecentToExisting(
|
|
||||||
const RecentPresetData &preset, std::vector<RecentPresetData> &recents)
|
|
||||||
{
|
|
||||||
Utils::erase_one(recents, preset);
|
|
||||||
Utils::prepend(recents, preset);
|
|
||||||
|
|
||||||
if (int(recents.size()) > m_max)
|
|
||||||
recents.pop_back();
|
|
||||||
|
|
||||||
return recents;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<RecentPresetData> RecentPresetsStore::fetchAll() const
|
|
||||||
{
|
|
||||||
m_settings->beginGroup(GROUP_NAME);
|
|
||||||
QVariant value = m_settings->value(WIZARDS);
|
|
||||||
m_settings->endGroup();
|
|
||||||
|
|
||||||
std::vector<RecentPresetData> 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<RecentPresetData> &recents)
|
|
||||||
{
|
|
||||||
return Utils::transform<QList>(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<RecentPresetData> RecentPresetsStore::decodeRecentPresets(const QVariantList &values)
|
|
||||||
{
|
|
||||||
return Utils::transform<std::vector>(values, [](const QVariant &value) {
|
|
||||||
return decodeOneRecentPreset(value.toString());
|
|
||||||
});
|
|
||||||
}
|
|
@@ -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 <vector>
|
|
||||||
#include <QPair>
|
|
||||||
#include <QSettings>
|
|
||||||
|
|
||||||
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<RecentPresetData> remove(const QString &categoryId, const QString &presetName);
|
|
||||||
std::vector<RecentPresetData> fetchAll() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<RecentPresetData> addRecentToExisting(const RecentPresetData &preset,
|
|
||||||
std::vector<RecentPresetData> &recents);
|
|
||||||
static QStringList encodeRecentPresets(const std::vector<RecentPresetData> &recents);
|
|
||||||
static std::vector<RecentPresetData> decodeRecentPresets(const QVariantList &values);
|
|
||||||
static RecentPresetData decodeOneRecentPreset(const QString &encoded);
|
|
||||||
void save(const std::vector<RecentPresetData> &recents);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QSettings *m_settings = nullptr;
|
|
||||||
int m_max = 10;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace StudioWelcome
|
|
@@ -37,8 +37,6 @@ QtcPlugin {
|
|||||||
"wizardfactories.h",
|
"wizardfactories.h",
|
||||||
"wizardhandler.cpp",
|
"wizardhandler.cpp",
|
||||||
"wizardhandler.h",
|
"wizardhandler.h",
|
||||||
"recentpresets.cpp",
|
|
||||||
"recentpresets.h",
|
|
||||||
"userpresets.cpp",
|
"userpresets.cpp",
|
||||||
"userpresets.h"
|
"userpresets.h"
|
||||||
]
|
]
|
||||||
|
@@ -24,43 +24,75 @@
|
|||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
#include "userpresets.h"
|
#include "userpresets.h"
|
||||||
|
#include "algorithm.h"
|
||||||
|
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonValue>
|
||||||
|
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
#include <utils/algorithm.h>
|
#include <memory>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
using namespace StudioWelcome;
|
using namespace StudioWelcome;
|
||||||
|
|
||||||
constexpr char PREFIX[] = "UserPresets";
|
FileStoreIo::FileStoreIo(const QString &fileName)
|
||||||
|
: m_file{std::make_unique<QFile>(fullFilePath(fileName))}
|
||||||
UserPresetsStore::UserPresetsStore()
|
|
||||||
{
|
|
||||||
m_settings = std::make_unique<QSettings>(fullFilePath(), QSettings::IniFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
UserPresetsStore::UserPresetsStore(std::unique_ptr<QSettings> &&settings)
|
|
||||||
: m_settings{std::move(settings)}
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void UserPresetsStore::savePresets(const std::vector<UserPresetData> &presets)
|
QByteArray FileStoreIo::read() const
|
||||||
{
|
{
|
||||||
m_settings->beginWriteArray(PREFIX, static_cast<int>(presets.size()));
|
m_file->open(QFile::ReadOnly | QFile::Text);
|
||||||
|
QByteArray data = m_file->readAll();
|
||||||
|
m_file->close();
|
||||||
|
|
||||||
for (size_t i = 0; i < presets.size(); ++i) {
|
return data;
|
||||||
m_settings->setArrayIndex(static_cast<int>(i));
|
}
|
||||||
const auto &preset = presets[i];
|
|
||||||
|
|
||||||
m_settings->setValue("categoryId", preset.categoryId);
|
void FileStoreIo::write(const QByteArray &data)
|
||||||
m_settings->setValue("wizardName", preset.wizardName);
|
{
|
||||||
m_settings->setValue("name", preset.name);
|
m_file->open(QFile::WriteOnly | QFile::Text);
|
||||||
m_settings->setValue("screenSize", preset.screenSize);
|
m_file->write(data);
|
||||||
m_settings->setValue("useQtVirtualKeyboard", preset.useQtVirtualKeyboard);
|
m_file->close();
|
||||||
m_settings->setValue("qtVersion", preset.qtVersion);
|
}
|
||||||
m_settings->setValue("styleName", preset.styleName);
|
|
||||||
|
QString FileStoreIo::fullFilePath(const QString &fileName) const
|
||||||
|
{
|
||||||
|
return Core::ICore::userResourcePath(fileName).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
UserPresetsStore::UserPresetsStore(const QString &fileName, StorePolicy policy)
|
||||||
|
: m_store{std::make_unique<FileStoreIo>(fileName)}
|
||||||
|
, m_policy{policy}
|
||||||
|
{}
|
||||||
|
|
||||||
|
UserPresetsStore::UserPresetsStore(std::unique_ptr<StoreIo> &&fileStore,
|
||||||
|
StorePolicy policy)
|
||||||
|
: m_store{std::move(fileStore)}
|
||||||
|
, m_policy{policy}
|
||||||
|
{}
|
||||||
|
|
||||||
|
void UserPresetsStore::savePresets(const std::vector<UserPresetData> &presetItems)
|
||||||
|
{
|
||||||
|
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)
|
bool UserPresetsStore::save(const UserPresetData &newPreset)
|
||||||
@@ -68,12 +100,30 @@ bool UserPresetsStore::save(const UserPresetData &newPreset)
|
|||||||
QTC_ASSERT(newPreset.isValid(), return false);
|
QTC_ASSERT(newPreset.isValid(), return false);
|
||||||
|
|
||||||
std::vector<UserPresetData> presetItems = fetchAll();
|
std::vector<UserPresetData> presetItems = fetchAll();
|
||||||
if (Utils::anyOf(presetItems,
|
|
||||||
[&newPreset](const UserPresetData &p) { return p.name == newPreset.name; })) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_reverse)
|
||||||
|
Utils::prepend(presetItems, newPreset);
|
||||||
|
else
|
||||||
presetItems.push_back(newPreset);
|
presetItems.push_back(newPreset);
|
||||||
|
|
||||||
|
if (m_maximum > -1 && static_cast<int>(presetItems.size()) > m_maximum) {
|
||||||
|
if (m_reverse)
|
||||||
|
presetItems.pop_back();
|
||||||
|
else
|
||||||
|
presetItems.erase(std::cbegin(presetItems));
|
||||||
|
}
|
||||||
|
|
||||||
savePresets(presetItems);
|
savePresets(presetItems);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -92,34 +142,45 @@ void UserPresetsStore::remove(const QString &category, const QString &name)
|
|||||||
savePresets(presetItems);
|
savePresets(presetItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<UserPresetData> UserPresetsStore::remove(const UserPresetData &preset)
|
||||||
|
{
|
||||||
|
std::vector<UserPresetData> presetItems = fetchAll();
|
||||||
|
bool erased = Utils::erase_one(presetItems, preset);
|
||||||
|
if (erased)
|
||||||
|
savePresets(presetItems);
|
||||||
|
|
||||||
|
return presetItems;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<UserPresetData> UserPresetsStore::fetchAll() const
|
std::vector<UserPresetData> UserPresetsStore::fetchAll() const
|
||||||
{
|
{
|
||||||
|
QByteArray data = m_store->read();
|
||||||
|
|
||||||
|
const QJsonDocument doc = QJsonDocument::fromJson(data);
|
||||||
|
if (!doc.isArray())
|
||||||
|
return {};
|
||||||
|
|
||||||
std::vector<UserPresetData> result;
|
std::vector<UserPresetData> result;
|
||||||
int size = m_settings->beginReadArray(PREFIX);
|
const QJsonArray jsonArray = doc.array();
|
||||||
if (size >= 0)
|
|
||||||
result.reserve(static_cast<size_t>(size) + 1);
|
|
||||||
|
|
||||||
for (int i = 0; i < size; ++i) {
|
for (const QJsonValue &value: jsonArray) {
|
||||||
m_settings->setArrayIndex(i);
|
if (!value.isObject())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const QJsonObject obj = value.toObject();
|
||||||
UserPresetData preset;
|
UserPresetData preset;
|
||||||
preset.categoryId = m_settings->value("categoryId").toString();
|
|
||||||
preset.wizardName = m_settings->value("wizardName").toString();
|
preset.categoryId = obj["categoryId"].toString();
|
||||||
preset.name = m_settings->value("name").toString();
|
preset.wizardName = obj["wizardName"].toString();
|
||||||
preset.screenSize = m_settings->value("screenSize").toString();
|
preset.name = obj["name"].toString();
|
||||||
preset.useQtVirtualKeyboard = m_settings->value("useQtVirtualKeyboard").toBool();
|
preset.screenSize = obj["screenSize"].toString();
|
||||||
preset.qtVersion = m_settings->value("qtVersion").toString();
|
preset.useQtVirtualKeyboard = obj["useQtVirtualKeyboard"].toBool();
|
||||||
preset.styleName = m_settings->value("styleName").toString();
|
preset.qtVersion = obj["qtVersion"].toString();
|
||||||
|
preset.styleName = obj["styleName"].toString();
|
||||||
|
|
||||||
if (preset.isValid())
|
if (preset.isValid())
|
||||||
result.push_back(std::move(preset));
|
result.push_back(preset);
|
||||||
}
|
}
|
||||||
m_settings->endArray();
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString UserPresetsStore::fullFilePath() const
|
|
||||||
{
|
|
||||||
return Core::ICore::userResourcePath("UserPresets.ini").toString();
|
|
||||||
}
|
|
||||||
|
@@ -25,8 +25,10 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <QSettings>
|
#include <QFile>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
namespace StudioWelcome {
|
namespace StudioWelcome {
|
||||||
|
|
||||||
@@ -68,24 +70,59 @@ inline bool operator==(const UserPresetData &lhs, const UserPresetData &rhs)
|
|||||||
return lhs.categoryId == rhs.categoryId && lhs.wizardName == rhs.wizardName
|
return lhs.categoryId == rhs.categoryId && lhs.wizardName == rhs.wizardName
|
||||||
&& lhs.name == rhs.name && lhs.screenSize == rhs.screenSize
|
&& lhs.name == rhs.name && lhs.screenSize == rhs.screenSize
|
||||||
&& lhs.useQtVirtualKeyboard == rhs.useQtVirtualKeyboard && lhs.qtVersion == rhs.qtVersion
|
&& 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<QFile> m_file;
|
||||||
|
|
||||||
|
Q_DISABLE_COPY(FileStoreIo)
|
||||||
|
};
|
||||||
|
|
||||||
class UserPresetsStore
|
class UserPresetsStore
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
UserPresetsStore();
|
UserPresetsStore(const QString &fileName, StorePolicy policy);
|
||||||
UserPresetsStore(std::unique_ptr<QSettings> &&settings);
|
UserPresetsStore(std::unique_ptr<StoreIo> &&store, StorePolicy policy);
|
||||||
|
|
||||||
bool save(const UserPresetData &preset);
|
bool save(const UserPresetData &preset);
|
||||||
void remove(const QString &category, const QString &name);
|
|
||||||
std::vector<UserPresetData> fetchAll() const;
|
std::vector<UserPresetData> fetchAll() const;
|
||||||
|
void remove(const QString &category, const QString &name);
|
||||||
|
std::vector<UserPresetData> remove(const UserPresetData &preset);
|
||||||
|
|
||||||
|
void setMaximum(int maximum) { m_maximum = maximum; }
|
||||||
|
void setReverseOrder() { m_reverse = true; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString fullFilePath() const;
|
|
||||||
void savePresets(const std::vector<UserPresetData> &presets);
|
void savePresets(const std::vector<UserPresetData> &presets);
|
||||||
|
|
||||||
std::unique_ptr<QSettings> m_settings;
|
std::unique_ptr<StoreIo> m_store;
|
||||||
|
StorePolicy m_policy = StorePolicy::UniqueNames;
|
||||||
|
bool m_reverse = false;
|
||||||
|
int m_maximum = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace StudioWelcome
|
} // namespace StudioWelcome
|
||||||
|
@@ -17,14 +17,12 @@ add_qtc_test(tst_qml_wizard
|
|||||||
SOURCES
|
SOURCES
|
||||||
wizardfactories-test.cpp
|
wizardfactories-test.cpp
|
||||||
stylemodel-test.cpp
|
stylemodel-test.cpp
|
||||||
recentpresets-test.cpp
|
|
||||||
userpresets-test.cpp
|
userpresets-test.cpp
|
||||||
presetmodel-test.cpp
|
presetmodel-test.cpp
|
||||||
test-utilities.h
|
test-utilities.h
|
||||||
test-main.cpp
|
test-main.cpp
|
||||||
"${StudioWelcomeDir}/wizardfactories.cpp"
|
"${StudioWelcomeDir}/wizardfactories.cpp"
|
||||||
"${StudioWelcomeDir}/stylemodel.cpp"
|
"${StudioWelcomeDir}/stylemodel.cpp"
|
||||||
"${StudioWelcomeDir}/recentpresets.cpp"
|
|
||||||
"${StudioWelcomeDir}/userpresets.cpp"
|
"${StudioWelcomeDir}/userpresets.cpp"
|
||||||
"${StudioWelcomeDir}/presetmodel.cpp"
|
"${StudioWelcomeDir}/presetmodel.cpp"
|
||||||
)
|
)
|
||||||
|
@@ -100,6 +100,14 @@ UserPresetData aUserPreset(const QString &categId, const QString &wizardName, co
|
|||||||
return preset;
|
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}))
|
MATCHER_P2(PresetIs, category, name, PrintToString(PresetItem{name, category}))
|
||||||
{
|
{
|
||||||
return arg->categoryId == category && arg->wizardName == name;
|
return arg->categoryId == category && arg->wizardName == name;
|
||||||
@@ -139,6 +147,7 @@ TEST(QdsPresetModel, haveSameArraySizeForPresetsAndCategories)
|
|||||||
PresetData data;
|
PresetData data;
|
||||||
|
|
||||||
data.setData(
|
data.setData(
|
||||||
|
/*wizard presets*/
|
||||||
{
|
{
|
||||||
aCategory("A.categ", "A", {"item a", "item b"}),
|
aCategory("A.categ", "A", {"item a", "item b"}),
|
||||||
aCategory("B.categ", "B", {"item c", "item d"}),
|
aCategory("B.categ", "B", {"item c", "item d"}),
|
||||||
@@ -157,6 +166,7 @@ TEST(QdsPresetModel, haveWizardPresetsNoRecents)
|
|||||||
|
|
||||||
// When
|
// When
|
||||||
data.setData(
|
data.setData(
|
||||||
|
/*wizard presets*/
|
||||||
{
|
{
|
||||||
aCategory("A.categ", "A", {"item a", "item b"}),
|
aCategory("A.categ", "A", {"item a", "item b"}),
|
||||||
aCategory("B.categ", "B", {"item c", "item d"}),
|
aCategory("B.categ", "B", {"item c", "item d"}),
|
||||||
@@ -179,6 +189,7 @@ TEST(QdsPresetModel, whenHaveUserPresetsButNoWizardPresetsReturnEmpty)
|
|||||||
|
|
||||||
// When
|
// When
|
||||||
data.setData({/*builtin presets*/},
|
data.setData({/*builtin presets*/},
|
||||||
|
/*user presets*/
|
||||||
{
|
{
|
||||||
aUserPreset("A.Mobile", "Scroll", "iPhone5"),
|
aUserPreset("A.Mobile", "Scroll", "iPhone5"),
|
||||||
aUserPreset("B.Desktop", "Launcher", "MacBook"),
|
aUserPreset("B.Desktop", "Launcher", "MacBook"),
|
||||||
@@ -196,9 +207,10 @@ TEST(QdsPresetModel, haveRecentsNoWizardPresets)
|
|||||||
|
|
||||||
data.setData({/*wizardPresets*/},
|
data.setData({/*wizardPresets*/},
|
||||||
{/*user presets*/},
|
{/*user presets*/},
|
||||||
|
/*recent presets*/
|
||||||
{
|
{
|
||||||
{"A.categ", "Desktop", "640 x 480"},
|
aRecentPreset("A.categ", "Desktop", "640 x 480"),
|
||||||
{"B.categ", "Mobile", "800 x 600"},
|
aRecentPreset("B.categ", "Mobile", "800 x 600"),
|
||||||
});
|
});
|
||||||
|
|
||||||
ASSERT_THAT(data.categories(), IsEmpty());
|
ASSERT_THAT(data.categories(), IsEmpty());
|
||||||
@@ -220,8 +232,8 @@ TEST(QdsPresetModel, recentsAddedWithWizardPresets)
|
|||||||
{/*user presets*/},
|
{/*user presets*/},
|
||||||
/*recents*/
|
/*recents*/
|
||||||
{
|
{
|
||||||
{"A.categ", "Desktop", "800 x 600"},
|
aRecentPreset("A.categ", "Desktop", "800 x 600"),
|
||||||
{"B.categ", "Mobile", "640 x 480"},
|
aRecentPreset("B.categ", "Mobile", "640 x 480"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
@@ -247,6 +259,7 @@ TEST(QdsPresetModel, userPresetsAddedWithWizardPresets)
|
|||||||
aCategory("A.categ", "A", {"Desktop", "item b"}),
|
aCategory("A.categ", "A", {"Desktop", "item b"}),
|
||||||
aCategory("B.categ", "B", {"Mobile"}),
|
aCategory("B.categ", "B", {"Mobile"}),
|
||||||
},
|
},
|
||||||
|
/*user presets*/
|
||||||
{
|
{
|
||||||
aUserPreset("A.categ", "Desktop", "Windows10"),
|
aUserPreset("A.categ", "Desktop", "Windows10"),
|
||||||
},
|
},
|
||||||
@@ -272,6 +285,7 @@ TEST(QdsPresetModel, doesNotAddUserPresetsOfNonExistingCategory)
|
|||||||
{
|
{
|
||||||
aCategory("A.categ", "A", {"Desktop"}), // Only category "A.categ" exists
|
aCategory("A.categ", "A", {"Desktop"}), // Only category "A.categ" exists
|
||||||
},
|
},
|
||||||
|
/*user presets*/
|
||||||
{
|
{
|
||||||
aUserPreset("Bad.Categ", "Desktop", "Windows8"), // Bad.Categ does not exist
|
aUserPreset("Bad.Categ", "Desktop", "Windows8"), // Bad.Categ does not exist
|
||||||
},
|
},
|
||||||
@@ -293,6 +307,7 @@ TEST(QdsPresetModel, doesNotAddUserPresetIfWizardPresetItRefersToDoesNotExist)
|
|||||||
{
|
{
|
||||||
aCategory("A.categ", "A", {"Desktop"}),
|
aCategory("A.categ", "A", {"Desktop"}),
|
||||||
},
|
},
|
||||||
|
/*user presets*/
|
||||||
{
|
{
|
||||||
aUserPreset("B.categ", "BadWizard", "Tablet"), // BadWizard referenced does not exist
|
aUserPreset("B.categ", "BadWizard", "Tablet"), // BadWizard referenced does not exist
|
||||||
},
|
},
|
||||||
@@ -314,6 +329,7 @@ TEST(QdsPresetModel, userPresetWithSameNameAsWizardPreset)
|
|||||||
{
|
{
|
||||||
aCategory("A.categ", "A", {"Desktop"}),
|
aCategory("A.categ", "A", {"Desktop"}),
|
||||||
},
|
},
|
||||||
|
/*user presets*/
|
||||||
{
|
{
|
||||||
aUserPreset("A.categ", "Desktop", "Desktop"),
|
aUserPreset("A.categ", "Desktop", "Desktop"),
|
||||||
},
|
},
|
||||||
@@ -326,58 +342,7 @@ TEST(QdsPresetModel, userPresetWithSameNameAsWizardPreset)
|
|||||||
ElementsAre(UserPresetIs("A.categ", "Desktop", "Desktop"))));
|
ElementsAre(UserPresetIs("A.categ", "Desktop", "Desktop"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(QdsPresetModel, recentOfUserPresetReferringToExistingWizardPreset)
|
TEST(QdsPresetModel, recentOfNonExistentWizardPreset)
|
||||||
{
|
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
// Given
|
|
||||||
PresetData data;
|
|
||||||
|
|
||||||
// When
|
|
||||||
data.setData(
|
|
||||||
/*wizard presets*/
|
|
||||||
{
|
|
||||||
aCategory("A.categ", "A", {"Desktop"}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
aUserPreset("A.categ", "Not-Desktop", "Windows 7"), // Non-existing Wizard Preset
|
|
||||||
},
|
|
||||||
/*recents*/
|
|
||||||
{
|
|
||||||
{"A.categ", "Windows 7", "200 x 300", /*is user*/true}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Then
|
|
||||||
ASSERT_THAT(data.categories(), ElementsAre("A"));
|
|
||||||
ASSERT_THAT(data.presets(), ElementsAre(ElementsAre(PresetIs("A.categ", "Desktop"))));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(QdsPresetModel, recentOfNonExistentUserPreset)
|
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
PresetData data;
|
PresetData data;
|
||||||
@@ -391,7 +356,7 @@ TEST(QdsPresetModel, recentOfNonExistentUserPreset)
|
|||||||
{/*user presets*/},
|
{/*user presets*/},
|
||||||
/*recents*/
|
/*recents*/
|
||||||
{
|
{
|
||||||
{"A.categ", "Windows 7", "200 x 300", /*is user*/true}
|
aRecentPreset("A.categ", "Windows 7", "200 x 300")
|
||||||
});
|
});
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
@@ -415,9 +380,9 @@ TEST(QdsPresetModel, recentsShouldNotBeSorted)
|
|||||||
{/*user presets*/},
|
{/*user presets*/},
|
||||||
/*recents*/
|
/*recents*/
|
||||||
{
|
{
|
||||||
{"Z.categ", "Z.desktop", "200 x 300"},
|
aRecentPreset("Z.categ", "Z.desktop", "200 x 300"),
|
||||||
{"B.categ", "Mobile", "200 x 300"},
|
aRecentPreset("B.categ", "Mobile", "200 x 300"),
|
||||||
{"A.categ", "Desktop", "200 x 300"},
|
aRecentPreset("A.categ", "Desktop", "200 x 300"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
@@ -442,9 +407,9 @@ TEST(QdsPresetModel, recentsOfSameWizardProjectButDifferentSizesAreRecognizedAsD
|
|||||||
{/*user presets*/},
|
{/*user presets*/},
|
||||||
/*recents*/
|
/*recents*/
|
||||||
{
|
{
|
||||||
{"B.categ", "Mobile", "400 x 400"},
|
aRecentPreset("B.categ", "Mobile", "400 x 400"),
|
||||||
{"B.categ", "Mobile", "200 x 300"},
|
aRecentPreset("B.categ", "Mobile", "200 x 300"),
|
||||||
{"A.categ", "Desktop", "640 x 480"},
|
aRecentPreset("A.categ", "Desktop", "640 x 480"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
@@ -454,6 +419,35 @@ TEST(QdsPresetModel, recentsOfSameWizardProjectButDifferentSizesAreRecognizedAsD
|
|||||||
PresetIs("A.categ", "Desktop", "640 x 480")));
|
PresetIs("A.categ", "Desktop", "640 x 480")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(QdsPresetModel, allowRecentsWithTheSameName)
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
PresetData data;
|
||||||
|
|
||||||
|
// When
|
||||||
|
data.setData(
|
||||||
|
/*wizard presets*/
|
||||||
|
{
|
||||||
|
aCategory("A.categ", "A", {"Desktop"}),
|
||||||
|
},
|
||||||
|
{/*user presets*/},
|
||||||
|
/*recents*/
|
||||||
|
{
|
||||||
|
/* 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("A.categ", "Desktop"),
|
||||||
|
PresetIs("A.categ", "Desktop"),
|
||||||
|
PresetIs("A.categ", "Desktop")));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(QdsPresetModel, outdatedRecentsAreNotShown)
|
TEST(QdsPresetModel, outdatedRecentsAreNotShown)
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
@@ -469,8 +463,8 @@ TEST(QdsPresetModel, outdatedRecentsAreNotShown)
|
|||||||
{/*user presets*/},
|
{/*user presets*/},
|
||||||
/*recents*/
|
/*recents*/
|
||||||
{
|
{
|
||||||
{"B.categ", "NoLongerExists", "400 x 400"},
|
aRecentPreset("B.categ", "NoLongerExists", "400 x 400"),
|
||||||
{"A.categ", "Desktop", "640 x 480"},
|
aRecentPreset("A.categ", "Desktop", "640 x 480"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
|
@@ -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 <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";
|
|
||||||
|
|
||||||
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<RecentPresetData> recents = store.fetchAll();
|
|
||||||
|
|
||||||
ASSERT_THAT(recents, IsEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(QdsRecentPresets, readEmptyRecentPresets)
|
|
||||||
{
|
|
||||||
RecentPresetsStore store = aStoreWithOne("");
|
|
||||||
|
|
||||||
std::vector<RecentPresetData> recents = store.fetchAll();
|
|
||||||
|
|
||||||
ASSERT_THAT(recents, IsEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(QdsRecentPresets, readOneRecentPresetAsList)
|
|
||||||
{
|
|
||||||
RecentPresetsStore store = aStoreWithRecents({"category/preset:640 x 480"});
|
|
||||||
|
|
||||||
std::vector<RecentPresetData> 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<RecentPresetData> 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<RecentPresetData> 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<RecentPresetData> recents = store.fetchAll();
|
|
||||||
|
|
||||||
ASSERT_THAT(recents, IsEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(QdsRecentPresets, readBadRecentPresetAsInt)
|
|
||||||
{
|
|
||||||
RecentPresetsStore store = aStoreWithOne(32);
|
|
||||||
|
|
||||||
std::vector<RecentPresetData> 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<RecentPresetData> 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<RecentPresetData> 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<RecentPresetData> 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<RecentPresetData> 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<RecentPresetData> 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<RecentPresetData> 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<RecentPresetData> 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<RecentPresetData> 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<RecentPresetData> 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<RecentPresetData> 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<RecentPresetData> 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<RecentPresetData> recents = store.fetchAll();
|
|
||||||
|
|
||||||
ASSERT_THAT(recents,
|
|
||||||
ElementsAre(RecentPresetData("A.Category", "Preset 3", "200 x 300"),
|
|
||||||
RecentPresetData("A.Category", "Preset 2", "200 x 300")));
|
|
||||||
}
|
|
@@ -29,6 +29,10 @@
|
|||||||
#include <utils/filepath.h>
|
#include <utils/filepath.h>
|
||||||
#include <utils/temporarydirectory.h>
|
#include <utils/temporarydirectory.h>
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
|
||||||
namespace StudioWelcome {
|
namespace StudioWelcome {
|
||||||
|
|
||||||
void PrintTo(const UserPresetData &preset, std::ostream *os)
|
void PrintTo(const UserPresetData &preset, std::ostream *os)
|
||||||
@@ -64,69 +68,85 @@ using namespace StudioWelcome;
|
|||||||
|
|
||||||
constexpr char ARRAY_NAME[] = "UserPresets";
|
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
|
class QdsUserPresets : public ::testing::Test
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
void SetUp()
|
void SetUp()
|
||||||
{
|
{
|
||||||
settings = std::make_unique<QSettings>(tempDir.filePath("test").toString(),
|
storeIo = std::make_unique<FakeStoreIo>();
|
||||||
QSettings::IniFormat);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UserPresetsStore anEmptyStore() { return UserPresetsStore{std::move(settings)}; }
|
UserPresetsStore anEmptyStore()
|
||||||
|
{
|
||||||
|
return UserPresetsStore{std::move(storeIo), StorePolicy::UniqueNames};
|
||||||
|
}
|
||||||
|
|
||||||
UserPresetsStore aStoreWithZeroItems()
|
UserPresetsStore aStoreWithZeroItems()
|
||||||
{
|
{
|
||||||
settings->beginWriteArray(ARRAY_NAME, 0);
|
storeIo->data = "[]";
|
||||||
settings->endArray();
|
|
||||||
|
|
||||||
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);
|
QJsonArray array({QJsonObject{{"categoryId", preset.categoryId},
|
||||||
settings->setArrayIndex(0);
|
{"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();
|
||||||
|
|
||||||
settings->setValue("categoryId", preset.categoryId);
|
return UserPresetsStore{std::move(storeIo), policy};
|
||||||
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)};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UserPresetsStore aStoreWithPresets(const std::vector<UserPresetData> &presets)
|
UserPresetsStore aStoreWithPresets(const std::vector<UserPresetData> &presetItems)
|
||||||
{
|
{
|
||||||
settings->beginWriteArray(ARRAY_NAME, presets.size());
|
QJsonArray array;
|
||||||
|
|
||||||
for (size_t i = 0; i < presets.size(); ++i) {
|
for (const auto &preset : presetItems) {
|
||||||
settings->setArrayIndex(i);
|
QJsonObject obj({{"categoryId", preset.categoryId},
|
||||||
const auto &preset = presets[i];
|
{"wizardName", preset.wizardName},
|
||||||
|
{"name", preset.name},
|
||||||
|
{"screenSize", preset.screenSize},
|
||||||
|
{"useQtVirtualKeyboard", preset.useQtVirtualKeyboard},
|
||||||
|
{"qtVersion", preset.qtVersion},
|
||||||
|
{"styleName", preset.styleName}});
|
||||||
|
|
||||||
settings->setValue("categoryId", preset.categoryId);
|
array.append(QJsonValue{obj});
|
||||||
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)};
|
QJsonDocument doc{array};
|
||||||
|
storeIo->data = doc.toJson();
|
||||||
|
|
||||||
|
return UserPresetsStore{std::move(storeIo), StorePolicy::UniqueNames};
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::TemporaryDirectory tempDir{"userpresets-XXXXXX"};
|
Utils::TemporaryDirectory tempDir{"userpresets-XXXXXX"};
|
||||||
std::unique_ptr<QSettings> settings;
|
std::unique_ptr<FakeStoreIo> storeIo;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString settingsPath;
|
QString storeIoPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************* TESTS *******************/
|
/******************* TESTS *******************/
|
||||||
@@ -234,10 +254,10 @@ TEST_F(QdsUserPresets, saveIncompletePreset)
|
|||||||
ASSERT_THAT(presets, ElementsAre(preset));
|
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"};
|
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"};
|
UserPresetData newPreset{"C.categ", "Empty", "Same Name", "100 x 30", false, "Qt 6", "Fusion"};
|
||||||
bool saved = store.save(newPreset);
|
bool saved = store.save(newPreset);
|
||||||
@@ -246,6 +266,30 @@ TEST_F(QdsUserPresets, cannotSavePresetWithSameName)
|
|||||||
ASSERT_THAT(store.fetchAll(), ElementsAreArray({existing}));
|
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)
|
TEST_F(QdsUserPresets, saveNewPreset)
|
||||||
{
|
{
|
||||||
UserPresetData existing{"A.categ", "3D App", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"};
|
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));
|
ASSERT_THAT(presets, ElementsAre(existing, newPreset));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(QdsUserPresets, canLimitPresetsToAMaximum)
|
||||||
|
{
|
||||||
|
std::vector<UserPresetData> 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<UserPresetData> 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)
|
TEST_F(QdsUserPresets, removeUserPresetFromEmptyStore)
|
||||||
{
|
{
|
||||||
UserPresetData preset{"C.categ", "2D App", "Android", "", false, "", ""};
|
UserPresetData preset{"C.categ", "2D App", "Android", "", false, "", ""};
|
||||||
|
Reference in New Issue
Block a user