aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarco Bubke <marco.bubke@qt.io>2020-06-18 19:46:01 +0200
committerTim Jenssen <tim.jenssen@qt.io>2020-06-30 10:28:07 +0000
commit1e6807c680924f39c071a5d5db4a3881adb14c19 (patch)
tree5b48ff6d75678e13e6422952a8ae5aed9f0916f8
parent009c2745e27a9b1e9bc3886f159c271fb491c7d4 (diff)
QmlDesigner: Add listmodeleditor
Task-number: QDS-2294 Change-Id: I66cae3a0d4265ab112eaf6b04e3a5972d185ff43 Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
-rw-r--r--src/plugins/qmldesigner/CMakeLists.txt7
-rw-r--r--src/plugins/qmldesigner/components/componentcore/componentcore_constants.h3
-rw-r--r--src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp64
-rw-r--r--src/plugins/qmldesigner/components/listmodeleditor/listmodeleditor.pri7
-rw-r--r--src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp163
-rw-r--r--src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.h (renamed from src/plugins/qmldesigner/components/listmodeleditor/listmodeleditorview.h)40
-rw-r--r--src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp269
-rw-r--r--src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h29
-rw-r--r--src/plugins/qmldesigner/components/listmodeleditor/listmodeleditorview.cpp35
-rw-r--r--src/plugins/qmldesigner/designercore/include/modelnode.h14
-rw-r--r--src/plugins/qmldesigner/designercore/model/modelnode.cpp19
-rw-r--r--src/plugins/qmldesigner/qmldesignerplugin.pro2
-rw-r--r--src/plugins/qmldesigner/qmldesignerplugin.qbs5
-rw-r--r--src/plugins/qmldesigner/qmldesignerunittestfiles.pri10
-rw-r--r--tests/unit/unittest/google-using-declarations.h2
-rw-r--r--tests/unit/unittest/gtest-creator-printing.cpp21
-rw-r--r--tests/unit/unittest/gtest-creator-printing.h8
-rw-r--r--tests/unit/unittest/gtest-qt-printing.cpp16
-rw-r--r--tests/unit/unittest/gtest-qt-printing.h4
-rw-r--r--tests/unit/unittest/listmodeleditor-test.cpp911
-rw-r--r--tests/unit/unittest/mocklistmodeleditorview.h59
-rw-r--r--tests/unit/unittest/unittest.pro6
22 files changed, 1638 insertions, 56 deletions
diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt
index 12b58ac14f..32c988b649 100644
--- a/src/plugins/qmldesigner/CMakeLists.txt
+++ b/src/plugins/qmldesigner/CMakeLists.txt
@@ -387,6 +387,13 @@ extend_qtc_plugin(QmlDesigner
)
extend_qtc_plugin(QmlDesigner
+ SOURCES_PREFIX components/listmodeleditor
+ SOURCES
+ listmodeleditordialog.cpp listmodeleditordialog.h
+ listmodeleditordialog.cpp listmodeleditormodel.h
+)
+
+extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX designercore
SOURCES
exceptions/exception.cpp
diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h
index 2fa19a5c24..a8db25f417 100644
--- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h
+++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h
@@ -181,6 +181,9 @@ const char addFlowActionToolTip[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu",
const char fitRootToScreenToolTip[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Fit the root element inside the available space.");
const char fitSelectionToScreenToolTip[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Fit the selected elements inside the available space.");
+const char editListModelDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu",
+ "Edit List Model...");
+
const int priorityFirst = 280;
const int prioritySelectionCategory = 220;
const int priorityQmlPreviewCategory = 200;
diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp
index 479f40203c..fb7d4ce202 100644
--- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp
@@ -41,10 +41,14 @@
#include <qmldesignerplugin.h>
#include <viewmanager.h>
+#include <listmodeleditor/listmodeleditordialog.h>
+#include <listmodeleditor/listmodeleditormodel.h>
+
#include <QHBoxLayout>
#include <QGraphicsLinearLayout>
#include <coreplugin/actionmanager/actionmanager.h>
+#include <coreplugin/icore.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <utils/utilsicons.h>
@@ -335,6 +339,64 @@ public:
}
};
+class EditListModelAction final : public ModelNodeContextMenuAction
+{
+public:
+ EditListModelAction()
+ : ModelNodeContextMenuAction("EditListModel",
+ ComponentCoreConstants::editListModelDisplayName,
+ {},
+ ComponentCoreConstants::rootCategory,
+ QKeySequence("Alt+e"),
+ 1001,
+ &openDialog,
+ &isListViewInBaseState,
+ &isListViewInBaseState)
+ {}
+
+ static bool isListViewInBaseState(const SelectionContext &selectionState)
+ {
+ return selectionState.isInBaseState() && selectionState.singleNodeIsSelected()
+ && selectionState.currentSingleSelectedNode().metaInfo().isSubclassOf(
+ "QtQuick.ListView");
+ }
+
+ bool isEnabled(const SelectionContext &) const override { return true; }
+
+ static ModelNode listModelNode(const ModelNode &listViewNode)
+ {
+ if (listViewNode.hasProperty("model")) {
+ if (listViewNode.hasBindingProperty("model"))
+ return listViewNode.bindingProperty("model").resolveToModelNode();
+ else if (listViewNode.hasNodeProperty("model"))
+ return listViewNode.nodeProperty("model").modelNode();
+ }
+
+ ModelNode newModel = listViewNode.view()->createModelNode("QtQml.Models.ListModel", 2, 15);
+ listViewNode.nodeProperty("mode").reparentHere(newModel);
+
+ return newModel;
+ }
+
+ static void openDialog(const SelectionContext &selectionState)
+ {
+ ListModelEditorModel model;
+
+ ModelNode targetNode = selectionState.targetNode();
+ if (!targetNode.isValid())
+ targetNode = selectionState.currentSingleSelectedNode();
+ if (!targetNode.isValid())
+ return;
+
+ model.setListModel(listModelNode(targetNode));
+
+ ListModelEditorDialog dialog{Core::ICore::mainWindow()};
+ dialog.setModel(&model);
+
+ dialog.exec();
+ }
+};
+
bool flowOptionVisible(const SelectionContext &context)
{
return QmlFlowViewNode::isValidQmlFlowViewNode(context.rootNode());
@@ -1217,6 +1279,8 @@ void DesignerActionManager::createDefaultDesignerActions()
priorityGenericToolBar));
addDesignerAction(new ChangeStyleAction());
+
+ addDesignerAction(new EditListModelAction);
}
void DesignerActionManager::createDefaultAddResourceHandler()
diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditor.pri b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditor.pri
new file mode 100644
index 0000000000..cd6938aab4
--- /dev/null
+++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditor.pri
@@ -0,0 +1,7 @@
+SOURCES += \
+ $$PWD/listmodeleditordialog.cpp \
+ $$PWD/listmodeleditormodel.cpp
+
+HEADERS += \
+ $$PWD/listmodeleditordialog.h \
+ $$PWD/listmodeleditormodel.h
diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp
new file mode 100644
index 0000000000..4a8745b62d
--- /dev/null
+++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp
@@ -0,0 +1,163 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 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 "listmodeleditordialog.h"
+#include "listmodeleditormodel.h"
+
+#include <theme.h>
+
+#include <coreplugin/icore.h>
+#include <utils/algorithm.h>
+#include <utils/stylehelper.h>
+
+#include <QHeaderView>
+#include <QInputDialog>
+#include <QKeyEvent>
+#include <QTableView>
+#include <QToolBar>
+
+#include <vector>
+
+namespace QmlDesigner {
+
+namespace {
+QIcon getIcon(Theme::Icon icon)
+{
+ const QString fontName = "qtds_propertyIconFont.ttf";
+
+ return Utils::StyleHelper::getIconFromIconFont(fontName, Theme::getIconUnicode(icon), 30, 30);
+}
+} // namespace
+
+ListModelEditorDialog::ListModelEditorDialog(QWidget *parent)
+ : QDialog(parent)
+{
+ resize((Core::ICore::mainWindow()->size() * 8) / 10);
+
+ QVBoxLayout *mainLayout = new QVBoxLayout(this);
+
+ QToolBar *toolBar = new QToolBar();
+ toolBar->setIconSize({30, 30});
+ mainLayout->addWidget(toolBar);
+ m_tableView = new QTableView;
+ mainLayout->addWidget(m_tableView);
+
+ m_addRowAction = toolBar->addAction(getIcon(Theme::Icon::addRowAfter), tr("Add Row"));
+ m_removeRowsAction = toolBar->addAction(getIcon(Theme::Icon::deleteRow), tr("Remove Columns"));
+ m_addColumnAction = toolBar->addAction(getIcon(Theme::Icon::addColumnAfter), tr("Add Column"));
+ m_removeColumnsAction = toolBar->addAction(getIcon(Theme::Icon::deleteColumn),
+ tr("Remove Columns"));
+}
+
+ListModelEditorDialog::~ListModelEditorDialog() = default;
+
+void ListModelEditorDialog::setModel(ListModelEditorModel *model)
+{
+ m_model = model;
+
+ connect(m_addRowAction, &QAction::triggered, m_model, &ListModelEditorModel::addRow);
+ connect(m_addColumnAction, &QAction::triggered, this, &ListModelEditorDialog::openColumnDialog);
+ connect(m_removeRowsAction, &QAction::triggered, this, &ListModelEditorDialog::removeRows);
+ connect(m_removeColumnsAction, &QAction::triggered, this, &ListModelEditorDialog::removeColumns);
+ connect(m_tableView->horizontalHeader(),
+ &QHeaderView::sectionDoubleClicked,
+ this,
+ &ListModelEditorDialog::changeHeader);
+
+ m_tableView->setModel(model);
+ m_tableView->horizontalHeader()->setMinimumSectionSize(60);
+ m_tableView->verticalHeader()->setMinimumSectionSize(25);
+ m_tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
+ m_tableView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
+}
+
+void ListModelEditorDialog::keyPressEvent(QKeyEvent *event)
+{
+ if (event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete) {
+ for (const QModelIndex index : m_tableView->selectionModel()->selectedIndexes())
+ m_model->setData(index, QVariant(), Qt::EditRole);
+ }
+}
+
+void ListModelEditorDialog::openColumnDialog()
+{
+ bool ok;
+ QString columnName = QInputDialog::getText(
+ this, tr("Add Property"), tr("Property Name:"), QLineEdit::Normal, "", &ok);
+ if (ok && !columnName.isEmpty())
+ m_model->addColumn(columnName);
+}
+
+void ListModelEditorDialog::removeRows()
+{
+ const QList<QModelIndex> indices = m_tableView->selectionModel()->selectedRows();
+ std::vector<int> rows;
+ rows.reserve(indices.size());
+
+ for (QModelIndex index : indices)
+ rows.push_back(index.row());
+
+ std::sort(rows.begin(), rows.end());
+
+ rows.erase(std::unique(rows.begin(), rows.end()), rows.end());
+
+ std::reverse(rows.begin(), rows.end());
+
+ for (int row : rows)
+ m_model->removeRow(row);
+}
+
+void ListModelEditorDialog::removeColumns()
+{
+ const QList<QModelIndex> indices = m_tableView->selectionModel()->selectedColumns();
+ std::vector<int> columns;
+ columns.reserve(indices.size());
+
+ for (QModelIndex index : indices)
+ columns.push_back(index.column());
+
+ std::sort(columns.begin(), columns.end());
+
+ columns.erase(std::unique(columns.begin(), columns.end()), columns.end());
+
+ std::reverse(columns.begin(), columns.end());
+
+ for (int row : columns)
+ m_model->removeColumn(row);
+}
+
+void ListModelEditorDialog::changeHeader(int column)
+{
+ const QString propertyName = QString::fromUtf8(m_model->propertyNames()[column]);
+
+ bool ok;
+ QString newPropertyName = QInputDialog::getText(
+ this, tr("Change Propertry"), tr("Column Name:"), QLineEdit::Normal, propertyName, &ok);
+
+ if (ok && !newPropertyName.isEmpty())
+ m_model->renameColumn(column, newPropertyName);
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditorview.h b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.h
index 6cfdddde5a..519d0869fa 100644
--- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditorview.h
+++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.h
@@ -25,14 +25,48 @@
#pragma once
-#include <abstractview.h>
+#include <QDialog>
+
+QT_BEGIN_NAMESPACE
+class QAbstractItemModel;
+class QTableView;
+QT_END_NAMESPACE
+
+namespace Ui {
+class ListModelEditorDialog;
+}
namespace QmlDesigner {
-class ListModelEditorView : public AbstractView
+class ListModelEditorModel;
+
+class ListModelEditorDialog : public QDialog
{
+ Q_OBJECT
+
public:
- ListModelEditorView();
+ explicit ListModelEditorDialog(QWidget *parent = nullptr);
+ ~ListModelEditorDialog();
+
+ void setModel(ListModelEditorModel *model);
+
+protected:
+ void keyPressEvent(QKeyEvent *) override;
+
+private:
+ void addRow();
+ void openColumnDialog();
+ void removeRows();
+ void removeColumns();
+ void changeHeader(int column);
+
+private:
+ ListModelEditorModel *m_model{};
+ QAction *m_addRowAction{};
+ QAction *m_removeRowsAction{};
+ QAction *m_addColumnAction{};
+ QAction *m_removeColumnsAction{};
+ QTableView *m_tableView{};
};
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp
index 050071dcbd..98722c3e8f 100644
--- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp
+++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp
@@ -25,11 +25,278 @@
#include "listmodeleditormodel.h"
+#include <abstractview.h>
+#include <nodelistproperty.h>
+#include <variantproperty.h>
+
+#include <QVariant>
+
+#include <algorithm>
+#include <iterator>
+#include <memory>
+
namespace QmlDesigner {
-ListModelEditorModel::ListModelEditorModel()
+class ListModelItem : public QStandardItem
+{
+public:
+ ListModelItem(ModelNode node, PropertyName propertyName)
+ : node(std::move(node))
+ , propertyName(propertyName)
+ {
+ setEditable(true);
+ }
+
+ QVariant maybeConvertToNumber(const QVariant &value)
+ {
+ bool canConvert = false;
+ double convertedValue = value.toDouble(&canConvert);
+ if (canConvert) {
+ return convertedValue;
+ }
+
+ return value;
+ }
+
+ QVariant data(int role) const override
+ {
+ if (role == Qt::BackgroundColorRole && hasInvalidValue)
+ return QColor{Qt::darkYellow};
+
+ return QStandardItem::data(role);
+ }
+
+ void setData(const QVariant &value, int role) override
+ {
+ if (role == Qt::DisplayRole || role == Qt::EditRole)
+ hasInvalidValue = !value.isValid();
+
+ if (role == Qt::EditRole) {
+ const QVariant &convertedValue = maybeConvertToNumber(value);
+ QStandardItem::setData(convertedValue, role);
+ if (value.isValid())
+ node.variantProperty(propertyName).setValue(convertedValue);
+ else
+ node.removeProperty(propertyName);
+ } else {
+ QStandardItem::setData(value, role);
+ }
+ }
+
+ void removeProperty() { node.removeProperty(propertyName); }
+
+ void renameProperty(const PropertyName &newPropertyName)
+ {
+ if (node.hasProperty(propertyName)) {
+ node.removeProperty(propertyName);
+ node.variantProperty(newPropertyName).setValue(data(Qt::EditRole));
+ }
+ propertyName = newPropertyName;
+ }
+
+public:
+ ModelNode node;
+ PropertyName propertyName;
+ bool hasInvalidValue = false;
+};
+
+namespace {
+QList<PropertyName> getPropertyNames(const ModelNode &listElementNode)
+{
+ auto properties = listElementNode.variantProperties();
+
+ QList<PropertyName> names;
+ names.reserve(properties.size());
+
+ for (const auto &property : properties)
+ names.push_back(property.name());
+
+ std::sort(names.begin(), names.end());
+
+ return names;
+}
+
+QList<PropertyName> mergeProperyNames(const QList<PropertyName> &first,
+ const QList<PropertyName> &second)
+{
+ QList<PropertyName> merged;
+ merged.reserve(first.size() + second.size());
+
+ std::set_union(first.begin(),
+ first.end(),
+ second.begin(),
+ second.end(),
+ std::back_inserter(merged));
+
+ return merged;
+}
+
+std::unique_ptr<ListModelItem> createItem(const ModelNode &listElementNode,
+ const PropertyName &propertyName)
+{
+ auto item = std::make_unique<ListModelItem>(listElementNode, propertyName);
+
+ QVariant value = listElementNode.variantProperty(propertyName).value();
+
+ item->setData(value, Qt::DisplayRole);
+
+ return item;
+}
+
+QList<QString> convertToStringList(const QList<PropertyName> &propertyNames)
+{
+ QList<QString> names;
+ names.reserve(propertyNames.size());
+
+ for (const auto &propertyName : propertyNames)
+ names.push_back(QString::fromUtf8(propertyName));
+
+ return names;
+}
+
+QList<PropertyName> createProperyNames(const QList<ModelNode> &listElementNodes)
+{
+ QList<PropertyName> propertyNames;
+ propertyNames.reserve(10);
+
+ for (const ModelNode &listElementNode : listElementNodes)
+ propertyNames = mergeProperyNames(getPropertyNames(listElementNode), propertyNames);
+
+ return propertyNames;
+}
+
+QList<QStandardItem *> createColumnItems(const ModelNode &listModelNode,
+ const PropertyName &propertyName)
+{
+ QList<QStandardItem *> items;
+ const auto listElementNodes = listModelNode.defaultNodeListProperty().toModelNodeList();
+
+ for (const ModelNode &listElementNode : listElementNodes)
+ items.push_back(createItem(listElementNode, propertyName).release());
+
+ return items;
+}
+
+void renameProperties(const QStandardItemModel *model,
+ int columnIndex,
+ const PropertyName &newPropertyName)
{
+ for (int rowIndex = 0; rowIndex < model->rowCount(); ++rowIndex)
+ static_cast<ListModelItem *>(model->item(rowIndex, columnIndex))->renameProperty(newPropertyName);
+}
+
+} // namespace
+
+void ListModelEditorModel::populateModel()
+{
+ const auto listElementNodes = m_listModelNode.defaultNodeListProperty().toModelNodeList();
+
+ m_propertyNames = createProperyNames(listElementNodes);
+
+ setHorizontalHeaderLabels(convertToStringList(m_propertyNames));
+
+ createItems(listElementNodes);
+}
+
+void ListModelEditorModel::createItems(const QList<ModelNode> &listElementNodes)
+{
+ for (const ModelNode &listElementNode : listElementNodes)
+ appendItems(listElementNode);
+}
+
+void ListModelEditorModel::appendItems(const ModelNode &listElementNode)
+{
+ QList<QStandardItem *> row;
+ row.reserve(m_propertyNames.size());
+ for (const PropertyName &propertyName : propertyNames())
+ row.push_back(createItem(listElementNode, propertyName).release());
+
+ appendRow(row);
+}
+
+void ListModelEditorModel::addRow()
+{
+ auto newElement = m_listModelNode.view()->createModelNode("QtQml.Models.ListElement", 2, 15);
+ m_listModelNode.defaultNodeListProperty().reparentHere(newElement);
+
+ appendItems(newElement);
+}
+
+void ListModelEditorModel::addColumn(const QString &columnName)
+{
+ PropertyName propertyName = columnName.toUtf8();
+
+ auto found = std::lower_bound(m_propertyNames.begin(), m_propertyNames.end(), propertyName);
+
+ if (found != m_propertyNames.end() && *found == columnName)
+ return;
+
+ int newIndex = static_cast<int>(std::distance(m_propertyNames.begin(), found));
+
+ m_propertyNames.insert(found, propertyName);
+
+ insertColumn(newIndex, createColumnItems(m_listModelNode, propertyName));
+
+ setHorizontalHeaderItem(newIndex, new QStandardItem(columnName));
+}
+
+bool ListModelEditorModel::setValue(int row, int column, QVariant value, Qt::ItemDataRole role)
+{
+ QModelIndex index = createIndex(row, column, invisibleRootItem());
+ bool success = setData(index, value, role);
+ emit dataChanged(index, index);
+
+ return success;
+}
+
+void ListModelEditorModel::removeColumn(int column)
+{
+ QList<QStandardItem *> columnItems = QStandardItemModel::takeColumn(column);
+ m_propertyNames.removeAt(column);
+
+ for (QStandardItem *columnItem : columnItems) {
+ static_cast<ListModelItem *>(columnItem)->removeProperty();
+ delete columnItem;
+ }
+}
+
+void ListModelEditorModel::removeRow(int row)
+{
+ QList<QStandardItem *> rowItems = QStandardItemModel::takeRow(row);
+
+ if (rowItems.size())
+ static_cast<ListModelItem *>(rowItems.front())->node.destroy();
+
+ qDeleteAll(rowItems);
+}
+
+void ListModelEditorModel::renameColumn(int oldColumn, const QString &newColumnName)
+{
+ const PropertyName newPropertyName = newColumnName.toUtf8();
+
+ auto found = std::lower_bound(m_propertyNames.begin(), m_propertyNames.end(), newPropertyName);
+
+ if (found != m_propertyNames.end() && *found == newPropertyName)
+ return;
+
+ int newColumn = static_cast<int>(std::distance(m_propertyNames.begin(), found));
+
+ if (oldColumn == newColumn) {
+ *found = newPropertyName;
+ renameProperties(this, newColumn, newPropertyName);
+ } else if (newColumn < oldColumn) {
+ m_propertyNames.insert(found, newPropertyName);
+ m_propertyNames.erase(std::next(m_propertyNames.begin(), oldColumn + 1));
+ insertColumn(newColumn, takeColumn(oldColumn));
+ renameProperties(this, newColumn, newPropertyName);
+ } else {
+ m_propertyNames.insert(found, newPropertyName);
+ m_propertyNames.erase(std::next(m_propertyNames.begin(), oldColumn));
+ insertColumn(newColumn - 1, takeColumn(oldColumn));
+ renameProperties(this, newColumn - 1, newPropertyName);
+ }
+ setHorizontalHeaderLabels(convertToStringList(m_propertyNames));
}
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h
index e9638a1a6d..35d41bee68 100644
--- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h
+++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h
@@ -25,14 +25,41 @@
#pragma once
+#include <modelnode.h>
+
#include <QStandardItemModel>
namespace QmlDesigner {
class ListModelEditorModel : public QStandardItemModel
{
+
public:
- ListModelEditorModel();
+ void setListModel(ModelNode node)
+ {
+ m_listModelNode = node;
+ populateModel();
+ }
+
+ void addRow();
+ void addColumn(const QString &columnName);
+
+ const QList<QmlDesigner::PropertyName> &propertyNames() const { return m_propertyNames; }
+
+ bool setValue(int row, int column, QVariant value, Qt::ItemDataRole role = Qt::EditRole);
+
+ void removeColumn(int column);
+ void removeRow(int row);
+ void renameColumn(int column, const QString &newColumnName);
+
+private:
+ void populateModel();
+ void createItems(const QList<ModelNode> &listElementNodes);
+ void appendItems(const ModelNode &listElementNode);
+
+private:
+ ModelNode m_listModelNode;
+ QList<QmlDesigner::PropertyName> m_propertyNames;
};
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditorview.cpp b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditorview.cpp
deleted file mode 100644
index c8d4017f1d..0000000000
--- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditorview.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2020 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 "listmodeleditorview.h"
-
-namespace QmlDesigner {
-
-ListModelEditorView::ListModelEditorView()
-{
-
-}
-
-} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/designercore/include/modelnode.h b/src/plugins/qmldesigner/designercore/include/modelnode.h
index 6f3d5eb6df..3197e746ab 100644
--- a/src/plugins/qmldesigner/designercore/include/modelnode.h
+++ b/src/plugins/qmldesigner/designercore/include/modelnode.h
@@ -91,9 +91,12 @@ public:
ModelNode(const Internal::InternalNodePointer &internalNode, Model *model, const AbstractView *view);
ModelNode(const ModelNode &modelNode, AbstractView *view);
ModelNode(const ModelNode &other);
+ ModelNode(ModelNode &&other);
~ModelNode();
- ModelNode& operator=(const ModelNode &other);
+ ModelNode &operator=(const ModelNode &other);
+ ModelNode &operator=(ModelNode &&other);
+
TypeName type() const;
QString simplifiedTypeName() const;
QString displayName() const;
@@ -226,6 +229,15 @@ public:
bool isSubclassOf(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1) const;
QIcon typeIcon() const;
+ friend void swap(ModelNode &first, ModelNode &second)
+ {
+ using std::swap;
+
+ swap(first.m_internalNode, second.m_internalNode);
+ swap(first.m_model, second.m_model);
+ swap(first.m_view, second.m_view);
+ }
+
private: // functions
Internal::InternalNodePointer internalNode() const;
diff --git a/src/plugins/qmldesigner/designercore/model/modelnode.cpp b/src/plugins/qmldesigner/designercore/model/modelnode.cpp
index b4c21564c7..eb2d285997 100644
--- a/src/plugins/qmldesigner/designercore/model/modelnode.cpp
+++ b/src/plugins/qmldesigner/designercore/model/modelnode.cpp
@@ -93,7 +93,25 @@ ModelNode::ModelNode(const ModelNode &modelNode, AbstractView *view)
m_model(modelNode.model()),
m_view(view)
{
+}
+
+ModelNode::ModelNode(ModelNode &&other)
+ : m_internalNode(std::move(other.m_internalNode))
+ , m_model(std::move(other.m_model))
+ , m_view(std::move(other.m_view))
+{
+ other.m_model = {};
+ other.m_view = {};
+}
+ModelNode &ModelNode::operator=(ModelNode &&other)
+{
+ ModelNode newNode;
+
+ swap(other, newNode);
+ swap(*this, newNode);
+
+ return *this;
}
/*! \brief contructs a invalid model node
@@ -103,7 +121,6 @@ ModelNode::ModelNode(const ModelNode &modelNode, AbstractView *view)
ModelNode::ModelNode():
m_internalNode(new InternalNode)
{
-
}
ModelNode::ModelNode(const ModelNode &other) = default;
diff --git a/src/plugins/qmldesigner/qmldesignerplugin.pro b/src/plugins/qmldesigner/qmldesignerplugin.pro
index bc0da99050..506b3d743b 100644
--- a/src/plugins/qmldesigner/qmldesignerplugin.pro
+++ b/src/plugins/qmldesigner/qmldesignerplugin.pro
@@ -33,7 +33,7 @@ include(components/bindingeditor/bindingeditor.pri)
include(components/annotationeditor/annotationeditor.pri)
include(components/richtexteditor/richtexteditor.pri)
include(components/transitioneditor/transitioneditor.pri)
-
+include(components/listmodeleditor/listmodeleditor.pri)
BUILD_PUPPET_IN_CREATOR_BINPATH = $$(BUILD_PUPPET_IN_CREATOR_BINPATH)
!isEmpty(BUILD_PUPPET_IN_CREATOR_BINPATH) {
diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs
index b1129a9bf3..dceaf8656d 100644
--- a/src/plugins/qmldesigner/qmldesignerplugin.qbs
+++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs
@@ -59,6 +59,7 @@ Project {
"components/stateseditor",
"components/texteditor",
"components/timelineeditor",
+ "compenents/listmodeleditor",
])
Properties {
@@ -843,6 +844,10 @@ Project {
"timelineeditor/timelineview.h",
"timelineeditor/timelinewidget.cpp",
"timelineeditor/timelinewidget.h",
+ "listmodeleditor/listmodeleditordialog.cpp ",
+ "listmodeleditor/listmodeleditordialog.h ",
+ "listmodeleditor/listmodeleditormodel.cpp ",
+ "listmodeleditor/listmodeleditordialog.h ",
"transitioneditor/transitioneditorview.cpp",
"transitioneditor/transitioneditorview.h",
"transitioneditor/transitioneditorwidget.cpp",
diff --git a/src/plugins/qmldesigner/qmldesignerunittestfiles.pri b/src/plugins/qmldesigner/qmldesignerunittestfiles.pri
index 282360a4ea..cd4d52e8d3 100644
--- a/src/plugins/qmldesigner/qmldesignerunittestfiles.pri
+++ b/src/plugins/qmldesigner/qmldesignerunittestfiles.pri
@@ -14,14 +14,12 @@ SOURCES += \
$$PWD/designercore/model/import.cpp \
$$PWD/designercore/model/abstractproperty.cpp \
$$PWD/designercore/model/abstractview.cpp \
- $$PWD/components/listmodeleditor/listmodeleditormodel.cpp \
$$PWD/designercore/model/internalproperty.cpp \
$$PWD/designercore/model/internalbindingproperty.cpp \
$$PWD/designercore/model/internalnodeabstractproperty.cpp \
$$PWD/designercore/model/internalnodelistproperty.cpp \
$$PWD/designercore/model/internalnodeproperty.cpp \
$$PWD/designercore/model/internalsignalhandlerproperty.cpp \
- $$PWD/designercore/model/internalproperty.cpp \
$$PWD/designercore/model/internalnode.cpp \
$$PWD/designercore/model/internalvariantproperty.cpp \
$$PWD/designercore/model/bindingproperty.cpp \
@@ -30,9 +28,9 @@ SOURCES += \
$$PWD/designercore/model/nodeproperty.cpp \
$$PWD/designercore/model/signalhandlerproperty.cpp \
$$PWD/designercore/model/variantproperty.cpp\
- $$PWD/designercore/model/annotation.cpp\
$$PWD/designercore/model/annotation.cpp \
- $$PWD/designercore/rewritertransaction.cpp
+ $$PWD/designercore/rewritertransaction.cpp \
+ $$PWD/components/listmodeleditor/listmodeleditormodel.cpp
HEADERS += \
$$PWD/designercore/include/modelnode.h \
@@ -41,7 +39,6 @@ HEADERS += \
$$PWD/designercore/include/import.h \
$$PWD/designercore/include/abstractproperty.h \
$$PWD/designercore/include/abstractview.h \
- $$PWD/components/listmodeleditor/listmodeleditormodel.h \
$$PWD/designercore/model/model_p.h \
$$PWD/designercore/include/qmldesignercorelib_global.h \
$$PWD/designercore/model/internalbindingproperty.h \
@@ -58,4 +55,5 @@ HEADERS += \
$$PWD/designercore/include/nodeproperty.h \
$$PWD/designercore/include/signalhandlerproperty.h \
$$PWD/designercore/include/variantproperty.h \
- $$PWD/designercore/rewritertransaction.h
+ $$PWD/designercore/rewritertransaction.h \
+ $$PWD/components/listmodeleditor/listmodeleditormodel.h
diff --git a/tests/unit/unittest/google-using-declarations.h b/tests/unit/unittest/google-using-declarations.h
index 1a92ff3388..de52483403 100644
--- a/tests/unit/unittest/google-using-declarations.h
+++ b/tests/unit/unittest/google-using-declarations.h
@@ -65,6 +65,8 @@ using testing::Property;
using testing::Return;
using testing::ReturnRef;
using testing::SafeMatcherCast;
+using testing::SaveArg;
+using testing::SaveArgPointee;
using testing::Sequence;
using testing::SizeIs;
using testing::StrEq;
diff --git a/tests/unit/unittest/gtest-creator-printing.cpp b/tests/unit/unittest/gtest-creator-printing.cpp
index 326058d7a9..20e44154d9 100644
--- a/tests/unit/unittest/gtest-creator-printing.cpp
+++ b/tests/unit/unittest/gtest-creator-printing.cpp
@@ -47,6 +47,7 @@
#include <filepathview.h>
#include <filestatus.h>
#include <includesearchpath.h>
+#include <modelnode.h>
#include <nativefilepath.h>
#include <pchpaths.h>
#include <pchtask.h>
@@ -68,6 +69,7 @@
#include <tooltipinfo.h>
#include <usedmacro.h>
#include <utils/link.h>
+#include <variantproperty.h>
#include <sqlite3ext.h>
@@ -1449,6 +1451,25 @@ std::ostream &operator<<(std::ostream &out, const Diagnostic &diag) {
} // namespace Internal
} // namespace ClangTools
+namespace QmlDesigner {
+
+std::ostream &operator<<(std::ostream &out, const ModelNode &node)
+{
+ if (!node.isValid())
+ return out << "(invalid)";
+
+ return out << "(" << node.id() << ")";
+}
+std::ostream &operator<<(std::ostream &out, const VariantProperty &property)
+{
+ if (!property.isValid())
+ return out << "(invalid)";
+
+ return out << "(" << property.parentModelNode() << ", " << property.name() << ", "
+ << property.value() << ")";
+}
+} // namespace QmlDesigner
+
void setFilePathCache(ClangBackEnd::FilePathCaching *cache)
{
filePathCache = cache;
diff --git a/tests/unit/unittest/gtest-creator-printing.h b/tests/unit/unittest/gtest-creator-printing.h
index e0cb55315f..565479be03 100644
--- a/tests/unit/unittest/gtest-creator-printing.h
+++ b/tests/unit/unittest/gtest-creator-printing.h
@@ -350,4 +350,12 @@ std::ostream &operator<<(std::ostream &out, const Diagnostic &diag);
} // namespace Internal
} // namespace CppTools
+namespace QmlDesigner {
+class ModelNode;
+class VariantProperty;
+
+std::ostream &operator<<(std::ostream &out, const ModelNode &node);
+std::ostream &operator<<(std::ostream &out, const VariantProperty &property);
+} // namespace QmlDesigner
+
void setFilePathCache(ClangBackEnd::FilePathCaching *filePathCache);
diff --git a/tests/unit/unittest/gtest-qt-printing.cpp b/tests/unit/unittest/gtest-qt-printing.cpp
index cd97883b16..c097fd0b4c 100644
--- a/tests/unit/unittest/gtest-qt-printing.cpp
+++ b/tests/unit/unittest/gtest-qt-printing.cpp
@@ -59,9 +59,11 @@ std::ostream &operator<<(std::ostream &out, const QVariant &variant)
QString output;
QDebug debug(&output);
- debug << variant;
+ debug.noquote().nospace() << variant;
- return out << output;
+ QByteArray utf8Text = output.toUtf8();
+
+ return out.write(utf8Text.data(), utf8Text.size());
}
std::ostream &operator<<(std::ostream &out, const QTextCharFormat &format)
@@ -88,4 +90,14 @@ void PrintTo(const QString &text, std::ostream *os)
*os << text;
}
+void PrintTo(const QVariant &variant, std::ostream *os)
+{
+ *os << variant;
+}
+
+void PrintTo(const QByteArray &text, std::ostream *os)
+{
+ *os << text;
+}
+
QT_END_NAMESPACE
diff --git a/tests/unit/unittest/gtest-qt-printing.h b/tests/unit/unittest/gtest-qt-printing.h
index 424762273b..ebaeb2c785 100644
--- a/tests/unit/unittest/gtest-qt-printing.h
+++ b/tests/unit/unittest/gtest-qt-printing.h
@@ -35,10 +35,12 @@ class QVariant;
class QString;
class QTextCharFormat;
-std::ostream &operator<<(std::ostream &out, const QVariant &variant);
+std::ostream &operator<<(std::ostream &out, const QVariant &QVariant);
std::ostream &operator<<(std::ostream &out, const QString &text);
std::ostream &operator<<(std::ostream &out, const QByteArray &byteArray);
std::ostream &operator<<(std::ostream &out, const QTextCharFormat &format);
void PrintTo(const QString &text, std::ostream *os);
+void PrintTo(const QVariant &variant, std::ostream *os);
+void PrintTo(const QByteArray &text, std::ostream *os);
QT_END_NAMESPACE
diff --git a/tests/unit/unittest/listmodeleditor-test.cpp b/tests/unit/unittest/listmodeleditor-test.cpp
index 7a876890c5..ca0913f865 100644
--- a/tests/unit/unittest/listmodeleditor-test.cpp
+++ b/tests/unit/unittest/listmodeleditor-test.cpp
@@ -27,21 +27,926 @@
#include "googletest.h"
+#include "mocklistmodeleditorview.h"
+
#include <qmldesigner/components/listmodeleditor/listmodeleditormodel.h>
-#include <qmldesigner/components/listmodeleditor/listmodeleditorview.h>
+#include <qmldesigner/designercore/include/abstractview.h>
#include <qmldesigner/designercore/include/model.h>
+#include <qmldesigner/designercore/include/nodelistproperty.h>
+#include <qmldesigner/designercore/include/variantproperty.h>
namespace {
+using QmlDesigner::AbstractProperty;
+using QmlDesigner::AbstractView;
+using QmlDesigner::ModelNode;
+
+MATCHER_P2(HasItem,
+ name,
+ value,
+ std::string(negation ? "hasn't " : "has ") + "(" + name + ", " + value + ")")
+{
+ QStandardItem *item = arg;
+
+ return item->data(Qt::UserRole).toString() == name && item->data(Qt::UserRole).toDouble() == value;
+}
+
+MATCHER(IsInvalid, std::string(negation ? "isn't null" : "is null"))
+{
+ return !arg.isValid();
+}
+
+MATCHER_P3(IsVariantProperty,
+ node,
+ name,
+ value,
+ std::string(negation ? "isn't " : "is ") + "(" + name + ", " + PrintToString(value) + ")")
+{
+ const QmlDesigner::VariantProperty &property = arg;
+
+ return property.parentModelNode() == node && property.name() == name && property.value() == value;
+}
+
+MATCHER_P2(IsVariantProperty,
+ name,
+ value,
+ std::string(negation ? "isn't " : "is ") + "(" + name + ", " + PrintToString(value) + ")")
+{
+ const QmlDesigner::VariantProperty &property = arg;
+
+ return property.name() == name && property.value() == value;
+}
+
+MATCHER_P2(IsAbstractProperty, node, name, std::string(negation ? "isn't " : "is ") + "(" + name + ")")
+{
+ const QmlDesigner::AbstractProperty &property = arg;
+
+ return property.parentModelNode() == node && property.name() == name;
+}
+
class ListModelEditor : public testing::Test
{
public:
- ListModelEditor() { designerModel->attachView(&view); }
+ ListModelEditor()
+ {
+ designerModel->attachView(&mockView);
+
+ emptyListModelNode = mockView.createModelNode("QtQml.Models.ListModel", 2, 15);
+
+ listModelNode = mockView.createModelNode("QtQml.Models.ListModel", 2, 15);
+ mockView.rootModelNode().defaultNodeListProperty().reparentHere(listModelNode);
+ element1 = createElement({{"name", "foo"}, {"value", 1}, {"value2", 42}});
+ element2 = createElement({{"value", 4}, {"name", "bar"}, {"image", "pic.png"}});
+ element3 = createElement({{"image", "pic.png"}, {"name", "poo"}, {"value", 111}});
+ }
+
+ using Entry = std::pair<QmlDesigner::PropertyName, QVariant>;
+
+ ModelNode createElement(std::initializer_list<Entry> entries)
+ {
+ auto element = mockView.createModelNode("QtQml.Models/ListElement", 2, 15);
+ listModelNode.defaultNodeListProperty().reparentHere(element);
+
+ for (const auto &entry : entries) {
+ element.variantProperty(entry.first).setValue(entry.second);
+ }
+
+ return element;
+ }
+
+ QList<QString> headerLabels(const QmlDesigner::ListModelEditorModel &model) const
+ {
+ QList<QString> labels;
+ labels.reserve(model.columnCount());
+
+ for (int i = 0; i < model.columnCount(); ++i)
+ labels.push_back(model.headerData(i, Qt::Horizontal).toString());
+
+ return labels;
+ }
+
+ QList<QList<QVariant>> displayValues() const
+ {
+ QList<QList<QVariant>> rows;
+
+ for (int rowIndex = 0; rowIndex < model.rowCount(); ++rowIndex) {
+ QList<QVariant> row;
+
+ for (int columnIndex = 0; columnIndex < model.columnCount(); ++columnIndex)
+ row.push_back(model.data(model.index(rowIndex, columnIndex), Qt::DisplayRole));
+
+ rows.push_back(row);
+ }
+
+ return rows;
+ }
+
+ QList<QList<QColor>> backgroundColors() const
+ {
+ QList<QList<QColor>> rows;
+
+ for (int rowIndex = 0; rowIndex < model.rowCount(); ++rowIndex) {
+ QList<QColor> row;
+
+ for (int columnIndex = 0; columnIndex < model.columnCount(); ++columnIndex)
+ row.push_back(
+ model.data(model.index(rowIndex, columnIndex), Qt::BackgroundColorRole)
+ .value<QColor>());
+
+ rows.push_back(row);
+ }
+
+ return rows;
+ }
+
+ QList<QList<QmlDesigner::VariantProperty>> properties() const
+ {
+ QList<QList<QmlDesigner::VariantProperty>> properties;
+ properties.reserve(10);
+
+ auto nodes = listModelNode.defaultNodeListProperty().toModelNodeList();
+
+ for (const ModelNode &node : nodes)
+ properties.push_back(node.variantProperties());
+
+ return properties;
+ }
protected:
std::unique_ptr<QmlDesigner::Model> designerModel{QmlDesigner::Model::create("QtQuick.Item", 1, 1)};
- QmlDesigner::ListModelEditorView view;
+ NiceMock<MockListModelEditorView> mockView;
QmlDesigner::ListModelEditorModel model;
+ ModelNode listModelNode;
+ ModelNode emptyListModelNode;
+ ModelNode element1;
+ ModelNode element2;
+ ModelNode element3;
};
+TEST_F(ListModelEditor, CreatePropertyNameSet)
+{
+ model.setListModel(listModelNode);
+
+ ASSERT_THAT(model.propertyNames(), ElementsAre("image", "name", "value", "value2"));
+}
+
+TEST_F(ListModelEditor, CreatePropertyNameSetForEmptyList)
+{
+ model.setListModel(emptyListModelNode);
+
+ ASSERT_THAT(model.propertyNames(), IsEmpty());
+}
+
+TEST_F(ListModelEditor, HorizontalLabels)
+{
+ model.setListModel(listModelNode);
+
+ ASSERT_THAT(headerLabels(model), ElementsAre("image", "name", "value", "value2"));
+}
+
+TEST_F(ListModelEditor, HorizontalLabelsForEmptyList)
+{
+ model.setListModel(emptyListModelNode);
+
+ ASSERT_THAT(headerLabels(model), IsEmpty());
+}
+
+TEST_F(ListModelEditor, DisplayValues)
+{
+ model.setListModel(listModelNode);
+
+ ASSERT_THAT(displayValues(),
+ ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42),
+ ElementsAre("pic.png", "bar", 4, IsInvalid()),
+ ElementsAre("pic.png", "poo", 111, IsInvalid())));
+}
+
+TEST_F(ListModelEditor, ChangeValueChangesDisplayValues)
+{
+ model.setListModel(listModelNode);
+
+ model.setValue(0, 1, "hello");
+
+ ASSERT_THAT(displayValues(),
+ ElementsAre(ElementsAre(IsInvalid(), "hello", 1, 42),
+ ElementsAre("pic.png", "bar", 4, IsInvalid()),
+ ElementsAre("pic.png", "poo", 111, IsInvalid())));
+}
+
+TEST_F(ListModelEditor, EditValueCallVariantPropertiesChanged)
+{
+ model.setListModel(listModelNode);
+
+ EXPECT_CALL(mockView,
+ variantPropertiesChanged(ElementsAre(IsVariantProperty(element1, "name", "hello")),
+ Eq(AbstractView::NoAdditionalChanges)));
+
+ model.setValue(0, 1, "hello");
+}
+
+TEST_F(ListModelEditor, ChangeDisplayValueCallsVariantPropertiesChanged)
+{
+ model.setListModel(listModelNode);
+
+ EXPECT_CALL(mockView,
+ variantPropertiesChanged(ElementsAre(IsVariantProperty(element1, "name", "hello")),
+ Eq(AbstractView::NoAdditionalChanges)))
+ .Times(0);
+
+ model.setValue(0, 1, "hello", Qt::DisplayRole);
+}
+
+TEST_F(ListModelEditor, AddRowAddedInvalidRow)
+{
+ model.setListModel(listModelNode);
+
+ model.addRow();
+
+ ASSERT_THAT(displayValues(),
+ ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42),
+ ElementsAre("pic.png", "bar", 4, IsInvalid()),
+ ElementsAre("pic.png", "poo", 111, IsInvalid()),
+ ElementsAre(IsInvalid(), IsInvalid(), IsInvalid(), IsInvalid())));
+}
+
+TEST_F(ListModelEditor, AddRowCreatesNewModelNodeAndReparents)
+{
+ model.setListModel(listModelNode);
+
+ EXPECT_CALL(mockView, nodeCreated(Property(&ModelNode::type, Eq("QtQml.Models.ListElement"))));
+ EXPECT_CALL(mockView,
+ nodeReparented(Property(&ModelNode::type, Eq("QtQml.Models.ListElement")),
+ Property(&AbstractProperty::parentModelNode, Eq(listModelNode)),
+ _,
+ _));
+
+ model.addRow();
+}
+
+TEST_F(ListModelEditor, ChangeAddedRowPropery)
+{
+ model.setListModel(listModelNode);
+ model.addRow();
+
+ model.setValue(3, 2, 22);
+
+ ASSERT_THAT(displayValues(),
+ ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42),
+ ElementsAre("pic.png", "bar", 4, IsInvalid()),
+ ElementsAre("pic.png", "poo", 111, IsInvalid()),
+ ElementsAre(IsInvalid(), IsInvalid(), 22, IsInvalid())));
+}
+
+TEST_F(ListModelEditor, ChangeAddedRowProperyCallsVariantPropertiesChanged)
+{
+ model.setListModel(listModelNode);
+ ModelNode element4;
+ ON_CALL(mockView, nodeReparented(_, _, _, _)).WillByDefault(SaveArg<0>(&element4));
+ model.addRow();
+
+ EXPECT_CALL(mockView,
+ variantPropertiesChanged(ElementsAre(IsVariantProperty(element4, "value", 22)),
+ Eq(AbstractView::PropertiesAdded)));
+
+ model.setValue(3, 2, 22);
+}
+
+TEST_F(ListModelEditor, AddColumnInsertsPropertyName)
+{
+ model.setListModel(listModelNode);
+
+ model.addColumn("other");
+
+ ASSERT_THAT(model.propertyNames(), ElementsAre("image", "name", "other", "value", "value2"));
+}
+
+TEST_F(ListModelEditor, AddColumnInsertsPropertyNameToEmptyModel)
+{
+ model.setListModel(emptyListModelNode);
+
+ model.addColumn("foo");
+
+ ASSERT_THAT(model.propertyNames(), ElementsAre("foo"));
+}
+
+TEST_F(ListModelEditor, AddTwiceColumnInsertsPropertyNameToEmptyModel)
+{
+ model.setListModel(emptyListModelNode);
+ model.addColumn("foo");
+
+ model.addColumn("foo2");
+
+ ASSERT_THAT(model.propertyNames(), ElementsAre("foo", "foo2"));
+}
+
+TEST_F(ListModelEditor, AddSameColumnInsertsPropertyName)
+{
+ model.setListModel(emptyListModelNode);
+ model.addColumn("foo");
+
+ model.addColumn("foo");
+
+ ASSERT_THAT(model.propertyNames(), ElementsAre("foo"));
+}
+
+TEST_F(ListModelEditor, AddColumnInsertsHeaderLabel)
+{
+ model.setListModel(listModelNode);
+
+ model.addColumn("other");
+
+ ASSERT_THAT(headerLabels(model), ElementsAre("image", "name", "other", "value", "value2"));
+}
+
+TEST_F(ListModelEditor, AddColumnInsertsHeaderLabelToEmptyModel)
+{
+ model.setListModel(emptyListModelNode);
+
+ model.addColumn("foo");
+
+ ASSERT_THAT(headerLabels(model), ElementsAre("foo"));
+}
+
+TEST_F(ListModelEditor, AddTwiceColumnInsertsHeaderLabelToEmptyModel)
+{
+ model.setListModel(emptyListModelNode);
+ model.addColumn("foo");
+
+ model.addColumn("foo2");
+
+ ASSERT_THAT(headerLabels(model), ElementsAre("foo", "foo2"));
+}
+
+TEST_F(ListModelEditor, AddSameColumnInsertsHeaderLabel)
+{
+ model.setListModel(emptyListModelNode);
+ model.addColumn("foo");
+
+ model.addColumn("foo");
+
+ ASSERT_THAT(headerLabels(model), ElementsAre("foo"));
+}
+
+TEST_F(ListModelEditor, AddColumnInsertsDisplayValues)
+{
+ model.setListModel(listModelNode);
+
+ model.addColumn("other");
+
+ ASSERT_THAT(displayValues(),
+ ElementsAre(ElementsAre(IsInvalid(), "foo", IsInvalid(), 1, 42),
+ ElementsAre("pic.png", "bar", IsInvalid(), 4, IsInvalid()),
+ ElementsAre("pic.png", "poo", IsInvalid(), 111, IsInvalid())));
+}
+
+TEST_F(ListModelEditor, ChangeAddColumnPropertyDisplayValue)
+{
+ model.setListModel(listModelNode);
+ model.addColumn("other");
+
+ model.setValue(1, 2, 22);
+
+ ASSERT_THAT(displayValues(),
+ ElementsAre(ElementsAre(IsInvalid(), "foo", IsInvalid(), 1, 42),
+ ElementsAre("pic.png", "bar", 22, 4, IsInvalid()),
+ ElementsAre("pic.png", "poo", IsInvalid(), 111, IsInvalid())));
+}
+
+TEST_F(ListModelEditor, ChangeAddColumnPropertyCallsVariantPropertiesChanged)
+{
+ model.setListModel(listModelNode);
+ model.addColumn("other");
+
+ EXPECT_CALL(mockView,
+ variantPropertiesChanged(ElementsAre(IsVariantProperty(element2, "other", 434)), _));
+
+ model.setValue(1, 2, 434);
+}
+
+TEST_F(ListModelEditor, RemoveColumnRemovesDisplayValues)
+{
+ model.setListModel(listModelNode);
+
+ model.removeColumn(2);
+
+ ASSERT_THAT(displayValues(),
+ ElementsAre(ElementsAre(IsInvalid(), "foo", 42),
+ ElementsAre("pic.png", "bar", IsInvalid()),
+ ElementsAre("pic.png", "poo", IsInvalid())));
+}
+
+TEST_F(ListModelEditor, RemoveColumnRemovesProperties)
+{
+ model.setListModel(listModelNode);
+
+ EXPECT_CALL(mockView, propertiesRemoved(ElementsAre(IsAbstractProperty(element2, "image"))));
+ EXPECT_CALL(mockView, propertiesRemoved(ElementsAre(IsAbstractProperty(element3, "image"))));
+
+ model.removeColumn(0);
+}
+
+TEST_F(ListModelEditor, RemoveColumnRemovesPropertyName)
+{
+ model.setListModel(listModelNode);
+
+ model.removeColumn(1);
+
+ ASSERT_THAT(model.propertyNames(), ElementsAre("image", "value", "value2"));
+}
+
+TEST_F(ListModelEditor, RemoveRowRemovesDisplayValues)
+{
+ model.setListModel(listModelNode);
+
+ model.removeRow(1);
+
+ ASSERT_THAT(displayValues(),
+ ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42),
+ ElementsAre("pic.png", "poo", 111, IsInvalid())));
+}
+
+TEST_F(ListModelEditor, RemoveRowRemovesElementInListModel)
+{
+ model.setListModel(listModelNode);
+
+ EXPECT_CALL(mockView, nodeRemoved(Eq(element2), _, _));
+
+ model.removeRow(1);
+}
+
+TEST_F(ListModelEditor, ConvertStringFloatToFloat)
+{
+ model.setListModel(listModelNode);
+
+ model.setValue(1, 1, "25.5");
+
+ ASSERT_THAT(element2.variantProperty("name").value().value<double>(), 25.5);
+ ASSERT_THAT(element2.variantProperty("name").value().type(), QVariant::Double);
+}
+
+TEST_F(ListModelEditor, ConvertStringIntegerToDouble)
+{
+ model.setListModel(listModelNode);
+
+ model.setValue(1, 1, "25");
+
+ ASSERT_THAT(element2.variantProperty("name").value().value<double>(), 25);
+ ASSERT_THAT(element2.variantProperty("name").value().type(), QVariant::Double);
+}
+
+TEST_F(ListModelEditor, DontConvertStringToNumber)
+{
+ model.setListModel(listModelNode);
+
+ model.setValue(1, 1, "hello");
+
+ ASSERT_THAT(element2.variantProperty("name").value().value<QString>(), "hello");
+ ASSERT_THAT(element2.variantProperty("name").value().type(), QVariant::String);
+}
+
+TEST_F(ListModelEditor, EmptyStringsRemovesProperty)
+{
+ model.setListModel(listModelNode);
+
+ model.setValue(1, 1, "");
+
+ ASSERT_THAT(element2.variantProperty("name").value().value<QString>(), Eq(""));
+}
+
+TEST_F(ListModelEditor, InvalidVariantRemovesProperty)
+{
+ model.setListModel(listModelNode);
+
+ model.setValue(1, 1, QVariant());
+
+ ASSERT_FALSE(element2.hasProperty("name"));
+}
+
+TEST_F(ListModelEditor, DispayValueIsChangedToDouble)
+{
+ model.setListModel(listModelNode);
+
+ model.setValue(1, 1, "25.5");
+
+ ASSERT_THAT(displayValues()[1][1].type(), QVariant::Double);
+}
+
+TEST_F(ListModelEditor, StringDispayValueIsNotChanged)
+{
+ model.setListModel(listModelNode);
+
+ model.setValue(1, 1, "25.5a");
+
+ ASSERT_THAT(displayValues()[1][1].type(), QVariant::String);
+}
+
+TEST_F(ListModelEditor, SetInvalidToDarkYellowBackgroundColor)
+{
+ model.setListModel(listModelNode);
+
+ ASSERT_THAT(
+ backgroundColors(),
+ ElementsAre(
+ ElementsAre(Qt::darkYellow, Not(Qt::darkYellow), Not(Qt::darkYellow), Not(Qt::darkYellow)),
+ ElementsAre(Not(Qt::darkYellow), Not(Qt::darkYellow), Not(Qt::darkYellow), Qt::darkYellow),
+ ElementsAre(Not(Qt::darkYellow), Not(Qt::darkYellow), Not(Qt::darkYellow), Qt::darkYellow)));
+}
+
+TEST_F(ListModelEditor, SettingValueChangesBackgroundColor)
+{
+ model.setListModel(listModelNode);
+
+ model.setValue(0, 0, "foo");
+
+ ASSERT_THAT(
+ backgroundColors(),
+ ElementsAre(
+ ElementsAre(Not(Qt::darkYellow), Not(Qt::darkYellow), Not(Qt::darkYellow), Not(Qt::darkYellow)),
+ ElementsAre(Not(Qt::darkYellow), Not(Qt::darkYellow), Not(Qt::darkYellow), Qt::darkYellow),
+ ElementsAre(Not(Qt::darkYellow), Not(Qt::darkYellow), Not(Qt::darkYellow), Qt::darkYellow)));
+}
+
+TEST_F(ListModelEditor, SettingValueChangesByDisplayRoleBackgroundColor)
+{
+ model.setListModel(listModelNode);
+
+ model.setValue(0, 0, "foo", Qt::DisplayRole);
+
+ ASSERT_THAT(
+ backgroundColors(),
+ ElementsAre(
+ ElementsAre(Not(Qt::darkYellow), Not(Qt::darkYellow), Not(Qt::darkYellow), Not(Qt::darkYellow)),
+ ElementsAre(Not(Qt::darkYellow), Not(Qt::darkYellow), Not(Qt::darkYellow), Qt::darkYellow),
+ ElementsAre(Not(Qt::darkYellow), Not(Qt::darkYellow), Not(Qt::darkYellow), Qt::darkYellow)));
+}
+
+TEST_F(ListModelEditor, ResettingValueChangesBackgroundColor)
+{
+ model.setListModel(listModelNode);
+
+ model.setValue(1, 1, QVariant{});
+
+ ASSERT_THAT(
+ backgroundColors(),
+ ElementsAre(
+ ElementsAre(Qt::darkYellow, Not(Qt::darkYellow), Not(Qt::darkYellow), Not(Qt::darkYellow)),
+ ElementsAre(Not(Qt::darkYellow), Qt::darkYellow, Not(Qt::darkYellow), Qt::darkYellow),
+ ElementsAre(Not(Qt::darkYellow), Not(Qt::darkYellow), Not(Qt::darkYellow), Qt::darkYellow)));
+}
+
+TEST_F(ListModelEditor, ResettingValueChangesByDisplayRoleBackgroundColor)
+{
+ model.setListModel(listModelNode);
+
+ model.setValue(1, 1, QVariant{}, Qt::DisplayRole);
+
+ ASSERT_THAT(
+ backgroundColors(),
+ ElementsAre(
+ ElementsAre(Qt::darkYellow, Not(Qt::darkYellow), Not(Qt::darkYellow), Not(Qt::darkYellow)),
+ ElementsAre(Not(Qt::darkYellow), Qt::darkYellow, Not(Qt::darkYellow), Qt::darkYellow),
+ ElementsAre(Not(Qt::darkYellow), Not(Qt::darkYellow), Not(Qt::darkYellow), Qt::darkYellow)));
+}
+
+TEST_F(ListModelEditor, SettingNullValueChangesBackgroundColor)
+{
+ model.setListModel(listModelNode);
+
+ model.setValue(0, 0, 0);
+
+ ASSERT_THAT(backgroundColors(),
+ ElementsAre(ElementsAre(_, _, _, _),
+ ElementsAre(_, _, _, Qt::darkYellow),
+ ElementsAre(_, _, _, Qt::darkYellow)));
+}
+
+TEST_F(ListModelEditor, DontRenamePropertyIfColumnNameExists)
+{
+ model.setListModel(listModelNode);
+
+ model.renameColumn(1, "value2");
+
+ ASSERT_THAT(model.propertyNames(), ElementsAre("image", "name", "value", "value2"));
+}
+
+TEST_F(ListModelEditor, DontRenameColumnIfColumnNameExists)
+{
+ model.setListModel(listModelNode);
+
+ model.renameColumn(1, "value2");
+
+ ASSERT_THAT(headerLabels(model), ElementsAre("image", "name", "value", "value2"));
+}
+
+TEST_F(ListModelEditor, DontRenameColumnIfColumnNameExistsDoesNotChangeDisplayValues)
+{
+ model.setListModel(listModelNode);
+
+ model.renameColumn(1, "value2");
+
+ ASSERT_THAT(displayValues(),
+ ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42),
+ ElementsAre("pic.png", "bar", 4, IsInvalid()),
+ ElementsAre("pic.png", "poo", 111, IsInvalid())));
+}
+
+TEST_F(ListModelEditor, DontRenameColumnIfColumnNameExistsDoesNotChangeProperties)
+{
+ model.setListModel(listModelNode);
+
+ model.renameColumn(1, "value2");
+
+ ASSERT_THAT(properties(),
+ ElementsAre(UnorderedElementsAre(IsVariantProperty("name", "foo"),
+ IsVariantProperty("value", 1),
+ IsVariantProperty("value2", 42)),
+ UnorderedElementsAre(IsVariantProperty("image", "pic.png"),
+ IsVariantProperty("name", "bar"),
+ IsVariantProperty("value", 4)),
+ UnorderedElementsAre(IsVariantProperty("image", "pic.png"),
+ IsVariantProperty("name", "poo"),
+ IsVariantProperty("value", 111))));
+}
+
+TEST_F(ListModelEditor, RenamePropertyButDontChangeOrder)
+{
+ model.setListModel(listModelNode);
+
+ model.renameColumn(1, "mood");
+
+ ASSERT_THAT(model.propertyNames(), ElementsAre("image", "mood", "value", "value2"));
+}
+
+TEST_F(ListModelEditor, RenameColumnButDontChangeOrder)
+{
+ model.setListModel(listModelNode);
+
+ model.renameColumn(1, "mood");
+
+ ASSERT_THAT(headerLabels(model), ElementsAre("image", "mood", "value", "value2"));
+}
+
+TEST_F(ListModelEditor, RenameColumnButDontChangeOrderDisplayValues)
+{
+ model.setListModel(listModelNode);
+
+ model.renameColumn(1, "mood");
+
+ ASSERT_THAT(displayValues(),
+ ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42),
+ ElementsAre("pic.png", "bar", 4, IsInvalid()),
+ ElementsAre("pic.png", "poo", 111, IsInvalid())));
+}
+
+TEST_F(ListModelEditor, RenameColumnButDontChangeOrderProperies)
+{
+ model.setListModel(listModelNode);
+
+ model.renameColumn(1, "mood");
+
+ ASSERT_THAT(properties(),
+ ElementsAre(UnorderedElementsAre(IsVariantProperty("mood", "foo"),
+ IsVariantProperty("value", 1),
+ IsVariantProperty("value2", 42)),
+ UnorderedElementsAre(IsVariantProperty("image", "pic.png"),
+ IsVariantProperty("mood", "bar"),
+ IsVariantProperty("value", 4)),
+ UnorderedElementsAre(IsVariantProperty("image", "pic.png"),
+ IsVariantProperty("mood", "poo"),
+ IsVariantProperty("value", 111))));
+}
+
+TEST_F(ListModelEditor, RemoveColumnAfterRenameColumn)
+{
+ model.setListModel(listModelNode);
+ model.renameColumn(1, "mood");
+
+ model.removeColumn(1);
+
+ ASSERT_THAT(properties(),
+ ElementsAre(UnorderedElementsAre(IsVariantProperty("value", 1),
+ IsVariantProperty("value2", 42)),
+ UnorderedElementsAre(IsVariantProperty("image", "pic.png"),
+ IsVariantProperty("value", 4)),
+ UnorderedElementsAre(IsVariantProperty("image", "pic.png"),
+ IsVariantProperty("value", 111))));
+}
+
+TEST_F(ListModelEditor, ChangeValueAfterRenameColumn)
+{
+ model.setListModel(listModelNode);
+ model.renameColumn(1, "mood");
+
+ model.setValue(1, 1, "taaa");
+
+ ASSERT_THAT(properties(),
+ ElementsAre(UnorderedElementsAre(IsVariantProperty("mood", "foo"),
+ IsVariantProperty("value", 1),
+ IsVariantProperty("value2", 42)),
+ UnorderedElementsAre(IsVariantProperty("image", "pic.png"),
+ IsVariantProperty("mood", "taaa"),
+ IsVariantProperty("value", 4)),
+ UnorderedElementsAre(IsVariantProperty("image", "pic.png"),
+ IsVariantProperty("mood", "poo"),
+ IsVariantProperty("value", 111))));
+}
+
+TEST_F(ListModelEditor, RemovePropertyAfterRenameColumn)
+{
+ model.setListModel(listModelNode);
+ model.renameColumn(1, "mood");
+
+ model.setValue(1, 1, {});
+
+ ASSERT_THAT(properties(),
+ ElementsAre(UnorderedElementsAre(IsVariantProperty("mood", "foo"),
+ IsVariantProperty("value", 1),
+ IsVariantProperty("value2", 42)),
+ UnorderedElementsAre(IsVariantProperty("image", "pic.png"),
+ IsVariantProperty("value", 4)),
+ UnorderedElementsAre(IsVariantProperty("image", "pic.png"),
+ IsVariantProperty("mood", "poo"),
+ IsVariantProperty("value", 111))));
+}
+
+TEST_F(ListModelEditor, RenameToPrecedingProperty)
+{
+ model.setListModel(listModelNode);
+
+ model.renameColumn(1, "alpha");
+
+ ASSERT_THAT(model.propertyNames(), ElementsAre("alpha", "image", "value", "value2"));
+}
+
+TEST_F(ListModelEditor, RenameToPrecedingColumn)
+{
+ model.setListModel(listModelNode);
+
+ model.renameColumn(1, "alpha");
+
+ ASSERT_THAT(headerLabels(model), ElementsAre("alpha", "image", "value", "value2"));
+}
+
+TEST_F(ListModelEditor, RenameToPrecedingColumnDisplayValues)
+{
+ model.setListModel(listModelNode);
+
+ model.renameColumn(1, "alpha");
+
+ ASSERT_THAT(displayValues(),
+ ElementsAre(ElementsAre("foo", IsInvalid(), 1, 42),
+ ElementsAre("bar", "pic.png", 4, IsInvalid()),
+ ElementsAre("poo", "pic.png", 111, IsInvalid())));
+}
+
+TEST_F(ListModelEditor, RenameToPrecedingColumnProperties)
+{
+ model.setListModel(listModelNode);
+
+ model.renameColumn(1, "alpha");
+
+ ASSERT_THAT(properties(),
+ ElementsAre(UnorderedElementsAre(IsVariantProperty("alpha", "foo"),
+ IsVariantProperty("value", 1),
+ IsVariantProperty("value2", 42)),
+ UnorderedElementsAre(IsVariantProperty("image", "pic.png"),
+ IsVariantProperty("alpha", "bar"),
+ IsVariantProperty("value", 4)),
+ UnorderedElementsAre(IsVariantProperty("image", "pic.png"),
+ IsVariantProperty("alpha", "poo"),
+ IsVariantProperty("value", 111))));
+}
+
+TEST_F(ListModelEditor, RenameToFollowingProperty)
+{
+ model.setListModel(listModelNode);
+
+ model.renameColumn(2, "zoo");
+
+ ASSERT_THAT(model.propertyNames(), ElementsAre("image", "name", "value2", "zoo"));
+}
+
+TEST_F(ListModelEditor, RenameToFollowingColumn)
+{
+ model.setListModel(listModelNode);
+
+ model.renameColumn(2, "zoo");
+
+ ASSERT_THAT(headerLabels(model), ElementsAre("image", "name", "value2", "zoo"));
+}
+
+TEST_F(ListModelEditor, RenameToFollowingColumnDisplayValues)
+{
+ model.setListModel(listModelNode);
+
+ model.renameColumn(2, "zoo");
+
+ ASSERT_THAT(displayValues(),
+ ElementsAre(ElementsAre(IsInvalid(), "foo", 42, 1),
+ ElementsAre("pic.png", "bar", IsInvalid(), 4),
+ ElementsAre("pic.png", "poo", IsInvalid(), 111)));
+}
+
+TEST_F(ListModelEditor, RenameToFollowingColumnProperties)
+{
+ model.setListModel(listModelNode);
+
+ model.renameColumn(2, "zoo");
+
+ ASSERT_THAT(properties(),
+ ElementsAre(UnorderedElementsAre(IsVariantProperty("name", "foo"),
+ IsVariantProperty("zoo", 1),
+ IsVariantProperty("value2", 42)),
+ UnorderedElementsAre(IsVariantProperty("image", "pic.png"),
+ IsVariantProperty("name", "bar"),
+ IsVariantProperty("zoo", 4)),
+ UnorderedElementsAre(IsVariantProperty("image", "pic.png"),
+ IsVariantProperty("name", "poo"),
+ IsVariantProperty("zoo", 111))));
+}
+
+TEST_F(ListModelEditor, RenamePropertiesWithInvalidValue)
+{
+ model.setListModel(listModelNode);
+
+ model.renameColumn(0, "mood");
+
+ ASSERT_THAT(properties(),
+ ElementsAre(UnorderedElementsAre(IsVariantProperty("name", "foo"),
+ IsVariantProperty("value", 1),
+ IsVariantProperty("value2", 42)),
+ UnorderedElementsAre(IsVariantProperty("mood", "pic.png"),
+ IsVariantProperty("name", "bar"),
+ IsVariantProperty("value", 4)),
+ UnorderedElementsAre(IsVariantProperty("mood", "pic.png"),
+ IsVariantProperty("name", "poo"),
+ IsVariantProperty("value", 111))));
+}
+
+TEST_F(ListModelEditor, ChangeValueAfterRenamePropertiesWithInvalidValue)
+{
+ model.setListModel(listModelNode);
+ model.renameColumn(0, "mood");
+
+ model.setValue(0, 0, "haaa");
+
+ ASSERT_THAT(properties(),
+ ElementsAre(UnorderedElementsAre(IsVariantProperty("mood", "haaa"),
+ IsVariantProperty("name", "foo"),
+ IsVariantProperty("value", 1),
+ IsVariantProperty("value2", 42)),
+ UnorderedElementsAre(IsVariantProperty("mood", "pic.png"),
+ IsVariantProperty("name", "bar"),
+ IsVariantProperty("value", 4)),
+ UnorderedElementsAre(IsVariantProperty("mood", "pic.png"),
+ IsVariantProperty("name", "poo"),
+ IsVariantProperty("value", 111))));
+}
+
+TEST_F(ListModelEditor, RemoveLastRow)
+{
+ model.setListModel(emptyListModelNode);
+ model.addColumn("mood");
+ model.addRow();
+
+ model.removeRow(0);
+
+ ASSERT_THAT(displayValues(), IsEmpty());
+}
+
+TEST_F(ListModelEditor, RemoveLastColumn)
+{
+ model.setListModel(emptyListModelNode);
+ model.addColumn("mood");
+ model.addRow();
+
+ model.removeColumn(0);
+
+ ASSERT_THAT(displayValues(), ElementsAre(IsEmpty()));
+}
+
+TEST_F(ListModelEditor, RemoveLastEmptyColumn)
+{
+ model.setListModel(emptyListModelNode);
+ model.addColumn("mood");
+ model.addRow();
+ model.removeRow(0);
+
+ model.removeColumn(0);
+
+ ASSERT_THAT(displayValues(), IsEmpty());
+}
+
+TEST_F(ListModelEditor, RemoveLastEmptyRow)
+{
+ model.setListModel(emptyListModelNode);
+ model.addColumn("mood");
+ model.addRow();
+ model.removeColumn(0);
+
+ model.removeRow(0);
+
+ ASSERT_THAT(displayValues(), IsEmpty());
+}
+
} // namespace
diff --git a/tests/unit/unittest/mocklistmodeleditorview.h b/tests/unit/unittest/mocklistmodeleditorview.h
new file mode 100644
index 0000000000..6bec164f33
--- /dev/null
+++ b/tests/unit/unittest/mocklistmodeleditorview.h
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 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 <googletest.h>
+
+#include <qmldesigner/designercore/include/abstractview.h>
+
+class MockListModelEditorView : public QmlDesigner::AbstractView
+{
+public:
+ MOCK_METHOD(void,
+ variantPropertiesChanged,
+ (const QList<QmlDesigner::VariantProperty> &propertyList,
+ PropertyChangeFlags propertyChange),
+ (override));
+ MOCK_METHOD(void, nodeCreated, (const QmlDesigner::ModelNode &createdNode), (override));
+ MOCK_METHOD(void,
+ nodeReparented,
+ (const QmlDesigner::ModelNode &node,
+ const QmlDesigner::NodeAbstractProperty &newPropertyParent,
+ const QmlDesigner::NodeAbstractProperty &oldPropertyParent,
+ AbstractView::PropertyChangeFlags propertyChange),
+ (override));
+ MOCK_METHOD(void,
+ propertiesRemoved,
+ (const QList<QmlDesigner::AbstractProperty> &propertyList),
+ (override));
+
+ MOCK_METHOD(void,
+ nodeRemoved,
+ (const QmlDesigner::ModelNode &removedNode,
+ const QmlDesigner::NodeAbstractProperty &parentProperty,
+ AbstractView::PropertyChangeFlags propertyChange),
+ (override));
+};
diff --git a/tests/unit/unittest/unittest.pro b/tests/unit/unittest/unittest.pro
index 2ccc0bdb54..3917237d0b 100644
--- a/tests/unit/unittest/unittest.pro
+++ b/tests/unit/unittest/unittest.pro
@@ -39,8 +39,11 @@ CONFIG(release, debug|release):QMAKE_LFLAGS += -Wl,--strip-debug
}
gcc:!clang: QMAKE_CXXFLAGS += -Wno-noexcept-type
-msvc: QMAKE_CXXFLAGS += /bigobj /wd4267 /wd4141 /wd4146 /wd4624
+msvc{
+QMAKE_CXXFLAGS += /bigobj /wd4267 /wd4141 /wd4146 /wd4624
+QMAKE_LFLAGS += /INCREMENTAL
+}
# create fake CppTools.json for the mime type definitions
dependencyList = "\"Dependencies\" : []"
cpptoolsjson.input = $$PWD/../../../src/plugins/cpptools/CppTools.json.in
@@ -244,6 +247,7 @@ HEADERS += \
mockclangpathwatcher.h \
mockclangpathwatchernotifier.h \
mockfilesystem.h \
+ mocklistmodeleditorview.h \
mockpchcreator.h \
mockpchmanagerclient.h \
mockpchmanagernotifier.h \