Ejemplo n.º 1
0
class CalibrationConfigWidget(QObject):
    """Emitted when GUI data has changed"""
    gui_data_changed = Signal()

    def __init__(self, parent=None):
        super(CalibrationConfigWidget, self).__init__(parent)

        self.cfg = HexrdConfig()

        loader = UiLoader()
        self.ui = loader.load_file('calibration_config_widget.ui', parent)

        self.detector_widgets_disabled = False

        # Turn off autocomplete for the QComboBox
        self.ui.cal_det_current.setCompleter(None)

        self.setup_connections()

        self.timer = None

    def setup_connections(self):
        self.ui.cal_det_current.installEventFilter(self)
        self.ui.cal_energy.valueChanged.connect(self.on_energy_changed)
        self.ui.cal_energy_wavelength.valueChanged.connect(
            self.on_energy_wavelength_changed)

        self.ui.cal_det_current.currentIndexChanged.connect(
            self.on_detector_changed)
        self.ui.cal_det_current.lineEdit().editingFinished.connect(
            self.on_detector_name_edited)
        self.ui.cal_det_remove.clicked.connect(self.on_detector_remove_clicked)
        self.ui.cal_det_add.clicked.connect(self.on_detector_add_clicked)

        all_widgets = self.get_all_widgets()
        skip_widgets = ['cal_det_current', 'cal_det_add', 'cal_det_remove']
        for widget in all_widgets:
            if widget.objectName() in skip_widgets:
                continue

            if isinstance(widget, QComboBox):
                widget.currentIndexChanged.connect(self.update_config_from_gui)
            elif isinstance(widget, QLineEdit):
                widget.textEdited.connect(self.update_config_from_gui)
            elif isinstance(widget, QPushButton):
                widget.pressed.connect(self.update_config_from_gui)
            else:
                widget.valueChanged.connect(self.update_config_from_gui)
                widget.valueChanged.connect(self.gui_value_changed)

        self.ui.cal_det_function.currentIndexChanged.connect(
            self.update_gui_from_config)
        self.ui.cal_det_buffer.clicked.connect(self._on_configure_buffer)

    def on_energy_changed(self):
        val = self.ui.cal_energy.value()

        # Make sure energy has same style
        kev_widget = getattr(self.ui, 'cal_energy')
        wave_widget = getattr(self.ui, 'cal_energy_wavelength')
        wave_widget.setStyleSheet(kev_widget.styleSheet())

        block_signals = self.ui.cal_energy_wavelength.blockSignals(True)
        try:
            new_wavelength = constants.KEV_TO_WAVELENGTH / val
            self.ui.cal_energy_wavelength.setValue(new_wavelength)
        finally:
            self.ui.cal_energy_wavelength.blockSignals(block_signals)

    def on_energy_wavelength_changed(self):
        val = self.ui.cal_energy_wavelength.value()

        block_signals = self.ui.cal_energy.blockSignals(True)
        try:
            new_energy = constants.WAVELENGTH_TO_KEV / val
            self.ui.cal_energy.setValue(new_energy)
        finally:
            self.ui.cal_energy.blockSignals(block_signals)

    def on_detector_changed(self):
        self.update_detector_from_config()

    def enable_detector_widgets(self, enable=True):
        detector_widgets = self.cfg.get_detector_widgets()
        detector_widgets.remove('cal_det_add')
        for widget in detector_widgets:
            gui_widget = getattr(self.ui, widget)
            gui_widget.setEnabled(enable)

        self.detector_widgets_disabled = not enable

    def on_detector_name_edited(self):
        new_name = self.ui.cal_det_current.currentText()
        detector_names = self.cfg.detector_names

        # Ignore it if there is already a detector with this name
        if new_name in detector_names:
            return

        # Get the old name from the underlying item
        idx = self.ui.cal_det_current.currentIndex()
        old_name = self.ui.cal_det_current.itemText(idx)

        self.ui.cal_det_current.setItemText(idx, new_name)
        self.cfg.rename_detector(old_name, new_name)

    def on_detector_remove_clicked(self):
        if self.ui.cal_det_current.count() <= 1:
            msg = 'Cannot remove last detector'
            QMessageBox.critical(self.ui, 'HEXRD', msg)
            return

        current_detector = self.get_current_detector()
        idx = self.ui.cal_det_current.currentIndex()

        self.cfg.remove_detector(current_detector)
        if idx > 0:
            self.ui.cal_det_current.setCurrentIndex(idx - 1)
        else:
            self.update_detector_from_config()

    def on_detector_add_clicked(self):
        combo = self.ui.cal_det_current
        current_detector = self.get_current_detector()
        detector_names = self.cfg.detector_names
        new_detector_name_base = 'detector_'
        for i in range(1000000):
            new_detector_name = new_detector_name_base + str(i + 1)
            if new_detector_name not in detector_names:
                self.cfg.add_detector(new_detector_name, current_detector)
                self.update_detector_from_config()
                new_ind = combo.findText(new_detector_name)
                combo.setCurrentIndex(new_ind)
                return

    def update_config_from_gui(self):
        """This function only updates the sender value"""
        sender = self.sender()
        name = sender.objectName()
        if name == 'cal_energy_wavelength':
            # Need to update the `cal_energy` entry instead
            sender = self.ui.cal_energy
            name = sender.objectName()

        value = self._get_gui_value(sender)
        current_detector = self.get_current_detector()
        self.cfg.set_val_from_widget_name(name, value, current_detector)

    def gui_value_changed(self):
        """We only want to emit a changed signal once editing is done"""
        if self.timer is None:
            self.timer = QTimer()
            self.timer.setSingleShot(True)
            self.timer.timeout.connect(self.gui_data_changed)

        # Start or restart our timer...
        self.timer.start(666)

    def update_gui_from_config(self):
        previously_blocked = self.block_all_signals()

        try:
            gui_yaml_paths = self.cfg.get_gui_yaml_paths()
            for var, path in gui_yaml_paths:
                if 'detector_name' in path:
                    # We give these special treatment
                    continue

                gui_var = getattr(self.ui, var)
                config_val = self.cfg.get_instrument_config_val(path)
                path[path.index('value')] = 'status'
                status_val = self.cfg.get_instrument_config_val(path)

                self._set_gui_value(gui_var, config_val, status_val)
        finally:
            self.unblock_all_signals(previously_blocked)

        # Update the wavelength also
        self.on_energy_changed()

        self.update_detector_from_config()

    def update_detector_from_config(self):
        previously_blocked = self.block_all_signals()

        try:
            combo_widget = self.ui.cal_det_current
            detector_names = self.cfg.detector_names
            if not detector_names:
                # Disable detector widgets if there is no valid detector
                if not self.detector_widgets_disabled:
                    self.enable_detector_widgets(enable=False)

                return
            elif self.detector_widgets_disabled:
                # Enable detector widgets if they were previously disabled
                self.enable_detector_widgets(enable=True)

            cur_detector = detector_names[0]
            if combo_widget.currentText() in detector_names:
                cur_detector = combo_widget.currentText()

            gui_yaml_paths = self.cfg.get_gui_yaml_paths(
                ['detectors', 'detector_name'])

            tilt_path = ['transform', 'tilt', 'value']
            dist_params_path = ['distortion', 'parameters']
            for var, path in gui_yaml_paths:
                if len(path) > 1 and path[:2] == dist_params_path:
                    # The distortion type should already be set before here
                    # Check for the number of params. If this is greater
                    # than the number of params, skip over it.
                    if not path[-1] < self.num_distortion_params:
                        continue

                gui_var = getattr(self.ui, var)
                full_path = ['detectors', cur_detector] + path
                config_val = self.cfg.get_instrument_config_val(full_path)
                full_path[full_path.index('value')] = 'status'
                status_val = self.cfg.get_instrument_config_val(full_path)

                if path[:-1] == tilt_path:
                    # Special treatment for tilt widgets
                    if HexrdConfig().rotation_matrix_euler() is None:
                        gui_var.setSuffix('')
                    else:
                        gui_var.setSuffix('°')
                        config_val = np.degrees(config_val).item()

                self._set_gui_value(gui_var, config_val, status_val)

            combo_items = []
            for i in range(combo_widget.count()):
                combo_items.append(combo_widget.itemText(i))

            if combo_items != detector_names:
                combo_widget.clear()
                combo_widget.addItems(detector_names)
                new_ind = combo_widget.findText(cur_detector)
                combo_widget.setCurrentIndex(new_ind)

        finally:
            self.unblock_all_signals(previously_blocked)

        # Update distortion parameter enable states
        self.update_distortion_params_enable_states()

    def get_current_detector(self):
        if self.detector_widgets_disabled:
            return None

        return self.ui.cal_det_current.currentText()

    def get_all_widgets(self):
        gui_yaml_paths = self.cfg.get_gui_yaml_paths()
        widgets = [x[0] for x in gui_yaml_paths]
        widgets += self.cfg.get_detector_widgets()
        widgets += ['cal_energy_wavelength']
        widgets = list(set(widgets))
        widgets = [getattr(self.ui, x) for x in widgets]
        return widgets

    def block_all_signals(self):
        previously_blocked = []
        all_widgets = self.get_all_widgets()

        for widget in all_widgets:
            previously_blocked.append(widget.blockSignals(True))

        return previously_blocked

    def unblock_all_signals(self, previously_blocked):
        all_widgets = self.get_all_widgets()

        for block, widget in zip(previously_blocked, all_widgets):
            widget.blockSignals(block)

    def _set_gui_value(self, gui_object, value, flag=None):
        """This is for calling various set methods for GUI variables

        For instance, QComboBox will call "setCurrentText", while
        QSpinBox will call "setValue".
        """
        if isinstance(gui_object, QComboBox):
            gui_object.setCurrentText(value)
        else:
            if flag == 1 and not gui_object.styleSheet():
                s = 'QSpinBox, QDoubleSpinBox { background-color: lightgray; }'
                gui_object.setStyleSheet(s)
            elif gui_object.styleSheet() and flag != 1:
                gui_object.setStyleSheet("")
            # If it is anything else, just assume setValue()
            gui_object.setValue(value)

    def _get_gui_value(self, gui_object):
        """This is for calling various get methods for GUI variables

        For instance, QComboBox will call "currentText", while
        QSpinBox will call "value".
        """
        if isinstance(gui_object, QComboBox):
            return gui_object.currentText()
        else:
            # If it is anything else, just assume value()
            return gui_object.value()

    @property
    def distortion(self):
        return self.ui.cal_det_function.currentText()

    @property
    def num_distortion_params(self):
        return HexrdConfig.num_distortion_parameters(self.distortion)

    def update_distortion_params_enable_states(self):
        num_params = self.num_distortion_params
        label_enabled = (num_params != 0)

        self.ui.distortion_parameters_label.setEnabled(label_enabled)
        self.ui.distortion_parameters_label.setVisible(label_enabled)

        base_str = 'cal_det_param_'
        for i in range(1000):
            widget_name = base_str + str(i)
            if not hasattr(self.ui, widget_name):
                break

            widget = getattr(self.ui, widget_name)
            enable = (num_params > i)
            widget.setEnabled(enable)
            widget.setVisible(enable)

    def eventFilter(self, target, event):
        # Unfortunately, when a user modifies the name in the editable
        # QComboBox 'cal_det_current', and then they press enter, it does
        # not emit QLineEdit.editingFinished(), but instead emits
        # QComboBox.currentIndexChanged(). This behavior is a little odd.
        # We need QLineEdit.editingFinished() so that the name gets updated.
        # If we call QLineEdit.editingFinished() here explicitly, it gets
        # emitted twice: once in this function, and once when the focus
        # gets cleared. Rather than calling it twice, let's just clear the
        # focus here so it gets called only once.
        if type(target) == QComboBox:
            if target.objectName() == 'cal_det_current':
                widget = self.ui.cal_det_current
                enter_keys = [Qt.Key_Return, Qt.Key_Enter]
                if type(event) == QKeyEvent and event.key() in enter_keys:
                    widget.lineEdit().clearFocus()
                    return True

                if type(event) == QFocusEvent and event.lostFocus():
                    # This happens either if enter is pressed, or if the
                    # user tabs out.
                    items = [widget.itemText(i) for i in range(widget.count())]
                    text = widget.currentText()
                    idx = widget.currentIndex()
                    if text in items and widget.itemText(idx) != text:
                        # Prevent the QComboBox from automatically changing
                        # the index to be that of the other item in the list.
                        # This is confusing behavior, and it's not what we
                        # want here.
                        widget.setCurrentIndex(idx)
                        # Let the widget lose focus
                        return False

        return False

    def _on_configure_buffer(self):
        # Disable button to prevent multiple dialogs
        self.ui.cal_det_buffer.setEnabled(False)
        detector = self.get_current_detector()
        dialog = PanelBufferDialog(detector, self.ui)
        dialog.show()
        # Re-enable button
        dialog.ui.finished.connect(
            lambda _: self.ui.cal_det_buffer.setEnabled(True))
