Exemple #1
0
    def __init__(self, facility, view=None):
        super(RunTabPresenter, self).__init__()
        self._facility = facility
        # Logger
        self.sans_logger = Logger("SANS")
        # Name of grpah to output to
        self.output_graph = 'SANS-Latest'
        self.progress = 0

        # Models that are being used by the presenter
        self._state_model = None
        self._table_model = TableModel()

        # Presenter needs to have a handle on the view since it delegates it
        self._view = None
        self.set_view(view)
        self._processing = False
        self.work_handler = WorkHandler()
        self.batch_process_runner = BatchProcessRunner(self.notify_progress, self.on_processing_finished, self.on_processing_error)

        # File information for the first input
        self._file_information = None
        self._clipboard = []

        # Settings diagnostic tab presenter
        self._settings_diagnostic_tab_presenter = SettingsDiagnosticPresenter(self)

        # Masking table presenter
        self._masking_table_presenter = MaskingTablePresenter(self)

        # Beam centre presenter
        self._beam_centre_presenter = BeamCentrePresenter(self, WorkHandler, BeamCentreModel, SANSCentreFinder)

        # Workspace Diagnostic page presenter
        self._workspace_diagnostic_presenter = DiagnosticsPagePresenter(self, WorkHandler, run_integral, create_state, self._facility)
    def __init__(self, facility, view=None):
        super(RunTabPresenter, self).__init__()
        self._facility = facility

        # Logger
        self.sans_logger = Logger("SANS")

        # Presenter needs to have a handle on the view since it delegates it
        self._view = None
        self.set_view(view)

        # Models that are being used by the presenter
        self._state_model = None
        self._table_model = None

        # Due to the nature of the DataProcessorWidget we need to provide an algorithm with at least one input
        # workspace and at least one output workspace. Our SANS state approach is not compatible with this. Hence
        # we provide a dummy workspace which is not used. We keep it invisible on the ADS and delete it when the
        # main_presenter is deleted.
        # This is not a nice solution but in line with the SANS dummy algorithm approach that we have provided
        # for the
        self._create_dummy_input_workspace()

        # File information for the first input
        self._file_information = None

        # Settings diagnostic tab presenter
        self._settings_diagnostic_tab_presenter = SettingsDiagnosticPresenter(
            self)

        # Masking table presenter
        self._masking_table_presenter = MaskingTablePresenter(self)

        # Beam centre presenter
        self._beam_centre_presenter = BeamCentrePresenter(self)
Exemple #3
0
    def test_that_set_scaling_is_not_called_when_file_information_does_not_exist(self):
        self.parent_presenter._file_information = None
        self.presenter = BeamCentrePresenter(self.parent_presenter, self.WorkHandler, self.BeamCentreModel,
                                             self.SANSCentreFinder)
        self.presenter.set_view(self.view)

        self.presenter.on_update_rows()
        self.presenter._beam_centre_model.set_scaling.assert_not_called()
    def test_that_set_scaling_is_not_called_when_file_information_does_not_exist(
            self, beam_centre_model_mock):
        self.parent_presenter._file_information = None  #mock.MagicMock(return_value = None)
        self.presenter = BeamCentrePresenter(self.parent_presenter)
        self.presenter.set_view(self.view)

        self.presenter.on_update_rows()
        self.presenter._beam_centre_model.set_scaling.assert_not_called()
    def test_that_set_scaling_is_called_on_update_rows_when_file_information_exists(
            self, beam_centre_model_mock):
        self.presenter = BeamCentrePresenter(self.parent_presenter)
        self.presenter.set_view(self.view)

        self.presenter.on_update_rows()
        self.presenter._beam_centre_model.set_scaling.assert_called_once_with(
            SANSInstrument.LARMOR)
 def setUp(self):
     self.parent_presenter = create_run_tab_presenter_mock(
         use_fake_state=False)
     self.parent_presenter._file_information = mock.MagicMock()
     self.parent_presenter._file_information.get_instrument = mock.MagicMock(
         return_value=SANSInstrument.LARMOR)
     self.presenter = BeamCentrePresenter(self.parent_presenter)
     self.view = create_mock_beam_centre_tab()
Exemple #7
0
 def setUp(self):
     self.parent_presenter = create_run_tab_presenter_mock(use_fake_state = False)
     self.view = create_mock_beam_centre_tab()
     self.WorkHandler = mock.MagicMock()
     self.BeamCentreModel = mock.MagicMock()
     self.SANSCentreFinder = mock.MagicMock()
     self.presenter = BeamCentrePresenter(self.parent_presenter, self.SANSCentreFinder,
                                          work_handler=self.WorkHandler, beam_centre_model=self.BeamCentreModel)
     self.presenter.connect_signals = mock.Mock()
     self.presenter.set_view(self.view)
Exemple #8
0
 def setUp(self):
     self.parent_presenter = create_run_tab_presenter_mock(use_fake_state = False)
     self.parent_presenter._file_information = mock.MagicMock()
     self.parent_presenter._file_information.get_instrument = mock.MagicMock(return_value = SANSInstrument.LARMOR)
     self.view = create_mock_beam_centre_tab()
     self.WorkHandler = mock.MagicMock()
     self.BeamCentreModel = mock.MagicMock()
     self.SANSCentreFinder = mock.MagicMock()
     self.presenter = BeamCentrePresenter(self.parent_presenter, self.WorkHandler, self.BeamCentreModel,
                                          self.SANSCentreFinder)
     self.presenter.connect_signals = mock.Mock()
    def test_that_on_run_clicked_calls_find_beam_centre(
            self, work_handler_mock):
        self.presenter = BeamCentrePresenter(self.parent_presenter)
        self.presenter.set_view(self.view)
        self.presenter.on_run_clicked()

        self.assertTrue(work_handler_mock.return_value.process.call_count == 1)
        self.assertTrue(work_handler_mock.return_value.process.call_args[0][1]
                        == find_beam_centre)
        self.assertTrue(work_handler_mock.return_value.process.call_args[0][3]
                        == self.presenter._beam_centre_model)
 def setUp(self):
     self.parent_presenter = create_run_tab_presenter_mock(use_fake_state = False)
     self.parent_presenter._file_information = mock.MagicMock()
     self.parent_presenter._file_information.get_instrument = mock.MagicMock(return_value = SANSInstrument.LARMOR)
     self.view = create_mock_beam_centre_tab()
     self.WorkHandler = mock.MagicMock()
     self.BeamCentreModel = mock.MagicMock()
     self.SANSCentreFinder = mock.MagicMock()
     self.presenter = BeamCentrePresenter(self.parent_presenter, self.WorkHandler, self.BeamCentreModel,
                                          self.SANSCentreFinder)
Exemple #11
0
class BeamCentrePresenterTest(unittest.TestCase):

    def setUp(self):
        self.parent_presenter = create_run_tab_presenter_mock(use_fake_state = False)
        self.view = create_mock_beam_centre_tab()
        self.WorkHandler = mock.MagicMock()
        self.BeamCentreModel = mock.MagicMock()
        self.SANSCentreFinder = mock.MagicMock()
        self.presenter = BeamCentrePresenter(self.parent_presenter, self.SANSCentreFinder,
                                             work_handler=self.WorkHandler, beam_centre_model=self.BeamCentreModel)
        self.presenter.connect_signals = mock.Mock()
        self.presenter.set_view(self.view)

    def test_that_on_run_clicked_calls_find_beam_centre(self):
        self.presenter.on_run_clicked()

        self.assertEqual(self.presenter._work_handler.process.call_count, 1)
        self.assertEqual(self.presenter._work_handler.process.call_args[0][1],  self.presenter._beam_centre_model.find_beam_centre)

    def test_that_on_run_clicked_updates_model_from_view(self):
        self.view.left_right = False
        self.view.lab_pos_1 = 100
        self.view.lab_pos_2 = -100
        self.presenter.set_view(self.view)
        self.presenter._beam_centre_model.scale_1 = 1000
        self.presenter._beam_centre_model.scale_2 = 1000

        self.presenter.on_run_clicked()

        self.assertFalse(self.presenter._beam_centre_model.left_right)
        self.assertEqual(self.presenter._beam_centre_model.lab_pos_1, 0.1)
        self.assertEqual(self.presenter._beam_centre_model.lab_pos_2, -0.1)

    def test_on_update_rows_skips_no_rows(self):
        self.parent_presenter.num_rows.return_value = 0
        self.assertFalse(self.WorkHandler.process.called)
        self.presenter.on_update_rows()

    def test_reset_inst_defaults_called_on_update_rows(self):
        self.parent_presenter.num_rows.return_value = 1
        self.parent_presenter.instrument = SANSInstrument.SANS2D

        self.presenter.on_update_rows()

        self.BeamCentreModel.reset_inst_defaults.assert_called_with(SANSInstrument.SANS2D)

    def test_that_set_scaling_is_called_on_update_instrument(self):
        self.presenter.set_view(self.view)

        self.presenter.on_update_instrument(SANSInstrument.LARMOR)
        self.presenter._beam_centre_model.set_scaling.assert_called_once_with(SANSInstrument.LARMOR)

    def test_that_view_on_update_instrument_is_called_on_update_instrument(self):
        self.presenter.set_view(self.view)

        self.presenter.on_update_instrument(SANSInstrument.LARMOR)
        self.view.on_update_instrument.assert_called_once_with(SANSInstrument.LARMOR)

    def test_that_on_processing_finished_updates_view(self):
        self.presenter.set_view(self.view)

        self.BeamCentreModel.lab_pos_1 = 1
        self.BeamCentreModel.lab_pos_2 = 2
        self.BeamCentreModel.hab_pos_1 = 3
        self.BeamCentreModel.hab_pos_2 = 4

        scale_1 = 1000
        scale_2 = 2000
        self.BeamCentreModel.scale_1 = scale_1
        self.BeamCentreModel.scale_2 = scale_2

        self.presenter.on_processing_finished_centre_finder()
        self.assertEqual(self.BeamCentreModel.lab_pos_1 * scale_1, self.view.lab_pos_1)
        self.assertEqual(self.BeamCentreModel.lab_pos_2 * scale_2, self.view.lab_pos_2)

        self.assertEqual(self.BeamCentreModel.hab_pos_1 * scale_1, self.view.hab_pos_1)
        self.assertEqual(self.BeamCentreModel.hab_pos_2 * scale_2, self.view.hab_pos_2)
        self.view.set_run_button_to_normal.assert_called_once_with()

    def test_that_update_hab_selected_enabled_hab_and_disabled_lab(self):
        self.presenter.set_view(self.view)
        self.presenter.update_hab_selected()

        # Check that the model has been updated
        self.assertTrue(self.presenter._beam_centre_model.update_hab)
        self.assertFalse(self.presenter._beam_centre_model.update_lab)

        # Check that we called a method to enable the HAB and a method to disable the LAB
        self.presenter._view.enable_update_hab.assert_called_once_with(True)
        self.presenter._view.enable_update_lab.assert_called_once_with(False)

    def test_that_update_lab_selected_enabled_lab_and_disabled_hab(self):
        self.presenter.set_view(self.view)
        self.presenter.update_lab_selected()

        # Check that the model has been updated
        self.assertFalse(self.presenter._beam_centre_model.update_hab)
        self.assertTrue(self.presenter._beam_centre_model.update_lab)

        # Check that we called a method to disable the HAB and a method to enable the LAB
        self.presenter._view.enable_update_hab.assert_called_once_with(False)
        self.presenter._view.enable_update_lab.assert_called_once_with(True)

    def test_that_update_all_selected_enabled_hab_and_lab(self):
        self.presenter.set_view(self.view)
        self.presenter.update_all_selected()

        # Check that the model has been updated
        self.assertTrue(self.presenter._beam_centre_model.update_hab)
        self.assertTrue(self.presenter._beam_centre_model.update_lab)

        # Check that we called a method to enable the HAB and a method to enable the LAB
        self.presenter._view.enable_update_hab.assert_called_once_with(True)
        self.presenter._view.enable_update_lab.assert_called_once_with(True)
class BeamCentrePresenterTest(unittest.TestCase):

    def setUp(self):
        self.parent_presenter = create_run_tab_presenter_mock(use_fake_state = False)
        self.parent_presenter._file_information = mock.MagicMock()
        self.parent_presenter._file_information.get_instrument = mock.MagicMock(return_value = SANSInstrument.LARMOR)
        self.view = create_mock_beam_centre_tab()
        self.WorkHandler = mock.MagicMock()
        self.BeamCentreModel = mock.MagicMock()
        self.SANSCentreFinder = mock.MagicMock()
        self.presenter = BeamCentrePresenter(self.parent_presenter, self.WorkHandler, self.BeamCentreModel,
                                             self.SANSCentreFinder)

    def test_that_on_run_clicked_calls_find_beam_centre(self):
        self.presenter.set_view(self.view)
        self.presenter.on_run_clicked()

        self.assertEqual(self.presenter._work_handler.process.call_count, 1)
        self.assertTrue(self.presenter._work_handler.process.call_args[0][1] == self.presenter._beam_centre_model.find_beam_centre)

    def test_that_on_run_clicked_updates_model_from_view(self):
        self.view.left_right = False
        self.view.lab_pos_1 = 100
        self.view.lab_pos_2 = -100
        self.presenter.set_view(self.view)
        self.presenter._beam_centre_model.scale_1 = 1000
        self.presenter._beam_centre_model.scale_2 = 1000

        self.presenter.on_run_clicked()

        self.assertFalse(self.presenter._beam_centre_model.left_right)
        self.assertEqual(self.presenter._beam_centre_model.lab_pos_1, 0.1)
        self.assertEqual(self.presenter._beam_centre_model.lab_pos_2, -0.1)

    def test_that_set_options_is_called_on_update_rows(self):
        self.presenter.set_view(self.view)

        self.view.set_options.assert_called_once_with(self.presenter._beam_centre_model)

        self.presenter.on_update_rows()
        self.assertTrue(self.view.set_options.call_count == 2)

    def test_that_set_scaling_is_called_on_update_rows_when_file_information_exists(self):
        self.presenter.set_view(self.view)

        self.presenter.on_update_rows()
        self.presenter._beam_centre_model.set_scaling.assert_called_once_with(SANSInstrument.LARMOR)

    def test_that_set_scaling_is_not_called_when_file_information_does_not_exist(self):
        self.parent_presenter._file_information = None
        self.presenter = BeamCentrePresenter(self.parent_presenter, self.WorkHandler, self.BeamCentreModel,
                                             self.SANSCentreFinder)
        self.presenter.set_view(self.view)

        self.presenter.on_update_rows()
        self.presenter._beam_centre_model.set_scaling.assert_not_called()

    def test_that_on_processing_finished_updates_view_and_model(self):
        self.presenter.set_view(self.view)
        result = {'pos1': 0.1, 'pos2': -0.1}
        self.presenter._beam_centre_model.scale_1 = 1000
        self.presenter._beam_centre_model.scale_2 = 1000

        self.presenter.on_processing_finished_centre_finder(result)
        self.assertEqual(result['pos1'], self.presenter._beam_centre_model.lab_pos_1)
        self.assertEqual(result['pos2'], self.presenter._beam_centre_model.lab_pos_2)
        self.assertEqual(self.view.lab_pos_1, self.presenter._beam_centre_model.scale_1*result['pos1'])
        self.assertEqual(self.view.lab_pos_2, self.presenter._beam_centre_model.scale_2*result['pos2'])
        self.view.set_run_button_to_normal.assert_called_once_with()
