From d24bf4d4ee19a8e40af41a8d17cde88dbf71c8d6 Mon Sep 17 00:00:00 2001
From: Edward Brown <edward.brown@stfc.ac.uk>
Date: Tue, 1 May 2018 16:34:49 +0100
Subject: [PATCH] Tidied up Cell implementation.

- Moved methods for operating on QStandardItem/Cell objects + custom role
  enum to CellStandardItem.h/cpp.
- Increased CellDelegate's awareness of custom roles.
- Changed storage-duration of QtFilterLeafNodes to automatic.
- Used new functionality to perform padding of group rows - this
  functionality should be moved into JobTreeView in a later commit.

Re #22263
---
 .../mantidqtpython/mantidqtpython_def.sip     | 41 +++++++----
 qt/widgets/common/CMakeLists.txt              |  2 +
 .../Common/Batch/CellDelegate.h               |  9 ++-
 .../Common/Batch/CellStandardItem.h           | 28 ++++++++
 .../Common/Batch/JobTreeView.h                | 21 +-----
 .../Common/Batch/QtFilterLeafNodes.h          |  3 +-
 .../Common/Batch/QtStandardItemTreeAdapter.h  | 10 ++-
 qt/widgets/common/src/Batch/Cell.cpp          |  2 +-
 qt/widgets/common/src/Batch/CellDelegate.cpp  | 27 ++++++--
 .../common/src/Batch/CellStandardItem.cpp     | 39 +++++++++++
 qt/widgets/common/src/Batch/JobTreeView.cpp   | 67 +++++-------------
 .../src/Batch/QtStandardItemTreeAdapter.cpp   | 68 +++++++++----------
 .../ui/batchwidget/batch_widget_gui.py        | 45 +++++++++---
 13 files changed, 225 insertions(+), 137 deletions(-)
 create mode 100644 qt/widgets/common/inc/MantidQtWidgets/Common/Batch/CellStandardItem.h
 create mode 100644 qt/widgets/common/src/Batch/CellStandardItem.cpp

diff --git a/qt/python/mantidqtpython/mantidqtpython_def.sip b/qt/python/mantidqtpython/mantidqtpython_def.sip
index 55519eb0826..8af6e1cc98e 100644
--- a/qt/python/mantidqtpython/mantidqtpython_def.sip
+++ b/qt/python/mantidqtpython/mantidqtpython_def.sip
@@ -1554,11 +1554,11 @@ public:
 
     void subscribe(JobTreeViewSubscriber& subscriber);
     void insertChildRowOf(const RowLocation &parent, int beforeRow,
-                          const std::vector<std::string> &rowText);
+                          const std::vector<MantidQt::MantidWidgets::Batch::Cell> &rowText);
     void insertChildRowOf(const RowLocation &parent, int beforeRow);
     void appendChildRowOf(const RowLocation &parent);
     void appendChildRowOf(const RowLocation &parentLocation,
-                          const std::vector<std::string> &rowText);
+                          const std::vector<MantidQt::MantidWidgets::Batch::Cell> &rowText);
     void removeRowAt(const RowLocation &location);
     void removeRows(std::vector<MantidQt::MantidWidgets::Batch::RowLocation> rowsToRemove);
 
@@ -1578,28 +1578,45 @@ public:
     void resetFilter();
     bool hasFilter() const;
 
-    void enableEditing(const RowLocation& row);
-    void disableEditing(const RowLocation& row);
-
     bool isOnlyChildOfRoot(const RowLocation& location) const;
-    std::vector<std::string> rowTextAt(const RowLocation &location) const;
-    void setRowTextAt(const RowLocation &location,
-                      const std::vector<std::string> &rowText);
-    std::string textAt(RowLocation location, int column);
-    void setTextAt(RowLocation location, int column, const std::string &cellText);
+    std::vector<MantidQt::MantidWidgets::Batch::Cell> cellsAt(const RowLocation &location) const;
+    void setCellsAt(const RowLocation &location,
+                    const std::vector<MantidQt::MantidWidgets::Batch::Cell> &cells);
+    Cell cellAt(RowLocation location, int column);
+    void setCellAt(RowLocation location, int column, const Cell &cellText);
 
     std::vector<MantidQt::MantidWidgets::Batch::RowLocation> selectedRowLocations() const;
     boost::optional<std::vector<std::vector<MantidQt::MantidWidgets::Batch::Row>>> selectedSubtrees() const;
     boost::optional<std::vector<MantidQt::MantidWidgets::Batch::RowLocation>> selectedSubtreeRoots() const;
 };
 
