Commit fbd8db8b authored by Stephen's avatar Stephen Committed by Peterson, Peter
Browse files

Implement custom table model

This model causes the view to load data in batches.
parent 102df5cb
......@@ -97,13 +97,21 @@ class DataCopier(UserNotifier):
left = selectionRange.left()
right = selectionRange.right()
# if we have all rows selected, but we are using the custom table model
# then we have to expand the selection to include non-visible rows
from mantidqt.widgets.workspacedisplay.table.table_model import TableModel
if isinstance(table.model(), TableModel):
if (bottom + 1 - top) == table.rowCount():
bottom = table.model().max_rows() - 1
data = []
index = selectionModel.currentIndex()
for i in range(top, bottom + 1):
for j in range(left, right):
data.append(str(index.sibling(i, j).data()))
index = table.model().createIndex(i, j)
data.append(str(table.model().data(index)))
data.append("\t")
data.append(str(index.sibling(i, right).data()))
index = table.model().createIndex(i, right)
data.append(str(table.model().data(index)))
data.append("\n")
# strip the string to remove the trailing new line
......
......@@ -94,6 +94,9 @@ class TableWorkspaceDisplayModel:
def get_column(self, index):
return self.ws.column(index)
def get_cell(self, row, column):
return self.ws.cell(row, column)
def get_number_of_rows(self):
return self.ws_num_rows
......
......@@ -7,8 +7,6 @@
# This file is part of mantidqt package.
from functools import partial
from qtpy.QtCore import Qt
from mantid.kernel import logger
from mantid.plots.utility import legend_set_draggable
from mantidqt.widgets.observers.ads_observer import WorkspaceDisplayADSObserver
......@@ -18,12 +16,10 @@ from mantidqt.widgets.workspacedisplay.status_bar_view import StatusBarView
from mantidqt.widgets.workspacedisplay.table.error_column import ErrorColumn
from mantidqt.widgets.workspacedisplay.table.model import TableWorkspaceDisplayModel
from mantidqt.widgets.workspacedisplay.table.plot_type import PlotType
from mantidqt.widgets.workspacedisplay.table.table_model import TableModel
from mantidqt.widgets.workspacedisplay.table.view import TableWorkspaceDisplayView
from mantidqt.widgets.workspacedisplay.table.tableworkspace_item import (
QStandardItem,
create_table_item,
RevertibleItem,
)
from mantidqt.widgets.workspacedisplay.table.tableworkspace_item import (QStandardItem, create_table_item, # noqa: F401
RevertibleItem) # noqa: F401
class TableWorkspaceDataPresenter(object):
......@@ -54,37 +50,16 @@ class TableWorkspaceDataPresenter(object):
"""
# deep copy the original headers so that they are not changed by the appending of the label
column_headers = self.model.original_column_headers()
num_headers = len(column_headers)
data_model = self.view.model()
data_model.setColumnCount(num_headers)
table_item_model = self.view.model()
extra_labels = self.model.build_current_labels()
if len(extra_labels) > 0:
for index, label in extra_labels:
column_headers[index] += str(label)
data_model.setHorizontalHeaderLabels(column_headers)
table_item_model.set_table_headers(column_headers)
def load_data(self, table):
num_rows = self.model.get_number_of_rows()
data_model = table.model()
data_model.setRowCount(num_rows)
num_cols = self.model.get_number_of_columns()
data_model.setColumnCount(num_cols)
for col in range(num_cols):
column_data = self.model.get_column(col)
editable = self.model.is_editable_column(col)
for row in range(num_rows):
data_model.setItem(row, col, self.create_item(column_data[row], editable))
def create_item(self, data, editable):
"""Create a QStandardItemModel for the data
:param data: The typed data to store
:param editable: True if it should be editable in the view
"""
return create_table_item(data, editable)
table.model().load_data(self.model)
class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, DataCopier):
......@@ -134,13 +109,10 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
one is used. Mainly intended for testing.
"""
model = model if model is not None else TableWorkspaceDisplayModel(ws)
view = view if view else TableWorkspaceDisplayView(self, parent)
table_model = TableModel(parent=parent, data_model=model)
view = view if view else TableWorkspaceDisplayView(self, parent, table_model=table_model)
TableWorkspaceDataPresenter.__init__(self, model, view)
# from mantid.api import IPeaksWorkspace
# self.is_peaks_worksapce = isinstance(ws, IPeaksWorkspace)
self.name = name if name else self.model.get_name()
self.container = (container if container else StatusBarView(
parent,
......@@ -161,9 +133,6 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
self.refresh()
# connect to cellChanged signal after the data has been loaded
self.view.model().itemChanged.connect(self.handleItemChanged)
def show_view(self):
self.container.show()
......@@ -187,25 +156,6 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
self.view.emit_repaint()
def handleItemChanged(self, item: QStandardItem):
"""
:type item: A reference to the item that has been edited
"""
if not isinstance(item, RevertibleItem):
# Do not perform any additional task for standard QStandardItem
return
try:
self.model.set_cell_data(item.row(), item.column(), item.data(Qt.DisplayRole),
item.is_v3d)
except ValueError:
item.reset()
self.view.show_warning(self.ITEM_CHANGED_INVALID_DATA_MESSAGE)
except Exception as x:
item.reset()
self.view.show_warning(self.ITEM_CHANGED_UNKNOWN_ERROR_MESSAGE.format(x))
else:
item.sync()
def action_copy_cells(self):
self.copy_cells(self.view)
......
# Mantid Repository : https://github.com/mantidproject/mantid
#
# Copyright © 2021 ISIS Rutherford Appleton Laboratory UKRI,
# NScD Oak Ridge National Laboratory, European Spallation Source,
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
# SPDX - License - Identifier: GPL - 3.0 +
# This file is part of the mantidqt package.
from qtpy.QtCore import Qt, QAbstractTableModel, QModelIndex
from mantid.kernel import V3D
BATCH_SIZE = 500
class TableModel(QAbstractTableModel):
"""
A QAbstractTableModel for use with a QTableView
This implementation loads rows of the table in batches
More batches are loaded when the user scrolls down in the table
"""
ITEM_CHANGED_INVALID_DATA_MESSAGE = "Error: Trying to set invalid data for the column."
ITEM_CHANGED_UNKNOWN_ERROR_MESSAGE = "Unknown error occurred: {}"
def __init__(self, data_model, parent=None):
super().__init__(parent=parent)
self._row_count = 0
self._data_model = data_model
self._row_count = 0
self._headers = []
def set_table_headers(self, headers):
self._headers = headers
def canFetchMore(self, index):
if index.isValid():
return False
return self._row_count < self._data_model.get_number_of_rows()
def fetchMore(self, index):
if index.isValid():
return
remainder = self._data_model.get_number_of_rows() - self._row_count
items_to_fetch = min(BATCH_SIZE, remainder)
if items_to_fetch < 0:
return
self.beginInsertRows(QModelIndex(), self._row_count, self._row_count + items_to_fetch - 1)
self._row_count += items_to_fetch
self.endInsertRows()
def rowCount(self, parent=QModelIndex()):
if parent.isValid():
return 0
else:
return self._row_count
def columnCount(self, parent=QModelIndex()):
if parent.isValid():
return 0
return self._data_model.get_number_of_columns()
def headerData(self, section, orientation, role):
if role in (Qt.DisplayRole, Qt.EditRole) and orientation == Qt.Horizontal:
if section < len(self._headers):
return self._headers[section]
else:
return super().headerData(section, orientation, role)
def setHeaderData(self, section, orientation, value, role=Qt.EditRole):
if role == Qt.EditRole and orientation == Qt.Horizontal:
self._defaultHeaders.insert(section, value)
self.headerDataChanged.emit(Qt.Horizontal, section, section + 1)
return True
else:
return False
def setData(self, index, value, role):
if index.isValid() and role == Qt.EditRole:
col = index.column()
row = index.row()
try:
self._data_model.set_cell_data(row, col, value, self.is_v3d(index))
except ValueError:
print(self.ITEM_CHANGED_INVALID_DATA_MESSAGE)
return False
except Exception as x:
print(self.ITEM_CHANGED_UNKNOWN_ERROR_MESSAGE.format(x))
return False
self.dataChanged.emit(index, index)
return True
else:
return False
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
if index.row() >= self.max_rows() or index.row() < 0:
return None
if role in (Qt.DisplayRole, Qt.EditRole):
data = self._data_model.get_cell(index.row(), index.column())
return str(data) if isinstance(data, V3D) else data
return None
def load_data(self, data_model):
self.beginResetModel()
self._data_model = data_model
self._row_count = 0
self.endResetModel()
def flags(self, index):
col = index.column()
editable = self._data_model.is_editable_column(col)
if editable:
return super().flags(index) | Qt.ItemIsEditable | Qt.ItemIsSelectable
else:
return super().flags(index) | Qt.ItemIsSelectable
def max_rows(self):
return self._data_model.get_number_of_rows()
def is_v3d(self, index):
col = index.column()
row = index.row()
return isinstance(self._data_model.get_cell(row, col), V3D)
......@@ -35,10 +35,10 @@ class PreciseDoubleFactory(QItemEditorFactory):
class TableWorkspaceDisplayView(QTableView):
repaint_signal = Signal()
def __init__(self, presenter=None, parent=None):
def __init__(self, presenter=None, parent=None, table_model=QStandardItemModel()):
super().__init__(parent)
self.data_model = QStandardItemModel(self)
self.setModel(self.data_model)
self.table_model = table_model
self.setModel(self.table_model)
self.presenter = presenter
self.COPY_ICON = mantidqt.icons.get_icon("mdi.content-copy")
......@@ -56,10 +56,10 @@ class TableWorkspaceDisplayView(QTableView):
header.sectionDoubleClicked.connect(self.handle_double_click)
def columnCount(self):
return self.data_model.columnCount()
return self.table_model.columnCount()
def rowCount(self):
return self.data_model.rowCount()
return self.table_model.rowCount()
def subscribe(self, presenter):
"""
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment