From b0dcc84c3cfc92a3970899f661c8d758402bd859 Mon Sep 17 00:00:00 2001
From: Conor Finn <conor.finn@stfc.ac.uk>
Date: Wed, 12 Feb 2020 16:45:15 +0000
Subject: [PATCH] RE #27779 Implement observer between tab widgets

Observer pattern uses has the data widget as an observable that sends
the plotting widget the workspaces to add or remove from the plot when
the 3rd column in the table is ticked and unticked respectively. The
pattern also handles the removal of indiviual workspaces or the
clearing of all tracked workspaces.
---
 .../fitting/data_handling/data_presenter.py   | 22 ++++++-
 .../tabs/fitting/data_handling/data_view.py   |  3 +
 .../data_handling/test/test_data_presenter.py | 64 ++++++++++++++++++-
 .../tabs/fitting/presenter.py                 |  7 ++
 4 files changed, 92 insertions(+), 4 deletions(-)

diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/data_presenter.py b/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/data_presenter.py
index fbf22015c72..024f708d4a6 100644
--- a/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/data_presenter.py
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/data_presenter.py
@@ -8,6 +8,7 @@
 from Engineering.gui.engineering_diffraction.tabs.common import create_error_message
 from mantid.simpleapi import logger
 from mantidqt.utils.asynchronous import AsyncTask
+from mantidqt.utils.observer_pattern import GenericObservable
 
 
 class FittingDataPresenter(object):
@@ -23,6 +24,12 @@ class FittingDataPresenter(object):
         self.view.set_enable_button_connection(self._enable_load_button)
         self.view.set_on_remove_selected_clicked(self._remove_selected_tracked_workspaces)
         self.view.set_on_remove_all_clicked(self._remove_all_tracked_workspaces)
+        self.view.set_on_table_cell_changed(self._handle_table_cell_changed)
+
+        # Observable Setup
+        self.plot_added_notifier = GenericObservable()
+        self.plot_removed_notifier = GenericObservable()
+        self.all_plots_removed_notifier = GenericObservable()
 
     def on_load_clicked(self):
         if self._validate():
@@ -31,7 +38,8 @@ class FittingDataPresenter(object):
 
     def remove_workspace(self, ws_name):
         if ws_name in self.get_loaded_workspaces():
-            self.get_loaded_workspaces().pop(ws_name)
+            removed = self.get_loaded_workspaces().pop(ws_name)
+            self.plot_removed_notifier.notify_subscribers(removed)
             self._repopulate_table()
 
     def rename_workspace(self, old_name, new_name):
@@ -42,6 +50,7 @@ class FittingDataPresenter(object):
 
     def clear_workspaces(self):
         self.get_loaded_workspaces().clear()
+        self.all_plots_removed_notifier.notify_subscribers()
         self.row_numbers.clear()
         self._repopulate_table()
 
@@ -89,13 +98,22 @@ class FittingDataPresenter(object):
         row_numbers = self._remove_selected_table_rows()
         for row_no in row_numbers:
             ws_name = self.row_numbers.pop(row_no)
-            self.get_loaded_workspaces().pop(ws_name)
+            removed = self.get_loaded_workspaces().pop(ws_name)
+            self.plot_removed_notifier.notify_subscribers(removed)
         self._repopulate_table()
 
     def _remove_all_tracked_workspaces(self):
         self.clear_workspaces()
         self._remove_all_table_rows()
 
+    def _handle_table_cell_changed(self, row, col):
+        if col == 2 and row in self.row_numbers:  # Is from the plot check column
+            ws = self.model.get_loaded_workspaces()[self.row_numbers[row]]
+            if self.view.get_table_item(row, col).checkState() == 2:  # Plot Box is checked
+                self.plot_added_notifier.notify_subscribers(ws)
+            else:  # Plot box is unchecked
+                self.plot_removed_notifier.notify_subscribers(ws)
+
     def _enable_load_button(self, enabled):
         self.view.set_load_button_enabled(enabled)
 
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/data_view.py b/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/data_view.py
index b07a1349cb6..e2a384439f5 100644
--- a/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/data_view.py
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/data_view.py
@@ -87,6 +87,9 @@ class FittingDataView(QtWidgets.QWidget, Ui_data):
     def get_selected_rows(self):
         return set(index.row() for index in self.table_selection.selectedIndexes())
 
+    def get_table_item(self, row, col):
+        return self.table_selection.item(row, col)
+
     # =================
     # State Getters
     # =================
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/test/test_data_presenter.py b/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/test/test_data_presenter.py
index aec6e02de3d..6ab9c13b0ef 100644
--- a/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/test/test_data_presenter.py
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/test/test_data_presenter.py
@@ -111,11 +111,13 @@ class FittingDataPresenterTest(unittest.TestCase):
         model_dict = {"name1": "ws1", "name2": "ws2"}
         self.model.get_loaded_workspaces.return_value = model_dict
         self.presenter.row_numbers = {"name1": 0, "name2": 1}
+        self.presenter.plot_removed_notifier = mock.MagicMock()
 
         self.presenter.remove_workspace("name1")
 
         self.assertEqual({"name2": "ws2"}, model_dict)
         self.assertEqual({"name2": 0}, self.presenter.row_numbers)
+        self.assertEqual(1, self.presenter.plot_removed_notifier.notify_subscribers.call_count)
 
     def test_remove_workspace_not_tracked(self):
         model_dict = {"name1": "ws1", "name2": "ws2"}
@@ -152,11 +154,13 @@ class FittingDataPresenterTest(unittest.TestCase):
         model_dict = {"name1": "ws1", "name2": "ws2"}
         self.model.get_loaded_workspaces.return_value = model_dict
         self.presenter.row_numbers = {"name1": 0, "name2": 1}