+class Cell {
+%TypeHeaderCode
+#include "MantidQtWidgets/Common/Batch/Cell.h"
+%End
+public:
+  Cell(const std::string &contentText);
+
+  const std::string &contentText() const;
+  int borderThickness() const;
+  const std::string & borderColor() const;
+  bool isEditable() const;
+
+  void setContentText(const std::string& contentText);
+  void setBorderThickness(int borderThickness);
+  void setBorderColor(const std::string& borderColor);
+  void setEditable(bool isEditable);
+  void disableEditing();
+  void enableEditing();
+};
+
 class Row {
 %TypeHeaderCode
 #include "MantidQtWidgets/Common/Batch/Row.h"
 %End
 public:
-  Row(RowLocation location, std::vector<std::string> cells);
-  const std::vector<std::string>& cells() const;
+  Row(RowLocation location, std::vector<MantidQt::MantidWidgets::Batch::Cell> cells);
+  const std::vector<MantidQt::MantidWidgets::Batch::Cell>& cells() const;
   const RowLocation& location() const;
 };
 
diff --git a/qt/widgets/common/CMakeLists.txt b/qt/widgets/common/CMakeLists.txt
index 6eaa8c69dd2..5221c2fb6e9 100644
--- a/qt/widgets/common/CMakeLists.txt
+++ b/qt/widgets/common/CMakeLists.txt
@@ -81,6 +81,7 @@ set ( SRC_FILES
   src/Batch/RowLocationAdapter.cpp
   src/Batch/Row.cpp
   src/Batch/Cell.cpp
+  src/Batch/CellStandardItem.cpp
   src/Batch/ExtractSubtrees.cpp
   src/Batch/FindSubtreeRoots.cpp
   src/Batch/JobTreeView.cpp
@@ -410,6 +411,7 @@ set ( INC_FILES
   inc/MantidQtWidgets/Common/Batch/RowLocationAdapter.h
   inc/MantidQtWidgets/Common/Batch/Row.h
   inc/MantidQtWidgets/Common/Batch/Cell.h
+  inc/MantidQtWidgets/Common/Batch/CellStandardItem.h
   inc/MantidQtWidgets/Common/Batch/CellDelegate.h
   inc/MantidQtWidgets/Common/Batch/ExtractSubtrees.h
   inc/MantidQtWidgets/Common/Batch/FindSubtreeRoots.h
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/CellDelegate.h b/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/CellDelegate.h
index 90ad6125003..4ed4db166d7 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/CellDelegate.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/CellDelegate.h
@@ -2,18 +2,25 @@
 #define MANTIDQTMANTIDWIDGETS_CELLDELEGATE_H_
 #include <QStyledItemDelegate>
 #include <QTreeView>
+#include <QStandardItemModel>
+#include "MantidQtWidgets/Common/Batch/QtFilterLeafNodes.h"
 
 namespace MantidQt {
 namespace MantidWidgets {
 namespace Batch {
 class CellDelegate : public QStyledItemDelegate {
 public:
-  explicit CellDelegate(QObject *parent, QTreeView const &view);
+  explicit CellDelegate(QObject *parent,
+                        QTreeView const &view,
+                        QtFilterLeafNodes const& filterModel,
+                        QStandardItemModel const& mainModel);
   void paint(QPainter *painter, const QStyleOptionViewItem &option,
              const QModelIndex &index) const override;
 
 private:
   QTreeView const &m_view;
+  QtFilterLeafNodes const& m_filteredModel;
+  QStandardItemModel const& m_mainModel;
 };
 }
 }
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/CellStandardItem.h b/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/CellStandardItem.h
new file mode 100644
index 00000000000..32bd9907604
--- /dev/null
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/CellStandardItem.h
@@ -0,0 +1,28 @@
+#ifndef MANTIDQTMANTIDWIDGETS_CELLSTANDARDITEM_H_
+#define MANTIDQTMANTIDWIDGETS_CELLSTANDARDITEM_H_
+#include <QColor>
+#include <QStandardItem>
+#include "MantidQtWidgets/Common/Batch/Cell.h"
+
+namespace MantidQt {
+namespace MantidWidgets {
+namespace Batch {
+
+enum CellUserRoles {
+  BorderColor = Qt::UserRole + 1,
+  BorderThickness
+};
+
+void setBorderThickness(QStandardItem &item, int borderThickness);
+int getBorderThickness(QStandardItem const &item);
+
+void setBorderColor(QStandardItem &item, QColor const& borderColor);
+QColor getBorderColor(QStandardItem const &item);
+
+void applyCellPropertiesToItem(Cell const &cell, QStandardItem &item);
+Cell extractCellPropertiesFromItem(QStandardItem const& item);
+
+}
+}
+}
+#endif // MANTIDQTMANTIDWIDGETS_CELLSTANDARDITEM_H_
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/JobTreeView.h b/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/JobTreeView.h
index 2ddd57d5567..5b56ba2c6d4 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/JobTreeView.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/JobTreeView.h
@@ -25,6 +25,7 @@ public:
       std::vector<RowLocation> const &locationsOfRowsToRemove) = 0;
   virtual void notifyCopyRowsRequested() = 0;
   virtual void notifyPasteRowsRequested() = 0;
+// TODO:  virtual void notifyFilterReset() = 0;
   virtual ~JobTreeViewSubscriber() = default;
 };
 
@@ -81,12 +82,6 @@ public:
   std::vector<RowLocation> selectedRowLocations() const;
   boost::optional<std::vector<Subtree>> selectedSubtrees() const;
   boost::optional<std::vector<RowLocation>> selectedSubtreeRoots() const;
-
-  void enableEditing(RowLocation const &row);
-  void enableEditing(RowLocation const &row, int cell);
-  void disableEditing(RowLocation const &row);
-  void disableEditing(RowLocation const &row, int cell);
-
   using QTreeView::edit;
 
 protected:
@@ -129,17 +124,6 @@ private:
   std::pair<QModelIndexForFilteredModel, bool>
   findOrMakeCellBelow(QModelIndexForFilteredModel const &index);
 
-  // QList<QStandardItem *>
-  // rowFromRowText(std::vector<std::string> const &rowText) const;
-  std::vector<std::string> rowTextFromRow(QModelIndex firstCellIndex) const;
-
-  QModelIndexForMainModel modelIndexAt(RowLocation const &location,
-                                       int column = 0) const;
-  boost::optional<QModelIndexForMainModel>
-  modelIndexIfExistsAt(RowLocation const &location, int column = 0) const;
-  RowLocation rowLocationAt(QModelIndexForMainModel const &index) const;
-  QStandardItem *modelItemAt(RowLocation const &location, int column = 0) const;
-
   bool hasEditorOpen() const;
 
   QtTreeCursorNavigation navigation() const;
@@ -147,9 +131,8 @@ private:
 
   JobTreeViewSubscriber *m_notifyee;
   QStandardItemModel m_mainModel;
-  int m_columnCount;
 
-  QtFilterLeafNodes *m_filteredModel;
+  QtFilterLeafNodes m_filteredModel;
   QModelIndexForMainModel m_lastEdited;
   bool m_hasEditorOpen;
 };
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/QtFilterLeafNodes.h b/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/QtFilterLeafNodes.h
index c508b5ecaa6..0b0acbf52d0 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/QtFilterLeafNodes.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/QtFilterLeafNodes.h
@@ -5,6 +5,7 @@
 #include <memory>
 #include "MantidQtWidgets/Common/Batch/RowLocation.h"
 #include "MantidQtWidgets/Common/Batch/RowLocationAdapter.h"