Ejemplo n.º 2
0
class CalibrationConfigWidget(QObject):
    """Emitted when GUI data has changed"""
    gui_data_changed = Signal()

    def __init__(self, parent=None):
        super(CalibrationConfigWidget, self).__init__(parent)

        self.cfg = HexrdConfig()

        loader = UiLoader()
        self.ui = loader.load_file('calibration_config_widget.ui', parent)

        self.detector_widgets_disabled = False

        self.setup_connections()

        self.timer = None

    def setup_connections(self):
        self.ui.cal_energy.valueChanged.connect(self.on_energy_changed)
        self.ui.cal_energy_wavelength.valueChanged.connect(
            self.on_energy_wavelength_changed)

        self.ui.cal_det_current.currentIndexChanged.connect(
            self.on_detector_changed)
        self.ui.cal_det_current.editTextChanged.connect(
            self.on_detector_name_edited)
        self.ui.cal_det_remove.clicked.connect(self.on_detector_remove_clicked)
        self.ui.cal_det_add.clicked.connect(self.on_detector_add_clicked)

        all_widgets = self.get_all_widgets()
        skip_widgets = ['cal_det_current', 'cal_det_add', 'cal_det_remove']
        for widget in all_widgets:
            if widget.objectName() in skip_widgets:
                continue

            if isinstance(widget, QComboBox):
                widget.currentIndexChanged.connect(self.update_config_from_gui)
            elif isinstance(widget, QLineEdit):
                widget.textEdited.connect(self.update_config_from_gui)
            elif isinstance(widget, QPushButton):
                widget.pressed.connect(self.update_config_from_gui)
            else:
                widget.valueChanged.connect(self.update_config_from_gui)
                widget.valueChanged.connect(self.gui_value_changed)

    def on_energy_changed(self):
        val = self.ui.cal_energy.value()

        # Make sure energy has same style
        kev_widget = getattr(self.ui, 'cal_energy')
        wave_widget = getattr(self.ui, 'cal_energy_wavelength')
        wave_widget.setStyleSheet(kev_widget.styleSheet())

        block_signals = self.ui.cal_energy_wavelength.blockSignals(True)
        try:
            new_wavelength = constants.KEV_TO_WAVELENGTH / val
            self.ui.cal_energy_wavelength.setValue(new_wavelength)
        finally:
            self.ui.cal_energy_wavelength.blockSignals(block_signals)

    def on_energy_wavelength_changed(self):
        val = self.ui.cal_energy_wavelength.value()

        block_signals = self.ui.cal_energy.blockSignals(True)
        try:
            new_energy = constants.WAVELENGTH_TO_KEV / val
            self.ui.cal_energy.setValue(new_energy)
        finally:
            self.ui.cal_energy.blockSignals(block_signals)

    def on_detector_changed(self):
        self.update_detector_from_config()

    def enable_detector_widgets(self, enable=True):
        detector_widgets = self.cfg.get_detector_widgets()
        detector_widgets.remove('cal_det_add')
        for widget in detector_widgets:
            gui_widget = getattr(self.ui, widget)
            gui_widget.setEnabled(enable)

        self.detector_widgets_disabled = not enable

    def on_detector_name_edited(self):
        new_name = self.ui.cal_det_current.currentText()
        detector_names = self.cfg.get_detector_names()

        # Ignore it if there is already a detector with this name
        if new_name in detector_names:
            return

        # Get the old name from the underlying item
        idx = self.ui.cal_det_current.currentIndex()
        old_name = self.ui.cal_det_current.itemText(idx)

        self.ui.cal_det_current.setItemText(idx, new_name)
        self.cfg.rename_detector(old_name, new_name)

    def on_detector_remove_clicked(self):
        current_detector = self.get_current_detector()
        idx = self.ui.cal_det_current.currentIndex()

        self.cfg.remove_detector(current_detector)
        if idx > 0:
            self.ui.cal_det_current.setCurrentIndex(idx - 1)
        else:
            self.update_detector_from_config()

    def on_detector_add_clicked(self):
        current_detector = self.get_current_detector()
        detector_names = self.cfg.get_detector_names()
        new_detector_name_base = 'detector_'
        for i in range(1000000):
            new_detector_name = new_detector_name_base + str(i + 1)
            if new_detector_name not in detector_names:
                self.cfg.add_detector(new_detector_name, current_detector)
                self.update_detector_from_config()
                self.ui.cal_det_current.setCurrentText(new_detector_name)
                return

    def update_config_from_gui(self):
        """This function only updates the sender value"""
        sender = self.sender()
        name = sender.objectName()
        if name == 'cal_energy_wavelength':
            # Need to update the `cal_energy` entry instead
            sender = self.ui.cal_energy
            name = sender.objectName()

        value = self._get_gui_value(sender)
        current_detector = self.get_current_detector()
        self.cfg.set_val_from_widget_name(name, value, current_detector)

    def gui_value_changed(self):
        """We only want to emit a changed signal once editing is done"""
        if self.timer is None:
            self.timer = QTimer()
            self.timer.setSingleShot(True)
            self.timer.timeout.connect(self.gui_data_changed)

        # Start or restart our timer...
        self.timer.start(666)

    def update_gui_from_config(self):
        previously_blocked = self.block_all_signals()

        try:
            gui_yaml_paths = self.cfg.get_gui_yaml_paths()
            for var, path in gui_yaml_paths:
                if 'detector_name' in path:
                    # We give these special treatment
                    continue

                gui_var = getattr(self.ui, var)
                config_val = self.cfg.get_instrument_config_val(path)
                path[path.index('value')] = 'status'
                status_val = self.cfg.get_instrument_config_val(path)

                self._set_gui_value(gui_var, config_val, status_val)
        finally:
            self.unblock_all_signals(previously_blocked)

        # Update the wavelength also
        self.on_energy_changed()

        self.update_detector_from_config()

    def update_detector_from_config(self):
        previously_blocked = self.block_all_signals()

        try:
            detector_names = self.cfg.get_detector_names()
            if not detector_names:
                # Disable detector widgets if there is no valid detector
                if not self.detector_widgets_disabled:
                    self.enable_detector_widgets(enable=False)

                return
            elif self.detector_widgets_disabled:
                # Enable detector widgets if they were previously disabled
                self.enable_detector_widgets(enable=True)

            cur_detector = detector_names[0]
            if self.ui.cal_det_current.currentText() in detector_names:
                cur_detector = self.ui.cal_det_current.currentText()

            gui_yaml_paths = self.cfg.get_gui_yaml_paths(
                ['detectors', 'detector_name'])

            tilt_path = ['transform', 'tilt', 'value']
            for var, path in gui_yaml_paths:
                gui_var = getattr(self.ui, var)
                full_path = ['detectors', cur_detector] + path
                config_val = self.cfg.get_instrument_config_val(full_path)
                full_path[full_path.index('value')] = 'status'
                status_val = self.cfg.get_instrument_config_val(full_path)

                if path[:-1] == tilt_path:
                    # Special treatment for tilt widgets
                    if HexrdConfig().rotation_matrix_euler() is None:
                        gui_var.setSuffix('')
                    else:
                        gui_var.setSuffix('°')
                        config_val = np.degrees(config_val).item()

                self._set_gui_value(gui_var, config_val, status_val)

            combo_items = []
            for i in range(self.ui.cal_det_current.count()):
                combo_items.append(self.ui.cal_det_current.itemText(i))

            if combo_items != detector_names:
                self.ui.cal_det_current.clear()
                self.ui.cal_det_current.addItems(detector_names)
                self.ui.cal_det_current.setCurrentText(cur_detector)

        finally:
            self.unblock_all_signals(previously_blocked)

    def get_current_detector(self):
        if self.detector_widgets_disabled:
            return None

        return self.ui.cal_det_current.currentText()

    def get_all_widgets(self):
        gui_yaml_paths = self.cfg.get_gui_yaml_paths()
        widgets = [x[0] for x in gui_yaml_paths]
        widgets += self.cfg.get_detector_widgets()
        widgets += ['cal_energy_wavelength']
        widgets = list(set(widgets))
        widgets = [getattr(self.ui, x) for x in widgets]
        return widgets

    def block_all_signals(self):
        previously_blocked = []
        all_widgets = self.get_all_widgets()

        for widget in all_widgets:
            previously_blocked.append(widget.blockSignals(True))

        return previously_blocked

    def unblock_all_signals(self, previously_blocked):
        all_widgets = self.get_all_widgets()

        for block, widget in zip(previously_blocked, all_widgets):
            widget.blockSignals(block)

    def _set_gui_value(self, gui_object, value, flag=None):
        """This is for calling various set methods for GUI variables

        For instance, QComboBox will call "setCurrentText", while
        QSpinBox will call "setValue".
        """
        if isinstance(gui_object, QComboBox):
            gui_object.setCurrentText(value)
        else:
            if flag == 1 and not gui_object.styleSheet():
                s = 'QSpinBox, QDoubleSpinBox { background-color: lightgray; }'
                gui_object.setStyleSheet(s)
            elif gui_object.styleSheet() and flag != 1:
                gui_object.setStyleSheet("")
            # If it is anything else, just assume setValue()
            gui_object.setValue(value)

    def _get_gui_value(self, gui_object):
        """This is for calling various get methods for GUI variables

        For instance, QComboBox will call "currentText", while
        QSpinBox will call "value".
        """
        if isinstance(gui_object, QComboBox):
            return gui_object.currentText()
        else:
            # If it is anything else, just assume value()
            return gui_object.value()