Exemple #13
0
class RunTabPresenter(object):
    class ConcreteRunTabListener(SANSDataProcessorGui.RunTabListener):
        def __init__(self, presenter):
            super(RunTabPresenter.ConcreteRunTabListener, self).__init__()
            self._presenter = presenter

        def on_user_file_load(self):
            self._presenter.on_user_file_load()

        def on_mask_file_add(self):
            self._presenter.on_mask_file_add()

        def on_batch_file_load(self):
            self._presenter.on_batch_file_load()

        def on_processed_clicked(self):
            self._presenter.on_processed_clicked()

        def on_processing_finished(self):
            self._presenter.on_processing_finished()

        def on_data_changed(self):
            self._presenter.on_data_changed()

        def on_manage_directories(self):
            self._presenter.on_manage_directories()

    def __init__(self, facility, view=None):
        super(RunTabPresenter, self).__init__()
        self._facility = facility

        # Logger
        self.sans_logger = Logger("SANS")

        # Presenter needs to have a handle on the view since it delegates it
        self._view = None
        self.set_view(view)

        # Models that are being used by the presenter
        self._state_model = None
        self._table_model = None

        # Due to the nature of the DataProcessorWidget we need to provide an algorithm with at least one input
        # workspace and at least one output workspace. Our SANS state approach is not compatible with this. Hence
        # we provide a dummy workspace which is not used. We keep it invisible on the ADS and delete it when the
        # main_presenter is deleted.
        # This is not a nice solution but in line with the SANS dummy algorithm approach that we have provided
        # for the
        self._create_dummy_input_workspace()

        # File information for the first input
        self._file_information = None

        # Settings diagnostic tab presenter
        self._settings_diagnostic_tab_presenter = SettingsDiagnosticPresenter(
            self)

        # Masking table presenter
        self._masking_table_presenter = MaskingTablePresenter(self)

        # Beam centre presenter
        self._beam_centre_presenter = BeamCentrePresenter(self)

    def __del__(self):
        self._delete_dummy_input_workspace()

    def _default_gui_setup(self):
        """
        Provides a default setup of the GUI. This is important for the initial start up, when the view is being set.
        """
        # Set the possible reduction modes
        reduction_mode_list = get_reduction_mode_strings_for_gui()
        self._view.set_reduction_modes(reduction_mode_list)

        # Set the step type options for wavelength
        range_step_types = [
            RangeStepType.to_string(RangeStepType.Lin),
            RangeStepType.to_string(RangeStepType.Log)
        ]
        self._view.wavelength_step_type = range_step_types

        # Set the geometry options. This needs to include the option to read the sample shape from file.
        sample_shape = [
            "Read from file",
            SampleShape.to_string(SampleShape.CylinderAxisUp),
            SampleShape.to_string(SampleShape.Cuboid),
            SampleShape.to_string(SampleShape.CylinderAxisAlong)
        ]
        self._view.sample_shape = sample_shape

        # Set the q range
        self._view.q_1d_step_type = range_step_types
        self._view.q_xy_step_type = range_step_types

        # Set the fit options
        fit_types = [
            FitType.to_string(FitType.Linear),
            FitType.to_string(FitType.Logarithmic),
            FitType.to_string(FitType.Polynomial)
        ]
        self._view.transmission_sample_fit_type = fit_types
        self._view.transmission_can_fit_type = fit_types

    # ------------------------------------------------------------------------------------------------------------------
    # Table + Actions
    # ------------------------------------------------------------------------------------------------------------------
    def set_view(self, view):
        """
        Sets the view
        :param view: the view is the SANSDataProcessorGui. The presenter needs to access some of the API
        """
        if view is not None:
            self._view = view

            # Add a listener to the view
            listener = RunTabPresenter.ConcreteRunTabListener(self)
            self._view.add_listener(listener)

            # Default gui setup
            self._default_gui_setup()

            # Set appropriate view for the state diagnostic tab presenter
            self._settings_diagnostic_tab_presenter.set_view(
                self._view.settings_diagnostic_tab)

            # Set appropriate view for the masking table presenter
            self._masking_table_presenter.set_view(self._view.masking_table)

            # Set the appropriate view for the beam centre presenter
            self._beam_centre_presenter.set_view(self._view.beam_centre)

    def on_user_file_load(self):
        """
        Loads the user file. Populates the models and the view.
        """
        try:
            # 1. Get the user file path from the view
            user_file_path = self._view.get_user_file_path()

            if not user_file_path:
                return
            # 2. Get the full file path
            user_file_path = FileFinder.getFullPath(user_file_path)
            if not os.path.exists(user_file_path):
                raise RuntimeError(
                    "The user path {} does not exist. Make sure a valid user file path"
                    " has been specified.".format(user_file_path))

            # Clear out the current view
            self._view.reset_all_fields_to_default()

            # 3. Read and parse the user file
            user_file_reader = UserFileReader(user_file_path)
            user_file_items = user_file_reader.read_user_file()

            # 4. Populate the model
            self._state_model = StateGuiModel(user_file_items)
            # 5. Update the views.
            self._update_view_from_state_model()

            # 6. Perform calls on child presenters
            self._masking_table_presenter.on_update_rows()
            self._settings_diagnostic_tab_presenter.on_update_rows()
            self._beam_centre_presenter.on_update_rows()

        except Exception as e:
            self.sans_logger.error(
                "Loading of the user file failed. Ensure that the path to your files has been added "
                "to the Mantid search directories! See here for more details: {}"
                .format(str(e)))

    def on_batch_file_load(self):
        """
        Loads a batch file and populates the batch table based on that.
        """
        try:
            # 1. Get the batch file from the view
            batch_file_path = self._view.get_batch_file_path()

            if not batch_file_path:
                return

            if not os.path.exists(batch_file_path):
                raise RuntimeError(
                    "The batch file path {} does not exist. Make sure a valid batch file path"
                    " has been specified.".format(batch_file_path))

            # 2. Read the batch file
            batch_file_parser = BatchCsvParser(batch_file_path)
            parsed_rows = batch_file_parser.parse_batch_file()
            # 3. Clear the table
            self._view.clear_table()

            # 4. Populate the table
            for row in parsed_rows:
                self._populate_row_in_table(row)

            # 5. Populate the selected instrument and the correct detector selection
            self._setup_instrument_specific_settings()

            # 6. Perform calls on child presenters
            self._masking_table_presenter.on_update_rows()
            self._settings_diagnostic_tab_presenter.on_update_rows()
            self._beam_centre_presenter.on_update_rows()

        except RuntimeError as e:
            self.sans_logger.error(
                "Loading of the batch file failed. Ensure that the path to your files has been added"
                " to the Mantid search directories! See here for more details: {}"
                .format(str(e)))

    def on_data_changed(self):
        # 1. Populate the selected instrument and the correct detector selection
        self._setup_instrument_specific_settings()

        # 2. Perform calls on child presenters
        self._masking_table_presenter.on_update_rows()
        self._settings_diagnostic_tab_presenter.on_update_rows()
        self._beam_centre_presenter.on_update_rows()

    def on_processed_clicked(self):
        """
        Prepares the batch reduction.

        0. Validate rows and create dummy workspace if it does not exist
        1. Sets up the states
        2. Adds a dummy input workspace
        3. Adds row index information
        """

        try:
            self.sans_logger.information("Starting processing of batch table.")
            # 0. Validate rows
            self._create_dummy_input_workspace()
            self._validate_rows()

            # 1. Set up the states and convert them into property managers
            states = self.get_states()
            if not states:
                raise RuntimeError(
                    "There seems to have been an issue with setting the states. Make sure that a user file"
                    " has been loaded")
            property_manager_service = PropertyManagerService()
            property_manager_service.add_states_to_pmds(states)

            # 2. Add dummy input workspace to Options column
            self._remove_dummy_workspaces_and_row_index()
            self._set_dummy_workspace()

            # 3. Add dummy row index to Options column
            self._set_indices()
        except Exception as e:
            self._view.halt_process_flag()
            self.sans_logger.error("Process halted due to: {}".format(str(e)))

    def on_processing_finished(self):
        self._remove_dummy_workspaces_and_row_index()

    def on_manage_directories(self):
        self._view.show_directory_manager()

    def on_mask_file_add(self):
        """
        We get the added mask file name and add it to the list of masks
        """
        new_mask_file = self._view.get_mask_file()
        if not new_mask_file:
            return
        new_mask_file_full_path = FileFinder.getFullPath(new_mask_file)
        if not new_mask_file_full_path:
            return

        # Add the new mask file to state model
        mask_files = self._state_model.mask_files

        mask_files.append(new_mask_file)
        self._state_model.mask_files = mask_files

        # Make sure that the sub-presenters are up to date with this change
        self._masking_table_presenter.on_update_rows()
        self._settings_diagnostic_tab_presenter.on_update_rows()
        self._beam_centre_presenter.on_update_rows()

    def _add_to_hidden_options(self, row, property_name, property_value):
        """
        Adds a new property to the Hidden Options column

        @param row: The row where the Options column is being altered
        @param property_name: The property name on the GUI algorithm.
        @param property_value: The value which is being set for the property.
        """
        entry = property_name + OPTIONS_EQUAL + str(property_value)
        options = self._get_hidden_options(row)
        if options:
            options += OPTIONS_SEPARATOR + entry
        else:
            options = entry
        self._set_hidden_options(options, row)

    def _set_hidden_options(self, value, row):
        self._view.set_cell(value, row, HIDDEN_OPTIONS_INDEX)

    def _get_options(self, row):
        return self._view.get_cell(row, OPTIONS_INDEX, convert_to=str)

    def _get_hidden_options(self, row):
        return self._view.get_cell(row, HIDDEN_OPTIONS_INDEX, convert_to=str)

    def is_empty_row(self, row):
        """
        Checks if a row has no entries. These rows will be ignored.
        :param row: the row index
        :return: True if the row is empty.
        """
        indices = range(OPTIONS_INDEX + 1)
        for index in indices:
            cell_value = self._view.get_cell(row, index, convert_to=str)
            if cell_value:
                return False
        return True

    def _remove_from_hidden_options(self, row, property_name):
        """
        Remove the entries in the hidden options column
        :param row: the row index
        :param property_name: the property name which is to be removed
        """
        options = self._get_hidden_options(row)
        # Remove the property entry and the value
        individual_options = options.split(",")
        clean_options = []
        for individual_option in individual_options:
            if property_name not in individual_option:
                clean_options.append(individual_option)
        clean_options = ",".join(clean_options)
        self._set_hidden_options(clean_options, row)

    def _validate_rows(self):
        """
        Validation of the rows. A minimal setup requires that ScatterSample is set.
        """
        # If SampleScatter is empty, then don't run the reduction.
        # We allow empty rows for now, since we cannot remove them from Python.
        number_of_rows = self._view.get_number_of_rows()
        for row in range(number_of_rows):
            if not self.is_empty_row(row):
                sample_scatter = self._view.get_cell(row, 0)
                if not sample_scatter:
                    raise RuntimeError(
                        "Row {} has not SampleScatter specified. Please correct this."
                        .format(row))

    def get_processing_options(self):
        """
        Creates a processing string for the data processor widget

        :return: A processing string for the data processor widget
        """
        global_options = ""

        # Check if optimizations should be used
        optimization_selection = "UseOptimizations=1" if self._view.use_optimizations else "UseOptimizations=0"
        global_options += optimization_selection

        # Get the output mode
        output_mode = self._view.output_mode
        output_mode_selection = "OutputMode=" + OutputMode.to_string(
            output_mode)
        global_options += ","
        global_options += output_mode_selection

        return global_options

    # ------------------------------------------------------------------------------------------------------------------
    # Controls
    # ------------------------------------------------------------------------------------------------------------------
    def disable_controls(self):
        """
        Disable all input fields and buttons during the execution of the reduction.
        """
        # TODO: think about enabling and disable some controls during reduction
        pass

    def enable_controls(self):
        """
        Enable all input fields and buttons after the execution has completed.
        """
        # TODO: think about enabling and disable some controls during reduction
        pass

    # ------------------------------------------------------------------------------------------------------------------
    # Table Model and state population
    # ------------------------------------------------------------------------------------------------------------------
    def get_states(self, row_index=None):
        """
        Gathers the state information for all rows.
        :param row_index: if a single row is selected, then only this row is returned, else all the state for all
                             rows is returned
        :return: a list of states
        """
        start_time_state_generation = time.time()

        # 1. Update the state model
        state_model_with_view_update = self._get_state_model_with_view_update()

        # 2. Update the table model
        table_model = self._get_table_model()

        # 3. Go through each row and construct a state object
        if table_model and state_model_with_view_update:
            states = self._create_states(state_model_with_view_update,
                                         table_model, row_index)
        else:
            states = None
        stop_time_state_generation = time.time()
        time_taken = stop_time_state_generation - start_time_state_generation
        self.sans_logger.information(
            "The generation of all states took {}s".format(time_taken))
        return states

    def get_row_indices(self):
        """
        Gets the indices of row which are not empty.
        :return: a list of row indices.
        """
        row_indices_which_are_not_empty = []
        number_of_rows = self._view.get_number_of_rows()
        for row in range(number_of_rows):
            if not self.is_empty_row(row):
                row_indices_which_are_not_empty.append(row)
        return row_indices_which_are_not_empty

    def get_state_for_row(self, row_index):
        """
        Creates the state for a particular row.
        :param row_index: the row index
        :return: a state if the index is valid and there is a state else None
        """
        states = self.get_states(row_index=row_index)
        if states is None:
            self.sans_logger.warning(
                "There does not seem to be data for a row {}.".format(
                    row_index))
            return None

        if row_index in list(states.keys()):
            if states:
                return states[row_index]
        return None

    def _update_view_from_state_model(self):
        # Front tab view
        self._set_on_view("zero_error_free")
        self._set_on_view("save_types")
        self._set_on_view("compatibility_mode")

        self._set_on_view("merge_scale")
        self._set_on_view("merge_shift")
        self._set_on_view("merge_scale_fit")
        self._set_on_view("merge_shift_fit")
        self._set_on_view("merge_q_range_start")
        self._set_on_view("merge_q_range_stop")

        # Settings tab view
        self._set_on_view("reduction_dimensionality")
        self._set_on_view("reduction_mode")
        self._set_on_view("event_slices")
        self._set_on_view("event_binning")
        self._set_on_view("merge_mask")

        self._set_on_view("wavelength_step_type")
        self._set_on_view("wavelength_min")
        self._set_on_view("wavelength_max")
        self._set_on_view("wavelength_step")

        self._set_on_view("absolute_scale")
        self._set_on_view("sample_shape")
        self._set_on_view("sample_height")
        self._set_on_view("sample_width")
        self._set_on_view("sample_thickness")
        self._set_on_view("z_offset")

        # Adjustment tab
        self._set_on_view("normalization_incident_monitor")
        self._set_on_view("normalization_interpolate")

        self._set_on_view("transmission_incident_monitor")
        self._set_on_view("transmission_interpolate")
        self._set_on_view("transmission_roi_files")
        self._set_on_view("transmission_mask_files")
        self._set_on_view("transmission_radius")
        self._set_on_view("transmission_monitor")
        self._set_on_view("transmission_mn_shift")
        self._set_on_view("show_transmission")

        self._set_on_view_transmission_fit()

        self._set_on_view("pixel_adjustment_det_1")
        self._set_on_view("pixel_adjustment_det_2")
        self._set_on_view("wavelength_adjustment_det_1")
        self._set_on_view("wavelength_adjustment_det_2")

        # Q tab
        self._set_on_view_q_rebin_string()
        self._set_on_view("q_xy_max")
        self._set_on_view("q_xy_step")
        self._set_on_view("q_xy_step_type")

        self._set_on_view("gravity_on_off")
        self._set_on_view("gravity_extra_length")

        self._set_on_view("use_q_resolution")
        self._set_on_view_q_resolution_aperture()
        self._set_on_view("q_resolution_delta_r")
        self._set_on_view("q_resolution_collimation_length")
        self._set_on_view("q_resolution_moderator_file")

        # Mask
        self._set_on_view("phi_limit_min")
        self._set_on_view("phi_limit_max")
        self._set_on_view("phi_limit_use_mirror")
        self._set_on_view("radius_limit_min")
        self._set_on_view("radius_limit_max")

        # Beam Centre
        self._beam_centre_presenter.set_on_view('lab_pos_1', self._state_model)
        self._beam_centre_presenter.set_on_view('lab_pos_2', self._state_model)
        self._beam_centre_presenter.set_on_view('hab_pos_1', self._state_model)
        self._beam_centre_presenter.set_on_view('hab_pos_2', self._state_model)

    def _set_on_view_transmission_fit_sample_settings(self):
        # Set transmission_sample_use_fit
        fit_type = self._state_model.transmission_sample_fit_type
        use_fit = fit_type is not FitType.NoFit
        self._view.transmission_sample_use_fit = use_fit

        # Set the polynomial order for sample
        polynomial_order = self._state_model.transmission_sample_polynomial_order if fit_type is FitType.Polynomial else 2  # noqa
        self._view.transmission_sample_polynomial_order = polynomial_order

        # Set the fit type for the sample
        fit_type = fit_type if fit_type is not FitType.NoFit else FitType.Linear
        self._view.transmission_sample_fit_type = fit_type

        # Set the wavelength
        wavelength_min = self._state_model.transmission_sample_wavelength_min
        wavelength_max = self._state_model.transmission_sample_wavelength_max
        if wavelength_min and wavelength_max:
            self._view.transmission_sample_use_wavelength = True
            self._view.transmission_sample_wavelength_min = wavelength_min
            self._view.transmission_sample_wavelength_max = wavelength_max

    def _set_on_view_transmission_fit(self):
        # Steps for adding the transmission fit to the view
        # 1. Check if individual settings exist. If so then set the view to separate, else set them to both
        # 2. Apply the settings
        separate_settings = self._state_model.has_transmission_fit_got_separate_settings_for_sample_and_can(
        )
        self._view.set_fit_selection(use_separate=separate_settings)

        if separate_settings:
            self._set_on_view_transmission_fit_sample_settings()

            # Set transmission_sample_can_fit
            fit_type_can = self._state_model.transmission_can_fit_type()
            use_can_fit = fit_type_can is FitType.NoFit
            self._view.transmission_can_use_fit = use_can_fit

            # Set the polynomial order for can
            polynomial_order_can = self._state_model.transmission_can_polynomial_order if fit_type_can is FitType.Polynomial else 2  # noqa
            self._view.transmission_can_polynomial_order = polynomial_order_can

            # Set the fit type for the can
            fit_type_can = fit_type_can if fit_type_can is not FitType.NoFit else FitType.Linear
            self.transmission_can_fit_type = fit_type_can

            # Set the wavelength
            wavelength_min = self._state_model.transmission_can_wavelength_min
            wavelength_max = self._state_model.transmission_can_wavelength_max
            if wavelength_min and wavelength_max:
                self._view.transmission_can_use_wavelength = True
                self._view.transmission_can_wavelength_min = wavelength_min
                self._view.transmission_can_wavelength_max = wavelength_max
        else:
            self._set_on_view_transmission_fit_sample_settings()

    def _set_on_view_q_resolution_aperture(self):
        self._set_on_view("q_resolution_source_a")
        self._set_on_view("q_resolution_sample_a")
        self._set_on_view("q_resolution_source_h")
        self._set_on_view("q_resolution_sample_h")
        self._set_on_view("q_resolution_source_w")
        self._set_on_view("q_resolution_sample_w")

        # If we have h1, h2, w1, and w2 selected then we want to select the rectangular aperture.
        is_rectangular = self._state_model.q_resolution_source_h and self._state_model.q_resolution_sample_h and \
                         self._state_model.q_resolution_source_w and self._state_model.q_resolution_sample_w  # noqa
        self._view.set_q_resolution_shape_to_rectangular(is_rectangular)

    def _set_on_view_q_rebin_string(self):
        """
        Maps the q_1d_rebin_string of the model to the q_1d_step and q_1d_step_type property of the view.
        """
        rebin_string = self._state_model.q_1d_rebin_string
        # Extract the min, max and step and step type from the rebin string
        elements = rebin_string.split(",")
        # If we have three elements then we want to set only the
        if len(elements) == 3:
            step_element = float(elements[1])
            step = abs(step_element)
            step_type = RangeStepType.Lin if step_element >= 0 else RangeStepType.Log

            # Set on the view
            self._view.q_1d_min_or_rebin_string = float(elements[0])
            self._view.q_1d_max = float(elements[2])
            self._view.q_1d_step = step
            self._view.q_1d_step_type = step_type
        else:
            # Set the rebin string
            self._view.q_1d_min_or_rebin_string = rebin_string
            self._view.q_1d_step_type = self._view.VARIABLE

    def _set_on_view(self, attribute_name):
        attribute = getattr(self._state_model, attribute_name)
        if attribute or isinstance(
                attribute, bool
        ):  # We need to be careful here. We don't want to set empty strings, or None, but we want to set boolean values. # noqa
            setattr(self._view, attribute_name, attribute)

    def _set_on_view_with_view(self, attribute_name, view):
        attribute = getattr(self._state_model, attribute_name)
        if attribute or isinstance(
                attribute, bool
        ):  # We need to be careful here. We don't want to set empty strings, or None, but we want to set boolean values. # noqa
            setattr(view, attribute_name, attribute)

    def _get_state_model_with_view_update(self):
        """
        Goes through all sub presenters and update the state model based on the views.

        Note that at the moment we have set up the view and the model such that the name of a property must be the same
        in the view and the model. This can be easily changed, but it also provides a good cohesion.
        """
        state_model = copy.deepcopy(self._state_model)

        # If we don't have a state model then return None
        if state_model is None:
            return state_model
        # Run tab view
        self._set_on_state_model("zero_error_free", state_model)
        self._set_on_state_model("save_types", state_model)
        self._set_on_state_model("compatibility_mode", state_model)
        self._set_on_state_model("merge_scale", state_model)
        self._set_on_state_model("merge_shift", state_model)
        self._set_on_state_model("merge_scale_fit", state_model)
        self._set_on_state_model("merge_shift_fit", state_model)
        self._set_on_state_model("merge_q_range_start", state_model)
        self._set_on_state_model("merge_q_range_stop", state_model)
        self._set_on_state_model("merge_mask", state_model)
        self._set_on_state_model("merge_max", state_model)
        self._set_on_state_model("merge_min", state_model)

        # Settings tab
        self._set_on_state_model("reduction_dimensionality", state_model)
        self._set_on_state_model("reduction_mode", state_model)
        self._set_on_state_model("event_slices", state_model)
        self._set_on_state_model("event_binning", state_model)

        self._set_on_state_model("wavelength_step_type", state_model)
        self._set_on_state_model("wavelength_min", state_model)
        self._set_on_state_model("wavelength_max", state_model)
        self._set_on_state_model("wavelength_step", state_model)

        self._set_on_state_model("absolute_scale", state_model)
        self._set_on_state_model("sample_shape", state_model)
        self._set_on_state_model("sample_height", state_model)
        self._set_on_state_model("sample_width", state_model)
        self._set_on_state_model("sample_thickness", state_model)
        self._set_on_state_model("z_offset", state_model)

        # Adjustment tab
        self._set_on_state_model("normalization_incident_monitor", state_model)
        self._set_on_state_model("normalization_interpolate", state_model)

        self._set_on_state_model("transmission_incident_monitor", state_model)
        self._set_on_state_model("transmission_interpolate", state_model)
        self._set_on_state_model("transmission_roi_files", state_model)
        self._set_on_state_model("transmission_mask_files", state_model)
        self._set_on_state_model("transmission_radius", state_model)
        self._set_on_state_model("transmission_monitor", state_model)
        self._set_on_state_model("transmission_mn_shift", state_model)
        self._set_on_state_model("show_transmission", state_model)

        self._set_on_state_model_transmission_fit(state_model)

        self._set_on_state_model("pixel_adjustment_det_1", state_model)
        self._set_on_state_model("pixel_adjustment_det_2", state_model)
        self._set_on_state_model("wavelength_adjustment_det_1", state_model)
        self._set_on_state_model("wavelength_adjustment_det_2", state_model)

        # Q tab
        self._set_on_state_model_q_1d_rebin_string(state_model)
        self._set_on_state_model("q_xy_max", state_model)
        self._set_on_state_model("q_xy_step", state_model)
        self._set_on_state_model("q_xy_step_type", state_model)

        self._set_on_state_model("gravity_on_off", state_model)
        self._set_on_state_model("gravity_extra_length", state_model)

        self._set_on_state_model("use_q_resolution", state_model)
        self._set_on_state_model("q_resolution_source_a", state_model)
        self._set_on_state_model("q_resolution_sample_a", state_model)
        self._set_on_state_model("q_resolution_source_h", state_model)
        self._set_on_state_model("q_resolution_sample_h", state_model)
        self._set_on_state_model("q_resolution_source_w", state_model)
        self._set_on_state_model("q_resolution_sample_w", state_model)
        self._set_on_state_model("q_resolution_delta_r", state_model)
        self._set_on_state_model("q_resolution_collimation_length",
                                 state_model)
        self._set_on_state_model("q_resolution_moderator_file", state_model)

        # Mask
        self._set_on_state_model("phi_limit_min", state_model)
        self._set_on_state_model("phi_limit_max", state_model)
        self._set_on_state_model("phi_limit_use_mirror", state_model)
        self._set_on_state_model("radius_limit_min", state_model)
        self._set_on_state_model("radius_limit_max", state_model)

        # Beam Centre
        self._beam_centre_presenter.set_on_state_model("lab_pos_1",
                                                       state_model)
        self._beam_centre_presenter.set_on_state_model("lab_pos_2",
                                                       state_model)

        return state_model

    def _set_on_state_model_transmission_fit(self, state_model):
        # Behaviour depends on the selection of the fit
        if self._view.use_same_transmission_fit_setting_for_sample_and_can():
            use_fit = self._view.transmission_sample_use_fit
            fit_type = self._view.transmission_sample_fit_type
            polynomial_order = self._view.transmission_sample_polynomial_order
            state_model.transmission_sample_fit_type = fit_type if use_fit else FitType.NoFit
            state_model.transmission_can_fit_type = fit_type if use_fit else FitType.NoFit
            state_model.transmission_sample_polynomial_order = polynomial_order
            state_model.transmission_can_polynomial_order = polynomial_order

            # Wavelength settings
            if self._view.transmission_sample_use_wavelength:
                wavelength_min = self._view.transmission_sample_wavelength_min
                wavelength_max = self._view.transmission_sample_wavelength_max
                state_model.transmission_sample_wavelength_min = wavelength_min
                state_model.transmission_sample_wavelength_max = wavelength_max
                state_model.transmission_can_wavelength_min = wavelength_min
                state_model.transmission_can_wavelength_max = wavelength_max
        else:
            # Sample
            use_fit_sample = self._view.transmission_sample_use_fit
            fit_type_sample = self._view.transmission_sample_fit_type
            polynomial_order_sample = self._view.transmission_sample_polynomial_order
            state_model.transmission_sample_fit_type = fit_type_sample if use_fit_sample else FitType.NoFit
            state_model.transmission_sample_polynomial_order = polynomial_order_sample

            # Wavelength settings
            if self._view.transmission_sample_use_wavelength:
                wavelength_min = self._view.transmission_sample_wavelength_min
                wavelength_max = self._view.transmission_sample_wavelength_max
                state_model.transmission_sample_wavelength_min = wavelength_min
                state_model.transmission_sample_wavelength_max = wavelength_max

            # Can
            use_fit_can = self._view.transmission_can_use_fit
            fit_type_can = self._view.transmission_can_fit_type
            polynomial_order_can = self._view.transmission_can_polynomial_order
            state_model.transmission_can_fit_type = fit_type_can if use_fit_can else FitType.NoFit
            state_model.transmission_can_polynomial_order = polynomial_order_can

            # Wavelength settings
            if self._view.transmission_can_use_wavelength:
                wavelength_min = self._view.transmission_can_wavelength_min
                wavelength_max = self._view.transmission_can_wavelength_max
                state_model.transmission_can_wavelength_min = wavelength_min
                state_model.transmission_can_wavelength_max = wavelength_max

    def _set_on_state_model_q_1d_rebin_string(self, state_model):
        q_1d_step_type = self._view.q_1d_step_type

        # If we are dealing with a simple rebin string then the step type is None
        if self._view.q_1d_step_type is None:
            state_model.q_1d_rebin_string = self._view.q_1d_min_or_rebin_string
        else:
            q_1d_min = self._view.q_1d_min_or_rebin_string
            q_1d_max = self._view.q_1d_max
            q_1d_step = self._view.q_1d_step
            if q_1d_min and q_1d_max and q_1d_step and q_1d_step_type:
                q_1d_rebin_string = str(q_1d_min) + ","
                q_1d_step_type_factor = -1. if q_1d_step_type is RangeStepType.Log else 1.
                q_1d_rebin_string += str(
                    q_1d_step_type_factor * q_1d_step) + ","
                q_1d_rebin_string += str(q_1d_max)
                state_model.q_1d_rebin_string = q_1d_rebin_string

    def _set_on_state_model(self, attribute_name, state_model):
        attribute = getattr(self._view, attribute_name)
        if attribute or isinstance(attribute, bool):
            setattr(state_model, attribute_name, attribute)

    def _get_table_model(self):
        # 1. Create a new table model
        user_file = self._view.get_user_file_path()
        batch_file = self._view.get_batch_file_path()

        table_model = TableModel()
        table_model.user_file = user_file
        self.batch_file = batch_file

        # 2. Iterate over each row, create a table row model and insert it
        number_of_rows = self._view.get_number_of_rows()
        for row in range(number_of_rows):
            sample_scatter = self._view.get_cell(row=row,
                                                 column=SAMPLE_SCATTER_INDEX,
                                                 convert_to=str)
            sample_scatter_period = self._view.get_cell(
                row=row, column=SAMPLE_SCATTER_PERIOD_INDEX, convert_to=str)
            sample_transmission = self._view.get_cell(
                row=row, column=SAMPLE_TRANSMISSION_INDEX, convert_to=str)
            sample_transmission_period = self._view.get_cell(
                row=row,
                column=SAMPLE_TRANSMISSION_PERIOD_INDEX,
                convert_to=str)  # noqa
            sample_direct = self._view.get_cell(row=row,
                                                column=SAMPLE_DIRECT_INDEX,
                                                convert_to=str)
            sample_direct_period = self._view.get_cell(
                row=row, column=SAMPLE_DIRECT_PERIOD_INDEX, convert_to=str)
            can_scatter = self._view.get_cell(row=row,
                                              column=CAN_SCATTER_INDEX,
                                              convert_to=str)
            can_scatter_period = self._view.get_cell(
                row=row, column=CAN_SCATTER_PERIOD_INDEX, convert_to=str)
            can_transmission = self._view.get_cell(
                row=row, column=CAN_TRANSMISSION_INDEX, convert_to=str)
            can_transmission_period = self._view.get_cell(
                row=row, column=CAN_TRANSMISSION_PERIOD_INDEX, convert_to=str)
            can_direct = self._view.get_cell(row=row,
                                             column=CAN_DIRECT_INDEX,
                                             convert_to=str)
            can_direct_period = self._view.get_cell(
                row=row, column=CAN_DIRECT_PERIOD_INDEX, convert_to=str)
            output_name = self._view.get_cell(row=row,
                                              column=OUTPUT_NAME_INDEX,
                                              convert_to=str)
            user_file = self._view.get_cell(row=row,
                                            column=USER_FILE_INDEX,
                                            convert_to=str)

            # Get the options string
            # We don't have to add the hidden column here, since it only contains information for the SANS
            # workflow to operate properly. It however does not contain information for the
            options_string = self._get_options(row)

            table_index_model = TableIndexModel(
                index=row,
                sample_scatter=sample_scatter,
                sample_scatter_period=sample_scatter_period,
                sample_transmission=sample_transmission,
                sample_transmission_period=sample_transmission_period,
                sample_direct=sample_direct,
                sample_direct_period=sample_direct_period,
                can_scatter=can_scatter,
                can_scatter_period=can_scatter_period,
                can_transmission=can_transmission,
                can_transmission_period=can_transmission_period,
                can_direct=can_direct,
                can_direct_period=can_direct_period,
                output_name=output_name,
                user_file=user_file,
                options_column_string=options_string)
            table_model.add_table_entry(row, table_index_model)
        return table_model

    def _create_states(self, state_model, table_model, row_index=None):
        """
        Here we create the states based on the settings in the models
        :param state_model: the state model object
        :param table_model: the table model object
        :param row_index: the selected row, if None then all rows are generated
        """
        number_of_rows = self._view.get_number_of_rows()
        if row_index is not None:
            # Check if the selected index is valid
            if row_index >= number_of_rows:
                return None
            rows = [row_index]
        else:
            rows = range(number_of_rows)
        states = {}
        gui_state_director = GuiStateDirector(table_model, state_model,
                                              self._facility)
        for row in rows:
            self.sans_logger.information(
                "Generating state for row {}".format(row))
            if not self.is_empty_row(row):
                row_user_file = table_model.get_row_user_file(row)
                if row_user_file:
                    user_file_path = FileFinder.getFullPath(row_user_file)
                    if not os.path.exists(user_file_path):
                        raise RuntimeError(
                            "The user path {} does not exist. Make sure a valid user file path"
                            " has been specified.".format(user_file_path))

                    user_file_reader = UserFileReader(user_file_path)
                    user_file_items = user_file_reader.read_user_file()

                    row_state_model = StateGuiModel(user_file_items)
                    row_gui_state_director = GuiStateDirector(
                        table_model, row_state_model, self._facility)
                    self._create_row_state(row_gui_state_director, states, row)
                else:
                    self._create_row_state(gui_state_director, states, row)
        return states

    def _create_row_state(self, director, states, row):
        try:
            state = director.create_state(row)
            states.update({row: state})
        except (ValueError, RuntimeError) as e:
            raise RuntimeError(
                "There was a bad entry for row {}. Ensure that the path to your files has "
                "been added to the Mantid search directories! See here for more "
                "details: {}".format(row, str(e)))

    def _populate_row_in_table(self, row):
        """
        Adds a row to the table
        """
        def get_string_entry(_tag, _row):
            _element = ""
            if _tag in _row:
                _element = _row[_tag]
            return _element

        def get_string_period(_tag):
            return "" if _tag == ALL_PERIODS else str(_tag)

        # 1. Pull out the entries
        sample_scatter = get_string_entry(BatchReductionEntry.SampleScatter,
                                          row)
        sample_scatter_period = get_string_entry(
            BatchReductionEntry.SampleScatterPeriod, row)
        sample_transmission = get_string_entry(
            BatchReductionEntry.SampleTransmission, row)
        sample_transmission_period = get_string_entry(
            BatchReductionEntry.SampleTransmissionPeriod, row)
        sample_direct = get_string_entry(BatchReductionEntry.SampleDirect, row)
        sample_direct_period = get_string_entry(
            BatchReductionEntry.SampleDirectPeriod, row)
        can_scatter = get_string_entry(BatchReductionEntry.CanScatter, row)
        can_scatter_period = get_string_entry(
            BatchReductionEntry.CanScatterPeriod, row)
        can_transmission = get_string_entry(
            BatchReductionEntry.CanTransmission, row)
        can_transmission_period = get_string_entry(
            BatchReductionEntry.CanScatterPeriod, row)
        can_direct = get_string_entry(BatchReductionEntry.CanDirect, row)
        can_direct_period = get_string_entry(
            BatchReductionEntry.CanDirectPeriod, row)
        output_name = get_string_entry(BatchReductionEntry.Output, row)

        # 2. Create entry that can be understood by table
        row_entry = "SampleScatter:{},ssp:{},SampleTrans:{},stp:{},SampleDirect:{},sdp:{}," \
                    "CanScatter:{},csp:{},CanTrans:{},ctp:{}," \
                    "CanDirect:{},cdp:{},OutputName:{}".format(sample_scatter,
                                                               get_string_period(sample_scatter_period),
                                                               sample_transmission,
                                                               get_string_period(sample_transmission_period),
                                                               sample_direct,
                                                               get_string_period(sample_direct_period),
                                                               can_scatter,
                                                               get_string_period(can_scatter_period),
                                                               can_transmission,
                                                               get_string_period(can_transmission_period),
                                                               can_direct,
                                                               get_string_period(can_direct_period),
                                                               output_name)

        self._view.add_row(row_entry)

    # ------------------------------------------------------------------------------------------------------------------
    # Settings
    # ------------------------------------------------------------------------------------------------------------------
    def _setup_instrument_specific_settings(self):
        # Get the first run number of the scatter data for the first table
        sample_scatter = self._view.get_cell(row=0, column=0, convert_to=str)

        # Check if it exists at all
        if not sample_scatter:
            return

        # Get the file information from
        file_information_factory = SANSFileInformationFactory()
        try:
            self._file_information = file_information_factory.create_sans_file_information(
                sample_scatter)
        except NotImplementedError:
            self.sans_logger.warning(
                "Could not get file information from {}.".format(
                    sample_scatter))
            self._file_information = None

        # Provide the instrument specific settings
        if self._file_information:
            # Set the instrument on the table
            instrument = self._file_information.get_instrument()
            self._view.set_instrument_settings(instrument)

            # Set the reduction mode
            reduction_mode_list = get_reduction_mode_strings_for_gui(
                instrument=instrument)
            self._view.set_reduction_modes(reduction_mode_list)
        else:
            self._view.set_instrument_settings(SANSInstrument.NoInstrument)
            reduction_mode_list = get_reduction_mode_strings_for_gui()
            self._view.set_reduction_modes(reduction_mode_list)

    # ------------------------------------------------------------------------------------------------------------------
    # Setting workaround for state in DataProcessorWidget
    # ------------------------------------------------------------------------------------------------------------------
    def _remove_dummy_workspaces_and_row_index(self):
        number_of_rows = self._view.get_number_of_rows()
        for row in range(number_of_rows):
            self._remove_from_hidden_options(row, "InputWorkspace")
            self._remove_from_hidden_options(row, "RowIndex")

    def _set_indices(self):
        number_of_rows = self._view.get_number_of_rows()
        for row in range(number_of_rows):
            to_set = Property.EMPTY_INT if self.is_empty_row(row) else row
            self._add_to_hidden_options(row, "RowIndex", to_set)

    def _set_dummy_workspace(self):
        number_of_rows = self._view.get_number_of_rows()
        for row in range(number_of_rows):
            self._add_to_hidden_options(
                row, "InputWorkspace",
                SANS_DUMMY_INPUT_ALGORITHM_PROPERTY_NAME)

    @staticmethod
    def _create_dummy_input_workspace():
        if not AnalysisDataService.doesExist(
                SANS_DUMMY_INPUT_ALGORITHM_PROPERTY_NAME):
            workspace = WorkspaceFactory.create("Workspace2D", 1, 1, 1)
            AnalysisDataService.addOrReplace(
                SANS_DUMMY_INPUT_ALGORITHM_PROPERTY_NAME, workspace)

    @staticmethod
    def _delete_dummy_input_workspace():
        if AnalysisDataService.doesExist(
                SANS_DUMMY_INPUT_ALGORITHM_PROPERTY_NAME):
            AnalysisDataService.remove(
                SANS_DUMMY_INPUT_ALGORITHM_PROPERTY_NAME)