+        self.presenter.all_plots_removed_notifier = mock.MagicMock()
 
         self.presenter.clear_workspaces()
 
         self.assertEqual({}, model_dict)
         self.assertEqual({}, self.presenter.row_numbers)
+        self.assertEqual(1, self.presenter.all_plots_removed_notifier.notify_subscribers.call_count)
 
     def test_replace_workspace_tracked(self):
         model_dict = {"name1": "ws1", "name2": "ws2"}
@@ -183,9 +187,11 @@ class FittingDataPresenterTest(unittest.TestCase):
         self.presenter.row_numbers = data_presenter.TwoWayRowDict()
         self.presenter.row_numbers["name1"] = 0
         self.presenter.row_numbers["name2"] = 1
-        model_dict = {"name1": "ws1", "name2": "ws2"}
+        self.presenter.row_numbers["name3"] = 2
+        model_dict = {"name1": "ws1", "name2": "ws2", "name3": "ws3"}
         self.model.get_loaded_workspaces.return_value = model_dict
-        self.view.remove_selected.return_value = [0]
+        self.view.remove_selected.return_value = [0, 2]
+        self.presenter.plot_removed_notifier = mock.MagicMock()
 
         self.presenter._remove_selected_tracked_workspaces()
 
@@ -194,6 +200,60 @@ class FittingDataPresenterTest(unittest.TestCase):
         test_dict["name2"] = 0
         self.assertEqual(self.presenter.row_numbers, test_dict)
         self.assertEqual(model_dict, {"name2": "ws2"})
+        self.assertEqual(2, self.presenter.plot_removed_notifier.notify_subscribers.call_count)
+
+    def test_handle_table_cell_changed_checkbox_ticked(self):
+        mocked_table_item = mock.MagicMock()
+        mocked_table_item.checkState.return_value = 2
+        self.view.get_table_item.return_value = mocked_table_item
+        self.presenter.row_numbers = data_presenter.TwoWayRowDict()
+        self.presenter.row_numbers["name1"] = 0
+        self.presenter.row_numbers["name2"] = 1
+        model_dict = {"name1": "ws1", "name2": "ws2"}
+        self.model.get_loaded_workspaces.return_value = model_dict
+        self.presenter.plot_added_notifier = mock.MagicMock()
+        self.presenter.plot_removed_notifier = mock.MagicMock()
+
+        self.presenter._handle_table_cell_changed(0, 2)
+
+        self.assertEqual(1, self.presenter.plot_added_notifier.notify_subscribers.call_count)
+        self.presenter.plot_added_notifier.notify_subscribers.assert_any_call("ws1")
+        self.assertEqual(0, self.presenter.plot_removed_notifier.notify_subscribers.call_count)
+
+    def test_handle_table_cell_changed_checkbox_unticked(self):
+        mocked_table_item = mock.MagicMock()
+        mocked_table_item.checkState.return_value = 0
+        self.view.get_table_item.return_value = mocked_table_item
+        self.presenter.row_numbers = data_presenter.TwoWayRowDict()
+        self.presenter.row_numbers["name1"] = 0
+        self.presenter.row_numbers["name2"] = 1
+        model_dict = {"name1": "ws1", "name2": "ws2"}
+        self.model.get_loaded_workspaces.return_value = model_dict
+        self.presenter.plot_added_notifier = mock.MagicMock()
+        self.presenter.plot_removed_notifier = mock.MagicMock()
+
+        self.presenter._handle_table_cell_changed(0, 2)
+
+        self.assertEqual(0, self.presenter.plot_added_notifier.notify_subscribers.call_count)
+        self.assertEqual(1, self.presenter.plot_removed_notifier.notify_subscribers.call_count)
+        self.presenter.plot_removed_notifier.notify_subscribers.assert_any_call("ws1")
+
+    def test_handle_table_cell_changed_other_element(self):
+        mocked_table_item = mock.MagicMock()
+        mocked_table_item.checkState.return_value = 2
+        self.view.get_table_item.return_value = mocked_table_item
+        self.presenter.row_numbers = data_presenter.TwoWayRowDict()
+        self.presenter.row_numbers["name1"] = 0
+        self.presenter.row_numbers["name2"] = 1
+        model_dict = {"name1": "ws1", "name2": "ws2"}
+        self.model.get_loaded_workspaces.return_value = model_dict
+        self.presenter.plot_added_notifier = mock.MagicMock()
+        self.presenter.plot_removed_notifier = mock.MagicMock()
+
+        self.presenter._handle_table_cell_changed(1, 1)
+
+        self.assertEqual(0, self.presenter.plot_added_notifier.notify_subscribers.call_count)
+        self.assertEqual(0, self.presenter.plot_removed_notifier.notify_subscribers.call_count)
 
 
 if __name__ == '__main__':
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/presenter.py b/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/presenter.py
index dbaaac5df17..b4ccf0c84e1 100644
--- a/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/presenter.py
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/presenter.py
@@ -15,3 +15,10 @@ class FittingPresenter(object):
 
         self.data_widget = FittingDataWidget(self.view, view=self.view.get_data_widget())
         self.plot_widget = FittingPlotWidget(self.view, view=self.view.get_plot_widget())
+
+        self.data_widget.presenter.plot_removed_notifier.add_subscriber(
+            self.plot_widget.workspace_removed_observer)
+        self.data_widget.presenter.plot_added_notifier.add_subscriber(
+            self.plot_widget.workspace_added_observer)
+        self.data_widget.presenter.all_plots_removed_notifier.add_subscriber(
+            self.plot_widget.all_workspaces_removed_observer)
-- 
GitLab