+#include "MantidQtWidgets/Common/DllOption.h"
 namespace MantidQt {
 namespace MantidWidgets {
 namespace Batch {
@@ -17,7 +18,7 @@ protected:
   virtual bool rowMeetsCriteria(RowLocation const& row) const = 0;
 };
 
-class QtFilterLeafNodes : public QSortFilterProxyModel {
+class EXPORT_OPT_MANTIDQT_COMMON QtFilterLeafNodes : public QSortFilterProxyModel {
 public:
   QtFilterLeafNodes(RowLocationAdapter rowLocation, QObject *parent = nullptr);
   void setPredicate(std::unique_ptr<RowPredicate> predicate);
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/QtStandardItemTreeAdapter.h b/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/QtStandardItemTreeAdapter.h
index f45c9ddd28b..9c51675c3e9 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/QtStandardItemTreeAdapter.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/QtStandardItemTreeAdapter.h
@@ -11,6 +11,14 @@ namespace MantidQt {
 namespace MantidWidgets {
 namespace Batch {
 
+template <typename Action>
+void enumerateCellsInRow(QModelIndexForMainModel const& startIndex, int columns, Action const& action) {
+  for (auto i = 0; i < columns; i++) {
+    auto cellIndex = startIndex.sibling(startIndex.row(), i);
+    action(cellIndex, i);
+  }
+}
+
 QModelIndexForMainModel rootModelIndex(QStandardItemModel const &model);
 QModelIndexForFilteredModel rootModelIndex(QSortFilterProxyModel const &model);
 
@@ -24,7 +32,7 @@ cellsAtRow(QStandardItemModel const &model,
            QModelIndexForMainModel const &firstCellIndex);
 
 EXPORT_OPT_MANTIDQT_COMMON void
-setCellsAtRow(QStandardItemModel const &model,
+setCellsAtRow(QStandardItemModel &model,
               QModelIndexForMainModel const &firstCellIndex,
               std::vector<Cell> const &cells);
 
diff --git a/qt/widgets/common/src/Batch/Cell.cpp b/qt/widgets/common/src/Batch/Cell.cpp
index 6c7f00c0846..6813d110070 100644
--- a/qt/widgets/common/src/Batch/Cell.cpp
+++ b/qt/widgets/common/src/Batch/Cell.cpp
@@ -6,7 +6,7 @@ namespace Batch {
 
 Cell::Cell(std::string const &contentText)
     : m_contentText(contentText), m_borderThickness(1), m_borderColor("grey"),
-      m_isEditable(false) {}
+      m_isEditable(true) {}
 
 std::string const &Cell::contentText() const { return m_contentText; }
 
diff --git a/qt/widgets/common/src/Batch/CellDelegate.cpp b/qt/widgets/common/src/Batch/CellDelegate.cpp
index 90f26d23677..d78c28a8835 100644
--- a/qt/widgets/common/src/Batch/CellDelegate.cpp
+++ b/qt/widgets/common/src/Batch/CellDelegate.cpp
@@ -1,25 +1,40 @@
 #include "MantidQtWidgets/Common/Batch/CellDelegate.h"
+#include "MantidQtWidgets/Common/Batch/CellStandardItem.h"
+#include "MantidQtWidgets/Common/Batch/QtStandardItemTreeAdapter.h"
 #include <QPainter>
 
 namespace MantidQt {
 namespace MantidWidgets {
 namespace Batch {
 
-CellDelegate::CellDelegate(QObject *parent, QTreeView const &view)
-    : QStyledItemDelegate(parent), m_view(view) {}
+CellDelegate::CellDelegate(QObject *parent, QTreeView const &view,
+                           QtFilterLeafNodes const &filteredModel,
+                           QStandardItemModel const &mainModel)
+    : QStyledItemDelegate(parent), m_view(view), m_filteredModel(filteredModel),
+      m_mainModel(mainModel) {}
 
 void CellDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
                          const QModelIndex &index) const {
   QStyledItemDelegate::paint(painter, option, index);
   painter->save();
-  auto pen =
-      (m_view.currentIndex() == index) ? QPen(Qt::black) : QPen(Qt::darkGray);
-  pen.setWidth((m_view.currentIndex() == index) ? 2 : 1);
+
+  auto* item = modelItemFromIndex(m_mainModel, fromMainModel(m_filteredModel.mapToSource(index), m_mainModel));
+  auto borderColor = getBorderColor(*item);
+  auto borderThickness = getBorderThickness(*item);
+
+  auto isCurrentCell = m_view.currentIndex() == index;
+  auto pen = ([isCurrentCell, &borderColor, item]() -> QPen {
+    if (isCurrentCell & item->isEditable())
+      return QPen(Qt::black);
+    else
+      return QPen(borderColor);
+  })();
+  pen.setWidth(isCurrentCell ? 2 : borderThickness);
+
   painter->setPen(pen);
   painter->drawRect(option.rect.adjusted(1, 1, -1, -1));
   painter->restore();
 }
-
 }
 }
 }
diff --git a/qt/widgets/common/src/Batch/CellStandardItem.cpp b/qt/widgets/common/src/Batch/CellStandardItem.cpp
new file mode 100644
index 00000000000..489b76dd4f4
--- /dev/null
+++ b/qt/widgets/common/src/Batch/CellStandardItem.cpp
@@ -0,0 +1,39 @@
+#include "MantidQtWidgets/Common/Batch/CellStandardItem.h"
+namespace MantidQt {
+namespace MantidWidgets {
+namespace Batch {
+
+void setBorderThickness(QStandardItem &item, int borderThickness) {
+  item.setData(borderThickness, CellUserRoles::BorderThickness);
+}
+
+int getBorderThickness(QStandardItem const &item) {
+  return item.data(CellUserRoles::BorderThickness).toInt();
+}
+
+void setBorderColor(QStandardItem &item, QColor const& borderColor) {
+  item.setData(borderColor, CellUserRoles::BorderColor);
+}
+
+QColor getBorderColor(QStandardItem const &item) {
+  return item.data(CellUserRoles::BorderColor).value<QColor>();
+}
+
+void applyCellPropertiesToItem(Cell const &cell, QStandardItem &item) {
+  item.setText(QString::fromStdString(cell.contentText()));
+  item.setEditable(cell.isEditable());
+  item.setData(QColor(cell.borderColor().c_str()), CellUserRoles::BorderColor);
+  item.setData(cell.borderThickness(), CellUserRoles::BorderThickness);
+}
+
+Cell extractCellPropertiesFromItem(QStandardItem const& item) {
+  auto cell = Cell(item.text().toStdString());
+  cell.setBorderThickness(getBorderThickness(item));
+  cell.setBorderColor(getBorderColor(item).name().toStdString());
+  cell.setEditable(cell.isEditable());
+  return cell;
+}
+
+}
+}
+}
diff --git a/qt/widgets/common/src/Batch/JobTreeView.cpp b/qt/widgets/common/src/Batch/JobTreeView.cpp
index f8eced34e78..10b1c4181ea 100644
--- a/qt/widgets/common/src/Batch/JobTreeView.cpp
+++ b/qt/widgets/common/src/Batch/JobTreeView.cpp
@@ -16,14 +16,15 @@ namespace MantidWidgets {
 namespace Batch {
 
 JobTreeView::JobTreeView(QStringList const &columnHeadings, QWidget *parent)
-    : QTreeView(parent), m_mainModel(this),
-      m_columnCount(columnHeadings.size()), m_filteredModel(),
+    : QTreeView(parent),
+      m_mainModel(this),
+      m_filteredModel(RowLocationAdapter(m_mainModel), this),
       m_lastEdited(QModelIndex()) {
 
   setModel(&m_mainModel);
   setHeaderLabels(columnHeadings);
   setSelectionMode(QAbstractItemView::ExtendedSelection);
-  setItemDelegate(new CellDelegate(this, *this));
+  setItemDelegate(new CellDelegate(this, *this, m_filteredModel, m_mainModel));
 }
 
 void JobTreeView::commitData(QWidget *editor) {
@@ -38,21 +39,21 @@ void JobTreeView::commitData(QWidget *editor) {
 }
 
 void JobTreeView::filterRowsBy(std::unique_ptr<RowPredicate> predicate) {
-  m_filteredModel->setPredicate(std::move(predicate));
+  m_filteredModel.setPredicate(std::move(predicate));
   expandAll();
 }
 
 void JobTreeView::filterRowsBy(RowPredicate *predicate) {
-  m_filteredModel->setPredicate(std::unique_ptr<RowPredicate>(predicate));
+  m_filteredModel.setPredicate(std::unique_ptr<RowPredicate>(predicate));
   expandAll();
 }
 
 void JobTreeView::resetFilter() {
   // TODO: m_notifyee->notifyFilterReset()
-  m_filteredModel->resetPredicate();
+  m_filteredModel.resetPredicate();
 }
 
-bool JobTreeView::hasFilter() const { return m_filteredModel->isReset(); }
+bool JobTreeView::hasFilter() const { return m_filteredModel.isReset(); }
 
 bool JobTreeView::edit(const QModelIndex &index, EditTrigger trigger,
                        QEvent *event) {
@@ -252,40 +253,6 @@ void JobTreeView::insertChildRowOf(RowLocation const &parent, int beforeRow,
                  rowFromCells(cells));
 }
 
-void JobTreeView::enableEditing(RowLocation const &row) {
-  if (!row.isRoot()) {
-    auto cellIndex = rowLocation().indexAt(row).untyped();
-    do {
-      cellIndex = rightOf(cellIndex);
-      auto *item = modelItemFromIndex(m_mainModel, fromMainModel(cellIndex));
-      item->setEditable(true);
-    } while (hasCellOnTheRight(cellIndex));
-  } else {
-    // auto cellIndex = rowLocation().indexAt(row);
-    // auto *item = modelItemFromIndex(firstCellIndex);
-    // item->setEditable(true);
-  }
-}
-
-void JobTreeView::enableEditing(RowLocation const &row, int cell) {}
-
-void JobTreeView::disableEditing(RowLocation const &row) {
-  if (!row.isRoot()) {
-    for (auto cellIndex = rowLocation().indexAt(row).untyped();
-         hasCellOnTheRight(cellIndex); cellIndex = rightOf(cellIndex)) {
-      auto *item = modelItemFromIndex(m_mainModel, fromMainModel(cellIndex));
-      item->setEditable(false);
-    }
-  } else {
-    auto *item = modelItemFromIndex(m_mainModel, rootModelIndex(m_mainModel));
-    item->setEditable(false);
-  }
-}
-
-void JobTreeView::disableEditing(RowLocation const &row, int cell) {
-  auto cellIndex = rowLocation().indexAt(row, cell).untyped();
-}
-
 void JobTreeView::appendChildRowOf(RowLocation const &parent) {
   appendEmptyChildRow(m_mainModel, rowLocation().indexAt(parent));
 }
@@ -307,13 +274,13 @@ JobTreeView::expanded(QModelIndexForFilteredModel const &index) {
   auto expandAt = index.untyped();
   while (expandAt.isValid()) {
     setExpanded(expandAt, true);
-    expandAt = m_filteredModel->parent(expandAt);
+    expandAt = m_filteredModel.parent(expandAt);
   }
   return index;
 }
 
 QtTreeCursorNavigation JobTreeView::navigation() const {
-  return QtTreeCursorNavigation(m_filteredModel);
+  return QtTreeCursorNavigation(&m_filteredModel);
 }
 
 RowLocationAdapter JobTreeView::rowLocation() const {
@@ -335,13 +302,13 @@ JobTreeView::findOrMakeCellBelow(QModelIndexForFilteredModel const &index) {
 QModelIndexForMainModel JobTreeView::mapToMainModel(
     QModelIndexForFilteredModel const &filteredModelIndex) const {
   return QModelIndexForMainModel(
-      m_filteredModel->mapToSource(filteredModelIndex.untyped()));
+      m_filteredModel.mapToSource(filteredModelIndex.untyped()));
 }
 
 QModelIndexForFilteredModel JobTreeView::mapToFilteredModel(
     QModelIndexForMainModel const &mainModelIndex) const {
   return QModelIndexForFilteredModel(
-      m_filteredModel->mapFromSource(mainModelIndex.untyped()));
+      m_filteredModel.mapFromSource(mainModelIndex.untyped()));
 }
 
 void JobTreeView::appendAndEditAtChildRow() {
@@ -362,10 +329,9 @@ void JobTreeView::appendAndEditAtRowBelow() {
 }
 
 void JobTreeView::enableFiltering() {
-  m_filteredModel = new QtFilterLeafNodes(rowLocation(), this);
-  m_filteredModel->setDynamicSortFilter(false);
-  m_filteredModel->setSourceModel(&m_mainModel);
-  setModel(m_filteredModel);
+  m_filteredModel.setDynamicSortFilter(false);
+  m_filteredModel.setSourceModel(&m_mainModel);
+  setModel(&m_filteredModel);
 }
 
 void JobTreeView::keyPressEvent(QKeyEvent *event) {
@@ -383,7 +349,6 @@ void JobTreeView::keyPressEvent(QKeyEvent *event) {
   } else if (event->key() == Qt::Key_C) {
     if (event->modifiers() & Qt::ControlModifier) {
       copySelectedRequested();
-    } else {
     }
   } else if (event->key() == Qt::Key_V) {
     if (event->modifiers() & Qt::ControlModifier) {
@@ -403,7 +368,7 @@ JobTreeView::fromMainModel(QModelIndex const &mainModelIndex) const {
 QModelIndexForFilteredModel
 JobTreeView::fromFilteredModel(QModelIndex const &filteredModelIndex) const {
   return ::MantidQt::MantidWidgets::Batch::fromFilteredModel(filteredModelIndex,
-                                                             *m_filteredModel);
+                                                             m_filteredModel);
 }
 
 QModelIndex JobTreeView::moveCursor(CursorAction cursorAction,
diff --git a/qt/widgets/common/src/Batch/QtStandardItemTreeAdapter.cpp b/qt/widgets/common/src/Batch/QtStandardItemTreeAdapter.cpp
index aaa22897496..8b15a057279 100644
--- a/qt/widgets/common/src/Batch/QtStandardItemTreeAdapter.cpp
+++ b/qt/widgets/common/src/Batch/QtStandardItemTreeAdapter.cpp
@@ -2,6 +2,7 @@
 #include "MantidQtWidgets/Common/Batch/QtBasicNavigation.h"
 #include "MantidQtWidgets/Common/Batch/StrictQModelIndices.h"
 #include "MantidQtWidgets/Common/Batch/AssertOrThrow.h"
+#include "MantidQtWidgets/Common/Batch/CellStandardItem.h"
 
 namespace MantidQt {
 namespace MantidWidgets {
@@ -89,12 +90,6 @@ insertEmptyChildRow(QStandardItemModel &model,
   return insertChildRow(model, parent, row, emptyRow(model.columnCount()));
 }
 
-void setTextAtCell(QStandardItemModel &model,
-                   QModelIndexForMainModel const &index,
-                   std::string const &newText) {
-  modelItemFromIndex(model, index)->setText(QString::fromStdString(newText));
-}
-
 QList<QStandardItem *> emptyRow(int columnCount) {
   auto cells = QList<QStandardItem *>();
   for (auto i = 0; i < columnCount; ++i)
@@ -109,43 +104,48 @@ QList<QStandardItem *> rowFromRowText(std::vector<std::string> const &rowText) {
   return rowCells;
 }
 
-std::vector<Cell>
-cellsAtRow(QStandardItemModel const &model,
-             QModelIndexForMainModel const &firstCellIndex) {
-  auto cells = std::vector<Cell>();
-  cells.reserve(model.columnCount());
-
-  for (auto i = 0; i < model.columnCount(); i++) {
-    auto cellIndex = firstCellIndex.sibling(firstCellIndex.row(), i);
-    cells.emplace_back(cellFromCellIndex(model, cellIndex));
-  }
-  return cells;
+void setCellAtCellIndex(QStandardItemModel &model,
+                        QModelIndexForMainModel const &cellIndex,
+                        Cell const &cell) {
+  auto *item = modelItemFromIndex(model, cellIndex);
+  applyCellPropertiesToItem(cell, *item);
 }
 
-void setBorderThickness(QStandardItem& item, int borderThickness) {
-  item.setData(borderThickness, Qt::UserRole + 2);
+void setCellsAtRow(QStandardItemModel &model,
+                   QModelIndexForMainModel const &firstCellIndex,
+                   std::vector<Cell> const &cells) {
+  enumerateCellsInRow(firstCellIndex, model.columnCount(),
+                      [&model, &cells](QModelIndexForMainModel const &cellIndex, int i) -> void {
+                        auto *item = modelItemFromIndex(model, cellIndex);
+                        applyCellPropertiesToItem(cells[i], *item);
+                      });
 }
 
-int getBorderThickness(QStandardItem const& item) {
-  return item.data(Qt::UserRole + 2).toInt();
-}
-
-void setBorderColor(QStandardItem& item, int borderThickness) {
-  item.setData(borderThickness, Qt::UserRole + 1);
+QList<QStandardItem *> rowFromCells(std::vector<Cell> const &cells) {
+  auto rowCells = QList<QStandardItem *>();
+  for (auto &&cell : cells) {
+    auto *item = new QStandardItem(QString::fromStdString(cell.contentText()));
+    applyCellPropertiesToItem(cell, *item);
+    rowCells.append(item);
+  }
+  return rowCells;
 }
 
-QColor getBorderColor(QStandardItem const& item) {
-  return item.data(Qt::UserRole + 1).value<QColor>();
+std::vector<Cell> cellsAtRow(QStandardItemModel const &model,
+                             QModelIndexForMainModel const &firstCellIndex) {
+  auto cells = std::vector<Cell>();
+  cells.reserve(model.columnCount());
+  enumerateCellsInRow(firstCellIndex, model.columnCount(),
+                      [&model, &cells](QModelIndexForMainModel const &cellIndex, int) -> void {
+                        cells.emplace_back(cellFromCellIndex(model, cellIndex));
+                      });
+  return cells;
 }
 
 Cell cellFromCellIndex(QStandardItemModel const &model,
-                         QModelIndexForMainModel const &index) {
-  auto* cellItem = modelItemFromIndex(model, index);
-  auto cell = Cell(cellItem->text().toStdString());
-  cell.setBorderThickness(getBorderThickness(*cellItem));
-  cell.setBorderColor(getBorderColor(*cellItem).name().toStdString());
-  cell.setEditable(cell.isEditable());
-  return cell;
+                       QModelIndexForMainModel const &index) {
+  auto *cellItem = modelItemFromIndex(model, index);
+  return extractCellPropertiesFromItem(*cellItem);
 }
 }
 }
diff --git a/scripts/Interface/ui/batchwidget/batch_widget_gui.py b/scripts/Interface/ui/batchwidget/batch_widget_gui.py
index 448e1818dd0..7e3f3174999 100644
--- a/scripts/Interface/ui/batchwidget/batch_widget_gui.py
+++ b/scripts/Interface/ui/batchwidget/batch_widget_gui.py
@@ -15,6 +15,29 @@ def row(path):
     return MantidQt.MantidWidgets.Batch.RowLocation(path)
 
 
+def cell(cell_text):
+    return MantidQt.MantidWidgets.Batch.Cell(cell_text)
+
+
+def row_from_text(*cell_texts):
+    return [cell(cell_text) for cell_text in cell_texts]
+
+
+def group_row(group_title, n_columns):
+    def dead_cell():
+        cell = uneditable_cell("")
+        cell.setBorderColor("transparent")
+        return cell
+
+    return [cell(group_title)] + [dead_cell() for n in range(n_columns -1)]
+
+
+def uneditable_cell(cell_text):
+    new_cell = cell(cell_text)
+    new_cell.disableEditing()
+    return new_cell
+    
+
 class RegexPredicate(MantidQt.MantidWidgets.Batch.RowPredicate):
     def __init__(self, view, text):
         super(MantidQt.MantidWidgets.Batch.RowPredicate, self).__init__()
@@ -27,7 +50,7 @@ class RegexPredicate(MantidQt.MantidWidgets.Batch.RowPredicate):
             self.re = re.compile('')
 
     def rowMeetsCriteria(self, location):
-        return bool(self.re.match(self.view.textAt(location, 0)))
+        return bool(self.re.match(self.view.cellAt(location, 0).contentText()))
 
 
 class DataProcessorGui(QtGui.QMainWindow, Ui_BatchWidgetWindow):
@@ -86,18 +109,18 @@ class DataProcessorGui(QtGui.QMainWindow, Ui_BatchWidgetWindow):
         self.table_signals.cellChanged.connect(self.on_cell_updated)
         self.table_signals.rowInserted.connect(self.on_row_inserted)
 
-        self.table.appendChildRowOf(row([]), ["A"])
+        self.table.appendChildRowOf(row([]), group_row("A", 8))
 
-        self.table.appendChildRowOf(row([]), ["B"])
+        self.table.appendChildRowOf(row([]), group_row("B", 8))
 
-        self.table.appendChildRowOf(row([0]), ["C",
-                                               "C",
-                                               "C",
-                                               "C",
-                                               "C",
-                                               "C",
-                                               "C",
-                                               "C"])
+        self.table.appendChildRowOf(row([0]), row_from_text("C",
+                                                            "C",
+                                                            "C",
+                                                            "C",
+                                                            "C",
+                                                            "C",
+                                                            "C",
+                                                            "C"))
         self.table.enableFiltering()
 
         self.filterBox = QtGui.QLineEdit(self)
-- 
GitLab