diff --git a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/matrixworkspacedisplay_common.py b/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/matrixworkspacedisplay_common.py index 66d406bc55df5213b3bd2f17cbb0b18f7e3d4b4e..40621e2032b4da330a29ccedb5b8a6f44670147d 100644 --- a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/matrixworkspacedisplay_common.py +++ b/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/matrixworkspacedisplay_common.py @@ -104,6 +104,10 @@ class MockSpectrum: class MockWorkspace: TEST_NAME = "THISISAtestWORKSPACE" + NUMBER_OF_ROWS_AND_COLUMNS = 5 + COLS = NUMBER_OF_ROWS_AND_COLUMNS + ROWS = COLS + @staticmethod def _return_MockSpectrumInfo(): return MockSpectrumInfo() @@ -136,16 +140,17 @@ class MockWorkspace: self.setCell = Mock() - self.name = None + self.name = Mock(return_value="MOCK_WORKSPACE_TEST") - def rowCount(self): - pass + self._column_names = [] + for i in range(self.COLS): + self._column_names.append("col{0}".format(i)) - def columnCount(self): - pass + self.getColumnNames = Mock(return_value=self._column_names) + self.column_count = self.COLS + self.columnCount = Mock(return_value=self.column_count) - def getColumnNames(self): - pass + self.row_count = self.ROWS + self.rowCount = Mock(return_value=self.row_count) - def column(self, index): - pass + self.column = Mock(return_value=[1] * self.row_count) diff --git a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/mock_matrixworkspacedisplay.py b/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/mock_matrixworkspacedisplay.py index d3856441e64e699b6baa7fc2adce46d80aff1600..bbf0884186fee7e3dfb0eeb9609b64c2cc358206 100644 --- a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/mock_matrixworkspacedisplay.py +++ b/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/mock_matrixworkspacedisplay.py @@ -32,8 +32,8 @@ class MockQItemRange(object): class MockQSelectionModel: - def __init__(self): - self.hasSelection = Mock() + def __init__(self, has_selection=True): + self.hasSelection = Mock(return_value=has_selection) self.selectedRows = None self.selectedColumns = None self.currentIndex = None diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/error_column.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/error_column.py index 481a8b98b75cff82ec00b75e08365b8b0d5607d6..60d5796e619a21faa63970fc761e7d2759ac26f9 100644 --- a/qt/python/mantidqt/widgets/tableworkspacedisplay/error_column.py +++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/error_column.py @@ -10,26 +10,29 @@ class ErrorColumn: - def __init__(self, column, error_for_column, label_index): + CANNOT_SET_Y_TO_BE_OWN_YERR_MESSAGE = "Cannot set Y column to be its own YErr" + UNHANDLED_COMPARISON_LOGIC_MESSAGE = "Unhandled comparison logic with type {}" + + def __init__(self, column, related_y_column, label_index): self.column = column - self.error_for_column = error_for_column - if self.column == self.error_for_column: - raise ValueError("Cannot set Y column to be its own YErr") + self.related_y_column = related_y_column + if self.column == self.related_y_column: + raise ValueError(self.CANNOT_SET_Y_TO_BE_OWN_YERR_MESSAGE) self.label_index = label_index def __eq__(self, other): if isinstance(other, ErrorColumn): - return self.error_for_column == other.error_for_column or self.column == other.column + return self.related_y_column == other.related_y_column or self.column == other.column elif isinstance(other, int): return self.column == other else: - raise RuntimeError("Unhandled comparison logic with type {}".format(type(other))) + raise RuntimeError(self.UNHANDLED_COMPARISON_LOGIC_MESSAGE.format(type(other))) def __cmp__(self, other): if isinstance(other, ErrorColumn): - return self.column == other.column or self.error_for_column == other.error_for_column + return self.column == other.column or self.related_y_column == other.related_y_column elif isinstance(other, int): return self.column == other else: - raise RuntimeError("Unhandled comparison logic with type {}".format(type(other))) + raise RuntimeError(self.UNHANDLED_COMPARISON_LOGIC_MESSAGE.format(type(other))) diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/marked_columns.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/marked_columns.py index 8c30c53d3e2fdd01d721a199cefed8c6d27b4493..d2894bb946d6ceff73d4daba2fd808c895031868 100644 --- a/qt/python/mantidqt/widgets/tableworkspacedisplay/marked_columns.py +++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/marked_columns.py @@ -52,9 +52,9 @@ class MarkedColumns: self._add(col_index, self.as_y, [self.as_x, self.as_y_err]) def add_y_err(self, err_column): - if err_column.error_for_column in self.as_x: + if err_column.related_y_column in self.as_x: raise ValueError("Trying to add YErr for column marked as X.") - elif err_column.error_for_column in self.as_y_err: + elif err_column.related_y_column in self.as_y_err: raise ValueError("Trying to add YErr for column marked as YErr.") # remove all labels for the column index len_before_remove = len(self.as_y) @@ -64,7 +64,7 @@ class MarkedColumns: # -> This means that columns have been removed, and the label_index is now _wrong_ # and has to be decremented to match the new label index correctly len_after_remove = len(self.as_y) - if err_column.error_for_column > err_column.column and len_after_remove < len_before_remove: + if err_column.related_y_column > err_column.column and len_after_remove < len_before_remove: err_column.label_index -= (len_before_remove - len_after_remove) self.as_y_err.append(err_column) @@ -75,7 +75,7 @@ class MarkedColumns: # we can only have 1 Y Err for Y, so iterating and removing's iterator invalidation is not an # issue as the code will exit immediately after the removal for col in self.as_y_err: - if col.error_for_column == col_index: + if col.related_y_column == col_index: self.as_y_err.remove(col) break @@ -105,7 +105,7 @@ class MarkedColumns: for yerr_col in self.as_y_err: # if found append the YErr's source column - so that the data from the columns # can be retrieved for plotting the errors - if yerr_col.error_for_column == col: + if yerr_col.related_y_column == col: yerr_for_col[col] = yerr_col.column return yerr_for_col diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/presenter.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/presenter.py index 1c05e2e6091ee22375c5505e9d18791fdb23dc24..a8597856db30d938035b740fe6d37f0c07b9c11d 100644 --- a/qt/python/mantidqt/widgets/tableworkspacedisplay/presenter.py +++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/presenter.py @@ -12,6 +12,7 @@ from __future__ import absolute_import, division, print_function from functools import partial from qtpy.QtCore import Qt +from mantid.kernel import logger from mantid.simpleapi import DeleteTableRows, StatisticsOfTableWorkspace from mantidqt.widgets.common.table_copying import copy_cells, show_no_selection_to_copy_toast @@ -29,6 +30,14 @@ class TableWorkspaceDisplay(object): TOO_MANY_SELECTED_FOR_PLOT = "Too many columns are selected to plot. Please select only 1." NUM_SELECTED_FOR_CONFIRMATION = 10 NO_COLUMN_MARKED_AS_X = "No columns marked as X." + ITEM_CHANGED_INVALID_DATA_MESSAGE = "Error: Trying to set invalid data for the column." + ITEM_CHANGED_UNKNOWN_ERROR_MESSAGE = "Unknown error occurred: {}" + TOO_MANY_TO_SET_AS_Y_ERR_MESSAGE = "Too many selected to set as Y Error" + CANNOT_PLOT_AGAINST_SELF_MESSAGE = "Cannot plot column against itself." + NO_ASSOCIATED_YERR_FOR_EACH_Y_MESSAGE = "There is no associated YErr for each selected Y column." + PLOT_FUNCTION_ERROR_MESSAGE = "One or more of the columns being plotted contain invalid data for Matplotlib.\n\nError message:\n{}" + INVALID_DATA_WINDOW_TITLE = "Invalid data - Mantid Workbench" + COLUMN_DISPLAY_LABEL = 'Column {}' def __init__(self, ws, plot=None, parent=None, model=None, view=None, name=None): """ @@ -36,7 +45,7 @@ class TableWorkspaceDisplay(object): :param ws: Workspace to be displayed :param parent: Parent of the widget - :param plot: Plotting function that will be used to plot workspaces. This requires MatPlotLib directly. + :param plot: Plotting function that will be used to plot workspaces. This requires Matplotlib directly. Passed in as parameter to allow mocking :param model: Model to be used by the widget. Passed in as parameter to allow mocking :param view: View to be used by the widget. Passed in as parameter to allow mocking @@ -76,9 +85,9 @@ class TableWorkspaceDisplay(object): self.model.set_cell_data(item.row(), item.column(), item.data(Qt.DisplayRole), item.is_v3d) item.update() except ValueError: - self.view.show_warning("Error: Trying to set invalid data for the column.") + self.view.show_warning(self.ITEM_CHANGED_INVALID_DATA_MESSAGE) except Exception as x: - self.view.show_warning("Unknown error occurred: {}".format(x)) + self.view.show_warning(self.ITEM_CHANGED_UNKNOWN_ERROR_MESSAGE.format(x)) finally: item.reset() @@ -202,23 +211,23 @@ class TableWorkspaceDisplay(object): def action_set_as_y(self): self._action_set_as(self.model.marked_columns.add_y) - def action_set_as_y_err(self, error_for_column, label_index): + def action_set_as_y_err(self, related_y_column, label_index): """ - :param error_for_column: The real index of the column for which the error is being marked + :param related_y_column: The real index of the column for which the error is being marked :param label_index: The index present in the label of the column for which the error is being marked This will be the number in <ColumnName>[Y10] -> the 10 """ try: - selected_columns = self._get_selected_columns(1, "Too many selected to set as Y Error") + selected_columns = self._get_selected_columns(1, self.TOO_MANY_TO_SET_AS_Y_ERR_MESSAGE) except ValueError: return selected_column = selected_columns[0] try: - err_column = ErrorColumn(selected_column, error_for_column, label_index) + err_column = ErrorColumn(selected_column, related_y_column, label_index) except ValueError as e: - self.view.show_warning(e.message) + self.view.show_warning(str(e)) return self.model.marked_columns.add_y_err(err_column) @@ -227,7 +236,7 @@ class TableWorkspaceDisplay(object): def action_set_as_none(self): self._action_set_as(self.model.marked_columns.remove) - def action_sort_ascending(self, order): + def action_sort(self, order): try: selected_columns = self._get_selected_columns(1, self.TOO_MANY_SELECTED_TO_SORT) except ValueError: @@ -270,7 +279,7 @@ class TableWorkspaceDisplay(object): pass if len(selected_columns) == 0: - self.view.show_warning("Cannot plot column against itself.") + self.view.show_warning(self.CANNOT_PLOT_AGAINST_SELF_MESSAGE) return self._do_plot(selected_columns, selected_x, plot_type) @@ -279,7 +288,7 @@ class TableWorkspaceDisplay(object): if plot_type == PlotType.LINEAR_WITH_ERR: yerr = self.model.marked_columns.find_yerr(selected_columns) if len(yerr) != len(selected_columns): - self.view.show_warning("There is no associated YErr for each selected Y column.") + self.view.show_warning(self.NO_ASSOCIATED_YERR_FOR_EACH_Y_MESSAGE) return x = self.model.get_column(selected_x) @@ -298,12 +307,11 @@ class TableWorkspaceDisplay(object): y = self.model.get_column(column) column_label = self.model.get_column_header(column) try: - plot_func(x, y, label='Column {}'.format(column_label), **kwargs) + plot_func(x, y, label=self.COLUMN_DISPLAY_LABEL.format(column_label), **kwargs) except ValueError as e: - # TODO log error? - self.view.show_warning( - "One or more of the columns being plotted contain invalid data for MatPlotLib." - "\n\nError message:\n{}".format(e), "Invalid data - Mantid Workbench") + error_message = self.PLOT_FUNCTION_ERROR_MESSAGE.format(e) + logger.error(error_message) + self.view.show_warning(error_message, self.INVALID_DATA_WINDOW_TITLE) return ax.set_ylabel(column_label) diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_marked_columns.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_marked_columns.py index 2d91244c05efa799f6b375048228fca58e48bac3..a4fdbd9d731a4f9e807ffdb1c72386cd53b027c2 100644 --- a/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_marked_columns.py +++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_marked_columns.py @@ -123,19 +123,19 @@ class MarkedColumnsTest(unittest.TestCase): -> The new YErr must replace the old one """ mc = MarkedColumns() - ec = ErrorColumn(column=2, error_for_column=4, label_index=0) + ec = ErrorColumn(column=2, related_y_column=4, label_index=0) mc.add_y_err(ec) self.assertEqual(1, len(mc.as_y_err)) self.assertEqual(2, mc.as_y_err[0].column) - self.assertEqual(4, mc.as_y_err[0].error_for_column) + self.assertEqual(4, mc.as_y_err[0].related_y_column) # different source column but contains error for the same column # adding this one should replace the first one - ec2 = ErrorColumn(column=2, error_for_column=5, label_index=0) + ec2 = ErrorColumn(column=2, related_y_column=5, label_index=0) mc.add_y_err(ec2) self.assertEqual(1, len(mc.as_y_err)) self.assertEqual(2, mc.as_y_err[0].column) - self.assertEqual(5, mc.as_y_err[0].error_for_column) + self.assertEqual(5, mc.as_y_err[0].related_y_column) def test_add_y_err_duplicate_column_different_reference_col(self): """ @@ -143,19 +143,19 @@ class MarkedColumnsTest(unittest.TestCase): -> The new YErr must replace the old one """ mc = MarkedColumns() - ec = ErrorColumn(column=2, error_for_column=4, label_index=0) + ec = ErrorColumn(column=2, related_y_column=4, label_index=0) mc.add_y_err(ec) self.assertEqual(1, len(mc.as_y_err)) self.assertEqual(2, mc.as_y_err[0].column) - self.assertEqual(4, mc.as_y_err[0].error_for_column) + self.assertEqual(4, mc.as_y_err[0].related_y_column) # different source column but contains error for the same column # adding this one should replace the first one - ec2 = ErrorColumn(column=3, error_for_column=4, label_index=0) + ec2 = ErrorColumn(column=3, related_y_column=4, label_index=0) mc.add_y_err(ec2) self.assertEqual(1, len(mc.as_y_err)) self.assertEqual(3, mc.as_y_err[0].column) - self.assertEqual(4, mc.as_y_err[0].error_for_column) + self.assertEqual(4, mc.as_y_err[0].related_y_column) def test_changing_y_to_x_removes_associated_yerr_columns(self): """ @@ -164,7 +164,7 @@ class MarkedColumnsTest(unittest.TestCase): """ mc = MarkedColumns() mc.add_y(4) - ec = ErrorColumn(column=2, error_for_column=4, label_index=0) + ec = ErrorColumn(column=2, related_y_column=4, label_index=0) mc.add_y_err(ec) # check that we have both a Y col and an associated YErr @@ -184,7 +184,7 @@ class MarkedColumnsTest(unittest.TestCase): """ mc = MarkedColumns() mc.add_y(4) - ec = ErrorColumn(column=2, error_for_column=4, label_index=0) + ec = ErrorColumn(column=2, related_y_column=4, label_index=0) mc.add_y_err(ec) # check that we have both a Y col and an associated YErr @@ -201,7 +201,7 @@ class MarkedColumnsTest(unittest.TestCase): mc = MarkedColumns() mc.add_y(4) mc.add_x(3) - ec = ErrorColumn(column=2, error_for_column=6, label_index=0) + ec = ErrorColumn(column=2, related_y_column=6, label_index=0) mc.add_y_err(ec) self.assertEqual(1, len(mc.as_x)) diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_presenter.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_presenter.py index 9fdd788ab0bceddab2ade8e0e3f7d73956223011..85a104e04d9716038a739ece1ab3d3e23aaf8354 100644 --- a/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_presenter.py +++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_presenter.py @@ -7,21 +7,552 @@ # This file is part of the mantid workbench. # # -# from __future__ import (absolute_import, division, print_function) +from __future__ import (absolute_import, division, print_function) -# import unittest +import unittest -# from mock import Mock +from mock import Mock, call, patch -# from mantidqt.widgets.matrixworkspacedisplay.test_helpers.matrixworkspacedisplay_common import MockQModelIndex, \ -# MockWorkspace -# from mantidqt.widgets.matrixworkspacedisplay.test_helpers.mock_matrixworkspacedisplay import MockQTableView -# from mantidqt.widgets.tableworkspacedisplay.presenter import TableWorkspaceDisplay -# from mantidqt.widgets.tableworkspacedisplay.test_helpers import MockTableWorkspaceDisplayView +from mantidqt.widgets.matrixworkspacedisplay.test_helpers.matrixworkspacedisplay_common import MockQModelIndex, \ + MockWorkspace +from mantidqt.widgets.matrixworkspacedisplay.test_helpers.mock_matrixworkspacedisplay import MockQSelectionModel +from mantidqt.widgets.tableworkspacedisplay.error_column import ErrorColumn +from mantidqt.widgets.tableworkspacedisplay.model import TableWorkspaceDisplayModel +from mantidqt.widgets.tableworkspacedisplay.plot_type import PlotType +from mantidqt.widgets.tableworkspacedisplay.presenter import TableWorkspaceDisplay +from mantidqt.widgets.tableworkspacedisplay.test_helpers.mock_plotlib import MockAx, MockPlotLib +from mantidqt.widgets.tableworkspacedisplay.view import TableWorkspaceDisplayView +from mantidqt.widgets.tableworkspacedisplay.workbench_table_widget_item import WorkbenchTableWidgetItem -# class TableWorkspaceDisplayPresenterTest(unittest.TestCase): -# def assertNotCalled(self, mock): -# self.assertEqual(0, mock.call_count) -# if __name__ == '__main__': -# unittest.main() +class MockQTable: + """ + Mocks the necessary functions to replace a QTableView on which data is being set. + """ + + def __init__(self): + self.setItem = Mock() + self.setRowCount = Mock() + + +def with_mock_presenter(add_selection_model=False, add_plot=False): + """ + Decorators with Arguments are a load of callback fun. Sources that were used for reference: + + https://stackoverflow.com/a/5929165/2823526 + + And an answer with a little more description of the logic behind it all + https://stackoverflow.com/a/25827070/2823526 + + :param add_selection_model: + """ + + def real_decorator(func, *args, **kwargs): + def wrapper(self, *args): + ws = MockWorkspace() + view = Mock(spec=TableWorkspaceDisplayView) + if add_selection_model: + mock_selection_model = MockQSelectionModel(has_selection=True) + mock_selection_model.selectedRows = Mock( + return_value=[MockQModelIndex(1, 1), MockQModelIndex(2, 2), MockQModelIndex(3, 3)]) + mock_selection_model.selectedColumns = Mock( + return_value=[MockQModelIndex(1, 1), MockQModelIndex(2, 2), MockQModelIndex(3, 3)]) + view.mock_selection_model = mock_selection_model + view.selectionModel.return_value = mock_selection_model + twd = TableWorkspaceDisplay(ws, view=view) + if add_plot: + twd.plot = MockPlotLib() + return func(self, ws, view, twd, *args) + + return wrapper + + return real_decorator + + +class TableWorkspaceDisplayPresenterTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + # Allow the MockWorkspace to work within the model + TableWorkspaceDisplayModel.ALLOWED_WORKSPACE_TYPES.append(MockWorkspace) + + def assertNotCalled(self, mock): + self.assertEqual(0, mock.call_count) + + def test_supports(self): + ws = MockWorkspace() + # the test will fail if the support check fails - an exception is raised + TableWorkspaceDisplay.supports(ws) + + def test_handleItemChanged(self): + ws = MockWorkspace() + view = Mock(spec=TableWorkspaceDisplayView) + twd = TableWorkspaceDisplay(ws, view=view) + item = Mock(spec=WorkbenchTableWidgetItem) + item.row.return_value = 5 + item.column.return_value = 5 + item.data.return_value = "magic parameter" + item.is_v3d = False + + twd.handleItemChanged(item) + + item.row.assert_called_once_with() + item.column.assert_called_once_with() + ws.setCell.assert_called_once_with(5, 5, "magic parameter") + item.update.assert_called_once_with() + item.reset.assert_called_once_with() + + @with_mock_presenter + def test_handleItemChanged_raises_ValueError(self, ws, view, twd): + item = Mock(spec=WorkbenchTableWidgetItem) + item.row.return_value = 5 + item.column.return_value = 5 + item.data.return_value = "magic parameter" + item.is_v3d = False + + # setCell will throw an exception as a side effect + ws.setCell.side_effect = ValueError + + twd.handleItemChanged(item) + + item.row.assert_called_once_with() + item.column.assert_called_once_with() + ws.setCell.assert_called_once_with(5, 5, "magic parameter") + view.show_warning.assert_called_once_with(TableWorkspaceDisplay.ITEM_CHANGED_INVALID_DATA_MESSAGE) + self.assertNotCalled(item.update) + item.reset.assert_called_once_with() + + @with_mock_presenter + def test_handleItemChanged_raises_Exception(self, ws, view, twd): + item = Mock(spec=WorkbenchTableWidgetItem) + + item.row.return_value = ws.ROWS + item.column.return_value = ws.COLS + item.data.return_value = "magic parameter" + item.is_v3d = False + + # setCell will throw an exception as a side effect + error_message = "TEST_EXCEPTION_MESSAGE" + ws.setCell.side_effect = Exception(error_message) + + twd.handleItemChanged(item) + + item.row.assert_called_once_with() + item.column.assert_called_once_with() + ws.setCell.assert_called_once_with(ws.ROWS, ws.COLS, "magic parameter") + view.show_warning.assert_called_once_with( + TableWorkspaceDisplay.ITEM_CHANGED_UNKNOWN_ERROR_MESSAGE.format(error_message)) + self.assertNotCalled(item.update) + item.reset.assert_called_once_with() + + @with_mock_presenter + def test_update_column_headers(self, ws, view, twd): + twd.update_column_headers() + + # setColumnCount is done twice - once in the TableWorkspaceDisplay initialisation, and once in the call + # to update_column_headers above + view.setColumnCount.assert_has_calls([call(ws.ROWS), call(ws.ROWS)]) + + @with_mock_presenter + def test_load_data(self, ws, _, twd): + mock_table = MockQTable() + twd.load_data(mock_table) + + mock_table.setRowCount.assert_called_once_with(ws.ROWS) + ws.columnCount.assert_called_once_with() + # set item is called on every item of the table + self.assertEqual(ws.ROWS * ws.COLS, mock_table.setItem.call_count) + + @patch('mantidqt.widgets.tableworkspacedisplay.presenter.copy_cells') + @with_mock_presenter + def test_action_copying(self, ws, view, twd, mock_copy_cells): + twd.action_copy_cells() + self.assertEqual(1, mock_copy_cells.call_count) + twd.action_copy_bin_values() + self.assertEqual(2, mock_copy_cells.call_count) + twd.action_copy_spectrum_values() + self.assertEqual(3, mock_copy_cells.call_count) + twd.action_keypress_copy() + self.assertEqual(4, mock_copy_cells.call_count) + + @patch('mantidqt.widgets.tableworkspacedisplay.presenter.DeleteTableRows') + @with_mock_presenter(add_selection_model=True) + def test_action_delete_row(self, ws, view, twd, mock_DeleteTableRows): + twd.action_delete_row() + mock_DeleteTableRows.assert_called_once_with(ws, "1,2,3") + view.mock_selection_model.hasSelection.assert_called_once_with() + view.mock_selection_model.selectedRows.assert_called_once_with() + + @patch('mantidqt.widgets.tableworkspacedisplay.presenter.show_no_selection_to_copy_toast') + @with_mock_presenter(add_selection_model=True) + def test_action_delete_row_no_selection(self, ws, view, twd, mock_no_selection_toast): + view.mock_selection_model.hasSelection = Mock(return_value=False) + twd.action_delete_row() + view.mock_selection_model.hasSelection.assert_called_once_with() + self.assertEqual(1, mock_no_selection_toast.call_count) + self.assertNotCalled(view.mock_selection_model.selectedRows) + + @with_mock_presenter(add_selection_model=True) + def test_get_selected_columns(self, ws, view, twd): + result = twd._get_selected_columns() + self.assertEqual([1, 2, 3], result) + + @patch('mantidqt.widgets.tableworkspacedisplay.presenter.show_no_selection_to_copy_toast') + @with_mock_presenter(add_selection_model=True) + def test_get_selected_columns_no_selection(self, ws, view, twd, mock_no_selection_toast): + view.mock_selection_model.hasSelection = Mock(return_value=False) + self.assertRaises(ValueError, twd._get_selected_columns) + self.assertEqual(1, mock_no_selection_toast.call_count) + + @with_mock_presenter(add_selection_model=True) + def test_get_selected_columns_over_max_selected(self, ws, view, twd): + mock_message = "Hi." + self.assertRaises(ValueError, twd._get_selected_columns, max_selected=1, message_if_over_max=mock_message) + view.show_warning.assert_called_once_with(mock_message) + + @patch('mantidqt.widgets.tableworkspacedisplay.presenter.show_no_selection_to_copy_toast') + @with_mock_presenter(add_selection_model=True) + def test_get_selected_columns_has_selected_but_no_columns(self, ws, view, twd, mock_no_selection_toast): + """ + There is a case where the user could have a selection (of cells or rows), but not columns. + """ + view.mock_selection_model.selectedColumns = Mock(return_value=[]) + self.assertRaises(ValueError, twd._get_selected_columns) + self.assertEqual(1, mock_no_selection_toast.call_count) + view.mock_selection_model.selectedColumns.assert_called_once_with() + + @patch('mantidqt.widgets.tableworkspacedisplay.presenter.TableWorkspaceDisplay') + @patch('mantidqt.widgets.tableworkspacedisplay.presenter.StatisticsOfTableWorkspace') + @with_mock_presenter(add_selection_model=True) + def test_action_statistics_on_columns(self, ws, view, twd, mock_StatisticsOfTableWorkspace, + mock_TableWorkspaceDisplay): + twd.action_statistics_on_columns() + + mock_StatisticsOfTableWorkspace.assert_called_once_with(ws, [1, 2, 3]) + # check that there was an attempt to make a new TableWorkspaceDisplay window + self.assertEqual(1, mock_TableWorkspaceDisplay.call_count) + + @with_mock_presenter(add_selection_model=True) + def test_action_hide_selected(self, ws, view, twd): + twd.action_hide_selected() + view.hideColumn.assert_has_calls([call(1), call(2), call(3)]) + + @with_mock_presenter(add_selection_model=True) + def test_action_show_all_columns(self, ws, view, twd): + view.columnCount = Mock(return_value=15) + twd.action_show_all_columns() + self.assertEqual(15, view.showColumn.call_count) + + @with_mock_presenter(add_selection_model=True) + def test_action_set_as(self, ws, view, twd): + mock_func = Mock() + twd._action_set_as(mock_func) + + self.assertEqual(3, mock_func.call_count) + + @with_mock_presenter(add_selection_model=True) + def test_action_set_as_x(self, ws, view, twd): + twd.action_set_as_x() + + self.assertEqual(3, len(twd.model.marked_columns.as_x)) + + @with_mock_presenter(add_selection_model=True) + def test_action_set_as_y(self, ws, view, twd): + twd.action_set_as_y() + + self.assertEqual(3, len(twd.model.marked_columns.as_y)) + + @with_mock_presenter(add_selection_model=True) + def test_action_set_as_none(self, ws, view, twd): + twd.action_set_as_none() + + self.assertEqual(0, len(twd.model.marked_columns.as_x)) + self.assertEqual(0, len(twd.model.marked_columns.as_y)) + self.assertEqual(0, len(twd.model.marked_columns.as_y_err)) + + @with_mock_presenter(add_selection_model=True) + def test_action_set_as_y_err(self, ws, view, twd): + view.mock_selection_model.selectedColumns = Mock(return_value=[MockQModelIndex(1, 1)]) + twd.action_set_as_y_err(2, "0") + self.assertEqual(1, len(twd.model.marked_columns.as_y_err)) + err_col = twd.model.marked_columns.as_y_err[0] + self.assertEqual(1, err_col.column) + self.assertEqual(2, err_col.related_y_column) + self.assertEqual("0", err_col.label_index) + + @with_mock_presenter(add_selection_model=True) + def test_action_set_as_y_err_too_many_selected(self, ws, view, twd): + twd.action_set_as_y_err(2, "0") + view.show_warning.assert_called_once_with(TableWorkspaceDisplay.TOO_MANY_TO_SET_AS_Y_ERR_MESSAGE) + + @with_mock_presenter(add_selection_model=True) + def test_action_set_as_y_err_failed_to_create_ErrorColumn(self, ws, view, twd): + view.mock_selection_model.selectedColumns = Mock(return_value=[MockQModelIndex(1, 1)]) + # this will fail as we're trying to set an YErr column for itself -> (try to set col 1 to be YERR for col 1) + twd.action_set_as_y_err(1, "0") + view.show_warning.assert_called_once_with(ErrorColumn.CANNOT_SET_Y_TO_BE_OWN_YERR_MESSAGE) + + @with_mock_presenter(add_selection_model=True) + def test_action_sort(self, ws, view, twd): + view.mock_selection_model.selectedColumns = Mock(return_value=[MockQModelIndex(0, 4444)]) + order = 1 + twd.action_sort(order) + view.sortByColumn.assert_called_once_with(4444, order) + + @with_mock_presenter(add_selection_model=True) + def test_action_sort_too_many(self, ws, view, twd): + twd.action_sort(1) + view.show_warning.assert_called_once_with(TableWorkspaceDisplay.TOO_MANY_SELECTED_TO_SORT) + + @with_mock_presenter(add_selection_model=True) + def test_get_plot_function_from_type(self, ws, view, twd): + mock_ax = MockAx() + result = twd._get_plot_function_from_type(mock_ax, PlotType.LINEAR) + self.assertEqual(result, mock_ax.plot) + + mock_ax = MockAx() + result = twd._get_plot_function_from_type(mock_ax, PlotType.SCATTER) + self.assertEqual(result, mock_ax.scatter) + + mock_ax = MockAx() + result = twd._get_plot_function_from_type(mock_ax, PlotType.LINE_AND_SYMBOL) + # the function created for LINE_AND_SYMBOL is a decorated ax.plot + self.assertTrue("functools.partial" in str(type(result))) + self.assertIsNotNone(result) + + mock_ax = MockAx() + result = twd._get_plot_function_from_type(mock_ax, PlotType.LINEAR_WITH_ERR) + self.assertEqual(result, mock_ax.errorbar) + + invalid_plot_type = 48903479 + self.assertRaises(ValueError, twd._get_plot_function_from_type, None, invalid_plot_type) + + @patch('mantidqt.widgets.tableworkspacedisplay.presenter.show_no_selection_to_copy_toast') + @with_mock_presenter(add_selection_model=True) + def test_action_plot_no_selected_columns(self, ws, view, twd, mock_show_no_selection_to_copy_toast): + view.mock_selection_model.selectedColumns.return_value = [] + twd.action_plot(PlotType.LINEAR) + mock_show_no_selection_to_copy_toast.assert_called_once_with() + + @with_mock_presenter(add_selection_model=True) + def test_action_plot_more_than_one_x(self, ws, view, twd): + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1), MockQModelIndex(1, 2)] + twd.action_set_as_x() + twd.action_plot(PlotType.LINEAR) + view.mock_selection_model.selectedColumns.assert_has_calls([call(), call()]) + view.show_warning.assert_called_once_with(TableWorkspaceDisplay.TOO_MANY_SELECTED_FOR_X) + + @patch('mantidqt.widgets.tableworkspacedisplay.presenter.TableWorkspaceDisplay._do_plot') + @with_mock_presenter(add_selection_model=True) + def test_action_plot_x_in_selection(self, ws, view, twd, mock_do_plot): + """ + Test that the plot is successful if there is an X in the selection, + and an unmarked column: which is used as the Y data + """ + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1)] + # set only the first column to be X + twd.action_set_as_x() + # add a second selected column, that should be used for Y + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1), MockQModelIndex(1, 2)] + twd.action_plot(PlotType.LINEAR) + mock_do_plot.assert_called_once_with([2], 1, PlotType.LINEAR) + + @patch('mantidqt.widgets.tableworkspacedisplay.presenter.TableWorkspaceDisplay._do_plot') + @with_mock_presenter(add_selection_model=True) + def test_action_plot_x_marked_but_not_selected(self, ws, view, twd, mock_do_plot): + """ + Test that the plot is successful if there is no X in the selection, but a column is marked X. + The selection contains only an unmarked column, which is used as the Y data + """ + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1)] + # set only the first column to be X + twd.action_set_as_x() + # change the selection to a second column, that should be used for Y, but is not marked as anything + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 2)] + twd.action_plot(PlotType.LINEAR) + mock_do_plot.assert_called_once_with([2], 1, PlotType.LINEAR) + + @patch('mantidqt.widgets.tableworkspacedisplay.presenter.TableWorkspaceDisplay._do_plot') + @with_mock_presenter(add_selection_model=True) + def test_action_plot_selection_without_x(self, ws, view, twd, mock_do_plot): + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1)] + twd.action_plot(PlotType.LINEAR) + view.show_warning.assert_called_once_with(TableWorkspaceDisplay.NO_COLUMN_MARKED_AS_X) + + @with_mock_presenter(add_selection_model=True) + def test_action_plot_column_against_itself(self, ws, view, twd): + """ + For example: mark a column as X and then try to do Right click -> plot -> line on it, using it as Y + this will fail as it's the same column + """ + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1)] + # set only the first column to be X + twd.action_set_as_x() + # change the selection to a second column, that should be used for Y, but is not marked as anything + twd.action_plot(PlotType.LINEAR) + view.show_warning.assert_called_once_with(TableWorkspaceDisplay.CANNOT_PLOT_AGAINST_SELF_MESSAGE) + + @with_mock_presenter(add_selection_model=True) + def test_do_action_plot_with_errors_missing_yerr_for_y_column(self, ws, view, twd): + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1)] + twd.action_set_as_x() + + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 2)] + twd.action_set_as_y() + + twd.action_plot(PlotType.LINEAR_WITH_ERR) + view.show_warning.assert_called_once_with(TableWorkspaceDisplay.NO_ASSOCIATED_YERR_FOR_EACH_Y_MESSAGE) + + @patch('mantidqt.widgets.tableworkspacedisplay.presenter.logger.error') + @with_mock_presenter(add_selection_model=True, add_plot=True) + def test_do_action_plot__plot_func_throws_error(self, ws, view, twd, mock_logger_error): + mock_plot_function = Mock() + error_message = "See bottom of keyboard for HEALTH WARNING" + mock_plot_function.side_effect = ValueError(error_message) + with patch( + 'mantidqt.widgets.tableworkspacedisplay.presenter.TableWorkspaceDisplay._get_plot_function_from_type') \ + as mock_get_plot_function_from_type: + mock_get_plot_function_from_type.return_value = mock_plot_function + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1)] + twd.action_set_as_x() + + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 2)] + twd.action_plot(PlotType.LINEAR) + + view.show_warning.assert_called_once_with( + TableWorkspaceDisplay.PLOT_FUNCTION_ERROR_MESSAGE.format(error_message), + TableWorkspaceDisplay.INVALID_DATA_WINDOW_TITLE) + mock_logger_error.assert_called_once_with( + TableWorkspaceDisplay.PLOT_FUNCTION_ERROR_MESSAGE.format(error_message)) + self.assertNotCalled(twd.plot.mock_fig.show) + self.assertNotCalled(twd.plot.mock_ax.legend) + + @with_mock_presenter(add_selection_model=True, add_plot=True) + def test_do_action_plot_success(self, ws, view, twd): + col_as_x = 1 + col_as_y = 2 + expected_x_data = twd.model.get_column(col_as_x) + expected_y_data = twd.model.get_column(col_as_y) + + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_x)] + twd.action_set_as_x() + + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y)] + twd.action_plot(PlotType.LINEAR) + + twd.plot.subplots.assert_called_once_with(subplot_kw={'projection': 'mantid'}) + twd.plot.mock_fig.canvas.set_window_title.assert_called_once_with(twd.model.get_name()) + twd.plot.mock_ax.set_xlabel.assert_called_once_with(twd.model.get_column_header(col_as_x)) + col_y_name = twd.model.get_column_header(col_as_y) + twd.plot.mock_ax.set_ylabel.assert_called_once_with(col_y_name) + twd.plot.mock_ax.plot.assert_called_once_with(expected_x_data, expected_y_data, + label=TableWorkspaceDisplay.COLUMN_DISPLAY_LABEL.format( + col_y_name)) + twd.plot.mock_fig.show.assert_called_once_with() + twd.plot.mock_ax.legend.assert_called_once_with() + + @with_mock_presenter(add_selection_model=True, add_plot=True) + def test_do_action_plot_multiple_y_success(self, ws, view, twd): + col_as_x = 1 + col_as_y1 = 2 + col_as_y2 = 3 + expected_x_data = twd.model.get_column(col_as_x) + expected_y1_data = twd.model.get_column(col_as_y1) + expected_y2_data = twd.model.get_column(col_as_y2) + + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_x)] + twd.action_set_as_x() + + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y1), + MockQModelIndex(1, col_as_y2)] + twd.action_plot(PlotType.LINEAR) + + twd.plot.subplots.assert_called_once_with(subplot_kw={'projection': 'mantid'}) + twd.plot.mock_fig.canvas.set_window_title.assert_called_once_with(twd.model.get_name()) + twd.plot.mock_ax.set_xlabel.assert_called_once_with(twd.model.get_column_header(col_as_x)) + + col_y1_name = twd.model.get_column_header(col_as_y1) + col_y2_name = twd.model.get_column_header(col_as_y2) + twd.plot.mock_ax.set_ylabel.assert_has_calls([call(col_y1_name), call(col_y2_name)]) + + twd.plot.mock_ax.plot.assert_has_calls([ + call(expected_x_data, expected_y1_data, + label=TableWorkspaceDisplay.COLUMN_DISPLAY_LABEL.format(col_y1_name)), + call(expected_x_data, expected_y2_data, + label=TableWorkspaceDisplay.COLUMN_DISPLAY_LABEL.format(col_y2_name))]) + twd.plot.mock_fig.show.assert_called_once_with() + twd.plot.mock_ax.legend.assert_called_once_with() + + @with_mock_presenter(add_selection_model=True, add_plot=True) + def test_do_action_plot_success_error_plot(self, ws, view, twd): + col_as_x = 1 + col_as_y = 2 + col_as_y_err = 3 + expected_x_data = twd.model.get_column(col_as_x) + expected_y_data = twd.model.get_column(col_as_y) + expected_y_err_data = twd.model.get_column(col_as_y_err) + + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_x)] + twd.action_set_as_x() + + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y_err)] + twd.action_set_as_y_err(col_as_y, '0') + + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y)] + twd.action_plot(PlotType.LINEAR_WITH_ERR) + + twd.plot.subplots.assert_called_once_with(subplot_kw={'projection': 'mantid'}) + twd.plot.mock_fig.canvas.set_window_title.assert_called_once_with(twd.model.get_name()) + twd.plot.mock_ax.set_xlabel.assert_called_once_with(twd.model.get_column_header(col_as_x)) + col_y_name = twd.model.get_column_header(col_as_y) + twd.plot.mock_ax.set_ylabel.assert_called_once_with(col_y_name) + twd.plot.mock_ax.errorbar.assert_called_once_with(expected_x_data, expected_y_data, + label=TableWorkspaceDisplay.COLUMN_DISPLAY_LABEL.format( + col_y_name), yerr=expected_y_err_data) + twd.plot.mock_fig.show.assert_called_once_with() + twd.plot.mock_ax.legend.assert_called_once_with() + + @with_mock_presenter(add_selection_model=True, add_plot=True) + def test_do_action_plot_multiple_y_success_error_plot(self, ws, view, twd): + col_as_x = 0 + col_as_y1 = 1 + col_as_y1_err = 2 + col_as_y2 = 3 + col_as_y2_err = 4 + + expected_x_data = twd.model.get_column(col_as_x) + expected_y1_data = twd.model.get_column(col_as_y1) + expected_y1_err_data = twd.model.get_column(col_as_y1_err) + expected_y2_data = twd.model.get_column(col_as_y2) + expected_y2_err_data = twd.model.get_column(col_as_y2_err) + + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_x)] + twd.action_set_as_x() + + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y1_err)] + twd.action_set_as_y_err(col_as_y1, '0') + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y2_err)] + twd.action_set_as_y_err(col_as_y2, '1') + + view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y1), + MockQModelIndex(1, col_as_y2)] + twd.action_plot(PlotType.LINEAR_WITH_ERR) + + twd.plot.subplots.assert_called_once_with(subplot_kw={'projection': 'mantid'}) + twd.plot.mock_fig.canvas.set_window_title.assert_called_once_with(twd.model.get_name()) + twd.plot.mock_ax.set_xlabel.assert_called_once_with(twd.model.get_column_header(col_as_x)) + + col_y1_name = twd.model.get_column_header(col_as_y1) + col_y2_name = twd.model.get_column_header(col_as_y2) + twd.plot.mock_ax.set_ylabel.assert_has_calls([call(col_y1_name), call(col_y2_name)]) + + twd.plot.mock_ax.errorbar.assert_has_calls([ + call(expected_x_data, expected_y1_data, + label=TableWorkspaceDisplay.COLUMN_DISPLAY_LABEL.format(col_y1_name), yerr=expected_y1_err_data), + call(expected_x_data, expected_y2_data, + label=TableWorkspaceDisplay.COLUMN_DISPLAY_LABEL.format(col_y2_name), yerr=expected_y2_err_data)]) + twd.plot.mock_fig.show.assert_called_once_with() + twd.plot.mock_ax.legend.assert_called_once_with() + + +if __name__ == '__main__': + unittest.main() diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/test_helpers/mock_plotlib.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/test_helpers/mock_plotlib.py new file mode 100644 index 0000000000000000000000000000000000000000..762a5a5b73feb4940fc9666f5e2767d9748e1fcd --- /dev/null +++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/test_helpers/mock_plotlib.py @@ -0,0 +1,59 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +# This file is part of the mantid workbench. +# +# + +""" +The aim of this module is to provide a swap-in object for +the matplotlib.pyplot module, which has the same interface. +Every function is instantiated as a mock, which allows to record +calls to the plotting functions, without executing any real +matplotlib code that requires a running GUI application. + +The mocking follows the real matplotlib structure so that it can be easily +swapped in when necessary. There are two ways to do that - either +by injecting the plotting dependency in the constructor or +by using mock.patch to replace the 'matplotlib.pyplot'. + +Note that not all matplotlib.pyplot functions have been added, +only the ones that have been necessary so far. If another function +needs to be mocked it can be freely added in the relevant class below +and it should not break any existing tests. +""" + + +from mock import Mock + + +class MockAx: + def __init__(self): + self.plot = Mock() + self.scatter = Mock() + self.errorbar = Mock() + self.legend = Mock() + self.set_xlabel = Mock() + self.set_ylabel = Mock() + + +class MockCanvas: + def __init__(self): + self.set_window_title = Mock() + + +class MockFig: + def __init__(self): + self.show = Mock() + self.mock_canvas = MockCanvas() + self.canvas = Mock(return_value=self.mock_canvas) + + +class MockPlotLib: + def __init__(self): + self.mock_ax = MockAx() + self.mock_fig = MockFig() + self.subplots = Mock(return_value=[self.mock_fig, self.mock_ax]) diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/view.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/view.py index 264535848db926b78f565e7ec1c5af6712156d41..f834523a4c2d6525130dd3b14d3579908a6e750a 100644 --- a/qt/python/mantidqt/widgets/tableworkspacedisplay/view.py +++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/view.py @@ -57,9 +57,6 @@ class TableWorkspaceDisplayView(QTableWidget): self.resize(600, 400) self.show() - def doubleClickedHeader(self): - print("Double clicked WOO") - def keyPressEvent(self, event): if event.matches(QKeySequence.Copy): self.presenter.action_keypress_copy() @@ -144,10 +141,10 @@ class TableWorkspaceDisplayView(QTableWidget): show_all_columns.triggered.connect(self.presenter.action_show_all_columns) sort_ascending = QAction("Sort Ascending", menu_main) - sort_ascending.triggered.connect(partial(self.presenter.action_sort_ascending, Qt.AscendingOrder)) + sort_ascending.triggered.connect(partial(self.presenter.action_sort, Qt.AscendingOrder)) sort_descending = QAction("Sort Descending", menu_main) - sort_descending.triggered.connect(partial(self.presenter.action_sort_ascending, Qt.DescendingOrder)) + sort_descending.triggered.connect(partial(self.presenter.action_sort, Qt.DescendingOrder)) menu_main.addAction(copy_bin_values) menu_main.addAction(self.make_separator(menu_main)) @@ -160,14 +157,18 @@ class TableWorkspaceDisplayView(QTableWidget): # If any columns are marked as Y then generate the set error menu if num_y_cols > 0: menu_set_as_y_err = QMenu("Set error for Y...") - for col in range(num_y_cols): - set_as_y_err = QAction("Y{}".format(col), menu_main) - # the column index of the column relative to the whole table, this is necessary + for label_index in range(num_y_cols): + set_as_y_err = QAction("Y{}".format(label_index), menu_main) + + # This is the column index of the Y column for which a YERR column is being added. + # The column index is relative to the whole table, this is necessary # so that later the data of the column marked as error can be retrieved - real_column_index = marked_y_cols[col] - # col here holds the index in the LABEL (multiple Y columns have labels Y0, Y1, YN...) + related_y_column = marked_y_cols[label_index] + + # label_index here holds the index in the LABEL (multiple Y columns have labels Y0, Y1, YN...) # this is NOT the same as the column relative to the WHOLE table - set_as_y_err.triggered.connect(partial(self.presenter.action_set_as_y_err, real_column_index, col)) + set_as_y_err.triggered.connect( + partial(self.presenter.action_set_as_y_err, related_y_column, label_index)) menu_set_as_y_err.addAction(set_as_y_err) menu_main.addMenu(menu_set_as_y_err)