class BeamCentrePresenterTest(unittest.TestCase):
    def setUp(self):
        self.parent_presenter = create_run_tab_presenter_mock(
            use_fake_state=False)
        self.parent_presenter._file_information = mock.MagicMock()
        self.parent_presenter._file_information.get_instrument = mock.MagicMock(
            return_value=SANSInstrument.LARMOR)
        self.presenter = BeamCentrePresenter(self.parent_presenter)
        self.view = create_mock_beam_centre_tab()

    @mock.patch('sans.gui_logic.presenter.beam_centre_presenter.WorkHandler')
    def test_that_on_run_clicked_calls_find_beam_centre(
            self, work_handler_mock):
        self.presenter = BeamCentrePresenter(self.parent_presenter)
        self.presenter.set_view(self.view)
        self.presenter.on_run_clicked()

        self.assertTrue(work_handler_mock.return_value.process.call_count == 1)
        self.assertTrue(work_handler_mock.return_value.process.call_args[0][1]
                        == find_beam_centre)
        self.assertTrue(work_handler_mock.return_value.process.call_args[0][3]
                        == self.presenter._beam_centre_model)

    def test_that_on_run_clicked_updates_model_from_view(self):
        self.view.left_right = False
        self.view.lab_pos_1 = 100
        self.view.lab_pos_2 = -100
        self.presenter.set_view(self.view)

        self.presenter.on_run_clicked()

        self.assertFalse(self.presenter._beam_centre_model.left_right)
        self.assertEqual(self.presenter._beam_centre_model.lab_pos_1, 0.1)
        self.assertEqual(self.presenter._beam_centre_model.lab_pos_2, -0.1)

    def test_that_set_options_is_called_on_update_rows(self):
        self.presenter.set_view(self.view)

        self.view.set_options.assert_called_once_with(
            self.presenter._beam_centre_model)

        self.presenter.on_update_rows()
        self.assertTrue(self.view.set_options.call_count == 2)

    @mock.patch(
        'sans.gui_logic.presenter.beam_centre_presenter.BeamCentreModel')
    def test_that_set_scaling_is_called_on_update_rows_when_file_information_exists(
            self, beam_centre_model_mock):
        self.presenter = BeamCentrePresenter(self.parent_presenter)
        self.presenter.set_view(self.view)

        self.presenter.on_update_rows()
        self.presenter._beam_centre_model.set_scaling.assert_called_once_with(
            SANSInstrument.LARMOR)

    @mock.patch(
        'sans.gui_logic.presenter.beam_centre_presenter.BeamCentreModel')
    def test_that_set_scaling_is_not_called_when_file_information_does_not_exist(
            self, beam_centre_model_mock):
        self.parent_presenter._file_information = None  #mock.MagicMock(return_value = None)
        self.presenter = BeamCentrePresenter(self.parent_presenter)
        self.presenter.set_view(self.view)

        self.presenter.on_update_rows()
        self.presenter._beam_centre_model.set_scaling.assert_not_called()

    def test_that_on_processing_finished_updates_view_and_model(self):
        self.presenter.set_view(self.view)
        result = {'pos1': 0.1, 'pos2': -0.1}

        self.presenter.on_processing_finished_centre_finder(result)

        self.assertEqual(result['pos1'],
                         self.presenter._beam_centre_model.lab_pos_1)
        self.assertEqual(result['pos2'],
                         self.presenter._beam_centre_model.lab_pos_2)
        self.assertEqual(
            self.view.lab_pos_1,
            self.presenter._beam_centre_model.scale_1 * result['pos1'])
        self.assertEqual(
            self.view.lab_pos_2,
            self.presenter._beam_centre_model.scale_2 * result['pos2'])
        self.view.set_run_button_to_normal.assert_called_once_with()

    @mock.patch(
        'sans.gui_logic.presenter.beam_centre_presenter.SANSCentreFinder')
    def test_that_find_beam_centre_initiates_centre_finder_with_correct_parameters(
            self, centre_finder_mock):
        state = mock.MagicMock()
        beam_centre_model = BeamCentreModel()
        beam_centre_model.lab_pos_1 = 0.1
        beam_centre_model.lab_pos_2 = -0.1
        find_direction = FindDirectionEnum.All

        find_beam_centre(state, beam_centre_model)

        centre_finder_mock.return_value.called_once_with(
            state,
            r_min=beam_centre_model.r_min,
            r_max=beam_centre_model.r_max,
            max_iter=beam_centre_model.max_iterations,
            x_start=beam_centre_model.lab_pos_1,
            y_start=beam_centre_model.lab_pos_2,
            tolerance=beam_centre_model.tolerance,
            find_direction=find_direction,
            reduction_method=True)
