From 7e902d3c77fab90a44ff5585537a563a68bc4803 Mon Sep 17 00:00:00 2001 From: Ali Kianian Date: Wed, 26 Mar 2025 13:46:47 +0200 Subject: [PATCH] ListModelEditor: Fix table action issues * Disable delete actions when no row/column is selected * Disable move row actions when no row is selected * Provide an action for adding row below/above * Update the current item selecion after moving rows * Fix shortcut action for moving up * Select rows and columns after inserting Fixes: QDS-15011 Change-Id: If03e083ffee3446341a37b8fd8fd8978bfc20745 Reviewed-by: Marco Bubke --- .../listmodeleditor/listmodeleditordialog.cpp | 93 ++++++- .../listmodeleditor/listmodeleditordialog.h | 11 +- .../listmodeleditor/listmodeleditormodel.cpp | 59 +++- .../listmodeleditor/listmodeleditormodel.h | 11 +- .../designercore/model/nodelistproperty.cpp | 3 +- .../listmodeleditor/listmodeleditor-test.cpp | 262 +++++++++++++++++- 6 files changed, 407 insertions(+), 32 deletions(-) diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp index 11440ea11da..bbbda2e095f 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp @@ -20,8 +20,6 @@ #include #include -#include - namespace QmlDesigner { namespace { @@ -46,7 +44,8 @@ ListModelEditorDialog::ListModelEditorDialog(QWidget *parent) m_tableView = new QTableView; mainLayout->addWidget(m_tableView); - m_addRowAction = toolBar->addAction(getIcon(Theme::Icon::addRowAfter), tr("Add Row")); + m_addRowAboveAction = toolBar->addAction(getIcon(Theme::Icon::addRowBefore), tr("Add Row Above")); + m_addRowBelowAction = toolBar->addAction(getIcon(Theme::Icon::addRowAfter), tr("Add Row Below")); m_removeRowsAction = toolBar->addAction(getIcon(Theme::Icon::deleteRow), tr("Remove Rows")); m_addColumnAction = toolBar->addAction(getIcon(Theme::Icon::addColumnAfter), tr("Add Column")); m_removeColumnsAction = toolBar->addAction(getIcon(Theme::Icon::deleteColumn), @@ -54,7 +53,7 @@ ListModelEditorDialog::ListModelEditorDialog(QWidget *parent) m_moveDownAction = toolBar->addAction(Icons::ARROW_DOWN.icon(), tr("Move Down (Ctrl + Down)")); m_moveDownAction->setShortcut(QKeySequence(Qt::Key_Down | Qt::CTRL)); m_moveUpAction = toolBar->addAction(Icons::ARROW_UP.icon(), tr("Move Up (Ctrl + Up)")); - m_moveDownAction->setShortcut(QKeySequence(Qt::Key_Up | Qt::CTRL)); + m_moveUpAction->setShortcut(QKeySequence(Qt::Key_Up | Qt::CTRL)); } ListModelEditorDialog::~ListModelEditorDialog() = default; @@ -63,7 +62,8 @@ void ListModelEditorDialog::setModel(ListModelEditorModel *model) { m_model = model; - connect(m_addRowAction, &QAction::triggered, m_model, &ListModelEditorModel::addRow); + connect(m_addRowAboveAction, &QAction::triggered, this, &ListModelEditorDialog::addRowAbove); + connect(m_addRowBelowAction, &QAction::triggered, this, &ListModelEditorDialog::addRowBelow); connect(m_addColumnAction, &QAction::triggered, this, &ListModelEditorDialog::openColumnDialog); connect(m_removeRowsAction, &QAction::triggered, this, &ListModelEditorDialog::removeRows); connect(m_removeColumnsAction, &QAction::triggered, this, &ListModelEditorDialog::removeColumns); @@ -79,16 +79,48 @@ void ListModelEditorDialog::setModel(ListModelEditorModel *model) m_tableView->verticalHeader()->setMinimumSectionSize(25); m_tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); m_tableView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + + connect(model, + &ListModelEditorModel::rowsInserted, + this, + &ListModelEditorDialog::onRowsInserted, + Qt::ConnectionType::UniqueConnection); + + connect(model, + &ListModelEditorModel::columnsInserted, + this, + &ListModelEditorDialog::onColumnsInserted, + Qt::ConnectionType::UniqueConnection); + + connect(m_tableView->selectionModel(), + &QItemSelectionModel::selectionChanged, + this, + &ListModelEditorDialog::updateSelection, + Qt::ConnectionType::UniqueConnection); + updateSelection(); } void ListModelEditorDialog::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete) { - for (const QModelIndex index : m_tableView->selectionModel()->selectedIndexes()) + const QModelIndexList selectedIndexes = m_tableView->selectionModel()->selectedIndexes(); + for (const QModelIndex index : selectedIndexes) m_model->setData(index, QVariant(), Qt::EditRole); } } +void ListModelEditorDialog::addRowAbove() +{ + // If there's no items in the model, or nothing is selected, The item should be prepended. + int curInteractionRow = ListModelEditorModel::currentInteractionRow(*m_tableView->selectionModel()); + m_model->addRow(std::max(0, curInteractionRow)); +} + +void ListModelEditorDialog::addRowBelow() +{ + m_model->addRow(ListModelEditorModel::nextInteractionRow(*m_tableView->selectionModel())); +} + void ListModelEditorDialog::openColumnDialog() { bool ok; @@ -126,13 +158,58 @@ void ListModelEditorDialog::changeHeader(int column) void ListModelEditorDialog::moveRowsDown() { QItemSelection selection = m_model->moveRowsDown(m_tableView->selectionModel()->selectedRows()); - m_tableView->selectionModel()->select(selection, QItemSelectionModel::Select); + m_tableView->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); + if (!selection.isEmpty()) { + m_tableView->selectionModel()->setCurrentIndex(selection.first().topLeft(), + QItemSelectionModel::Current); + } } void ListModelEditorDialog::moveRowsUp() { QItemSelection selection = m_model->moveRowsUp(m_tableView->selectionModel()->selectedRows()); - m_tableView->selectionModel()->select(selection, QItemSelectionModel::Select); + m_tableView->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); + if (!selection.isEmpty()) { + m_tableView->selectionModel()->setCurrentIndex(selection.first().topLeft(), + QItemSelectionModel::Current); + } +} + +void ListModelEditorDialog::updateSelection() +{ + QItemSelectionModel *selection = m_tableView->selectionModel(); + bool hasRowSelection = !selection->selectedRows().isEmpty(); + bool hasColumnSelection = !selection->selectedColumns().isEmpty(); + const int rows = m_tableView->model()->rowCount(); + + m_moveUpAction->setEnabled(hasRowSelection && !selection->isRowSelected(0)); + m_moveDownAction->setEnabled(hasRowSelection && !selection->isRowSelected(rows - 1)); + m_removeRowsAction->setEnabled(hasRowSelection); + m_removeColumnsAction->setEnabled(hasColumnSelection); +} + +void ListModelEditorDialog::onRowsInserted(const QModelIndex &parent, int first, int last) +{ + QItemSelectionModel *selection = m_tableView->selectionModel(); + auto model = selection->model(); + const int cols = model->columnCount(parent); + auto topLeft = model->index(first, 0, parent); + auto bottomRight = model->index(last, cols - 1, parent); + QItemSelection rowsSelection{topLeft, bottomRight}; + + selection->select(rowsSelection, QItemSelectionModel::ClearAndSelect); +} + +void ListModelEditorDialog::onColumnsInserted(const QModelIndex &parent, int first, int last) +{ + QItemSelectionModel *selection = m_tableView->selectionModel(); + auto model = selection->model(); + const int rows = model->rowCount(parent); + auto topLeft = model->index(0, first, parent); + auto bottomRight = model->index(rows - 1, last, parent); + QItemSelection columnSelection{topLeft, bottomRight}; + + selection->select(columnSelection, QItemSelectionModel::ClearAndSelect); } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.h b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.h index 8afafad79d7..d881c45b871 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.h +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.h @@ -8,6 +8,7 @@ QT_BEGIN_NAMESPACE class QAbstractItemModel; class QTableView; +class QModelIndex; QT_END_NAMESPACE namespace Ui { @@ -32,17 +33,23 @@ protected: void keyPressEvent(QKeyEvent *) override; private: - void addRow(); + void addRowAbove(); + void addRowBelow(); void openColumnDialog(); void removeRows(); void removeColumns(); void changeHeader(int column); void moveRowsDown(); void moveRowsUp(); + void updateSelection(); + + void onRowsInserted(const QModelIndex &parent, int first, int last); + void onColumnsInserted(const QModelIndex &parent, int first, int last); private: ListModelEditorModel *m_model{}; - QAction *m_addRowAction{}; + QAction *m_addRowBelowAction{}; + QAction *m_addRowAboveAction{}; QAction *m_removeRowsAction{}; QAction *m_addColumnAction{}; QAction *m_removeColumnsAction{}; diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp index 0eb654bf0d0..02c1de2e0ad 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -213,14 +214,23 @@ void ListModelEditorModel::createItems(const QList &listElementNodes) appendItems(listElementNode); } -void ListModelEditorModel::appendItems(const ModelNode &listElementNode) +static QList createRow(const auto &propertyNames, const auto &listElementNode) { QList row; - row.reserve(m_propertyNames.size()); - for (const PropertyName &propertyName : propertyNames()) + row.reserve(propertyNames.size()); + for (const PropertyName &propertyName : propertyNames) row.push_back(createItem(listElementNode, propertyName).release()); + return row; +} - appendRow(row); +void ListModelEditorModel::insertItems(const ModelNode &listElementNode, int index) +{ + insertRow(index, createRow(m_propertyNames, listElementNode)); +} + +void ListModelEditorModel::appendItems(const ModelNode &listElementNode) +{ + appendRow(createRow(m_propertyNames, listElementNode)); } void ListModelEditorModel::setListModel(ModelNode node) @@ -234,12 +244,18 @@ void ListModelEditorModel::setListView(ModelNode listView) setListModel(listModelNode(listView, m_createModelCallback, m_goIntoComponentCallback)); } -void ListModelEditorModel::addRow() +void ListModelEditorModel::addRow(int rowIndex) { - auto newElement = m_createElementCallback(); - m_listModelNode.defaultNodeListProperty().reparentHere(newElement); + if (rowIndex < 0 || rowIndex > rowCount()) + return; - appendItems(newElement); + NodeListProperty defaultNodeListProperty = m_listModelNode.defaultNodeListProperty(); + defaultNodeListProperty.view()->executeInTransaction(__FUNCTION__, [&] { + auto newElement = m_createElementCallback(); + defaultNodeListProperty.reparentHere(newElement); + defaultNodeListProperty.slide(defaultNodeListProperty.count() - 1, rowIndex); + insertItems(newElement, rowIndex); + }); } void ListModelEditorModel::addColumn(const QString &columnName) @@ -271,7 +287,7 @@ bool ListModelEditorModel::setValue(int row, int column, QVariant value, Qt::Ite void ListModelEditorModel::removeColumn(int column) { - QList columnItems = QStandardItemModel::takeColumn(column); + QList columnItems = Super::takeColumn(column); m_propertyNames.removeAt(column); for (QStandardItem *columnItem : columnItems) { @@ -302,7 +318,7 @@ void ListModelEditorModel::removeRows(const QList &indices) void ListModelEditorModel::removeRow(int row) { - QList rowItems = QStandardItemModel::takeRow(row); + QList rowItems = Super::takeRow(row); if (rowItems.size()) static_cast(rowItems.front())->node.destroy(); @@ -409,4 +425,27 @@ std::vector ListModelEditorModel::filterRows(const QList &indi return rows; } +static int interactionRow(const QItemSelectionModel &selectionModel, + QModelIndex &(QModelIndexList::*defaultRowSelector)()) +{ + QModelIndexList selectedRows = selectionModel.selectedRows(); + auto defaultRow = std::mem_fn(defaultRowSelector); + int index = !selectedRows.isEmpty() ? defaultRow(selectedRows).row() : -1; + if (index < 0 && selectionModel.hasSelection()) + index = defaultRow(selectionModel.selectedIndexes()).row(); + if (index < 0 && selectionModel.currentIndex().isValid()) + index = selectionModel.currentIndex().row(); + return index; +} + +int ListModelEditorModel::currentInteractionRow(const QItemSelectionModel &selectionModel) +{ + return interactionRow(selectionModel, &QModelIndexList::first); +} + +int ListModelEditorModel::nextInteractionRow(const QItemSelectionModel &selectionModel) +{ + return interactionRow(selectionModel, &QModelIndexList::last) + 1; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h index 049c7e888d2..017d8acb334 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h @@ -12,8 +12,9 @@ namespace QmlDesigner { class ListModelEditorModel : public QStandardItemModel { - using QStandardItemModel::removeColumns; - using QStandardItemModel::removeRows; + using Super = QStandardItemModel; + using Super::removeColumns; + using Super::removeRows; public: ListModelEditorModel(std::function createModelCallback, @@ -28,7 +29,7 @@ public: void setListView(ModelNode listView); - void addRow(); + void addRow(int rowIndex); void addColumn(const QString &columnName); const QList &propertyNames() const { return m_propertyNames; } @@ -44,11 +45,15 @@ public: static std::vector filterColumns(const QList &indices); static std::vector filterRows(const QList &indices); + static int currentInteractionRow(const QItemSelectionModel &selectionModel); + static int nextInteractionRow(const QItemSelectionModel &selectionModel); + private: void removeRow(int row); void removeColumn(int column); void populateModel(); void createItems(const QList &listElementNodes); + void insertItems(const ModelNode &listElementNode, int index); void appendItems(const ModelNode &listElementNode); private: diff --git a/src/plugins/qmldesigner/libs/designercore/model/nodelistproperty.cpp b/src/plugins/qmldesigner/libs/designercore/model/nodelistproperty.cpp index c77771a52da..2428c6542ca 100644 --- a/src/plugins/qmldesigner/libs/designercore/model/nodelistproperty.cpp +++ b/src/plugins/qmldesigner/libs/designercore/model/nodelistproperty.cpp @@ -67,7 +67,8 @@ void NodeListProperty::slide(int from, int to) const if (!isValid()) return; - if (to < 0 || to > count() - 1 || from < 0 || from > count() - 1) + const int count = this->count(); + if (to < 0 || to > count - 1 || from < 0 || from > count - 1) return; privateModel()->changeNodeOrder(internalNodeSharedPointer(), name(), from, to); diff --git a/tests/unit/tests/unittests/listmodeleditor/listmodeleditor-test.cpp b/tests/unit/tests/unittests/listmodeleditor/listmodeleditor-test.cpp index 12013dfaeb9..be2741b8d5d 100644 --- a/tests/unit/tests/unittests/listmodeleditor/listmodeleditor-test.cpp +++ b/tests/unit/tests/unittests/listmodeleditor/listmodeleditor-test.cpp @@ -178,11 +178,31 @@ public: QModelIndex index(int row, int column) const { return model.index(row, column); } + int rowCount() const { return model.rowCount(); } + QList elements(const ModelNode &node) const { return node.defaultNodeListProperty().toModelNodeList(); } + QItemSelection rowSelection(std::initializer_list rows) const + { + QItemSelection selection; + for (int row : rows) + selection.select(model.index(row, 0), model.index(row, model.columnCount() - 1)); + return selection; + } + + QItemSelection itemSelection(std::initializer_list> items) const + { + QItemSelection selection; + for (const auto [row, column] : items) { + QModelIndex idx = index(row, column); + selection.select(idx, idx); + } + return selection; + } + protected: NiceMock projectStorageTriggerUpdateMock; NiceMock pathCacheMock{"/path/foo.qml"}; @@ -200,6 +220,7 @@ protected: QmlDesigner::ListModelEditorModel model{[&] { return mockView.createModelNode("ListModel"); }, [&] { return mockView.createModelNode("ListElement"); }, goIntoComponentMock.AsStdFunction()}; + QItemSelectionModel selectionModel{&model}; ModelNode listViewNode; ModelNode listModelNode; ModelNode emptyListModelNode; @@ -293,7 +314,7 @@ TEST_F(ListModelEditor, add_row_added_invalid_row) { model.setListModel(listModelNode); - model.addRow(); + model.addRow(rowCount()); ASSERT_THAT(displayValues(), ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42), @@ -302,6 +323,67 @@ TEST_F(ListModelEditor, add_row_added_invalid_row) ElementsAre(IsInvalid(), IsInvalid(), IsInvalid(), IsInvalid()))); } +TEST_F(ListModelEditor, add_row_to_zero_index_on_empty_model_works) +{ + model.setListModel(emptyListModelNode); + model.addColumn("foo"); + model.addColumn("bar"); + + model.addRow(0); + + ASSERT_THAT(displayValues(), ElementsAre(ElementsAre(IsInvalid(), IsInvalid()))); +} + +TEST_F(ListModelEditor, add_row_to_zero_index_on_non_empty_model_works) +{ + model.setListModel(listModelNode); + + model.addRow(0); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre(IsInvalid(), IsInvalid(), IsInvalid(), IsInvalid()), + ElementsAre(IsInvalid(), "foo", 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()))); +} + +TEST_F(ListModelEditor, add_row_to_index_one_works) +{ + model.setListModel(listModelNode); + + model.addRow(1); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42), + ElementsAre(IsInvalid(), IsInvalid(), IsInvalid(), IsInvalid()), + ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()))); +} + +TEST_F(ListModelEditor, add_row_to_negative_index_does_nothing) +{ + model.setListModel(listModelNode); + + model.addRow(-1); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()))); +} + +TEST_F(ListModelEditor, add_row_to_int_max_index_does_nothing) +{ + model.setListModel(listModelNode); + + model.addRow(std::numeric_limits::max()); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()))); +} + TEST_F(ListModelEditor, add_row_creates_new_model_node_and_reparents) { model.setListModel(listModelNode); @@ -313,13 +395,177 @@ TEST_F(ListModelEditor, add_row_creates_new_model_node_and_reparents) _, _)); - model.addRow(); + model.addRow(rowCount()); +} + +TEST_F(ListModelEditor, current_interaction_row_returns_invalid_on_empty_selection) +{ + QItemSelectionModel emptySelection; + + int currentRow = ListModelEditorModel::currentInteractionRow(emptySelection); + + ASSERT_THAT(currentRow, Eq(-1)); +} + +TEST_F(ListModelEditor, current_interaction_row_returns_0_when_first_row_is_selected) +{ + model.setListModel(listModelNode); + selectionModel.select(rowSelection({0}), QItemSelectionModel::Select); + + int currentRow = ListModelEditorModel::currentInteractionRow(selectionModel); + + ASSERT_THAT(currentRow, Eq(0)); +} + +TEST_F(ListModelEditor, current_interaction_row_returns_1_when_second_row_is_selected) +{ + model.setListModel(listModelNode); + selectionModel.select(rowSelection({1}), QItemSelectionModel::Select); + + int currentRow = ListModelEditorModel::currentInteractionRow(selectionModel); + + ASSERT_THAT(currentRow, Eq(1)); +} + +TEST_F(ListModelEditor, current_interaction_row_returns_first_selected_row_when_multiple_rows_selected) +{ + model.setListModel(listModelNode); + selectionModel.select(rowSelection({1, 2}), QItemSelectionModel::Select); + + int currentRow = ListModelEditorModel::currentInteractionRow(selectionModel); + + ASSERT_THAT(currentRow, Eq(1)); +} + +TEST_F(ListModelEditor, current_interaction_row_returns_first_selected_item_row_when_no_row_selected) +{ + model.setListModel(listModelNode); + selectionModel.select(itemSelection({{2, 2}, {1, 2}}), QItemSelectionModel::Select); + + int currentRow = ListModelEditorModel::currentInteractionRow(selectionModel); + + ASSERT_THAT(currentRow, Eq(2)); +} + +TEST_F(ListModelEditor, current_interaction_row_returns_current_row_when_nothing_selected) +{ + model.setListModel(listModelNode); + selectionModel.setCurrentIndex(index(2, 2), QItemSelectionModel::Current); + + int currentRow = ListModelEditorModel::currentInteractionRow(selectionModel); + + ASSERT_THAT(currentRow, Eq(2)); +} + +TEST_F(ListModelEditor, current_interaction_row_prefers_selected_index_to_current_index) +{ + model.setListModel(listModelNode); + selectionModel.select(itemSelection({{2, 2}}), QItemSelectionModel::Select); + selectionModel.setCurrentIndex(index(1, 1), QItemSelectionModel::Current); + + int currentRow = ListModelEditorModel::currentInteractionRow(selectionModel); + + ASSERT_THAT(currentRow, Eq(2)); +} + +TEST_F(ListModelEditor, current_interaction_row_prefers_selected_row_to_selected_index) +{ + model.setListModel(listModelNode); + selectionModel.select(itemSelection({{1, 1}}), QItemSelectionModel::Select); + selectionModel.select(rowSelection({2}), QItemSelectionModel::Select); + + int currentRow = ListModelEditorModel::currentInteractionRow(selectionModel); + + ASSERT_THAT(currentRow, Eq(2)); +} + +TEST_F(ListModelEditor, next_interaction_row_returns_zero_on_empty_selection) +{ + QItemSelectionModel emptySelection; + + int nextRow = ListModelEditorModel::nextInteractionRow(emptySelection); + + ASSERT_THAT(nextRow, Eq(0)); +} + +TEST_F(ListModelEditor, next_interaction_row_returns_1_when_first_row_is_selected) +{ + model.setListModel(listModelNode); + selectionModel.select(rowSelection({0}), QItemSelectionModel::Select); + + int nextRow = ListModelEditorModel::nextInteractionRow(selectionModel); + + ASSERT_THAT(nextRow, Eq(1)); +} + +TEST_F(ListModelEditor, next_interaction_row_returns_2_when_second_row_is_selected) +{ + model.setListModel(listModelNode); + selectionModel.select(rowSelection({1}), QItemSelectionModel::Select); + + int nextRow = ListModelEditorModel::nextInteractionRow(selectionModel); + + ASSERT_THAT(nextRow, Eq(2)); +} + +TEST_F(ListModelEditor, + next_interaction_row_returns_next_row_after_last_selected_row_when_multiple_rows_selected) +{ + model.setListModel(listModelNode); + selectionModel.select(rowSelection({1, 2}), QItemSelectionModel::Select); + + int nextRow = ListModelEditorModel::nextInteractionRow(selectionModel); + + ASSERT_THAT(nextRow, Eq(3)); +} + +TEST_F(ListModelEditor, + next_interaction_row_returns_next_row_after_last_selected_item_row_when_no_row_selected) +{ + model.setListModel(listModelNode); + selectionModel.select(itemSelection({{2, 2}, {1, 2}}), QItemSelectionModel::Select); + + int nextRow = ListModelEditorModel::nextInteractionRow(selectionModel); + + ASSERT_THAT(nextRow, Eq(2)); +} + +TEST_F(ListModelEditor, next_interaction_row_returns_next_row_after_current_row_when_nothing_selected) +{ + model.setListModel(listModelNode); + selectionModel.setCurrentIndex(index(2, 2), QItemSelectionModel::Current); + + int nextRow = ListModelEditorModel::nextInteractionRow(selectionModel); + + ASSERT_THAT(nextRow, Eq(3)); +} + +TEST_F(ListModelEditor, next_interaction_row_prefers_selected_index_to_current_index) +{ + model.setListModel(listModelNode); + selectionModel.select(itemSelection({{2, 2}}), QItemSelectionModel::Select); + selectionModel.setCurrentIndex(index(1, 1), QItemSelectionModel::Current); + + int nextRow = ListModelEditorModel::nextInteractionRow(selectionModel); + + ASSERT_THAT(nextRow, Eq(3)); +} + +TEST_F(ListModelEditor, next_interaction_row_prefers_selected_row_to_selected_index) +{ + model.setListModel(listModelNode); + selectionModel.select(itemSelection({{1, 1}}), QItemSelectionModel::Select); + selectionModel.select(rowSelection({2}), QItemSelectionModel::Select); + + int nextRow = ListModelEditorModel::nextInteractionRow(selectionModel); + + ASSERT_THAT(nextRow, Eq(3)); } TEST_F(ListModelEditor, change_added_row_propery) { model.setListModel(listModelNode); - model.addRow(); + model.addRow(rowCount()); model.setValue(3, 2, 22); @@ -335,7 +581,7 @@ TEST_F(ListModelEditor, change_added_row_propery_calls_variant_properties_change model.setListModel(listModelNode); ModelNode element4; ON_CALL(mockView, nodeReparented(_, _, _, _)).WillByDefault(SaveArg<0>(&element4)); - model.addRow(); + model.addRow(rowCount()); EXPECT_CALL(mockView, variantPropertiesChanged(ElementsAre(IsVariantProperty(element4, "value", 22)), @@ -940,7 +1186,7 @@ TEST_F(ListModelEditor, remove_last_row) { model.setListModel(emptyListModelNode); model.addColumn("mood"); - model.addRow(); + model.addRow(rowCount()); model.removeRows({index(0, 0)}); @@ -951,7 +1197,7 @@ TEST_F(ListModelEditor, remove_last_empty_row) { model.setListModel(emptyListModelNode); model.addColumn("mood"); - model.addRow(); + model.addRow(rowCount()); model.removeColumns({index(0, 0)}); model.removeRows({index(0, 0)}); @@ -963,7 +1209,7 @@ TEST_F(ListModelEditor, remove_last_column) { model.setListModel(emptyListModelNode); model.addColumn("mood"); - model.addRow(); + model.addRow(rowCount()); model.removeColumns({index(0, 0)}); @@ -974,7 +1220,7 @@ TEST_F(ListModelEditor, remove_last_empty_column) { model.setListModel(emptyListModelNode); model.addColumn("mood"); - model.addRow(); + model.addRow(rowCount()); model.removeRows({index(0, 0)}); model.removeColumns({index(0, 0)});