From ff50cce8c030e1471c1ce09f48c7f5513ca8ba51 Mon Sep 17 00:00:00 2001 From: Edward Brown <edward.brown@stfc.ac.uk> Date: Tue, 10 Apr 2018 09:55:41 +0100 Subject: [PATCH] Added modified JobTreeView from batch_widget_prototype. - Added a modified version of JobTreeView from the batch_widget_prototype branch - Refactored out QtStandardItemTreeAdapter which can be tested separately from the view. - Also transfered QtTreeCursorNavigation, the class used to implement the tab based navigation. Re #22263 --- qt/widgets/common/CMakeLists.txt | 9 + .../Common/Batch/JobTreeView.h | 84 ++++++++++ .../Common/Batch/QtStandardItemTreeAdapter.h | 55 +++++++ .../Common/Batch/QtTreeCursorNavigation.h | 35 ++++ qt/widgets/common/src/Batch/JobTreeView.cpp | 155 ++++++++++++++++++ .../src/Batch/QtStandardItemTreeAdapter.cpp | 124 ++++++++++++++ .../src/Batch/QtTreeCursorNavigation.cpp | 98 +++++++++++ .../common/test/Batch/QtAdaptedModelTest.h | 127 ++++++++++++++ 8 files changed, 687 insertions(+) create mode 100644 qt/widgets/common/inc/MantidQtWidgets/Common/Batch/JobTreeView.h create mode 100644 qt/widgets/common/inc/MantidQtWidgets/Common/Batch/QtStandardItemTreeAdapter.h create mode 100644 qt/widgets/common/inc/MantidQtWidgets/Common/Batch/QtTreeCursorNavigation.h create mode 100644 qt/widgets/common/src/Batch/JobTreeView.cpp create mode 100644 qt/widgets/common/src/Batch/QtStandardItemTreeAdapter.cpp create mode 100644 qt/widgets/common/src/Batch/QtTreeCursorNavigation.cpp create mode 100644 qt/widgets/common/test/Batch/QtAdaptedModelTest.h diff --git a/qt/widgets/common/CMakeLists.txt b/qt/widgets/common/CMakeLists.txt index 1028f8cfc0b..4d738a7502a 100644 --- a/qt/widgets/common/CMakeLists.txt +++ b/qt/widgets/common/CMakeLists.txt @@ -78,6 +78,9 @@ set ( SRC_FILES src/DataProcessorUI/QtDataProcessorOptionsDialog.cpp src/DataProcessorUI/VectorString.cpp src/Batch/RowLocation.cpp + src/Batch/JobTreeView.cpp + src/Batch/QtStandardItemTreeAdapter.cpp + src/Batch/QtTreeCursorNavigation.cpp src/DataSelector.cpp src/DiagResults.cpp src/DoubleSpinBox.cpp @@ -178,6 +181,9 @@ set ( MOC_FILES inc/MantidQtWidgets/Common/AlgorithmSelectorWidget.h inc/MantidQtWidgets/Common/CheckboxHeader.h inc/MantidQtWidgets/Common/Batch/RowLocation.h + inc/MantidQtWidgets/Common/Batch/QtStandardItemTreeAdapter.h + inc/MantidQtWidgets/Common/Batch/QtTreeCursorNavigation.h + inc/MantidQtWidgets/Common/Batch/JobTreeView.h inc/MantidQtWidgets/Common/DataProcessorUI/AbstractTreeModel.h inc/MantidQtWidgets/Common/DataProcessorUI/QtCommandAdapter.h inc/MantidQtWidgets/Common/DataProcessorUI/GenericDataProcessorPresenter.h @@ -532,6 +538,9 @@ set( TEST_FILES test/DataProcessorUI/TwoLevelTreeManagerTest.h test/DataProcessorUI/WhiteListTest.h test/DataProcessorUI/GenericDataProcessorPresenterTest.h + + test/Batch/QtAdaptedModelTest.h + test/ParseKeyValueStringTest.h test/DataProcessorUI/QOneLevelTreeModelTest.h test/DataProcessorUI/QTwoLevelTreeModelTest.h diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/JobTreeView.h b/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/JobTreeView.h new file mode 100644 index 00000000000..9a901e70d15 --- /dev/null +++ b/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/JobTreeView.h @@ -0,0 +1,84 @@ +#ifndef MANTIDQTMANTIDWIDGETS_JOBTREEVIEW_H_ +#define MANTIDQTMANTIDWIDGETS_JOBTREEVIEW_H_ +#include "MantidQtWidgets/Common/DllOption.h" +#include "MantidQtWidgets/Common/Batch/RowLocation.h" +#include "MantidQtWidgets/Common/Batch/QtTreeCursorNavigation.h" +#include "MantidQtWidgets/Common/Batch/QtStandardItemTreeAdapter.h" + +#include <QTreeView> + +namespace MantidQt { +namespace MantidWidgets { +namespace Batch { + +class IJobTreeViewSubscriber { +public: + virtual void notifyCellChanged(RowLocation itemIndex, int column, + std::string newValue) = 0; + virtual void notifyRowInserted(RowLocation itemIndex) = 0; + virtual void notifyRowRemoved(RowLocation itemIndex) = 0; + virtual void + notifySelectedRowsChanged(std::vector<RowLocation> const &selection) = 0; +}; + +class EXPORT_OPT_MANTIDQT_COMMON JobTreeView : public QTreeView { + Q_OBJECT +public: + // JobTreeView(QWidget *parent = nullptr); + JobTreeView(QStringList const &columnHeadings, QWidget *parent = nullptr); + + void subscribe(IJobTreeViewSubscriber &subscriber); + + void insertChildRowOf(RowLocation const &parent, int beforeRow, + std::vector<std::string> const &rowText); + void insertChildRowOf(RowLocation const &parent, int beforeRow); + void appendChildRowOf(RowLocation const &parent); + void appendChildRowOf(RowLocation const &parentLocation, + std::vector<std::string> const &rowText); + + void removeRowAt(RowLocation const &location); + + std::vector<std::string> rowTextAt(RowLocation const &location) const; + void setRowTextAt(RowLocation const &location, + std::vector<std::string> const &rowText); + + std::string textAt(RowLocation location, int column); + void setTextAt(RowLocation location, int column, std::string const &cellText); + + QModelIndex moveCursor(CursorAction cursorAction, + Qt::KeyboardModifiers modifiers) override; + +protected: + void keyPressEvent(QKeyEvent *event) override; + void setHeaderLabels(QStringList const &columnHeadings); + +private: + void make(QModelIndex const &){}; + + QModelIndex expanded(QModelIndex const &index); + QModelIndex editAt(QModelIndex const &index); + + QModelIndex applyNavigationResult(QtTreeCursorNavigationResult const &result); + QModelIndex findOrMakeCellBelow(QModelIndex const &index); + + QList<QStandardItem *> + rowFromRowText(std::vector<std::string> const &rowText) const; + std::vector<std::string> rowTextFromRow(QModelIndex firstCellIndex) const; + + QModelIndex modelIndexAt(RowLocation const &location, int column = 0) const; + RowLocation rowLocationAt(QModelIndex const &index) const; + QStandardItem *modelItemAt(RowLocation const &location, int column = 0) const; + + QStandardItem *modelItemFromIndex(QModelIndex const &location) const; + + QtTreeCursorNavigation navigation() const; + QtStandardItemMutableTreeAdapter adaptedModel(); + QtStandardItemTreeAdapter const adaptedModel() const; + + IJobTreeViewSubscriber *m_notifyee; + QStandardItemModel m_model; +}; +} +} +} +#endif // MANTIDQTMANTIDWIDGETS_JOBTREEVIEW_H_ diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/QtStandardItemTreeAdapter.h b/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/QtStandardItemTreeAdapter.h new file mode 100644 index 00000000000..6c3487849c6 --- /dev/null +++ b/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/QtStandardItemTreeAdapter.h @@ -0,0 +1,55 @@ +#ifndef MANTIDQTMANTIDWIDGETS_TREEITEMMODELADAPTER_H_ +#define MANTIDQTMANTIDWIDGETS_TREEITEMMODELADAPTER_H_ +#include "MantidQtWidgets/Common/DllOption.h" +#include <QStandardItemModel> + +namespace MantidQt { +namespace MantidWidgets { +namespace Batch { + +class EXPORT_OPT_MANTIDQT_COMMON QtStandardItemTreeAdapter { +public: + QtStandardItemTreeAdapter(QStandardItemModel const &model); + + QModelIndex rootModelIndex() const; + + QStandardItem const *modelItemFromIndex(QModelIndex const &index) const; + + QList<QStandardItem *> + rowFromRowText(std::vector<std::string> const &rowText) const; + std::vector<std::string> rowTextFromRow(QModelIndex firstCellIndex) const; + + QList<QStandardItem *> emptyRow() const; + +private: + QStandardItemModel const &model() const; + QStandardItemModel const* m_model; +}; + +class EXPORT_OPT_MANTIDQT_COMMON QtStandardItemMutableTreeAdapter : public QtStandardItemTreeAdapter { +public: + QtStandardItemMutableTreeAdapter(QStandardItemModel &model); + + QModelIndex appendEmptySiblingRow(QModelIndex const &index); + QModelIndex appendSiblingRow(QModelIndex const &index, + QList<QStandardItem *> cells); + + QModelIndex appendEmptyChildRow(QModelIndex const &parent); + QModelIndex appendChildRow(QModelIndex const &parent, + QList<QStandardItem *> cells); + + QModelIndex insertChildRow(QModelIndex const &parent, int column, + QList<QStandardItem *> cells); + QModelIndex insertEmptyChildRow(QModelIndex const &parent, int column); + QStandardItem *modelItemFromIndex(QModelIndex const &index); + + void removeRowAt(QModelIndex const &index); + +private: + QStandardItemModel &model(); + QStandardItemModel *m_model; +}; +} +} +} +#endif // MANTIDQTMANTIDWIDGETS_TREEITEMMODELADAPTER_H_ diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/QtTreeCursorNavigation.h b/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/QtTreeCursorNavigation.h new file mode 100644 index 00000000000..cf5a009f029 --- /dev/null +++ b/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/QtTreeCursorNavigation.h @@ -0,0 +1,35 @@ +#ifndef MANTIDQTMANTIDWIDGETS_QTTREECURSORNAVIGATION_H_ +#define MANTIDQTMANTIDWIDGETS_QTTREECURSORNAVIGATION_H_ +#include <QModelIndex> +#include <utility> +namespace MantidQt { +namespace MantidWidgets { + +using QtTreeCursorNavigationResult = std::pair<bool, QModelIndex>; +class QtTreeCursorNavigation { +public: + QtTreeCursorNavigation(QAbstractItemModel const* model); + QModelIndex moveCursorPrevious(QModelIndex const ¤tIndex) const; + QtTreeCursorNavigationResult + moveCursorNext(QModelIndex const ¤tIndex) const; + + QModelIndex previousCellInThisRow(QModelIndex const &index) const; + QModelIndex lastCellInPreviousRow(QModelIndex const &index) const; + QModelIndex lastCellInParentRowElseNone(QModelIndex const &index) const; + QModelIndex firstCellOnNextRow(QModelIndex const &rowAbove) const; + QModelIndex nextCellOnThisRow(QModelIndex const &index) const; + QModelIndex lastRowInThisNode(QModelIndex const &index) const; + + bool isNotLastCellOnThisRow(QModelIndex const &index) const; + bool isNotLastRowInThisNode(QModelIndex const &index) const; + bool isNotFirstCellInThisRow(QModelIndex const &index) const; + bool isNotFirstRowInThisNode(QModelIndex const &index) const; + +private: + QAbstractItemModel const* model; + QtTreeCursorNavigationResult withoutAppendedRow(QModelIndex const &index) const; + QtTreeCursorNavigationResult withAppendedRow(QModelIndex const& index) const; +}; +} +} +#endif // MANTIDQTMANTIDWIDGETS_QTTREECURSORNAVIGATION_H_ diff --git a/qt/widgets/common/src/Batch/JobTreeView.cpp b/qt/widgets/common/src/Batch/JobTreeView.cpp new file mode 100644 index 00000000000..db7bbdc74df --- /dev/null +++ b/qt/widgets/common/src/Batch/JobTreeView.cpp @@ -0,0 +1,155 @@ +#include "MantidQtWidgets/Common/Batch/JobTreeView.h" +#include "MantidQtWidgets/Common/Batch/QtTreeCursorNavigation.h" +#include "MantidQtWidgets/Common/Batch/AssertOrThrow.h" +#include <QKeyEvent> +#include <QStandardItemModel> +#include <QSortFilterProxyModel> +namespace MantidQt { +namespace MantidWidgets { +namespace Batch { + +JobTreeView::JobTreeView(QStringList const &columnHeadings, QWidget *parent) + : QTreeView(parent), m_model(this) { + setModel(&m_model); + setHeaderLabels(columnHeadings); +} + +void JobTreeView::setHeaderLabels(QStringList const &columnHeadings) { + m_model.setHorizontalHeaderLabels(columnHeadings); + + for (auto i = 0; i < model()->columnCount(); ++i) + resizeColumnToContents(i); +} + +void JobTreeView::subscribe(IJobTreeViewSubscriber &subscriber) { + m_notifyee = &subscriber; +} + +QModelIndex JobTreeView::modelIndexAt(RowLocation const &location, + int column) const { + auto parentIndex = adaptedModel().rootModelIndex(); + if (location.isRoot()) { + return parentIndex; + } else { + auto &path = location.path(); + for (auto it = path.cbegin(); it != path.cend() - 1; ++it) + parentIndex = model()->index(*it, column, parentIndex); + assertOrThrow( + model()->hasIndex(location.rowRelativeToParent(), 0, parentIndex), + "modelIndexAt: Location refers to an index which does not " + "exist in the model."); + return model()->index(location.rowRelativeToParent(), column, parentIndex); + } +} + +RowLocation JobTreeView::rowLocationAt(QModelIndex const &index) const { + if (index.isValid()) { + auto pathComponents = RowPath(); + auto currentIndex = index; + while (currentIndex.isValid()) { + pathComponents.insert(pathComponents.begin(), currentIndex.row()); + currentIndex = index.parent(); + } + return RowLocation(pathComponents); + } else { + return RowLocation({}); + } +} + +void JobTreeView::removeRowAt(RowLocation const &location) { + adaptedModel().removeRowAt(modelIndexAt(location)); +} + +void JobTreeView::insertChildRowOf(RowLocation const &parent, int beforeRow) { + adaptedModel().insertEmptyChildRow(modelIndexAt(parent), beforeRow); +} + +void JobTreeView::insertChildRowOf(RowLocation const &parent, int beforeRow, + std::vector<std::string> const &rowText) { + adaptedModel().insertChildRow(modelIndexAt(parent), beforeRow, + adaptedModel().rowFromRowText(rowText)); +} + +void JobTreeView::appendChildRowOf(RowLocation const &parent) { + adaptedModel().appendEmptyChildRow(modelIndexAt(parent)); +} + +void JobTreeView::appendChildRowOf(RowLocation const &parent, + std::vector<std::string> const &rowText) { + adaptedModel().appendChildRow(modelIndexAt(parent), + adaptedModel().rowFromRowText(rowText)); +} + +QModelIndex JobTreeView::editAt(QModelIndex const &index) { + clearSelection(); + setCurrentIndex(index); + edit(index); + return index; +} + +QModelIndex JobTreeView::expanded(QModelIndex const &index) { + auto expandAt = index; + while (expandAt.isValid()) { + setExpanded(expandAt, true); + expandAt = model()->parent(expandAt); + } + return index; +} + +QtStandardItemMutableTreeAdapter JobTreeView::adaptedModel() { + return QtStandardItemMutableTreeAdapter(m_model); +} + +QtStandardItemTreeAdapter const JobTreeView::adaptedModel() const { + return QtStandardItemTreeAdapter(m_model); +} + +QtTreeCursorNavigation JobTreeView::navigation() const { + return QtTreeCursorNavigation(model()); +} + +QModelIndex JobTreeView::findOrMakeCellBelow(QModelIndex const &index) { + if (navigation().isNotLastRowInThisNode(index)) { + return moveCursor(QAbstractItemView::MoveDown, Qt::NoModifier); + } else { + return adaptedModel().appendEmptySiblingRow(index); + } +} + +void JobTreeView::keyPressEvent(QKeyEvent *event) { + if (event->key() == Qt::Key_Return) { + event->accept(); + if (event->modifiers() & Qt::ControlModifier) + editAt(adaptedModel().appendEmptyChildRow(currentIndex())); + else { + auto below = findOrMakeCellBelow(currentIndex()); + editAt(expanded(below)); + } + } else { + QTreeView::keyPressEvent(event); + } +} + +QModelIndex +JobTreeView::applyNavigationResult(QtTreeCursorNavigationResult const &result) { + auto shouldMakeNewRowBelow = result.first; + if (shouldMakeNewRowBelow) + return expanded(adaptedModel().appendEmptySiblingRow(result.second)); + else + return result.second; +} + +QModelIndex JobTreeView::moveCursor(CursorAction cursorAction, + Qt::KeyboardModifiers modifiers) { + if (cursorAction == QAbstractItemView::MoveNext) { + return applyNavigationResult(navigation().moveCursorNext(currentIndex())); + } else if (cursorAction == QAbstractItemView::MovePrevious) { + return navigation().moveCursorPrevious(currentIndex()); + } else { + return QTreeView::moveCursor(cursorAction, modifiers); + } +} + +} // namespace Batch +} // namespace MantidWidgets +} // namespace MantidQt diff --git a/qt/widgets/common/src/Batch/QtStandardItemTreeAdapter.cpp b/qt/widgets/common/src/Batch/QtStandardItemTreeAdapter.cpp new file mode 100644 index 00000000000..b33a22793f6 --- /dev/null +++ b/qt/widgets/common/src/Batch/QtStandardItemTreeAdapter.cpp @@ -0,0 +1,124 @@ +#include "MantidQtWidgets/Common/Batch/QtStandardItemTreeAdapter.h" +#include "MantidQtWidgets/Common/Batch/AssertOrThrow.h" + +namespace MantidQt { +namespace MantidWidgets { +namespace Batch { + +QtStandardItemMutableTreeAdapter::QtStandardItemMutableTreeAdapter( + QStandardItemModel &model) + : QtStandardItemTreeAdapter(model), m_model(&model) {} + +QtStandardItemTreeAdapter::QtStandardItemTreeAdapter( + QStandardItemModel const &model) + : m_model(&model) {} + +QModelIndex QtStandardItemTreeAdapter::rootModelIndex() const { + return QModelIndex(); +} + +QStandardItem const * +QtStandardItemTreeAdapter::modelItemFromIndex(QModelIndex const &index) const { + if (index.isValid()) { + auto *item = model().itemFromIndex(index); + assertOrThrow(item != nullptr, + "modelItemFromIndex: Index must point to a valid item."); + return item; + } else + return model().invisibleRootItem(); +} + +QStandardItem * +QtStandardItemMutableTreeAdapter::modelItemFromIndex(QModelIndex const &index) { + if (index.isValid()) { + auto *item = model().itemFromIndex(index); + assertOrThrow(item != nullptr, + "modelItemFromIndex: Index must point to a valid item."); + return item; + } else + return model().invisibleRootItem(); +} + +void QtStandardItemMutableTreeAdapter::removeRowAt(QModelIndex const &index) { + if (index.isValid()) { + auto *parent = modelItemFromIndex(model().parent(index)); + parent->removeRow(index.row()); + } else { + model().removeRows(0, model().rowCount()); + } +} + +QModelIndex QtStandardItemMutableTreeAdapter::appendEmptySiblingRow( + QModelIndex const &index) { + return appendEmptyChildRow(model().parent(index)); +} + +QModelIndex QtStandardItemMutableTreeAdapter::appendSiblingRow( + QModelIndex const &index, QList<QStandardItem *> cells) { + return appendChildRow(model().parent(index), cells); +} + +QModelIndex QtStandardItemMutableTreeAdapter::appendEmptyChildRow( + QModelIndex const &parent) { + return appendChildRow(parent, emptyRow()); +} + +QModelIndex +QtStandardItemMutableTreeAdapter::appendChildRow(QModelIndex const &parent, + QList<QStandardItem *> cells) { + auto *const parentItem = modelItemFromIndex(parent); + parentItem->appendRow(cells); + return model().index(model().rowCount(parent) - 1, 0, parent); +} + +QModelIndex QtStandardItemMutableTreeAdapter::insertChildRow( + QModelIndex const &parent, int row, QList<QStandardItem *> cells) { + auto *const parentItem = modelItemFromIndex(parent); + parentItem->insertRow(row, cells); + return model().index(row, 0, parent); +} + +QModelIndex +QtStandardItemMutableTreeAdapter::insertEmptyChildRow(QModelIndex const &parent, + int row) { + return insertChildRow(parent, row, emptyRow()); +} + +QList<QStandardItem *> QtStandardItemTreeAdapter::emptyRow() const { + auto cells = QList<QStandardItem *>(); + for (auto i = 0; i < model().columnCount(); ++i) + cells.append(new QStandardItem("")); + return cells; +} + +QList<QStandardItem *> QtStandardItemTreeAdapter::rowFromRowText( + std::vector<std::string> const &rowText) const { + auto rowCells = QList<QStandardItem *>(); + for (auto &cellText : rowText) + rowCells.append(new QStandardItem(QString::fromStdString(cellText))); + return rowCells; +} + +std::vector<std::string> +QtStandardItemTreeAdapter::rowTextFromRow(QModelIndex firstCellIndex) const { + auto rowText = std::vector<std::string>(); + rowText.reserve(model().columnCount()); + + for (auto i = 0; i < model().columnCount(); i++) { + auto *cell = + modelItemFromIndex(firstCellIndex.sibling(firstCellIndex.row(), i)); + rowText.emplace_back(cell->text().toStdString()); + } + return rowText; +} + +QStandardItemModel const &QtStandardItemTreeAdapter::model() const { + return *m_model; +} + +QStandardItemModel &QtStandardItemMutableTreeAdapter::model() { + return *m_model; +} +} +} +} diff --git a/qt/widgets/common/src/Batch/QtTreeCursorNavigation.cpp b/qt/widgets/common/src/Batch/QtTreeCursorNavigation.cpp new file mode 100644 index 00000000000..9a86691d343 --- /dev/null +++ b/qt/widgets/common/src/Batch/QtTreeCursorNavigation.cpp @@ -0,0 +1,98 @@ +#include "MantidQtWidgets/Common/Batch/QtTreeCursorNavigation.h" + +namespace MantidQt { +namespace MantidWidgets { + +QtTreeCursorNavigation::QtTreeCursorNavigation(QAbstractItemModel const *model) : model(model) {} + +QtTreeCursorNavigationResult +QtTreeCursorNavigation::withoutAppendedRow(QModelIndex const &index) const { + return std::make_pair(false, index); +} + +QtTreeCursorNavigationResult +QtTreeCursorNavigation::withAppendedRow(QModelIndex const &index) const { + return std::make_pair(true, index); +} + +std::pair<bool, QModelIndex> +QtTreeCursorNavigation::moveCursorNext(QModelIndex const ¤tIndex) const { + if (currentIndex.isValid()) { + if (isNotLastCellOnThisRow(currentIndex)) + return withoutAppendedRow(nextCellOnThisRow(currentIndex)); + else if (isNotLastRowInThisNode(currentIndex)) + return withoutAppendedRow(firstCellOnNextRow(currentIndex)); + else + return withAppendedRow(currentIndex); + } else + return withoutAppendedRow(QModelIndex()); +} + +QModelIndex QtTreeCursorNavigation::moveCursorPrevious( + QModelIndex const ¤tIndex) const { + if (currentIndex.isValid()) { + if (isNotFirstCellInThisRow(currentIndex)) + return previousCellInThisRow(currentIndex); + else if (isNotFirstRowInThisNode(currentIndex)) + return lastCellInPreviousRow(currentIndex); + else + return lastCellInParentRowElseNone(currentIndex); + } else + return QModelIndex(); +} + +bool QtTreeCursorNavigation::isNotFirstCellInThisRow( + QModelIndex const &index) const { + return index.column() > 0; +} + +bool QtTreeCursorNavigation::isNotFirstRowInThisNode( + QModelIndex const &index) const { + return index.row() > 0; +} + +QModelIndex +QtTreeCursorNavigation::previousCellInThisRow(QModelIndex const &index) const { + return index.sibling(index.row(), index.column() - 1); +} + +QModelIndex +QtTreeCursorNavigation::lastCellInPreviousRow(QModelIndex const &index) const { + return index.sibling(index.row() - 1, model->columnCount() - 1); +} + +QModelIndex +QtTreeCursorNavigation::lastRowInThisNode(QModelIndex const &parent) const { + return model->index(model->rowCount(parent) - 1, 0, parent); +} + +QModelIndex QtTreeCursorNavigation::lastCellInParentRowElseNone( + QModelIndex const &index) const { + auto parent = model->parent(index); + if (parent.isValid()) + return parent.sibling(parent.row(), model->columnCount() - 1); + else + return QModelIndex(); +} + +QModelIndex +QtTreeCursorNavigation::firstCellOnNextRow(QModelIndex const &index) const { + return index.sibling(index.row() + 1, 0); +} + +QModelIndex +QtTreeCursorNavigation::nextCellOnThisRow(QModelIndex const &index) const { + return index.sibling(index.row(), index.column() + 1); +} + +bool QtTreeCursorNavigation::isNotLastCellOnThisRow( + QModelIndex const &index) const { + return index.column() + 1 < model->columnCount(); +} + +bool QtTreeCursorNavigation::isNotLastRowInThisNode( + QModelIndex const &index) const { + return index.row() + 1 < model->rowCount(index.parent()); +} +} +} diff --git a/qt/widgets/common/test/Batch/QtAdaptedModelTest.h b/qt/widgets/common/test/Batch/QtAdaptedModelTest.h new file mode 100644 index 00000000000..3b5ed7decc8 --- /dev/null +++ b/qt/widgets/common/test/Batch/QtAdaptedModelTest.h @@ -0,0 +1,127 @@ +#ifndef MANTID_MANTIDWIDGETS_QTADAPTEDMODELTEST_H +#define MANTID_MANTIDWIDGETS_QTADAPTEDMODELTEST_H + +#include <cxxtest/TestSuite.h> +#include <gtest/gtest.h> +#include <QModelIndex> +#include <QStandardItemModel> +#include "MantidKernel/make_unique.h" +#include "MantidQtWidgets/Common/Batch/QtStandardItemTreeAdapter.h" + +using namespace MantidQt::MantidWidgets; +using namespace MantidQt::MantidWidgets::Batch; +using namespace testing; + +class QtAdaptedModelTest : public CxxTest::TestSuite { +public: + // This pair of boilerplate methods prevent the suite being created statically + // This means the constructor isn't called when running other tests + static QtAdaptedModelTest *createSuite() { return new QtAdaptedModelTest(); } + static void destroySuite(QtAdaptedModelTest *suite) { delete suite; } + + QtStandardItemMutableTreeAdapter adapt(QStandardItemModel *model) { + return QtStandardItemMutableTreeAdapter(*model); + } + + std::unique_ptr<QStandardItemModel> emptyModel() const { + return Mantid::Kernel::make_unique<QStandardItemModel>(); + } + + void testInvalidIndexIsRoot() { + auto model = emptyModel(); + auto adaptedModel = adapt(model.get()); + + TS_ASSERT_EQUALS(adaptedModel.rootModelIndex(), QModelIndex()); + } + + void testCanGetRootItemFromRootIndex() { + auto model = emptyModel(); + auto adaptedModel = adapt(model.get()); + + auto rootIndex = QModelIndex(); + TS_ASSERT_EQUALS(adaptedModel.modelItemFromIndex(rootIndex), + model->invisibleRootItem()); + } + + void testAppendChildNode() { + auto model = emptyModel(); + auto adaptedModel = adapt(model.get()); + + auto *expectedChildItem = new QStandardItem("Some Dummy Text"); + adaptedModel.appendChildRow(QModelIndex(), {expectedChildItem}); + + TS_ASSERT_EQUALS(expectedChildItem, model->invisibleRootItem()->child(0)); + } + + void testInsertChildNodeBetweenTwoSiblings() { + auto model = emptyModel(); + auto adaptedModel = adapt(model.get()); + + auto *sibling0 = new QStandardItem("Sibling 0"); + auto *sibling1 = new QStandardItem("Sibling 1"); + + auto *rootItem = model->invisibleRootItem(); + rootItem->appendRow({sibling0}); + rootItem->appendRow({sibling1}); + + auto *newSibling = new QStandardItem("Some Dummy Text"); + + adaptedModel.insertChildRow(QModelIndex(), 1, {newSibling}); + TS_ASSERT_EQUALS(newSibling, model->invisibleRootItem()->child(1)); + } + + void testAppendSiblingNodeAfterSiblings() { + auto model = emptyModel(); + auto adaptedModel = adapt(model.get()); + + auto *sibling0 = new QStandardItem("Sibling 0"); + auto *sibling1 = new QStandardItem("Sibling 1"); + + auto *rootItem = model->invisibleRootItem(); + rootItem->appendRow({sibling0}); + rootItem->appendRow({sibling1}); + + auto rootIndex = QModelIndex(); + auto sibling0Index = model->index(/*row=*/0, /*column=*/0, rootIndex); + auto *newSibling = new QStandardItem("Some Dummy Text"); + + adaptedModel.appendSiblingRow(sibling0Index, {newSibling}); + TS_ASSERT_EQUALS(newSibling, model->invisibleRootItem()->child(2)); + } + + void testCanRowTextCorrectForAppendedRow() { + auto model = emptyModel(); + auto adaptedModel = adapt(model.get()); + + auto const firstCellText = QString("First Cell"); + auto *firstCell = new QStandardItem(firstCellText); + auto const secondCellText = QString("Second Cell"); + auto *secondCell = new QStandardItem(secondCellText); + + auto *rootItem = model->invisibleRootItem(); + rootItem->appendRow({firstCell, secondCell}); + + auto rootIndex = QModelIndex(); + auto childRowIndex = model->index(/*row=*/0, /*column=*/0, rootIndex); + + auto rowText = adaptedModel.rowTextFromRow(childRowIndex); + + TS_ASSERT_EQUALS(rowText.size(), 2u); + TS_ASSERT_EQUALS(firstCellText, QString::fromStdString(rowText[0])); + TS_ASSERT_EQUALS(secondCellText, QString::fromStdString(rowText[1])); + } + + void testCanCreateCellsFromStringVector() { + auto model = emptyModel(); + auto adaptedModel = adapt(model.get()); + + auto const rowText = std::vector<std::string>({"First Cell", "Second Cell"}); + auto const row = adaptedModel.rowFromRowText(rowText); + + TS_ASSERT_EQUALS(row.size(), 2u); + TS_ASSERT_EQUALS(rowText[0], row[0]->text().toStdString()); + TS_ASSERT_EQUALS(rowText[1], row[1]->text().toStdString()); + } +}; + +#endif // MANTID_MANTIDWIDGETS_QTADAPTEDMODELTEST_H -- GitLab