class RunTabPresenter(object):
    class ConcreteRunTabListener(SANSDataProcessorGui.RunTabListener):
        def __init__(self, presenter):
            super(RunTabPresenter.ConcreteRunTabListener, self).__init__()
            self._presenter = presenter

        def on_user_file_load(self):
            self._presenter.on_user_file_load()

        def on_mask_file_add(self):
            self._presenter.on_mask_file_add()

        def on_batch_file_load(self):
            self._presenter.on_batch_file_load()

        def on_processed_clicked(self):
            self._presenter.on_processed_clicked()

        def on_multi_period_selection(self, show_periods):
            self._presenter.on_multiperiod_changed(show_periods)

        def on_data_changed(self, row, column, new_value, old_value):
            self._presenter.on_data_changed(row, column, new_value, old_value)

        def on_manage_directories(self):
            self._presenter.on_manage_directories()

        def on_instrument_changed(self):
            self._presenter.on_instrument_changed()

        def on_row_inserted(self, index, row):
            self._presenter.on_row_inserted(index, row)

        def on_rows_removed(self, rows):
            self._presenter.on_rows_removed(rows)

        def on_copy_rows_requested(self):
            self._presenter.on_copy_rows_requested()

        def on_paste_rows_requested(self):
            self._presenter.on_paste_rows_requested()

        def on_insert_row(self):
            self._presenter.on_insert_row()

        def on_erase_rows(self):
            self._presenter.on_erase_rows()

        def on_cut_rows(self):
            self._presenter.on_cut_rows_requested()

    class ProcessListener(WorkHandler.WorkListener):
        def __init__(self, presenter):
            super(RunTabPresenter.ProcessListener, self).__init__()
            self._presenter = presenter

        def on_processing_finished(self, result):
            self._presenter.on_processing_finished(result)

        def on_processing_error(self, error):
            self._presenter.on_processing_error(error)

    def __init__(self, facility, view=None):
        super(RunTabPresenter, self).__init__()
        self._facility = facility
        # Logger
        self.sans_logger = Logger("SANS")
        # Name of grpah to output to
        self.output_graph = 'SANS-Latest'
        self.progress = 0

        # Models that are being used by the presenter
        self._state_model = None
        self._table_model = TableModel()

        # Presenter needs to have a handle on the view since it delegates it
        self._view = None
        self.set_view(view)
        self._processing = False
        self.work_handler = WorkHandler()
        self.batch_process_runner = BatchProcessRunner(
            self.notify_progress, self.on_processing_finished,
            self.on_processing_error)

        # File information for the first input
        self._file_information = None
        self._clipboard = []

        # Settings diagnostic tab presenter
        self._settings_diagnostic_tab_presenter = SettingsDiagnosticPresenter(
            self)

        # Masking table presenter
        self._masking_table_presenter = MaskingTablePresenter(self)

        # Beam centre presenter
        self._beam_centre_presenter = BeamCentrePresenter(
            self, WorkHandler, BeamCentreModel, SANSCentreFinder)

        # Workspace Diagnostic page presenter
        self._workspace_diagnostic_presenter = DiagnosticsPagePresenter(
            self, WorkHandler, run_integral, create_state, self._facility)

    def _default_gui_setup(self):
        """
        Provides a default setup of the GUI. This is important for the initial start up, when the view is being set.
        """
        # Set the possible reduction modes
        reduction_mode_list = get_reduction_mode_strings_for_gui()
        self._view.set_reduction_modes(reduction_mode_list)

        # Set the possible instruments
        instrument_list = get_instrument_strings_for_gui()
        self._view.set_instruments(instrument_list)

        # Set the step type options for wavelength
        range_step_types = [
            RangeStepType.to_string(RangeStepType.Lin),
            RangeStepType.to_string(RangeStepType.Log),
            RangeStepType.to_string(RangeStepType.RangeLog),
            RangeStepType.to_string(RangeStepType.RangeLin)
        ]
        self._view.wavelength_step_type = range_step_types

        # Set the geometry options. This needs to include the option to read the sample shape from file.
        sample_shape = [
            "Read from file", SampleShape.Cylinder, SampleShape.FlatPlate,
            SampleShape.Disc
        ]
        self._view.sample_shape = sample_shape

        # Set the q range
        self._view.q_1d_step_type = [
            RangeStepType.to_string(RangeStepType.Lin),
            RangeStepType.to_string(RangeStepType.Log)
        ]
        self._view.q_xy_step_type = [
            RangeStepType.to_string(RangeStepType.Lin)
        ]

        # Set the fit options
        fit_types = [
            FitType.to_string(FitType.Linear),
            FitType.to_string(FitType.Logarithmic),
            FitType.to_string(FitType.Polynomial)
        ]
        self._view.transmission_sample_fit_type = fit_types
        self._view.transmission_can_fit_type = fit_types

    # ------------------------------------------------------------------------------------------------------------------
    # Table + Actions
    # ------------------------------------------------------------------------------------------------------------------
    def set_view(self, view):
        """
        Sets the view
        :param view: the view is the SANSDataProcessorGui. The presenter needs to access some of the API
        """
        if view is not None:
            self._view = view

            # Add a listener to the view
            listener = RunTabPresenter.ConcreteRunTabListener(self)
            self._view.add_listener(listener)

            # Default gui setup
            self._default_gui_setup()

            # Set appropriate view for the state diagnostic tab presenter
            self._settings_diagnostic_tab_presenter.set_view(
                self._view.settings_diagnostic_tab)

            # Set appropriate view for the masking table presenter
            self._masking_table_presenter.set_view(self._view.masking_table)

            # Set the appropriate view for the beam centre presenter
            self._beam_centre_presenter.set_view(self._view.beam_centre)

            # Set the appropriate view for the diagnostic page
            self._workspace_diagnostic_presenter.set_view(
                self._view.diagnostic_page, self._view.instrument)

            self._view.setup_layout()
            self._view.set_hinting_line_edit_for_column(
                15, self._table_model.get_options_hint_strategy())

    def on_user_file_load(self):
        """
        Loads the user file. Populates the models and the view.
        """
        try:
            # 1. Get the user file path from the view
            user_file_path = self._view.get_user_file_path()

            if not user_file_path:
                return
            # 2. Get the full file path
            user_file_path = FileFinder.getFullPath(user_file_path)
            if not os.path.exists(user_file_path):
                raise RuntimeError(
                    "The user path {} does not exist. Make sure a valid user file path"
                    " has been specified.".format(user_file_path))
            self._table_model.user_file = user_file_path
            # Clear out the current view
            self._view.reset_all_fields_to_default()

            # 3. Read and parse the user file
            user_file_reader = UserFileReader(user_file_path)
            user_file_items = user_file_reader.read_user_file()

            # 4. Populate the model
            self._state_model = StateGuiModel(user_file_items)
            # 5. Update the views.
            self._update_view_from_state_model()
            self._beam_centre_presenter.update_centre_positions(
                self._state_model)

            self._beam_centre_presenter.on_update_rows()
            self._masking_table_presenter.on_update_rows()
            self._workspace_diagnostic_presenter.on_user_file_load(
                user_file_path)

        except Exception as e:
            self.sans_logger.error(
                "Loading of the user file failed. {}".format(str(e)))
            self.display_warning_box('Warning',
                                     'Loading of the user file failed.',
                                     str(e))

    def on_batch_file_load(self):
        """
        Loads a batch file and populates the batch table based on that.
        """
        try:
            # 1. Get the batch file from the view
            batch_file_path = self._view.get_batch_file_path()

            if not batch_file_path:
                return

            if not os.path.exists(batch_file_path):
                raise RuntimeError(
                    "The batch file path {} does not exist. Make sure a valid batch file path"
                    " has been specified.".format(batch_file_path))

            self._table_model.batch_file = batch_file_path

            # 2. Read the batch file
            batch_file_parser = BatchCsvParser(batch_file_path)
            parsed_rows = batch_file_parser.parse_batch_file()

            # 3. Populate the table
            self._table_model.clear_table_entries()
            for index, row in enumerate(parsed_rows):
                self._add_row_to_table_model(row, index)
            self._table_model.remove_table_entries([len(parsed_rows)])

            self.update_view_from_table_model()

            self._beam_centre_presenter.on_update_rows()
            self._masking_table_presenter.on_update_rows()

        except RuntimeError as e:
            self.sans_logger.error(
                "Loading of the batch file failed. {}".format(str(e)))
            self.display_warning_box('Warning',
                                     'Loading of the batch file failed',
                                     str(e))

    def _add_row_to_table_model(self, row, index):
        """
        Adds a row to the table
        """
        def get_string_entry(_tag, _row):
            _element = ""
            if _tag in _row:
                _element = _row[_tag]
            return _element

        def get_string_period(_tag):
            return "" if _tag == ALL_PERIODS else str(_tag)

        # 1. Pull out the entries
        sample_scatter = get_string_entry(BatchReductionEntry.SampleScatter,
                                          row)
        sample_scatter_period = get_string_period(
            get_string_entry(BatchReductionEntry.SampleScatterPeriod, row))
        sample_transmission = get_string_entry(
            BatchReductionEntry.SampleTransmission, row)
        sample_transmission_period = \
            get_string_period(get_string_entry(BatchReductionEntry.SampleTransmissionPeriod, row))
        sample_direct = get_string_entry(BatchReductionEntry.SampleDirect, row)
        sample_direct_period = get_string_period(
            get_string_entry(BatchReductionEntry.SampleDirectPeriod, row))
        can_scatter = get_string_entry(BatchReductionEntry.CanScatter, row)
        can_scatter_period = get_string_period(
            get_string_entry(BatchReductionEntry.CanScatterPeriod, row))
        can_transmission = get_string_entry(
            BatchReductionEntry.CanTransmission, row)
        can_transmission_period = get_string_period(
            get_string_entry(BatchReductionEntry.CanScatterPeriod, row))
        can_direct = get_string_entry(BatchReductionEntry.CanDirect, row)
        can_direct_period = get_string_period(
            get_string_entry(BatchReductionEntry.CanDirectPeriod, row))
        output_name = get_string_entry(BatchReductionEntry.Output, row)
        file_information_factory = SANSFileInformationFactory()
        file_information = file_information_factory.create_sans_file_information(
            sample_scatter)
        sample_thickness = file_information._thickness
        user_file = get_string_entry(BatchReductionEntry.UserFile, row)

        row_entry = [
            sample_scatter, sample_scatter_period, sample_transmission,
            sample_transmission_period, sample_direct, sample_direct_period,
            can_scatter, can_scatter_period, can_transmission,
            can_transmission_period, can_direct, can_direct_period,
            output_name, user_file, sample_thickness, ''
        ]

        table_index_model = TableIndexModel(*row_entry)

        self._table_model.add_table_entry(index, table_index_model)

    def update_view_from_table_model(self):
        self._view.clear_table()
        self._view.hide_period_columns()
        for row_index, row in enumerate(self._table_model._table_entries):
            row_entry = [str(x) for x in row.to_list()]
            self._view.add_row(row_entry)
            self._view.change_row_color(
                row_state_to_colour_mapping[row.row_state], row_index + 1)
            self._view.set_row_tooltip(row.tool_tip, row_index + 1)
            if row.isMultiPeriod():
                self._view.show_period_columns()
        self._view.remove_rows([0])
        self._view.clear_selection()

    def on_data_changed(self, row, column, new_value, old_value):
        self._table_model.update_table_entry(row, column, new_value)
        self._view.change_row_color(
            row_state_to_colour_mapping[RowState.Unprocessed], row)
        self._view.set_row_tooltip('', row)
        self._beam_centre_presenter.on_update_rows()
        self._masking_table_presenter.on_update_rows()

    def on_instrument_changed(self):
        self._setup_instrument_specific_settings()

    def on_processed_clicked(self):
        """
        Prepares the batch reduction.

        0. Validate rows and create dummy workspace if it does not exist
        1. Sets up the states
        2. Adds a dummy input workspace
        3. Adds row index information
        """
        try:
            self._view.disable_buttons()
            self._processing = True
            self.sans_logger.information("Starting processing of batch table.")

            # 1. Set up the states and convert them into property managers
            selected_rows = self._view.get_selected_rows()
            selected_rows = selected_rows if selected_rows else range(
                self._table_model.get_number_of_rows())
            for row in selected_rows:
                self._table_model.reset_row_state(row)
            self.update_view_from_table_model()
            states, errors = self.get_states(row_index=selected_rows)

            for row, error in errors.items():
                self.on_processing_error(row, error)

            if not states:
                self.on_processing_finished(None)
                return

            # 4. Create the graph if continuous output is specified
            if mantidplot:
                if self._view.plot_results and not mantidplot.graph(
                        self.output_graph):
                    mantidplot.newGraph(self.output_graph)

            # Check if optimizations should be used
            use_optimizations = self._view.use_optimizations

            # Get the output mode
            output_mode = self._view.output_mode

            # Check if results should be plotted
            plot_results = self._view.plot_results

            # Get the name of the graph to output to
            output_graph = self.output_graph

            self.progress = 0
            setattr(self._view, 'progress_bar_value', self.progress)
            setattr(self._view, 'progress_bar_maximum', len(states))
            self.batch_process_runner.process_states(states, use_optimizations,
                                                     output_mode, plot_results,
                                                     output_graph)

        except Exception as e:
            self._view.enable_buttons()
            self.sans_logger.error("Process halted due to: {}".format(str(e)))
            self.display_warning_box('Warning', 'Process halted', str(e))

    def on_multiperiod_changed(self, show_periods):
        if show_periods:
            self._view.show_period_columns()
        else:
            self._view.hide_period_columns()

    def display_warning_box(self, title, text, detailed_text):
        self._view.display_message_box(title, text, detailed_text)

    def notify_progress(self, row):
        self.increment_progress()
        message = ''
        self._table_model.set_row_to_processed(row, message)
        self.update_view_from_table_model()

    def on_processing_finished(self, result):
        self._view.enable_buttons()
        self._processing = False

    def on_processing_error(self, row, error_msg):
        self.increment_progress()
        self._table_model.set_row_to_error(row, error_msg)
        self.update_view_from_table_model()

    def increment_progress(self):
        self.progress = self.progress + 1
        setattr(self._view, 'progress_bar_value', self.progress)

    def on_row_inserted(self, index, row):
        row_table_index = TableIndexModel(*row)
        self._table_model.add_table_entry(index, row_table_index)

    def on_insert_row(self):
        selected_rows = self._view.get_selected_rows()
        selected_row = selected_rows[
            0] + 1 if selected_rows else self._table_model.get_number_of_rows(
            )
        table_entry_row = self._table_model.create_empty_row()
        self._table_model.add_table_entry(selected_row, table_entry_row)
        self.update_view_from_table_model()

    def on_erase_rows(self):
        selected_rows = self._view.get_selected_rows()
        empty_row = TableModel.create_empty_row()
        for row in selected_rows:
            self._table_model.replace_table_entries([row], [empty_row])
        self.update_view_from_table_model()

    def on_rows_removed(self, rows):
        self._table_model.remove_table_entries(rows)
        self.update_view_from_table_model()

    def on_copy_rows_requested(self):
        selected_rows = self._view.get_selected_rows()
        self._clipboard = []
        for row in selected_rows:
            data_from_table_model = self._table_model.get_table_entry(
                row).to_list()
            self._clipboard.append(data_from_table_model)

    def on_cut_rows_requested(self):
        self.on_copy_rows_requested()
        rows = self._view.get_selected_rows()
        self.on_rows_removed(rows)

    def on_paste_rows_requested(self):
        if self._clipboard:
            selected_rows = self._view.get_selected_rows()
            selected_rows = selected_rows if selected_rows else [
                self._table_model.get_number_of_rows()
            ]
            replacement_table_index_models = [
                TableIndexModel(*x) for x in self._clipboard
            ]
            self._table_model.replace_table_entries(
                selected_rows, replacement_table_index_models)
            self.update_view_from_table_model()

    def on_manage_directories(self):
        self._view.show_directory_manager()

    def get_row_indices(self):
        """
        Gets the indices of row which are not empty.
        :return: a list of row indices.
        """
        row_indices_which_are_not_empty = []
        number_of_rows = self._table_model.get_number_of_rows()
        for row in range(number_of_rows):
            if not self.is_empty_row(row):
                row_indices_which_are_not_empty.append(row)
        return row_indices_which_are_not_empty

    def on_mask_file_add(self):
        """
        We get the added mask file name and add it to the list of masks
        """
        new_mask_file = self._view.get_mask_file()
        if not new_mask_file:
            return
        new_mask_file_full_path = FileFinder.getFullPath(new_mask_file)
        if not new_mask_file_full_path:
            return

        # Add the new mask file to state model
        mask_files = self._state_model.mask_files

        mask_files.append(new_mask_file)
        self._state_model.mask_files = mask_files

        # Make sure that the sub-presenters are up to date with this change
        self._masking_table_presenter.on_update_rows()
        self._settings_diagnostic_tab_presenter.on_update_rows()
        self._beam_centre_presenter.on_update_rows()

    def is_empty_row(self, row):
        """
        Checks if a row has no entries. These rows will be ignored.
        :param row: the row index
        :return: True if the row is empty.
        """
        return self._table_model.is_empty_row(row)

    # def _validate_rows(self):
    #     """
    #     Validation of the rows. A minimal setup requires that ScatterSample is set.
    #     """
    #     # If SampleScatter is empty, then don't run the reduction.
    #     # We allow empty rows for now, since we cannot remove them from Python.
    #     number_of_rows = self._table_model.get_number_of_rows()
    #     for row in range(number_of_rows):
    #         if not self.is_empty_row(row):
    #             sample_scatter = self._view.get_cell(row, 0)
    #             if not sample_scatter:
    #                 raise RuntimeError("Row {} has not SampleScatter specified. Please correct this.".format(row))

    # ------------------------------------------------------------------------------------------------------------------
    # Controls
    # ------------------------------------------------------------------------------------------------------------------
    def disable_controls(self):
        """
        Disable all input fields and buttons during the execution of the reduction.
        """
        # TODO: think about enabling and disable some controls during reduction
        pass

    def enable_controls(self):
        """
        Enable all input fields and buttons after the execution has completed.
        """
        # TODO: think about enabling and disable some controls during reduction
        pass

    # ------------------------------------------------------------------------------------------------------------------
    # Table Model and state population
    # ------------------------------------------------------------------------------------------------------------------
    def get_states(self, row_index=None, file_lookup=True):
        """
        Gathers the state information for all rows.
        :param row_index: if a single row is selected, then only this row is returned, else all the state for all
                             rows is returned
        :return: a list of states
        """
        start_time_state_generation = time.time()

        # 1. Update the state model
        state_model_with_view_update = self._get_state_model_with_view_update()
        # 2. Update the table model
        table_model = self._table_model

        # 3. Go through each row and construct a state object
        if table_model and state_model_with_view_update:
            states, errors = create_states(state_model_with_view_update,
                                           table_model,
                                           self._view.instrument,
                                           self._facility,
                                           row_index=row_index,
                                           file_lookup=file_lookup)
        else:
            states = None
            errors = None
        stop_time_state_generation = time.time()
        time_taken = stop_time_state_generation - start_time_state_generation
        self.sans_logger.information(
            "The generation of all states took {}s".format(time_taken))
        return states, errors

    def get_state_for_row(self, row_index, file_lookup=True):
        """
        Creates the state for a particular row.
        :param row_index: the row index
        :return: a state if the index is valid and there is a state else None
        """
        states, errors = self.get_states(row_index=[row_index],
                                         file_lookup=file_lookup)
        if states is None:
            self.sans_logger.warning(
                "There does not seem to be data for a row {}.".format(
                    row_index))
            return None

        if row_index in list(states.keys()):
            if states:
                return states[row_index]
        return None

    def _update_view_from_state_model(self):
        # Front tab view
        self._set_on_view("zero_error_free")
        self._set_on_view("save_types")
        self._set_on_view("compatibility_mode")

        self._set_on_view("merge_scale")
        self._set_on_view("merge_shift")
        self._set_on_view("merge_scale_fit")
        self._set_on_view("merge_shift_fit")
        self._set_on_view("merge_q_range_start")
        self._set_on_view("merge_q_range_stop")
        self._set_on_view("merge_max")
        self._set_on_view("merge_min")

        # Settings tab view
        self._set_on_view("reduction_dimensionality")
        self._set_on_view("reduction_mode")
        self._set_on_view("event_slices")
        self._set_on_view("event_binning")
        self._set_on_view("merge_mask")

        self._set_on_view("wavelength_step_type")
        self._set_on_view("wavelength_min")
        self._set_on_view("wavelength_max")
        self._set_on_view("wavelength_step")

        self._set_on_view("absolute_scale")
        self._set_on_view("sample_shape")
        self._set_on_view("sample_height")
        self._set_on_view("sample_width")
        self._set_on_view("sample_thickness")
        self._set_on_view("z_offset")

        # Adjustment tab
        self._set_on_view("normalization_incident_monitor")
        self._set_on_view("normalization_interpolate")

        self._set_on_view("transmission_incident_monitor")
        self._set_on_view("transmission_interpolate")
        self._set_on_view("transmission_roi_files")
        self._set_on_view("transmission_mask_files")
        self._set_on_view("transmission_radius")
        self._set_on_view("transmission_monitor")
        self._set_on_view("transmission_mn_shift")
        self._set_on_view("show_transmission")

        self._set_on_view_transmission_fit()

        self._set_on_view("pixel_adjustment_det_1")
        self._set_on_view("pixel_adjustment_det_2")
        self._set_on_view("wavelength_adjustment_det_1")
        self._set_on_view("wavelength_adjustment_det_2")

        # Q tab
        self._set_on_view_q_rebin_string()
        self._set_on_view("q_xy_max")
        self._set_on_view("q_xy_step")
        self._set_on_view("q_xy_step_type")

        self._set_on_view("gravity_on_off")
        self._set_on_view("gravity_extra_length")

        self._set_on_view("use_q_resolution")
        self._set_on_view_q_resolution_aperture()
        self._set_on_view("q_resolution_delta_r")
        self._set_on_view("q_resolution_collimation_length")
        self._set_on_view("q_resolution_moderator_file")

        self._set_on_view("r_cut")
        self._set_on_view("w_cut")

        # Mask
        self._set_on_view("phi_limit_min")
        self._set_on_view("phi_limit_max")
        self._set_on_view("phi_limit_use_mirror")
        self._set_on_view("radius_limit_min")
        self._set_on_view("radius_limit_max")

    def _set_on_view_transmission_fit_sample_settings(self):
        # Set transmission_sample_use_fit
        fit_type = self._state_model.transmission_sample_fit_type
        use_fit = fit_type is not FitType.NoFit
        self._view.transmission_sample_use_fit = use_fit

        # Set the polynomial order for sample
        polynomial_order = self._state_model.transmission_sample_polynomial_order if fit_type is FitType.Polynomial else 2  # noqa
        self._view.transmission_sample_polynomial_order = polynomial_order

        # Set the fit type for the sample
        fit_type = fit_type if fit_type is not FitType.NoFit else FitType.Linear
        self._view.transmission_sample_fit_type = fit_type

        # Set the wavelength
        wavelength_min = self._state_model.transmission_sample_wavelength_min
        wavelength_max = self._state_model.transmission_sample_wavelength_max
        if wavelength_min and wavelength_max:
            self._view.transmission_sample_use_wavelength = True
            self._view.transmission_sample_wavelength_min = wavelength_min
            self._view.transmission_sample_wavelength_max = wavelength_max

    def _set_on_view_transmission_fit(self):
        # Steps for adding the transmission fit to the view
        # 1. Check if individual settings exist. If so then set the view to separate, else set them to both
        # 2. Apply the settings
        separate_settings = self._state_model.has_transmission_fit_got_separate_settings_for_sample_and_can(
        )
        self._view.set_fit_selection(use_separate=separate_settings)

        if separate_settings:
            self._set_on_view_transmission_fit_sample_settings()

            # Set transmission_sample_can_fit
            fit_type_can = self._state_model.transmission_can_fit_type()
            use_can_fit = fit_type_can is FitType.NoFit
            self._view.transmission_can_use_fit = use_can_fit

            # Set the polynomial order for can
            polynomial_order_can = self._state_model.transmission_can_polynomial_order if fit_type_can is FitType.Polynomial else 2  # noqa
            self._view.transmission_can_polynomial_order = polynomial_order_can

            # Set the fit type for the can
            fit_type_can = fit_type_can if fit_type_can is not FitType.NoFit else FitType.Linear
            self.transmission_can_fit_type = fit_type_can

            # Set the wavelength
            wavelength_min = self._state_model.transmission_can_wavelength_min
            wavelength_max = self._state_model.transmission_can_wavelength_max
            if wavelength_min and wavelength_max:
                self._view.transmission_can_use_wavelength = True
                self._view.transmission_can_wavelength_min = wavelength_min
                self._view.transmission_can_wavelength_max = wavelength_max
        else:
            self._set_on_view_transmission_fit_sample_settings()

    def _set_on_view_q_resolution_aperture(self):
        self._set_on_view("q_resolution_source_a")
        self._set_on_view("q_resolution_sample_a")
        self._set_on_view("q_resolution_source_h")
        self._set_on_view("q_resolution_sample_h")
        self._set_on_view("q_resolution_source_w")
        self._set_on_view("q_resolution_sample_w")

        # If we have h1, h2, w1, and w2 selected then we want to select the rectangular aperture.
        is_rectangular = self._state_model.q_resolution_source_h and self._state_model.q_resolution_sample_h and \
                         self._state_model.q_resolution_source_w and self._state_model.q_resolution_sample_w  # noqa
        self._view.set_q_resolution_shape_to_rectangular(is_rectangular)

    def _set_on_view_q_rebin_string(self):
        """
        Maps the q_1d_rebin_string of the model to the q_1d_step and q_1d_step_type property of the view.
        """
        rebin_string = self._state_model.q_1d_rebin_string
        # Extract the min, max and step and step type from the rebin string
        elements = rebin_string.split(",")
        # If we have three elements then we want to set only the
        if len(elements) == 3:
            step_element = float(elements[1])
            step = abs(step_element)
            step_type = RangeStepType.Lin if step_element >= 0 else RangeStepType.Log

            # Set on the view
            self._view.q_1d_min_or_rebin_string = float(elements[0])
            self._view.q_1d_max = float(elements[2])
            self._view.q_1d_step = step
            self._view.q_1d_step_type = step_type
        else:
            # Set the rebin string
            self._view.q_1d_min_or_rebin_string = rebin_string
            self._view.q_1d_step_type = self._view.VARIABLE

    def _set_on_view(self, attribute_name):
        attribute = getattr(self._state_model, attribute_name)
        if attribute or isinstance(
                attribute, bool
        ):  # We need to be careful here. We don't want to set empty strings, or None, but we want to set boolean values. # noqa
            setattr(self._view, attribute_name, attribute)

    def _set_on_view_with_view(self, attribute_name, view):
        attribute = getattr(self._state_model, attribute_name)
        if attribute or isinstance(
                attribute, bool
        ):  # We need to be careful here. We don't want to set empty strings, or None, but we want to set boolean values. # noqa
            setattr(view, attribute_name, attribute)

    def _get_state_model_with_view_update(self):
        """
        Goes through all sub presenters and update the state model based on the views.

        Note that at the moment we have set up the view and the model such that the name of a property must be the same
        in the view and the model. This can be easily changed, but it also provides a good cohesion.
        """
        state_model = copy.deepcopy(self._state_model)

        # If we don't have a state model then return None
        if state_model is None:
            return state_model
        # Run tab view
        self._set_on_state_model("zero_error_free", state_model)
        self._set_on_state_model("save_types", state_model)
        self._set_on_state_model("compatibility_mode", state_model)
        self._set_on_state_model("merge_scale", state_model)
        self._set_on_state_model("merge_shift", state_model)
        self._set_on_state_model("merge_scale_fit", state_model)
        self._set_on_state_model("merge_shift_fit", state_model)
        self._set_on_state_model("merge_q_range_start", state_model)
        self._set_on_state_model("merge_q_range_stop", state_model)
        self._set_on_state_model("merge_mask", state_model)
        self._set_on_state_model("merge_max", state_model)
        self._set_on_state_model("merge_min", state_model)

        # Settings tab
        self._set_on_state_model("reduction_dimensionality", state_model)
        self._set_on_state_model("reduction_mode", state_model)
        self._set_on_state_model("event_slices", state_model)
        self._set_on_state_model("event_binning", state_model)

        self._set_on_state_model("wavelength_step_type", state_model)
        self._set_on_state_model("wavelength_min", state_model)
        self._set_on_state_model("wavelength_max", state_model)
        self._set_on_state_model("wavelength_step", state_model)
        self._set_on_state_model("wavelength_range", state_model)

        self._set_on_state_model("absolute_scale", state_model)
        self._set_on_state_model("sample_shape", state_model)
        self._set_on_state_model("sample_height", state_model)
        self._set_on_state_model("sample_width", state_model)
        self._set_on_state_model("sample_thickness", state_model)
        self._set_on_state_model("z_offset", state_model)

        # Adjustment tab
        self._set_on_state_model("normalization_incident_monitor", state_model)
        self._set_on_state_model("normalization_interpolate", state_model)

        self._set_on_state_model("transmission_incident_monitor", state_model)
        self._set_on_state_model("transmission_interpolate", state_model)
        self._set_on_state_model("transmission_roi_files", state_model)
        self._set_on_state_model("transmission_mask_files", state_model)
        self._set_on_state_model("transmission_radius", state_model)
        self._set_on_state_model("transmission_monitor", state_model)
        self._set_on_state_model("transmission_mn_shift", state_model)
        self._set_on_state_model("show_transmission", state_model)

        self._set_on_state_model_transmission_fit(state_model)

        self._set_on_state_model("pixel_adjustment_det_1", state_model)
        self._set_on_state_model("pixel_adjustment_det_2", state_model)
        self._set_on_state_model("wavelength_adjustment_det_1", state_model)
        self._set_on_state_model("wavelength_adjustment_det_2", state_model)

        # Q tab
        self._set_on_state_model_q_1d_rebin_string(state_model)
        self._set_on_state_model("q_xy_max", state_model)
        self._set_on_state_model("q_xy_step", state_model)
        self._set_on_state_model("q_xy_step_type", state_model)

        self._set_on_state_model("gravity_on_off", state_model)
        self._set_on_state_model("gravity_extra_length", state_model)

        self._set_on_state_model("use_q_resolution", state_model)
        self._set_on_state_model("q_resolution_source_a", state_model)
        self._set_on_state_model("q_resolution_sample_a", state_model)
        self._set_on_state_model("q_resolution_source_h", state_model)
        self._set_on_state_model("q_resolution_sample_h", state_model)
        self._set_on_state_model("q_resolution_source_w", state_model)
        self._set_on_state_model("q_resolution_sample_w", state_model)
        self._set_on_state_model("q_resolution_delta_r", state_model)
        self._set_on_state_model("q_resolution_collimation_length",
                                 state_model)
        self._set_on_state_model("q_resolution_moderator_file", state_model)

        self._set_on_state_model("r_cut", state_model)
        self._set_on_state_model("w_cut", state_model)

        # Mask
        self._set_on_state_model("phi_limit_min", state_model)
        self._set_on_state_model("phi_limit_max", state_model)
        self._set_on_state_model("phi_limit_use_mirror", state_model)
        self._set_on_state_model("radius_limit_min", state_model)
        self._set_on_state_model("radius_limit_max", state_model)

        # Beam Centre
        self._beam_centre_presenter.set_on_state_model("lab_pos_1",
                                                       state_model)
        self._beam_centre_presenter.set_on_state_model("lab_pos_2",
                                                       state_model)

        return state_model

    def _set_on_state_model_transmission_fit(self, state_model):
        # Behaviour depends on the selection of the fit
        if self._view.use_same_transmission_fit_setting_for_sample_and_can():
            use_fit = self._view.transmission_sample_use_fit
            fit_type = self._view.transmission_sample_fit_type
            polynomial_order = self._view.transmission_sample_polynomial_order
            state_model.transmission_sample_fit_type = fit_type if use_fit else FitType.NoFit
            state_model.transmission_can_fit_type = fit_type if use_fit else FitType.NoFit
            state_model.transmission_sample_polynomial_order = polynomial_order
            state_model.transmission_can_polynomial_order = polynomial_order

            # Wavelength settings
            if self._view.transmission_sample_use_wavelength:
                wavelength_min = self._view.transmission_sample_wavelength_min
                wavelength_max = self._view.transmission_sample_wavelength_max
                state_model.transmission_sample_wavelength_min = wavelength_min
                state_model.transmission_sample_wavelength_max = wavelength_max
                state_model.transmission_can_wavelength_min = wavelength_min
                state_model.transmission_can_wavelength_max = wavelength_max
        else:
            # Sample
            use_fit_sample = self._view.transmission_sample_use_fit
            fit_type_sample = self._view.transmission_sample_fit_type
            polynomial_order_sample = self._view.transmission_sample_polynomial_order
            state_model.transmission_sample_fit_type = fit_type_sample if use_fit_sample else FitType.NoFit
            state_model.transmission_sample_polynomial_order = polynomial_order_sample

            # Wavelength settings
            if self._view.transmission_sample_use_wavelength:
                wavelength_min = self._view.transmission_sample_wavelength_min
                wavelength_max = self._view.transmission_sample_wavelength_max
                state_model.transmission_sample_wavelength_min = wavelength_min
                state_model.transmission_sample_wavelength_max = wavelength_max

            # Can
            use_fit_can = self._view.transmission_can_use_fit
            fit_type_can = self._view.transmission_can_fit_type
            polynomial_order_can = self._view.transmission_can_polynomial_order
            state_model.transmission_can_fit_type = fit_type_can if use_fit_can else FitType.NoFit
            state_model.transmission_can_polynomial_order = polynomial_order_can

            # Wavelength settings
            if self._view.transmission_can_use_wavelength:
                wavelength_min = self._view.transmission_can_wavelength_min
                wavelength_max = self._view.transmission_can_wavelength_max
                state_model.transmission_can_wavelength_min = wavelength_min
                state_model.transmission_can_wavelength_max = wavelength_max

    def _set_on_state_model_q_1d_rebin_string(self, state_model):
        q_1d_step_type = self._view.q_1d_step_type

        # If we are dealing with a simple rebin string then the step type is None
        if self._view.q_1d_step_type is None:
            state_model.q_1d_rebin_string = self._view.q_1d_min_or_rebin_string
        else:
            q_1d_min = self._view.q_1d_min_or_rebin_string
            q_1d_max = self._view.q_1d_max
            q_1d_step = self._view.q_1d_step
            if q_1d_min and q_1d_max and q_1d_step and q_1d_step_type:
                q_1d_rebin_string = str(q_1d_min) + ","
                q_1d_step_type_factor = -1. if q_1d_step_type is RangeStepType.Log else 1.
                q_1d_rebin_string += str(
                    q_1d_step_type_factor * q_1d_step) + ","
                q_1d_rebin_string += str(q_1d_max)
                state_model.q_1d_rebin_string = q_1d_rebin_string

    def _set_on_state_model(self, attribute_name, state_model):
        attribute = getattr(self._view, attribute_name)
        if attribute is not None and attribute != '':
            setattr(state_model, attribute_name, attribute)

    def get_cell_value(self, row, column):
        return self._view.get_cell(row=row,
                                   column=self.table_index[column],
                                   convert_to=str)

    # ------------------------------------------------------------------------------------------------------------------
    # Settings
    # ------------------------------------------------------------------------------------------------------------------
    def _setup_instrument_specific_settings(self, instrument=None):
        if not instrument:
            instrument = self._view.instrument

        self._view.set_instrument_settings(instrument)
        self._beam_centre_presenter.on_update_instrument(instrument)
        self._workspace_diagnostic_presenter.set_instrument_settings(
            instrument)
