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

Separate table presenters for batch and standard table models

parent 38d096ea
......@@ -248,7 +248,7 @@ class WorkspaceWidget(PluginWidget):
except ValueError:
try:
TableWorkspaceDisplay.supports(ws)
presenter = TableWorkspaceDisplay(ws, plot=matplotlib.pyplot, parent=self)
presenter = TableWorkspaceDisplay(ws, plot=matplotlib.pyplot, parent=self, batch=True)
presenter.show_view()
except ValueError:
logger.error("Could not open workspace: {0} with neither "
......
......@@ -10,13 +10,13 @@
from enum import Enum
# local imports
from mantidqt.widgets.workspacedisplay.table.presenter \
import TableWorkspaceDataPresenter, create_table_item
from mantidqt.widgets.workspacedisplay.table.presenter_standard \
import TableWorkspaceDataPresenterStandard, create_table_item
from .model import create_peaksviewermodel
from ..adsobsever import SliceViewerADSObserver
class PeaksWorkspaceDataPresenter(TableWorkspaceDataPresenter):
class PeaksWorkspaceDataPresenter(TableWorkspaceDataPresenterStandard):
"""Override create_item method to format table columns more
appropriately
"""
......
......@@ -16,53 +16,15 @@ 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.presenter_batch import TableWorkspaceDataPresenterBatch
from mantidqt.widgets.workspacedisplay.table.presenter_standard import TableWorkspaceDataPresenterStandard
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, # noqa: F401
RevertibleItem) # noqa: F401
class TableWorkspaceDataPresenter(object):
"""Presenter to handle just displaying data from a table-like object.
Useful for other widgets wishing to embed just the table display"""
__slots__ = ("model", "view")
def __init__(self, model=None, view=None):
"""
:param model: A reference to the model holding the table information
:param view: A reference to the view that is displayed to the user
"""
self.model = model
self.view = view
def refresh(self):
"""Fully refresh the display. Updates column headers and reloads the data"""
self.update_column_headers()
self.load_data(self.view)
def update_column_headers(self):
"""
:param extra_labels: Extra labels to be appended to the column headers.
Expected format: [(id, label), (2, "X"),...]
:type extra_labels: List[Tuple[int, str]]
:return:
"""
# deep copy the original headers so that they are not changed by the appending of the label
column_headers = self.model.original_column_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)
table_item_model.setHorizontalHeaderLabels(column_headers)
def load_data(self, table):
table.model().load_data(self.model)
class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, DataCopier):
class TableWorkspaceDisplay(ObservingPresenter, DataCopier):
A_LOT_OF_THINGS_TO_PLOT_MESSAGE = (
"You selected {} spectra to plot. Are you sure you want to plot that many?")
TOO_MANY_SELECTED_FOR_X = ("Too many columns are selected to use as X. Please select only 1.")
......@@ -94,6 +56,7 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
container=None,
window_width=600,
window_height=400,
batch=False,
):
"""
Creates a display for the provided workspace.
......@@ -108,15 +71,13 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
:param ads_observer: ADS observer to be used by the presenter. If not provided the default
one is used. Mainly intended for testing.
"""
model = model if model is not None else TableWorkspaceDisplayModel(ws)
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)
self.name = name if name else self.model.get_name()
view, model = self.create_table(ws, parent, model, view, batch)
self.view = view
self.model = model
self.name = name if name else model.get_name()
self.container = (container if container else StatusBarView(
parent,
self.view,
view,
self.name,
window_width=window_width,
window_height=window_height,
......@@ -127,15 +88,35 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
self.parent = parent
self.plot = plot
self.view.set_context_menu_actions(self.view)
self.ads_observer = (ads_observer if ads_observer else WorkspaceDisplayADSObserver(self))
self.refresh()
self.presenter.refresh()
def show_view(self):
self.container.show()
def create_table(self, ws, parent, model, view, batch):
if batch:
view, model = self._create_table_batch(ws, parent, view, model)
else:
view, model = self._create_table_standard(ws, parent, view, model)
view.set_context_menu_actions(view)
return view, model
def _create_table_standard(self, ws, parent, view, model):
model = model if model is not None else TableWorkspaceDisplayModel(ws)
view = view if view else TableWorkspaceDisplayView(presenter=self, parent=parent)
self.presenter = TableWorkspaceDataPresenterStandard(model, view)
return view, model
def _create_table_batch(self, ws, parent, view, model):
model = model if model is not None else TableWorkspaceDisplayModel(ws)
table_model = TableModel(parent=parent, data_model=model)
view = view if view else TableWorkspaceDisplayView(presenter=self, parent=parent, table_model=table_model)
self.presenter = TableWorkspaceDataPresenterBatch(model, view)
return view, model
@classmethod
def supports(cls, ws):
"""
......@@ -146,30 +127,30 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
return TableWorkspaceDisplayModel.supports(ws)
def replace_workspace(self, workspace_name, workspace):
if self.model.workspace_equals(workspace_name):
if self.presenter.model.workspace_equals(workspace_name):
# stops triggering itemChanged signal while the data is being reloaded
self.view.blockSignals(True)
self.presenter.view.blockSignals(True)
self.model = TableWorkspaceDisplayModel(workspace)
self.load_data(self.view)
self.view.blockSignals(False)
self.presenter.model = TableWorkspaceDisplayModel(workspace)
self.presenter.load_data(self.presenter.view)
self.presenter.view.blockSignals(False)
self.view.emit_repaint()
self.presenter.view.emit_repaint()
def action_copy_cells(self):
self.copy_cells(self.view)
self.copy_cells(self.presenter.view)
def action_copy_bin_values(self):
self.copy_cells(self.view)
self.copy_cells(self.presenter.view)
def action_copy_spectrum_values(self):
self.copy_cells(self.view)
self.copy_cells(self.presenter.view)
def action_keypress_copy(self):
self.copy_cells(self.view)
self.copy_cells(self.presenter.view)
def action_delete_row(self):
selection_model = self.view.selectionModel()
selection_model = self.presenter.view.selectionModel()
if not selection_model.hasSelection():
self.notify_no_selection_to_copy()
return
......@@ -178,10 +159,10 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
selected_rows_list = [index.row() for index in selected_rows]
selected_rows_str = ",".join([str(row) for row in selected_rows_list])
self.model.delete_rows(selected_rows_str)
self.presenter.model.delete_rows(selected_rows_str)
def _get_selected_columns(self, max_selected=None, message_if_over_max=None):
selection_model = self.view.selectionModel()
selection_model = self.presenter.view.selectionModel()
if not selection_model.hasSelection():
self.notify_no_selection_to_copy()
raise ValueError("No selection")
......@@ -191,7 +172,7 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
if max_selected and message_if_over_max and num_selected_columns > max_selected:
# if over the maximum allowed selection
self.view.show_warning(message_if_over_max)
self.presenter.view.show_warning(message_if_over_max)
raise ValueError("Too many selected")
elif num_selected_columns == 0:
# if no columns are selected
......@@ -209,7 +190,7 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
except ValueError:
return
stats = self.model.get_statistics(selected_columns)
stats = self.presenter.model.get_statistics(selected_columns)
TableWorkspaceDisplay(stats,
parent=self.parent,
name="Column Statistics of {}".format(self.name))
......@@ -220,11 +201,11 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
except ValueError:
return
for column_index in selected_columns:
self.view.hideColumn(column_index)
self.presenter.view.hideColumn(column_index)
def action_show_all_columns(self):
for column_index in range(self.view.columnCount()):
self.view.showColumn(column_index)
for column_index in range(self.presenter.view.columnCount()):
self.presenter.view.showColumn(column_index)
def _action_set_as(self, add_to_list_func, type):
try:
......@@ -234,15 +215,15 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
for col in selected_columns:
add_to_list_func(col)
self.model.set_column_type(col, type)
self.presenter.model.set_column_type(col, type)
self.update_column_headers()
self.presenter.update_column_headers()
def action_set_as_x(self):
self._action_set_as(self.model.marked_columns.add_x, 1)
self._action_set_as(self.presenter.model.marked_columns.add_x, 1)
def action_set_as_y(self):
self._action_set_as(self.model.marked_columns.add_y, 2)
self._action_set_as(self.presenter.model.marked_columns.add_y, 2)
def action_set_as_y_err(self, related_y_column):
"""
......@@ -259,20 +240,20 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
try:
err_column = ErrorColumn(selected_column, related_y_column)
except ValueError as e:
self.view.show_warning(str(e))
self.presenter.view.show_warning(str(e))
return
removed_items = self.model.marked_columns.add_y_err(err_column)
removed_items = self.presenter.model.marked_columns.add_y_err(err_column)
# if a column other than the one the user has just picked as a y err column has been affected,
# reset it's type to None
for col in removed_items:
if col != selected_column:
self.model.set_column_type(int(col), 0)
self.model.set_column_type(selected_column, 5, related_y_column)
self.presenter.model.set_column_type(int(col), 0)
self.presenter.model.set_column_type(selected_column, 5, related_y_column)
self.update_column_headers()
def action_set_as_none(self):
self._action_set_as(self.model.marked_columns.remove, 0)
self._action_set_as(self.presenter.model.marked_columns.remove, 0)
def action_sort(self, sort_ascending):
"""
......@@ -284,7 +265,7 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
except ValueError:
return
self.model.sort(selected_column, sort_ascending)
self.presenter.model.sort(selected_column, sort_ascending)
def action_plot(self, plot_type):
try:
......@@ -292,12 +273,12 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
except ValueError:
return
x_cols = list(set(selected_columns).intersection(self.model.marked_columns.as_x))
x_cols = list(set(selected_columns).intersection(self.presenter.model.marked_columns.as_x))
num_x_cols = len(x_cols)
# if there is more than 1 column marked as X in the selection
# -> show toast to the user and do nothing
if num_x_cols > 1:
self.view.show_warning(self.TOO_MANY_SELECTED_FOR_X)
self.presenter.view.show_warning(self.TOO_MANY_SELECTED_FOR_X)
return
elif num_x_cols == 1:
# Only 1 X column present in the current selection model
......@@ -306,11 +287,11 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
else:
# No X column present in the current selection model
# -> Use the first column marked as X (if present)
if len(self.model.marked_columns.as_x) == 0:
if len(self.presenter.model.marked_columns.as_x) == 0:
# If no columns are marked as X show user message and exit
self.view.show_warning(self.NO_COLUMN_MARKED_AS_X)
self.presenter.view.show_warning(self.NO_COLUMN_MARKED_AS_X)
return
selected_x = self.model.marked_columns.as_x[0]
selected_x = self.presenter.model.marked_columns.as_x[0]
try:
# Remove the X column from the selected columns, this is
......@@ -320,7 +301,7 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
pass
if len(selected_columns) == 0:
self.view.show_warning(self.CANNOT_PLOT_AGAINST_SELF_MESSAGE)
self.presenter.view.show_warning(self.CANNOT_PLOT_AGAINST_SELF_MESSAGE)
return
self._do_plot(selected_columns, selected_x, plot_type)
......@@ -330,7 +311,7 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
def _do_plot(self, selected_columns, selected_x, plot_type):
if self._is_error_plot(plot_type):
yerr = self.model.marked_columns.find_yerr(selected_columns)
yerr = self.presenter.model.marked_columns.find_yerr(selected_columns)
# remove the Y error columns if they are in the selection for plotting
# this prevents them from being treated as Y columns
for err_col in yerr.values():
......@@ -340,17 +321,17 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
# the column is not contained within the selected one
pass
if len(yerr) != len(selected_columns):
column_headers = self.model.original_column_headers()
self.view.show_warning(
column_headers = self.presenter.model.original_column_headers()
self.presenter.view.show_warning(
self.NO_ASSOCIATED_YERR_FOR_EACH_Y_MESSAGE.format(",".join(
[column_headers[col] for col in selected_columns])))
return
x = self.model.get_column(selected_x)
x = self.presenter.model.get_column(selected_x)
fig, ax = self.plot.subplots(subplot_kw={"projection": "mantid"})
fig.canvas.set_window_title(self.model.get_name())
ax.set_xlabel(self.model.get_column_header(selected_x))
ax.wsName = self.model.get_name()
fig.canvas.set_window_title(self.presenter.model.get_name())
ax.set_xlabel(self.presenter.model.get_column_header(selected_x))
ax.wsName = self.presenter.model.get_name()
plot_func = self._get_plot_function_from_type(ax, plot_type)
kwargs = {}
......@@ -358,17 +339,17 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
# if the errors are being plotted, retrieve the data for the column
if self._is_error_plot(plot_type):
yerr_column = yerr[column]
yerr_column_data = self.model.get_column(yerr_column)
yerr_column_data = self.presenter.model.get_column(yerr_column)
kwargs["yerr"] = yerr_column_data
y = self.model.get_column(column)
column_label = self.model.get_column_header(column)
y = self.presenter.model.get_column(column)
column_label = self.presenter.model.get_column_header(column)
try:
plot_func(x, y, label=self.COLUMN_DISPLAY_LABEL.format(column_label), **kwargs)
except ValueError as e:
error_message = self.PLOT_FUNCTION_ERROR_MESSAGE.format(e)
logger.error(error_message)
self.view.show_warning(error_message, self.INVALID_DATA_WINDOW_TITLE)
self.presenter.view.show_warning(error_message, self.INVALID_DATA_WINDOW_TITLE)
return
ax.set_ylabel(column_label)
......@@ -391,4 +372,4 @@ class TableWorkspaceDisplay(TableWorkspaceDataPresenter, ObservingPresenter, Dat
return plot_func
def get_columns_marked_as_y(self):
return self.model.marked_columns.as_y[:]
return self.presenter.model.marked_columns.as_y[:]
# 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 mantidqt package.
from abc import ABC, abstractmethod
class TableWorkspaceDataPresenterBase(ABC):
"""
Presenter to handle just displaying data from a table-like object.
Useful for other widgets wishing to embed just the table display
"""
__slots__ = ("model", "view")
def __init__(self, model=None, view=None):
"""
:param model: A reference to the model holding the table data
:param view: A reference to the view that is displayed to the user
"""
self.model = model
self.view = view
def refresh(self):
"""
Fully refresh the display. Updates column headers and reloads the data
"""
self.update_column_headers()
self.load_data(self.view)
@abstractmethod
def update_column_headers(self):
"""
Update column headers of the table
"""
pass
@abstractmethod
def load_data(self, table):
"""
Load new data into in the input table
"""
pass
# 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 mantidqt package.
from mantidqt.widgets.workspacedisplay.table.presenter_base import TableWorkspaceDataPresenterBase
class TableWorkspaceDataPresenterBatch(TableWorkspaceDataPresenterBase):
def __init__(self, view, model):
super().__init__(view, model)
def load_data(self, table):
table.model().load_data(self.model)
def update_column_headers(self):
"""
:param extra_labels: Extra labels to be appended to the column headers.
Expected format: [(id, label), (2, "X"),...]
:type extra_labels: List[Tuple[int, str]]
:return:
"""
# deep copy the original headers so that they are not changed by the appending of the label
column_headers = self.model.original_column_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)
table_item_model.setHorizontalHeaderLabels(column_headers)
# 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 mantidqt package.
from mantidqt.widgets.workspacedisplay.table.presenter_base import TableWorkspaceDataPresenterBase
from mantidqt.widgets.workspacedisplay.table.tableworkspace_item import create_table_item
class TableWorkspaceDataPresenterStandard(TableWorkspaceDataPresenterBase):
def __init__(self, view, model):
super().__init__(view, model)
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 update_column_headers(self):
# 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)
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)
@staticmethod
def create_item(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)
Markdown is supported
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