class BeamCentrePresenterTest(unittest.TestCase):
    def setUp(self):
        self.parent_presenter = create_run_tab_presenter_mock(
            use_fake_state=False)
        self.parent_presenter._file_information = mock.MagicMock()
        self.parent_presenter._file_information.get_instrument = mock.MagicMock(
            return_value=SANSInstrument.LARMOR)
        self.view = create_mock_beam_centre_tab()
        self.WorkHandler = mock.MagicMock()
        self.BeamCentreModel = mock.MagicMock()
        self.SANSCentreFinder = mock.MagicMock()
        self.presenter = BeamCentrePresenter(self.parent_presenter,
                                             self.WorkHandler,
                                             self.BeamCentreModel,
                                             self.SANSCentreFinder)

    def test_that_on_run_clicked_calls_find_beam_centre(self):
        self.presenter.set_view(self.view)
        self.presenter.on_run_clicked()

        self.assertEqual(self.presenter._work_handler.process.call_count, 1)
        self.assertTrue(self.presenter._work_handler.process.call_args[0][1] ==
                        self.presenter._beam_centre_model.find_beam_centre)

    def test_that_on_run_clicked_updates_model_from_view(self):
        self.view.left_right = False
        self.view.lab_pos_1 = 100
        self.view.lab_pos_2 = -100
        self.presenter.set_view(self.view)
        self.presenter._beam_centre_model.scale_1 = 1000
        self.presenter._beam_centre_model.scale_2 = 1000

        self.presenter.on_run_clicked()

        self.assertFalse(self.presenter._beam_centre_model.left_right)
        self.assertEqual(self.presenter._beam_centre_model.lab_pos_1, 0.1)
        self.assertEqual(self.presenter._beam_centre_model.lab_pos_2, -0.1)

    def test_that_set_options_is_called_on_update_rows(self):
        self.presenter.set_view(self.view)

        self.view.set_options.assert_called_once_with(
            self.presenter._beam_centre_model)

        self.presenter.on_update_rows()
        self.assertTrue(self.view.set_options.call_count == 2)

    def test_that_model_reset_to_defaults_for_instrument_is_called_on_update_rows(
            self):
        self.presenter.set_view(self.view)

        self.presenter.on_update_rows()

        self.assertEqual(
            self.presenter._beam_centre_model.reset_to_defaults_for_instrument.
            call_count, 1)

    def test_that_set_scaling_is_called_on_update_instrument(self):
        self.presenter.set_view(self.view)

        self.presenter.on_update_instrument(SANSInstrument.LARMOR)
        self.presenter._beam_centre_model.set_scaling.assert_called_once_with(
            SANSInstrument.LARMOR)

    def test_that_view_on_update_instrument_is_called_on_update_instrument(
            self):
        self.presenter.set_view(self.view)

        self.presenter.on_update_instrument(SANSInstrument.LARMOR)
        self.view.on_update_instrument.assert_called_once_with(
            SANSInstrument.LARMOR)

    def test_that_on_processing_finished_updates_view_and_model(self):
        self.presenter.set_view(self.view)
        result = {'pos1': 0.1, 'pos2': -0.1}
        self.presenter._beam_centre_model.scale_1 = 1000
        self.presenter._beam_centre_model.scale_2 = 1000
        self.presenter._beam_centre_model.update_lab = True
        self.presenter._beam_centre_model.update_hab = True

        self.presenter.on_processing_finished_centre_finder(result)
        self.assertEqual(result['pos1'],
                         self.presenter._beam_centre_model.lab_pos_1)
        self.assertEqual(result['pos2'],
                         self.presenter._beam_centre_model.lab_pos_2)
        self.assertEqual(
            self.view.lab_pos_1,
            self.presenter._beam_centre_model.scale_1 * result['pos1'])
        self.assertEqual(
            self.view.lab_pos_2,
            self.presenter._beam_centre_model.scale_2 * result['pos2'])
        self.assertEqual(result['pos1'],
                         self.presenter._beam_centre_model.hab_pos_1)
        self.assertEqual(result['pos2'],
                         self.presenter._beam_centre_model.hab_pos_2)
        self.assertEqual(
            self.view.hab_pos_1,
            self.presenter._beam_centre_model.scale_1 * result['pos1'])
        self.assertEqual(
            self.view.hab_pos_2,
            self.presenter._beam_centre_model.scale_2 * result['pos2'])
        self.view.set_run_button_to_normal.assert_called_once_with()

    def test_that_on_processing_finished_updates_does_not_update_view_and_model_when_update_disabled(
            self):
        self.presenter.set_view(self.view)
        result = {'pos1': 0.1, 'pos2': -0.1}
        result_1 = {'pos1': 0.2, 'pos2': -0.2}
        self.presenter._beam_centre_model.scale_1 = 1000
        self.presenter._beam_centre_model.scale_2 = 1000
        self.presenter._beam_centre_model.update_lab = True
        self.presenter._beam_centre_model.update_hab = True
        self.presenter.on_processing_finished_centre_finder(result)
        self.presenter._beam_centre_model.update_lab = False
        self.presenter._beam_centre_model.update_hab = False

        self.presenter.on_processing_finished_centre_finder(result_1)

        self.assertEqual(result['pos1'],
                         self.presenter._beam_centre_model.lab_pos_1)
        self.assertEqual(result['pos2'],
                         self.presenter._beam_centre_model.lab_pos_2)
        self.assertEqual(
            self.view.lab_pos_1,
            self.presenter._beam_centre_model.scale_1 * result['pos1'])
        self.assertEqual(
            self.view.lab_pos_2,
            self.presenter._beam_centre_model.scale_2 * result['pos2'])
        self.assertEqual(result['pos1'],
                         self.presenter._beam_centre_model.hab_pos_1)
        self.assertEqual(result['pos2'],
                         self.presenter._beam_centre_model.hab_pos_2)
        self.assertEqual(
            self.view.hab_pos_1,
            self.presenter._beam_centre_model.scale_1 * result['pos1'])
        self.assertEqual(
            self.view.hab_pos_2,
            self.presenter._beam_centre_model.scale_2 * result['pos2'])
        self.assertEqual(self.view.set_run_button_to_normal.call_count, 2)
Exemple #17
0
class BeamCentrePresenterTest(unittest.TestCase):

    def setUp(self):
        self.parent_presenter = create_run_tab_presenter_mock(use_fake_state = False)
        self.view = create_mock_beam_centre_tab()
        self.WorkHandler = mock.MagicMock()
        self.BeamCentreModel = mock.MagicMock()
        self.SANSCentreFinder = mock.MagicMock()

        self.presenter = BeamCentrePresenter(self.parent_presenter, beam_centre_model=self.BeamCentreModel)
        self.presenter.connect_signals = mock.Mock()
        self.presenter.set_view(self.view)
        self.presenter._worker = mock.create_autospec(self.presenter._worker)

    def test_that_on_run_clicked_calls_find_beam_centre(self):
        self.presenter.on_run_clicked()
        self.presenter._worker.find_beam_centre.assert_called_once_with(
            mock.ANY, self.BeamCentreModel.pack_beam_centre_settings.return_value)

    def test_beam_centre_finder_update_handles_str(self):
        # Since the view might give us mixed types check we can handle str
        model = self.BeamCentreModel
        model.rear_pos_1 = "1"
        model.rear_pos_2 = 1
        model.front_pos_1 = 2
        model.front_pos_2 = "2"

        self.presenter.on_processing_finished_centre_finder()
        for i in [self.view.rear_pos_1, self.view.rear_pos_2]:
            self.assertEquals(1, i)
        for i in [self.view.front_pos_1, self.view.front_pos_2]:
            self.assertEquals(2, i)

    def test_that_on_run_clicked_updates_model_from_view(self):
        self.view.left_right = False
        self.view.rear_pos_1 = 100
        self.view.rear_pos_2 = -100
        self.presenter.set_view(self.view)

        self.presenter.on_run_clicked()

        self.assertFalse(self.presenter._beam_centre_model.left_right)
        self.assertEqual(self.presenter._beam_centre_model.rear_pos_1, 100)
        self.assertEqual(self.presenter._beam_centre_model.rear_pos_2, -100)

    def test_on_update_rows_skips_no_rows(self):
        self.parent_presenter.num_rows.return_value = 0
        self.assertFalse(self.WorkHandler.process.called)
        self.presenter.on_update_rows()

    def test_reset_inst_defaults_called_on_update_rows(self):
        self.parent_presenter.num_rows.return_value = 1
        self.parent_presenter.instrument = SANSInstrument.SANS2D

        self.presenter.on_update_rows()

        self.BeamCentreModel.reset_inst_defaults.assert_called_with(SANSInstrument.SANS2D)

    def test_that_on_processing_finished_updates_view(self):
        self.presenter.set_view(self.view)

        self.BeamCentreModel.rear_pos_1 = 1
        self.BeamCentreModel.rear_pos_2 = 2
        self.BeamCentreModel.front_pos_1 = 3
        self.BeamCentreModel.front_pos_2 = 4

        self.presenter.on_processing_finished_centre_finder()
        self.assertEqual(self.BeamCentreModel.rear_pos_1, self.view.rear_pos_1)
        self.assertEqual(self.BeamCentreModel.rear_pos_2, self.view.rear_pos_2)

        self.assertEqual(self.BeamCentreModel.front_pos_1, self.view.front_pos_1)
        self.assertEqual(self.BeamCentreModel.front_pos_2, self.view.front_pos_2)
        self.view.set_run_button_to_normal.assert_called_once_with()

    def test_on_update_positions_handles_strings(self):
        self.BeamCentreModel.rear_pos_1 = "foo"
        self.BeamCentreModel.rear_pos_2 = ""
        self.presenter.update_centre_positions()

        self.assertEqual("foo", self.view.rear_pos_1)
        self.assertEqual("", self.view.rear_pos_2)

    def test_on_success_forwards_new_vals(self):
        expected_vals = mock.NonCallableMock()
        self.presenter.on_update_centre_values(expected_vals)
        self.BeamCentreModel.update_centre_positions.assert_called_once_with(expected_vals)

    def test_that_update_front_selected_enabled_front_and_disabled_rear(self):
        self.presenter.set_view(self.view)
        self.presenter.update_front_selected()

        # Check that the model has been updated
        self.assertTrue(self.presenter._beam_centre_model.update_front)
        self.assertFalse(self.presenter._beam_centre_model.update_rear)

        # Check that we called a method to enable the front and a method to disable the rear
        self.presenter._view.enable_update_front.assert_called_once_with(True)
        self.presenter._view.enable_update_rear.assert_called_once_with(False)

    def test_that_update_rear_selected_enabled_rear_and_disabled_front(self):
        self.presenter.set_view(self.view)
        self.presenter.update_rear_selected()

        # Check that the model has been updated
        self.assertFalse(self.presenter._beam_centre_model.update_front)
        self.assertTrue(self.presenter._beam_centre_model.update_rear)

        # Check that we called a method to disable the front and a method to enable the rear
        self.presenter._view.enable_update_front.assert_called_once_with(False)
        self.presenter._view.enable_update_rear.assert_called_once_with(True)

    def test_that_update_all_selected_enabled_front_and_rear(self):
        self.presenter.set_view(self.view)
        self.presenter.update_all_selected()

        # Check that the model has been updated
        self.assertTrue(self.presenter._beam_centre_model.update_front)
        self.assertTrue(self.presenter._beam_centre_model.update_rear)

        # Check that we called a method to enable the front and a method to enable the rear
        self.presenter._view.enable_update_front.assert_called_once_with(True)
        self.presenter._view.enable_update_rear.assert_called_once_with(True)

    def test_copies_into_internal_model(self):
        mocked_external_model = mock.NonCallableMock()

        attr_list = ["rear_pos_1", "rear_pos_2", "front_pos_1", "front_pos_2"]

        for attr in attr_list:
            setattr(mocked_external_model, attr, random.randint(0, 1000)/100)  # Simulate a random FP value
        self.presenter.copy_centre_positions(mocked_external_model)

        for attr in attr_list:
            self.assertEqual(getattr(mocked_external_model, attr), getattr(self.presenter._beam_centre_model, attr))

    def test_copies_without_front_into_internal_model(self):
        mocked_external_model = mock.NonCallableMock()
        rear_attr = ["rear_pos_1", "rear_pos_2"]
        front_list = ["front_pos_1", "front_pos_2"]

        for attr in front_list:
            # On instruments with no front this can be a blank string
            setattr(mocked_external_model, attr, '')

        for attr in rear_attr:
            setattr(mocked_external_model, attr, random.randint(0, 1000)/100)  # Simulate a random FP value
        self.presenter.copy_centre_positions(mocked_external_model)

        for attr in rear_attr:
            self.assertEqual(getattr(mocked_external_model, attr), getattr(self.presenter._beam_centre_model, attr))

        # When front isn't present we should take rear values
        for front_attr, rear_attr in zip(front_list, rear_attr):
            self.assertEqual(getattr(mocked_external_model, rear_attr),
                             getattr(self.presenter._beam_centre_model, front_attr))

    def test_on_update_rows_updates_centres(self):
        # As the rear centres can update when the rows change (due to different run number groups)
        # we should always update from the model
        self.presenter.update_centre_positions = mock.Mock()
        self.presenter.on_update_rows()
        self.presenter.update_centre_positions.assert_called_once()

    def test_update_center_positions_with_valid_front(self):
        self.BeamCentreModel.rear_pos_1 = 1.1
        self.BeamCentreModel.rear_pos_2 = 1.2
        self.BeamCentreModel.front_pos_1 = 2.1
        self.BeamCentreModel.front_pos_2 = 2.2
        self.presenter.update_centre_positions()

        self.assertEqual(1.1, self.view.rear_pos_1)
        self.assertEqual(1.2, self.view.rear_pos_2)
        self.assertEqual(2.1, self.view.front_pos_1)
        self.assertEqual(2.2, self.view.front_pos_2)

    def test_update_center_positions_with_empty_front(self):
        self.BeamCentreModel.rear_pos_1 = 1.1
        self.BeamCentreModel.rear_pos_2 = 1.2
        self.BeamCentreModel.front_pos_1 = ""
        self.BeamCentreModel.front_pos_2 = ""
        self.presenter.update_centre_positions()

        self.assertEqual(1.1, self.view.rear_pos_1)
        self.assertEqual(1.2, self.view.rear_pos_2)
        self.assertEqual(self.BeamCentreModel.rear_pos_1, self.view.front_pos_1)
        self.assertEqual(self.BeamCentreModel.rear_pos_2, self.view.front_pos_2)

    def test_update_center_positions_with_zero_front(self):
        self.BeamCentreModel.rear_pos_1 = 1.1
        self.BeamCentreModel.rear_pos_2 = 1.2
        # Zero is a valid value, so should be respected
        self.BeamCentreModel.front_pos_1 = 0.
        self.BeamCentreModel.front_pos_2 = 0.
        self.presenter.update_centre_positions()

        self.assertEqual(1.1, self.view.rear_pos_1)
        self.assertEqual(1.2, self.view.rear_pos_2)
        self.assertEqual(0., self.view.front_pos_1)
        self.assertEqual(0., self.view.front_pos_2)