def initialize_content(self):
        # Validators
        self._summary.detector_offset_edit.setValidator(QDoubleValidator(self._summary.detector_offset_edit))
        self._summary.sample_dist_edit.setValidator(QDoubleValidator(self._summary.sample_dist_edit))
        self._summary.n_q_bins_edit.setValidator(QIntValidator(self._summary.n_q_bins_edit))

        # Event connections
        self._summary.detector_offset_chk.clicked.connect(self._det_offset_clicked)
        self._summary.sample_dist_chk.clicked.connect(self._sample_dist_clicked)
        self._summary.help_button.clicked.connect(self._show_help)

        self._summary.dark_current_check.clicked.connect(self._dark_clicked)
        self._summary.dark_browse_button.clicked.connect(self._dark_browse)
        self._summary.dark_plot_button.clicked.connect(self._dark_plot_clicked)

        # Output directory
        g2 = QButtonGroup(self)
        g2.addButton(self._summary.select_output_dir_radio)
        g2.addButton(self._summary.use_data_dir_radio)
        g2.setExclusive(True)
        self._summary.select_output_dir_radio.clicked.connect(self._output_dir_clicked)
        self._summary.use_data_dir_radio.clicked.connect(self._output_dir_clicked)
        self._summary.output_dir_browse_button.clicked.connect(self._output_dir_browse)
        self._output_dir_clicked()

        # Lin/log option
        g3 = QButtonGroup(self)
        g3.addButton(self._summary.log_binning_radio)
        g3.addButton(self._summary.lin_binning_radio)
        g3.setExclusive(True)

        # Q range
        self._summary.n_q_bins_edit.setText("100")

        self._summary.scale_edit.setText("1")

        self._summary.instr_name_label.hide()
        self._dark_clicked(self._summary.dark_current_check.isChecked())

        # Mask Connections
        self._summary.mask_browse_button.clicked.connect(self._mask_browse_clicked)
        self._summary.mask_plot_button.clicked.connect(self._mask_plot_clicked)
        self._summary.mask_check.clicked.connect(self._mask_checked)

        # Absolute scale connections and validators
        self._summary.scale_edit.setValidator(QDoubleValidator(self._summary.scale_edit))
        self._summary.scale_beam_radius_edit.setValidator(QDoubleValidator(self._summary.scale_beam_radius_edit))
        self._summary.scale_att_trans_edit.setValidator(QDoubleValidator(self._summary.scale_att_trans_edit))
        self._summary.scale_data_browse_button.clicked.connect(self._scale_data_browse)
        self._summary.scale_data_plot_button.clicked.connect(self._scale_data_plot_clicked)
        self._summary.beamstop_chk.clicked.connect(self._beamstop_clicked)
        self._summary.scale_chk.clicked.connect(self._scale_clicked)
        self._scale_clicked(self._summary.scale_chk.isChecked())

        # TOF cut validator
        self._summary.low_tof_edit.setValidator(QDoubleValidator(self._summary.low_tof_edit))
        self._summary.high_tof_edit.setValidator(QDoubleValidator(self._summary.high_tof_edit))

        # TOF connections
        self._summary.tof_cut_chk.clicked.connect(self._tof_clicked)

        # Monitor normalization
        self._summary.beam_monitor_chk.clicked.connect(self._beam_monitor_clicked)
        self._summary.beam_monitor_browse_button.clicked.connect(self._beam_monitor_reference_browse)

        # Resolution validator
        self._summary.sample_apert_edit.setValidator(QDoubleValidator(self._summary.sample_apert_edit))
        self._summary.resolution_chk.clicked.connect(self._resolution_clicked)

        # Since EQSANS does not currently use the absolute scale calculation, expose it in debug mode only for now
        if not self._settings.debug:
            self._summary.config_options_layout.deleteLater()
            self._summary.abs_scale_options_layout.deleteLater()
            self._summary.abs_scale_direct_beam_layout.deleteLater()
            self._summary.monitor_layout.deleteLater()
            self._summary.direct_beam_label.hide()
            self._summary.att_trans_label.hide()
            self._summary.beamstop_chk.hide()
            self._summary.scale_data_edit.hide()
            self._summary.scale_data_plot_button.hide()
            self._summary.scale_data_browse_button.hide()
            self._summary.scale_att_trans_edit.hide()
            self._summary.scale_beam_radius_edit.hide()
            self._summary.scale_chk.hide()
            self._summary.beam_monitor_chk.hide()
            self._summary.tof_correction_chk.hide()
            self._summary.beam_monitor_edit.hide()
            self._summary.beam_monitor_browse_button.hide()

            # Same thing for sample-detector distance and offset: not yet hooked in
            self._summary.geometry_options_groupbox.hide()

            # Hide expert options
            #self._summary.config_mask_chk.hide()
            self._summary.tof_cut_chk.hide()
            self._summary.low_tof_edit.hide()
            self._summary.high_tof_edit.hide()
            self._summary.low_tof_label.hide()
            self._summary.high_tof_label.hide()

            if not self._settings.advanced:
                self._summary.att_scale_factor_label.hide()
                self._summary.scale_edit.hide()
                self._summary.mask_groupbox.hide()
                self._summary.solid_angle_chk.hide()
                self._summary.resolution_chk.hide()
                self._summary.sample_apert_edit.hide()
                self._summary.sample_apert_label.hide()

        # We need the EQSANS data proxy for a quick load of a file for masking purposes, but
        # we don't want to show the plot button. Turn this off for the moment.
        if True or not self._has_instrument_view:
            self._summary.dark_plot_button.hide()
            self._summary.scale_data_plot_button.hide()
Exemple #2
0
    def initialize_content(self):
        """
            Declare the validators and event connections for the
            widgets loaded through the .ui file.
        """
        # Sample data

        # Validators
        self._content.transmission_edit.setValidator(
            QDoubleValidator(self._content.transmission_edit))
        self._content.dtransmission_edit.setValidator(
            QDoubleValidator(self._content.dtransmission_edit))
        self._content.beam_radius_edit.setValidator(
            QDoubleValidator(self._content.beam_radius_edit))
        self._content.sample_thickness_edit.setValidator(
            QDoubleValidator(self._content.sample_thickness_edit))

        # Connections
        self._content.data_file_browse_button.clicked.connect(
            self._data_file_browse)
        self._content.calculate_radio.clicked.connect(self._calculate_clicked)
        self._content.fix_trans_radio.clicked.connect(self._calculate_clicked)

        self._content.empty_button.clicked.connect(self._empty_browse)
        self._content.sample_button.clicked.connect(self._sample_browse)

        self._content.data_file_plot_button.clicked.connect(
            self._data_file_plot)
        self._content.empty_plot_button.clicked.connect(self._empty_plot)
        self._content.sample_plot_button.clicked.connect(self._sample_plot)

        # Calculate/Fix radio button
        g1 = QButtonGroup(self)
        g1.addButton(self._content.calculate_radio)
        g1.addButton(self._content.fix_trans_radio)
        g1.setExclusive(True)

        if not self._settings.debug:
            self._content.fix_transmission_layout.deleteLater()
            self._content.calculate_radio.hide()
            self._content.fix_trans_radio.hide()
            self._content.plus_minus_label.hide()
            self._content.transmission_edit.hide()
            self._content.dtransmission_edit.hide()

        if not self._has_instrument_view:
            self._content.data_file_plot_button.hide()
            self._content.empty_plot_button.hide()
            self._content.sample_plot_button.hide()

        # Background ##########
        # Validators
        self._content.bck_transmission_edit.setValidator(
            QDoubleValidator(self._content.bck_transmission_edit))
        self._content.bck_dtransmission_edit.setValidator(
            QDoubleValidator(self._content.bck_dtransmission_edit))
        self._content.bck_beam_radius_edit.setValidator(
            QDoubleValidator(self._content.beam_radius_edit))
        #self._content.bck_thickness_edit.setValidator(QDoubleValidator(self._content.bck_thickness_edit))

        # Connections
        self._content.background_chk.clicked.connect(self._background_clicked)
        self._content.background_browse.clicked.connect(
            self._background_browse)
        self._content.bck_calculate_radio.clicked.connect(
            self._bck_calculate_clicked)
        self._content.bck_fix_trans_radio.clicked.connect(
            self._bck_calculate_clicked)

        self._content.bck_empty_button.clicked.connect(self._bck_empty_browse)
        self._content.bck_sample_button.clicked.connect(
            self._bck_sample_browse)

        self._content.background_plot_button.clicked.connect(
            self._background_plot_clicked)
        self._content.bck_empty_plot_button.clicked.connect(
            self._bck_empty_plot)
        self._content.bck_sample_plot_button.clicked.connect(
            self._bck_sample_plot)

        # Calculate/Fix radio button
        g2 = QButtonGroup(self)
        g2.addButton(self._content.bck_calculate_radio)
        g2.addButton(self._content.bck_fix_trans_radio)
        g2.setExclusive(True)

        if not self._settings.debug:
            self._content.bck_fix_transmission_layout.deleteLater()
            self._content.bck_calculate_radio.hide()
            self._content.bck_fix_trans_radio.hide()
            self._content.bck_plus_minus_label.hide()
            self._content.bck_transmission_edit.hide()
            self._content.bck_dtransmission_edit.hide()

            if not self._settings.advanced:
                self._content.theta_dep_chk.hide()
                self._content.bck_theta_dep_chk.hide()
                self._content.sample_thickness_label.hide()
                self._content.sample_thickness_edit.hide()
                #self._content.bck_thickness_label.hide()
                #self._content.bck_thickness_edit.hide()

        if not self._has_instrument_view:
            self._content.background_plot_button.hide()
            self._content.bck_empty_plot_button.hide()
            self._content.bck_sample_plot_button.hide()
Exemple #3
0
class PyDMEnumButton(QWidget, PyDMWritableWidget, WidgetType):
    """
    A QWidget that renders buttons for every option of Enum Items.
    For now three types of buttons can be rendered:
    - Push Button
    - Radio Button

    Parameters
    ----------
    parent : QWidget
        The parent widget for the Label
    init_channel : str, optional
        The channel to be used by the widget.

    Signals
    -------
    send_value_signal : int, float, str, bool or np.ndarray
        Emitted when the user changes the value.
    """
    Q_ENUMS(WidgetType)
    WidgetType = WidgetType

    def __init__(self, parent=None, init_channel=None):
        QWidget.__init__(self, parent)
        PyDMWritableWidget.__init__(self, init_channel=init_channel)
        self._has_enums = False
        self._checkable = True
        self.setLayout(QGridLayout(self))
        self._btn_group = QButtonGroup()
        self._btn_group.setExclusive(True)
        self._btn_group.buttonClicked[int].connect(self.handle_button_clicked)
        self._widget_type = WidgetType.PushButton
        self._orientation = Qt.Vertical
        self._widgets = []
        self.rebuild_widgets()

    def minimumSizeHint(self):
        """
        This property holds the recommended minimum size for the widget.

        Returns
        -------
        QSize
        """
        # This is totally arbitrary, I just want *some* visible nonzero size
        return QSize(50, 100)

    @Property("QStringList")
    def items(self):
        """
        Items to be displayed in the button group.

        This property can be overridden by the items coming from the control system.
        Because C++ QStringList expects a list type, we need to make sure that None is never returned.

        Returns
        -------
        List[str]
        """
        return self.enum_strings or []

    @items.setter
    def items(self, value):
        self.enum_strings_changed(value)

    @Property(WidgetType)
    def widgetType(self):
        """
        The widget type to be used when composing the group.

        Returns
        -------
        WidgetType
        """
        return self._widget_type

    @widgetType.setter
    def widgetType(self, new_type):
        """
        The widget type to be used when composing the group.

        Parameters
        ----------
        new_type : WidgetType
        """
        if new_type != self._widget_type:
            self._widget_type = new_type
            self.rebuild_widgets()

    @Property(Qt.Orientation)
    def orientation(self):
        """
        Whether to lay out the bit indicators vertically or horizontally.

        Returns
        -------
        int
        """
        return self._orientation

    @orientation.setter
    def orientation(self, new_orientation):
        """
        Whether to lay out the bit indicators vertically or horizontally.

        Parameters
        -------
        new_orientation : Qt.Orientation, int
        """
        if new_orientation != self._orientation:
            self._orientation = new_orientation
            self.rebuild_layout()

    @Property(bool)
    def checkable(self):
        """
        Whether or not the button should be checkable.

        Returns
        -------
        bool
        """
        return self._checkable

    @checkable.setter
    def checkable(self, value):
        if value != self._checkable:
            self._checkable = value
            for widget in self._widgets:
                widget.setCheckable(value)

    @Slot(int)
    def handle_button_clicked(self, id):
        """
        Handles the event of a button being clicked.

        Parameters
        ----------
        id : int
            The clicked button id.
        """
        self.send_value_signal.emit(id)

    def clear(self):
        """
        Remove all inner widgets from the layout
        """
        for col in range(0, self.layout().columnCount()):
            for row in range(0, self.layout().rowCount()):
                item = self.layout().itemAtPosition(row, col)
                if item is not None:
                    w = item.widget()
                    if w is not None:
                        self.layout().removeWidget(w)

    def rebuild_widgets(self):
        """
        Rebuild the list of widgets based on a new enum or generates a default
        list of fake strings so we can see something at Designer.
        """

        def generate_widgets(items):
            while len(self._widgets) != 0:
                w = self._widgets.pop(0)
                self._btn_group.removeButton(w)
                w.deleteLater()

            for idx, entry in enumerate(items):
                w = class_for_type[self._widget_type](parent=self)
                w.setCheckable(self.checkable)
                w.setText(entry)
                self._widgets.append(w)
                self._btn_group.addButton(w, idx)

        self.clear()
        if self._has_enums:
            generate_widgets(self.enum_strings)
        else:
            generate_widgets(["Item 1", "Item 2", "Item ..."])

        self.rebuild_layout()

    def rebuild_layout(self):
        """
        Method to reorganize the top-level widget and its contents
        according to the layout property values.
        """
        self.clear()
        if self.orientation == Qt.Vertical:
            for i, widget in enumerate(self._widgets):
                self.layout().addWidget(widget, i, 0)
        elif self.orientation == Qt.Horizontal:
            for i, widget in enumerate(self._widgets):
                self.layout().addWidget(widget, 0, i)

    def check_enable_state(self):
        """
        Checks whether or not the widget should be disable.
        This method also disables the widget and add a Tool Tip
        with the reason why it is disabled.

        """
        status = self._write_access and self._connected and self._has_enums
        tooltip = ""
        if not self._connected:
            tooltip += "Channel is disconnected."
        elif not self._write_access:
            if data_plugins.is_read_only():
                tooltip += "Running PyDM on Read-Only mode."
            else:
                tooltip += "Access denied by Channel Access Security."
        elif not self._has_enums:
            tooltip += "Enums not available."

        self.setToolTip(tooltip)
        self.setEnabled(status)

    def value_changed(self, new_val):
        """
        Callback invoked when the Channel value is changed.

        Parameters
        ----------
        new_val : int
            The new value from the channel.
        """
        if new_val is not None and new_val != self.value:
            super(PyDMEnumButton, self).value_changed(new_val)
            btn = self._btn_group.button(new_val)
            if btn:
                btn.setChecked(True)

    def enum_strings_changed(self, new_enum_strings):
        """
        Callback invoked when the Channel has new enum values.
        This callback also triggers a value_changed call so the
        new enum values to be broadcasted.

        Parameters
        ----------
        new_enum_strings : tuple
            The new list of values
        """
        if new_enum_strings is not None \
                and new_enum_strings != self.enum_strings:
            super(PyDMEnumButton, self).enum_strings_changed(new_enum_strings)
            self._has_enums = True
            self.check_enable_state()
            self.rebuild_widgets()

    def paintEvent(self, _):
        """
        Paint events are sent to widgets that need to update themselves,
        for instance when part of a widget is exposed because a covering
        widget was moved.

        At PyDMDrawing this method handles the alarm painting with parameters
        from the stylesheet, configures the brush, pen and calls ```draw_item```
        so the specifics can be performed for each of the drawing classes.

        Parameters
        ----------
        event : QPaintEvent
        """
        painter = QPainter(self)
        opt = QStyleOption()
        opt.initFrom(self)
        self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self)
        painter.setRenderHint(QPainter.Antialiasing)
Exemple #4
0
    def initialize_content(self):
        """
            Declare the validators and event connections for the
            widgets loaded through the .ui file.
        """
        # Sample data

        # Validators
        self._content.transmission_edit.setValidator(QDoubleValidator(self._content.transmission_edit))
        self._content.dtransmission_edit.setValidator(QDoubleValidator(self._content.dtransmission_edit))
        self._content.beam_radius_edit.setValidator(QDoubleValidator(self._content.beam_radius_edit))
        self._content.sample_thickness_edit.setValidator(QDoubleValidator(self._content.sample_thickness_edit))

        # Connections
        self._content.data_file_browse_button.clicked.connect(self._data_file_browse)
        self._content.calculate_radio.clicked.connect(self._calculate_clicked)
        self._content.fix_trans_radio.clicked.connect(self._calculate_clicked)

        self._content.empty_button.clicked.connect(self._empty_browse)
        self._content.sample_button.clicked.connect(self._sample_browse)

        self._content.data_file_plot_button.clicked.connect(self._data_file_plot)
        self._content.empty_plot_button.clicked.connect(self._empty_plot)
        self._content.sample_plot_button.clicked.connect(self._sample_plot)

        # Calculate/Fix radio button
        g1 = QButtonGroup(self)
        g1.addButton(self._content.calculate_radio)
        g1.addButton(self._content.fix_trans_radio)
        g1.setExclusive(True)

        if not self._settings.debug:
            self._content.fix_transmission_layout.deleteLater()
            self._content.calculate_radio.hide()
            self._content.fix_trans_radio.hide()
            self._content.plus_minus_label.hide()
            self._content.transmission_edit.hide()
            self._content.dtransmission_edit.hide()

        if not self._has_instrument_view:
            self._content.data_file_plot_button.hide()
            self._content.empty_plot_button.hide()
            self._content.sample_plot_button.hide()

        # Background ##########
        # Validators
        self._content.bck_transmission_edit.setValidator(QDoubleValidator(self._content.bck_transmission_edit))
        self._content.bck_dtransmission_edit.setValidator(QDoubleValidator(self._content.bck_dtransmission_edit))
        self._content.bck_beam_radius_edit.setValidator(QDoubleValidator(self._content.beam_radius_edit))
        #self._content.bck_thickness_edit.setValidator(QDoubleValidator(self._content.bck_thickness_edit))

        # Connections
        self._content.background_chk.clicked.connect(self._background_clicked)
        self._content.background_browse.clicked.connect(self._background_browse)
        self._content.bck_calculate_radio.clicked.connect(self._bck_calculate_clicked)
        self._content.bck_fix_trans_radio.clicked.connect(self._bck_calculate_clicked)

        self._content.bck_empty_button.clicked.connect(self._bck_empty_browse)
        self._content.bck_sample_button.clicked.connect(self._bck_sample_browse)

        self._content.background_plot_button.clicked.connect(self._background_plot_clicked)
        self._content.bck_empty_plot_button.clicked.connect(self._bck_empty_plot)
        self._content.bck_sample_plot_button.clicked.connect(self._bck_sample_plot)

        # Calculate/Fix radio button
        g2 = QButtonGroup(self)
        g2.addButton(self._content.bck_calculate_radio)
        g2.addButton(self._content.bck_fix_trans_radio)
        g2.setExclusive(True)

        if not self._settings.debug:
            self._content.bck_fix_transmission_layout.deleteLater()
            self._content.bck_calculate_radio.hide()
            self._content.bck_fix_trans_radio.hide()
            self._content.bck_plus_minus_label.hide()
            self._content.bck_transmission_edit.hide()
            self._content.bck_dtransmission_edit.hide()

            if not self._settings.advanced:
                self._content.theta_dep_chk.hide()
                self._content.bck_theta_dep_chk.hide()
                self._content.sample_thickness_label.hide()
                self._content.sample_thickness_edit.hide()
                #self._content.bck_thickness_label.hide()
                #self._content.bck_thickness_edit.hide()

        if not self._has_instrument_view:
            self._content.background_plot_button.hide()
            self._content.bck_empty_plot_button.hide()
            self._content.bck_sample_plot_button.hide()
Exemple #5
0
class BaseWindow(SiriusMainWindow):
    """Base class."""
    def __init__(self, parent=None, prefix=_VACA_PREFIX):
        """Init."""
        super().__init__(parent)
        self.prefix = prefix
        self._curr_dir = _os.path.abspath(_os.path.dirname(__file__))

    def _setupUi(self):
        # menubar
        self.menubar = QMenuBar(self)
        self.menubar.setNativeMenuBar(False)
        self.setMenuBar(self.menubar)
        self.menu = self.menubar.addMenu("Open...")
        self._setupMenu()

        # auxiliar diagnostics widget
        self.auxdig_wid = None
        self._setupDiagWidget()

        # lattice widget
        self.lattice_wid = QSvgWidget(
            _os.path.join(self._curr_dir, self.SVG_FILE))

        # screens view widget (create only one ScrnView)
        self._scrns_wids_dict = dict()
        self._currScrn = 0
        scrn_wid = SiriusScrnView(parent=self,
                                  prefix=self.prefix,
                                  device=self._scrns[self._currScrn])
        scrn_wid.setVisible(True)
        self._scrns_wids_dict[self._currScrn] = scrn_wid
        self.scrns_wid = QWidget()
        lay_scrns = QGridLayout(self.scrns_wid)
        lay_scrns.addWidget(scrn_wid)

        # correction widget
        self.corr_wid = QGroupBox('Screens and Correctors Panel')
        self._scrns_sel_bg = QButtonGroup(parent=self.corr_wid)
        self._scrns_sel_bg.setExclusive(True)
        self._setupScrnsCorrsWidget()

        vlay1 = QVBoxLayout()
        if self.auxdig_wid:
            vlay1.addWidget(self.auxdig_wid)
        vlay1.addWidget(self.scrns_wid)
        vlay2 = QVBoxLayout()
        vlay2.addWidget(self.lattice_wid)
        vlay2.addWidget(self.corr_wid)

        cw = QWidget()
        lay = QHBoxLayout(cw)
        lay.addLayout(vlay1)
        lay.addLayout(vlay2)
        self.setCentralWidget(cw)

    def _setupMenu(self):
        raise NotImplementedError

    def _setupScrnsCorrsWidget(self):
        raise NotImplementedError

    def _setupDiagWidget(self):
        raise NotImplementedError

    @Slot()
    def _setScrnWidget(self):
        scrn_obj = self._scrns_wids_dict[self._currScrn]
        scrn_obj.setVisible(False)

        sender = self.sender()
        self._currScrn = self._scrns_sel_bg.id(sender)

        if self._currScrn not in self._scrns_wids_dict.keys():
            scrn_obj = SiriusScrnView(parent=self,
                                      prefix=self.prefix,
                                      device=self._scrns[self._currScrn])
            self.scrns_wid.layout().addWidget(scrn_obj, 2, 0)
            self._scrns_wids_dict[self._currScrn] = scrn_obj
        else:
            scrn_obj = self._scrns_wids_dict[self._currScrn]

        self._scrns_wids_dict[self._currScrn].setVisible(True)

    def _create_headerline(self, labels):
        """Create and return a headerline."""
        hl = QWidget()
        hl.setLayout(QHBoxLayout())
        hl.layout().setContentsMargins(0, 9, 0, 0)

        glay = None
        for text, width in labels:
            if not width:
                if glay:
                    hl.layout().addLayout(glay)
                hl.layout().addStretch()
                glay = QGridLayout()
                glay.setAlignment(Qt.AlignCenter)
                glay.setContentsMargins(0, 0, 0, 0)
                c = 0
            else:
                label = QLabel(text, self)
                label.setStyleSheet("""
                    min-width:valueem; min-height:1.29em; max-height:1.29em;
                    font-weight:bold; qproperty-alignment: AlignCenter;
                    """.replace('value', str(width)))
                glay.addWidget(label, 0, c)
                c += 1
        return hl

    def _create_scrn_summwidget(self, scrn_device, scrn_idx):
        """Create and return a screen detail widget."""
        cb_scrn = QCheckBox(scrn_device.get_nickname(dev=True), self)
        self._scrns_sel_bg.addButton(cb_scrn)
        self._scrns_sel_bg.setId(cb_scrn, scrn_idx)
        if scrn_idx == self._currScrn:
            cb_scrn.setChecked(True)
        cb_scrn.clicked.connect(self._setScrnWidget)
        cb_scrn.setStyleSheet("""
            min-width:6.5em; max-width:6.5em; font-weight:bold;""")

        led_camenbl = SiriusLedState(
            self,
            scrn_device.substitute(prefix=self.prefix, propty='CamEnbl-Sts'))
        led_camenbl.setStyleSheet("min-width:3.2em; max-width:3.2em;")

        cb_scrntype = PyDMEnumComboBox(
            self,
            scrn_device.substitute(prefix=self.prefix, propty='ScrnType-Sel'))
        cb_scrntype.setSizePolicy(QSzPlcy.Minimum, QSzPlcy.Fixed)
        cb_scrntype.setStyleSheet("min-width:4.5em;max-width:4.5em;")

        lb_scrntype = PyDMLabel(
            self,
            scrn_device.substitute(prefix=self.prefix, propty='ScrnType-Sts'))
        lb_scrntype.setStyleSheet("min-width:4.5em; max-width:4.5em;")
        lb_scrntype.setAlignment(Qt.AlignCenter)

        led_scrntype = PyDMLed(self,
                               scrn_device.substitute(prefix=self.prefix,
                                                      propty='ScrnType-Sts'),
                               color_list=[
                                   PyDMLed.LightGreen, PyDMLed.Red,
                                   PyDMLed.Red, PyDMLed.Yellow
                               ])
        led_scrntype.shape = 2
        led_scrntype.setStyleSheet("""min-width:4.5em; max-width:4.5em;""")

        wid = QWidget()
        lay = QGridLayout(wid)
        lay.setAlignment(Qt.AlignCenter)
        lay.addWidget(cb_scrn, 1, 1)
        lay.addWidget(led_camenbl, 1, 2)
        lay.addWidget(cb_scrntype, 1, 3)
        lay.addWidget(lb_scrntype, 1, 4)
        lay.addWidget(led_scrntype, 2, 4)
        return wid

    def _create_corr_summwidget(self, corr):
        """Create and return a corrector detail widget."""
        wid = QWidget()
        wid.setSizePolicy(QSzPlcy.Preferred, QSzPlcy.Maximum)
        lay = QGridLayout(wid)
        lay.setContentsMargins(0, 0, 0, 0)
        lay.setAlignment(Qt.AlignCenter)

        propty_sp = 'Current-SP' if corr.sec == 'LI' else 'Kick-SP'
        propty_mon = propty_sp.replace('SP', 'Mon')

        led = SiriusLedState(
            self, corr.substitute(prefix=self.prefix, propty='PwrState-Sts'))
        led.setStyleSheet("max-width:1.29em;")
        lay.addWidget(led, 1, 1)

        nickname = corr.get_nickname(sec=corr.sec == 'LI', dev=True)
        pb = QPushButton(nickname, self)
        if corr.dis == 'PU':
            util.connect_window(pb, PUDetailWindow, parent=self, devname=corr)
        else:
            util.connect_window(pb, PSDetailWindow, parent=self, psname=corr)
        pb.setStyleSheet("""
            min-width:6em; max-width:6em; min-height:1.29em;""")
        lay.addWidget(pb, 1, 2)

        sp_kick = PyDMSpinboxScrollbar(
            self, corr.substitute(prefix=self.prefix, propty=propty_sp))
        sp_kick.setStyleSheet("QDoubleSpinBox{min-width:4em; max-width:4em; }"
                              "QScrollBar{max-width:4em;}")
        sp_kick.spinbox.precisionFromPV = False
        sp_kick.spinbox.precision = 1
        sp_kick.scrollbar.limitsFromPV = True
        lay.addWidget(sp_kick, 1, 3, 2, 1)

        lb_kick = PyDMLabel(
            self, corr.substitute(prefix=self.prefix, propty=propty_mon))
        lb_kick.setStyleSheet("""
            min-width:5em; max-width:5em; min-height:1.29em;""")
        lb_kick.showUnits = True
        lb_kick.precisionFromPV = False
        lb_kick.precision = 1
        lb_kick.setAlignment(Qt.AlignCenter)
        lay.addWidget(lb_kick, 1, 4)
        return wid
Exemple #6
0
class PyDMEnumButton(QWidget, PyDMWritableWidget, WidgetType):
    """
    A QWidget that renders buttons for every option of Enum Items.
    For now three types of buttons can be rendered:
    - Push Button
    - Radio Button

    Parameters
    ----------
    parent : QWidget
        The parent widget for the Label
    init_channel : str, optional
        The channel to be used by the widget.

    Signals
    -------
    send_value_signal : int, float, str, bool or np.ndarray
        Emitted when the user changes the value.
    """
    Q_ENUMS(WidgetType)
    WidgetType = WidgetType

    def __init__(self, parent=None, init_channel=None):
        QWidget.__init__(self, parent)
        PyDMWritableWidget.__init__(self, init_channel=init_channel)
        self._has_enums = False
        self.setLayout(QGridLayout(self))
        self._btn_group = QButtonGroup()
        self._btn_group.setExclusive(True)
        self._btn_group.buttonClicked[int].connect(self.handle_button_clicked)
        self._widget_type = WidgetType.PushButton
        self._orientation = Qt.Vertical
        self._widgets = []
        self.rebuild_widgets()

    def minimumSizeHint(self):
        """
        This property holds the recommended minimum size for the widget.

        Returns
        -------
        QSize
        """
        # This is totally arbitrary, I just want *some* visible nonzero size
        return QSize(50, 100)

    @Property(WidgetType)
    def widgetType(self):
        """
        The widget type to be used when composing the group.

        Returns
        -------
        WidgetType
        """
        return self._widget_type

    @widgetType.setter
    def widgetType(self, new_type):
        """
        The widget type to be used when composing the group.

        Parameters
        ----------
        new_type : WidgetType
        """
        if new_type != self._widget_type:
            self._widget_type = new_type
            self.rebuild_widgets()

    @Property(Qt.Orientation)
    def orientation(self):
        """
        Whether to lay out the bit indicators vertically or horizontally.

        Returns
        -------
        int
        """
        return self._orientation

    @orientation.setter
    def orientation(self, new_orientation):
        """
        Whether to lay out the bit indicators vertically or horizontally.

        Parameters
        -------
        new_orientation : Qt.Orientation, int
        """
        if new_orientation != self._orientation:
            self._orientation = new_orientation
            self.rebuild_layout()

    @Slot(int)
    def handle_button_clicked(self, id):
        """
        Handles the event of a button being clicked.

        Parameters
        ----------
        id : int
            The clicked button id.
        """
        self.send_value_signal.emit(id)

    def clear(self):
        """
        Remove all inner widgets from the layout
        """
        for col in range(0, self.layout().columnCount()):
            for row in range(0, self.layout().rowCount()):
                item = self.layout().itemAtPosition(row, col)
                if item is not None:
                    w = item.widget()
                    if w is not None:
                        self.layout().removeWidget(w)

    def rebuild_widgets(self):
        """
        Rebuild the list of widgets based on a new enum or generates a default
        list of fake strings so we can see something at Designer.
        """

        def generate_widgets(items):
            while len(self._widgets) != 0:
                w = self._widgets.pop(0)
                self._btn_group.removeButton(w)
                w.deleteLater()

            for idx, entry in enumerate(items):
                w = class_for_type[self._widget_type](parent=self)
                w.setCheckable(True)
                w.setText(entry)
                self._widgets.append(w)
                self._btn_group.addButton(w, idx)

        self.clear()
        if self._has_enums:
            generate_widgets(self.enum_strings)
        else:
            generate_widgets(["Item 1", "Item 2", "Item ..."])

        self.rebuild_layout()

    def rebuild_layout(self):
        """
        Method to reorganize the top-level widget and its contents
        according to the layout property values.
        """
        self.clear()
        if self.orientation == Qt.Vertical:
            for i, widget in enumerate(self._widgets):
                self.layout().addWidget(widget, i, 0)
        elif self.orientation == Qt.Horizontal:
            for i, widget in enumerate(self._widgets):
                self.layout().addWidget(widget, 0, i)

    def check_enable_state(self):
        """
        Checks whether or not the widget should be disable.
        This method also disables the widget and add a Tool Tip
        with the reason why it is disabled.

        """
        status = self._write_access and self._connected and self._has_enums
        tooltip = ""
        if not self._connected:
            tooltip += "Channel is disconnected."
        elif not self._write_access:
            if data_plugins.is_read_only():
                tooltip += "Running PyDM on Read-Only mode."
            else:
                tooltip += "Access denied by Channel Access Security."
        elif not self._has_enums:
            tooltip += "Enums not available."

        self.setToolTip(tooltip)
        self.setEnabled(status)

    def value_changed(self, new_val):
        """
        Callback invoked when the Channel value is changed.

        Parameters
        ----------
        new_val : int
            The new value from the channel.
        """
        if new_val is not None and new_val != self.value:
            super(PyDMEnumButton, self).value_changed(new_val)
            btn = self._btn_group.button(new_val)
            if btn:
                btn.setChecked(True)

    def enum_strings_changed(self, new_enum_strings):
        """
        Callback invoked when the Channel has new enum values.
        This callback also triggers a value_changed call so the
        new enum values to be broadcasted.

        Parameters
        ----------
        new_enum_strings : tuple
            The new list of values
        """
        if new_enum_strings is not None \
                and new_enum_strings != self.enum_strings:
            super(PyDMEnumButton, self).enum_strings_changed(new_enum_strings)
            self._has_enums = True
            self.check_enable_state()
            self.rebuild_widgets()
Exemple #7
0
class ConsoleRestartDialog(QDialog):
    """
    Dialog to apply preferences that need a restart of the console kernel.
    """

    # Constants for actions when Preferences require a kernel restart
    NO_RESTART = 1
    RESTART_CURRENT = 2
    RESTART_ALL = 3

    def __init__(self, parent):
        super(ConsoleRestartDialog, self).__init__(parent)
        self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint)
        self._parent = parent
        self._action = self.NO_RESTART
        self._action_string = {
            self.NO_RESTART: _("Keep Existing Kernels"),
            self.RESTART_CURRENT: _("Restart Current Kernel"),
            self.RESTART_ALL: _("Restart All Kernels")
        }
        # Dialog widgets
        # Text
        self._text_label = QLabel(
            _("By default, some IPython console preferences will be "
              "applied to new consoles only. To apply preferences to "
              "existing consoles, select from the options below.<br><br>"
              "Please note: applying changes to running consoles will force"
              " a kernel restart and all current work will be lost."), self)
        self._text_label.setWordWrap(True)
        self._text_label.setFixedWidth(450)

        # Checkboxes
        self._restart_current = QCheckBox(
            _("Apply to current console and restart kernel"), self)
        self._restart_all = QCheckBox(
            _("Apply to all existing consoles and restart all kernels"), self)
        self._checkbox_group = QButtonGroup(self)
        self._checkbox_group.setExclusive(False)
        self._checkbox_group.addButton(self._restart_current,
                                       id=self.RESTART_CURRENT)
        self._checkbox_group.addButton(self._restart_all, id=self.RESTART_ALL)

        self._action_button = QPushButton(self._action_string[self.NO_RESTART],
                                          parent=self)

        # Dialog Layout
        layout = QVBoxLayout(self)
        layout.addWidget(self._text_label)
        layout.addSpacing(5)
        layout.addWidget(self._restart_current)
        layout.addWidget(self._restart_all)
        layout.addSpacing(10)
        layout.addWidget(self._action_button, 0, Qt.AlignRight)
        layout.setContentsMargins(20, 20, 20, 20)
        self.setLayout(layout)

        # Signals
        self._checkbox_group.buttonToggled.connect(
            self.update_action_button_text)
        self._action_button.clicked.connect(self.accept)

    def update_action_button_text(self, checkbox, is_checked):
        """
        Update action button text.

        Takes into account the given checkbox to update the text.
        """
        checkbox_id = self._checkbox_group.id(checkbox)
        if is_checked:
            text = self._action_string[checkbox_id]
            self._checkbox_group.buttonToggled.disconnect(
                self.update_action_button_text)
            self._restart_current.setChecked(False)
            self._restart_all.setChecked(False)
            checkbox.setChecked(True)
            self._checkbox_group.buttonToggled.connect(
                self.update_action_button_text)
        else:
            text = self._action_string[self.NO_RESTART]
        self._action_button.setText(text)

    def get_action_value(self):
        """
        Return tuple indicating True or False for the available actions.
        """
        restart_current = self._restart_current.isChecked()
        restart_all = self._restart_all.isChecked()
        no_restart = not any([restart_all, restart_current])
        return restart_all, restart_current, no_restart
Exemple #8
0
class QtSearchInput(QWidget):
    """
    Qt view for SearchInput

    Parameters
    ----------
    model: SearchInput
    """

    def __init__(self, model, *args, **kwargs):
        self.model = model
        super().__init__(*args, **kwargs)

        self.setLayout(QFormLayout())
        # Radiobuttons to quickly select default time period
        self.all_widget = QRadioButton("All")
        self.year_widget = QRadioButton("1 Year")
        self.month_widget = QRadioButton("30 Days")
        self.week_widget = QRadioButton("1 Week")
        self.today_widget = QRadioButton("24h")
        self.hour_widget = QRadioButton("1 Hour")
        self.radio_button_group = QButtonGroup()
        self.radio_button_group.addButton(self.all_widget)
        self.radio_button_group.addButton(self.year_widget)
        self.radio_button_group.addButton(self.month_widget)
        self.radio_button_group.addButton(self.week_widget)
        self.radio_button_group.addButton(self.today_widget)
        self.radio_button_group.addButton(self.hour_widget)
        default_period_layout = QGridLayout()
        default_period_layout.setHorizontalSpacing(85)
        default_period_layout.setVerticalSpacing(10)
        default_period_layout.addWidget(self.all_widget, 0, 0, 1, 2)
        default_period_layout.addWidget(self.year_widget, 1, 0, 1, 2)
        default_period_layout.addWidget(self.month_widget, 2, 0, 1, 2)
        default_period_layout.addWidget(self.week_widget, 0, 1, 1, 2)
        default_period_layout.addWidget(self.today_widget, 1, 1, 1, 2)
        default_period_layout.addWidget(self.hour_widget, 2, 1, 1, 2)
        self.layout().addRow("When:", default_period_layout)

        # TODO: rethink if restriction to acceptable timedelta values is required
        # from ..models.search.search_input import SearchInput
        # self.allowed = {timedelta(days=-1), timedelta(days=-30), timedelta(minutes=-60), timedelta(days=-7),
        #                 timedelta(days=-365)}
        # def time_validator(since=None, until=None):
        #     """
        #     Enforce that since and until are values that a UI can represent.
        #     This is an example similar to what will be used in the Qt UI.
        #     """
        #     now = timedelta()
        #     if isinstance(since, timedelta):
        #         if not (until is None or until == now):
        #             raise ValueError(
        #                 "This UI cannot express since=timedelta(...) unless until "
        #                 "is timedelta() or None."
        #             )
        #         for item in allowed:
        #             if since == item:
        #                 break
        #         else:
        #             # No matches
        #             raise ValueError(
        #                 "This UI can only express since as a timedelta if it is "
        #                 f"one of {allowed}. The value {since} is not allowed"
        #             )
        # s = SearchInput()
        # s.time_validator = time_validator

        # "Since: <datetime picker>"
        self.since_widget = QDateTimeEdit()
        self.since_widget.setCalendarPopup(True)
        self.since_widget.setDisplayFormat("yyyy-MM-dd HH:mm")
        self.layout().addRow("Since:", self.since_widget)

        # "Until: <datetime picker>"
        self.until_widget = QDateTimeEdit()
        self.until_widget.setCalendarPopup(True)
        self.until_widget.setDisplayFormat("yyyy-MM-dd HH:mm")
        self.layout().addRow("Until:", self.until_widget)

        # Refresh Button
        self.refresh_button = QPushButton("Refresh")
        self.layout().addWidget(self.refresh_button)

        # Changes to the GUI update the model.
        self.since_widget.dateTimeChanged.connect(self.on_since_view_changed)
        self.until_widget.dateTimeChanged.connect(self.on_until_view_changed)

        self.refresh_button.clicked.connect(self.model.request_reload)
        self.model.events.reload.connect(self.on_reload)
        self.model.events.query.connect(self.on_reload)
        # Changes to the model update the GUI.
        self.model.events.since.connect(self.on_since_model_changed)
        self.model.events.until.connect(self.on_until_model_changed)

        # connect QRadioButtons and change date dropdowns (since/until widgets) accordingly
        self.hour_widget.toggled.connect(self.on_toggle_hour)
        self.today_widget.toggled.connect(self.on_toggle_24h)
        self.week_widget.toggled.connect(self.on_toggle_week)
        self.month_widget.toggled.connect(self.on_toggle_month)
        self.year_widget.toggled.connect(self.on_toggle_year)
        self.all_widget.toggled.connect(self.on_toggle_all)

        self.all_widget.setChecked(True)

    def on_reload(self, event):
        now = datetime.now(LOCAL_TIMEZONE)
        if isinstance(self.model.since, timedelta):
            with _blocked(self.since_widget):
                self.since_widget.setDateTime(as_qdatetime(now + self.model.since))
        if isinstance(self.model.until, timedelta):
            with _blocked(self.until_widget):
                self.until_widget.setDateTime(as_qdatetime(now + self.model.until))

    def on_since_view_changed(self, qdatetime):
        # When GUI is updated
        self.model.since = QDateTime.toPython(qdatetime)

    def on_since_model_changed(self, event):
        # When model is updated (e.g. from console or by clicking a QRadioButton)
        now = datetime.now(LOCAL_TIMEZONE)
        if isinstance(event.date, timedelta):
            qdatetime = as_qdatetime(now + event.date)
            if event.date == timedelta(minutes=-60):
                self.hour_widget.setChecked(True)
            elif event.date == timedelta(days=-1):
                self.today_widget.setChecked(True)
            elif event.date == timedelta(days=-7):
                self.week_widget.setChecked(True)
            elif event.date == timedelta(days=-30):
                self.month_widget.setChecked(True)
            elif event.date == timedelta(days=-365):
                self.year_widget.setChecked(True)
            else:
                # No checkbox associated with this custom timedelta
                pass
        else:
            # Must be a datetime
            if event.date == ADA_LOVELACE_BIRTHDAY:
                self.all_widget.setChecked(True)
            else:
                self.uncheck_radiobuttons()
            qdatetime = as_qdatetime(event.date)
        with _blocked(self.since_widget):
            self.since_widget.setDateTime(qdatetime)
        with _blocked(self.until_widget):
            self.until_widget.setDateTime(as_qdatetime(now))

    def on_until_view_changed(self, qdatetime):
        # When GUI is updated
        self.model.until = QDateTime.toPython(qdatetime)

    def on_until_model_changed(self, event):
        # When model is updated (e.g. from console or by clicking a QRadioButton)
        if not isinstance(event.date, timedelta):
            qdatetime = as_qdatetime(event.date)
            self.uncheck_radiobuttons()
            with _blocked(self.until_widget):
                self.until_widget.setDateTime(qdatetime)

    def on_toggle_24h(self):
        if self.today_widget.isChecked():
            self.model.since = timedelta(days=-1)
            self.model.until = timedelta()

    def on_toggle_hour(self):
        if self.hour_widget.isChecked():
            self.model.since = timedelta(minutes=-60)
            self.model.until = timedelta()

    def on_toggle_week(self):
        if self.week_widget.isChecked():
            self.model.since = timedelta(days=-7)
            self.model.until = timedelta()

    def on_toggle_month(self):
        if self.month_widget.isChecked():
            self.model.since = timedelta(days=-30)
            self.model.until = timedelta()

    def on_toggle_year(self):
        if self.year_widget.isChecked():
            self.model.since = timedelta(days=-365)
            self.model.until = timedelta()

    def on_toggle_all(self):
        # Search for all catalogs since Ada Lovelace's Birthday.
        if self.all_widget.isChecked():
            self.model.since = ADA_LOVELACE_BIRTHDAY
            self.model.until = timedelta()

    def uncheck_radiobuttons(self):
        self.radio_button_group.setExclusive(False)
        self.all_widget.setChecked(False)
        self.year_widget.setChecked(False)
        self.month_widget.setChecked(False)
        self.week_widget.setChecked(False)
        self.today_widget.setChecked(False)
        self.hour_widget.setChecked(False)
        self.radio_button_group.setExclusive(True)
Exemple #9
0
    def initialize_content(self):
        # Validators
        self._summary.detector_offset_edit.setValidator(QDoubleValidator(self._summary.detector_offset_edit))
        self._summary.sample_dist_edit.setValidator(QDoubleValidator(self._summary.sample_dist_edit))
        self._summary.n_q_bins_edit.setValidator(QIntValidator(self._summary.n_q_bins_edit))

        # Event connections
        self._summary.detector_offset_chk.clicked.connect(self._det_offset_clicked)
        self._summary.sample_dist_chk.clicked.connect(self._sample_dist_clicked)
        self._summary.help_button.clicked.connect(self._show_help)

        self._summary.dark_current_check.clicked.connect(self._dark_clicked)
        self._summary.dark_browse_button.clicked.connect(self._dark_browse)
        self._summary.dark_plot_button.clicked.connect(self._dark_plot_clicked)

        # Output directory
        g2 = QButtonGroup(self)
        g2.addButton(self._summary.select_output_dir_radio)
        g2.addButton(self._summary.use_data_dir_radio)
        g2.setExclusive(True)
        self._summary.select_output_dir_radio.clicked.connect(self._output_dir_clicked)
        self._summary.use_data_dir_radio.clicked.connect(self._output_dir_clicked)
        self._summary.output_dir_browse_button.clicked.connect(self._output_dir_browse)
        self._output_dir_clicked()

        # Lin/log option
        g3 = QButtonGroup(self)
        g3.addButton(self._summary.log_binning_radio)
        g3.addButton(self._summary.lin_binning_radio)
        g3.setExclusive(True)

        # Q range
        self._summary.n_q_bins_edit.setText("100")

        self._summary.scale_edit.setText("1")

        self._summary.instr_name_label.hide()
        self._dark_clicked(self._summary.dark_current_check.isChecked())

        # Mask Connections
        self._summary.mask_browse_button.clicked.connect(self._mask_browse_clicked)
        self._summary.mask_plot_button.clicked.connect(self._mask_plot_clicked)
        self._summary.mask_check.clicked.connect(self._mask_checked)

        # Absolute scale connections and validators
        self._summary.scale_edit.setValidator(QDoubleValidator(self._summary.scale_edit))
        self._summary.scale_beam_radius_edit.setValidator(QDoubleValidator(self._summary.scale_beam_radius_edit))
        self._summary.scale_att_trans_edit.setValidator(QDoubleValidator(self._summary.scale_att_trans_edit))
        self._summary.scale_data_browse_button.clicked.connect(self._scale_data_browse)
        self._summary.scale_data_plot_button.clicked.connect(self._scale_data_plot_clicked)
        self._summary.beamstop_chk.clicked.connect(self._beamstop_clicked)
        self._summary.scale_chk.clicked.connect(self._scale_clicked)
        self._scale_clicked(self._summary.scale_chk.isChecked())

        # TOF cut validator
        self._summary.low_tof_edit.setValidator(QDoubleValidator(self._summary.low_tof_edit))
        self._summary.high_tof_edit.setValidator(QDoubleValidator(self._summary.high_tof_edit))

        # TOF connections
        self._summary.tof_cut_chk.clicked.connect(self._tof_clicked)

        # Monitor normalization
        self._summary.beam_monitor_chk.clicked.connect(self._beam_monitor_clicked)
        self._summary.beam_monitor_browse_button.clicked.connect(self._beam_monitor_reference_browse)

        # Resolution validator
        self._summary.sample_apert_edit.setValidator(QDoubleValidator(self._summary.sample_apert_edit))
        self._summary.resolution_chk.clicked.connect(self._resolution_clicked)

        # Since EQSANS does not currently use the absolute scale calculation, expose it in debug mode only for now
        if not self._settings.debug:
            self._summary.config_options_layout.deleteLater()
            self._summary.abs_scale_options_layout.deleteLater()
            self._summary.abs_scale_direct_beam_layout.deleteLater()
            self._summary.monitor_layout.deleteLater()
            self._summary.direct_beam_label.hide()
            self._summary.att_trans_label.hide()
            self._summary.beamstop_chk.hide()
            self._summary.scale_data_edit.hide()
            self._summary.scale_data_plot_button.hide()
            self._summary.scale_data_browse_button.hide()
            self._summary.scale_att_trans_edit.hide()
            self._summary.scale_beam_radius_edit.hide()
            self._summary.scale_chk.hide()
            self._summary.beam_monitor_chk.hide()
            self._summary.tof_correction_chk.hide()
            self._summary.beam_monitor_edit.hide()
            self._summary.beam_monitor_browse_button.hide()

            # Same thing for sample-detector distance and offset: not yet hooked in
            self._summary.geometry_options_groupbox.hide()

            # Hide expert options
            #self._summary.config_mask_chk.hide()
            self._summary.tof_cut_chk.hide()
            self._summary.low_tof_edit.hide()
            self._summary.high_tof_edit.hide()
            self._summary.low_tof_label.hide()
            self._summary.high_tof_label.hide()

            if not self._settings.advanced:
                self._summary.att_scale_factor_label.hide()
                self._summary.scale_edit.hide()
                self._summary.mask_groupbox.hide()
                self._summary.solid_angle_chk.hide()
                self._summary.resolution_chk.hide()
                self._summary.sample_apert_edit.hide()
                self._summary.sample_apert_label.hide()

        # We need the EQSANS data proxy for a quick load of a file for masking purposes, but
        # we don't want to show the plot button. Turn this off for the moment.
        if True or not self._has_instrument_view:
            self._summary.dark_plot_button.hide()
            self._summary.scale_data_plot_button.hide()
Exemple #10
0
    def initialize_content(self):
        """
            Initialize the content of the frame
        """
        # Validators
        self._content.low_scale_edit.setValidator(QDoubleValidator(self._content.low_scale_edit))
        self._content.medium_scale_edit.setValidator(QDoubleValidator(self._content.medium_scale_edit))
        self._content.high_scale_edit.setValidator(QDoubleValidator(self._content.high_scale_edit))

        self._content.low_min_edit.setValidator(QDoubleValidator(self._content.low_min_edit))
        self._content.low_max_edit.setValidator(QDoubleValidator(self._content.low_max_edit))
        self._content.medium_min_edit.setValidator(QDoubleValidator(self._content.medium_min_edit))
        self._content.medium_max_edit.setValidator(QDoubleValidator(self._content.medium_max_edit))

        # Browse buttons
        self._content.low_q_browse_button.clicked.connect(self._low_q_browse)
        self._content.medium_q_browse_button.clicked.connect(self._medium_q_browse)
        self._content.high_q_browse_button.clicked.connect(self._high_q_browse)

        self._content.low_q_combo.activated.connect(self._update_low_q)
        self._content.medium_q_combo.activated.connect(self._update_medium_q)
        self._content.high_q_combo.activated.connect(self._update_high_q)

        # Radio buttons
        self._content.low_radio.clicked.connect(self._low_q_selected)
        self._content.medium_radio.clicked.connect(self._medium_q_selected)
        self._content.high_radio.clicked.connect(self._high_q_selected)

        # Selection buttons
        self._content.low_range_button.clicked.connect(self._low_range)
        self._content.medium_range_button.clicked.connect(self._medium_range)

        # Scale factors
        self._content.low_scale_edit.returnPressed.connect(self._update_low_scale)
        self._content.medium_scale_edit.returnPressed.connect(self._update_medium_scale)
        self._content.high_scale_edit.returnPressed.connect(self._update_high_scale)

        # Apply and save buttons
        self._content.apply_button.clicked.connect(self._apply)
        self._content.save_result_button.clicked.connect(self._save_result)

        # Create button group for data set selection
        g = QButtonGroup(self)
        g.addButton(self._content.low_radio)
        g.addButton(self._content.medium_radio)
        g.addButton(self._content.high_radio)
        g.setExclusive(True)
        self._content.low_radio.setChecked(True)

        self._content.low_q_combo.insertItem(0, "")
        self.populate_combobox(self._content.low_q_combo)
        self._content.low_q_combo.setEditable(True)

        self._content.medium_q_combo.insertItem(0, "")
        self.populate_combobox(self._content.medium_q_combo)
        self._content.medium_q_combo.setEditable(True)

        self._content.high_q_combo.insertItem(0, "")
        self.populate_combobox(self._content.high_q_combo)
        self._content.high_q_combo.setEditable(True)
        # pylint: disable = no-self-argument

        class ShowEventFilter(QObject):
            def eventFilter(obj_self, filteredObj, event):
                if event.type() == QEvent.HoverEnter:
                    self.populate_combobox(filteredObj)
                    filteredObj.update()
                elif event.type() == QEvent.KeyPress:
                    if filteredObj == self._content.low_q_combo:
                        self._low_q_modified = True
                    elif filteredObj == self._content.medium_q_combo:
                        self._medium_q_modified = True
                    elif filteredObj == self._content.high_q_combo:
                        self._high_q_modified = True

                    if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter:
                        filteredObj.setItemText(0, filteredObj.lineEdit().text())
                        if filteredObj == self._content.low_q_combo:
                            self._update_low_q()
                        elif filteredObj == self._content.medium_q_combo:
                            self._update_medium_q()
                        elif filteredObj == self._content.high_q_combo:
                            self._update_high_q()
                        return True

                return QObject.eventFilter(obj_self, filteredObj, event)

        eventFilter = ShowEventFilter(self)
        self._content.low_q_combo.installEventFilter(eventFilter)
        self._content.medium_q_combo.installEventFilter(eventFilter)
        self._content.high_q_combo.installEventFilter(eventFilter)
Exemple #11
0
class PeriodicTableWidget(QWidget):

    selectionChanged = Signal()

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        # Widgets, layouts and signals
        self._group = QButtonGroup()

        layout = QGridLayout()
        layout.setSpacing(0)

        ## Element
        for z, position in _ELEMENT_POSITIONS.items():
            widget = ElementPushButton(z)
            widget.setCheckable(True)
            layout.addWidget(widget, *position)
            self._group.addButton(widget, z)

        ## Labels
        layout.addWidget(QLabel(''), 7, 0) # Dummy
        layout.addWidget(QLabel('*'), 5, 2, Qt.AlignRight)
        layout.addWidget(QLabel('*'), 8, 2, Qt.AlignRight)
        layout.addWidget(QLabel('**'), 6, 2, Qt.AlignRight)
        layout.addWidget(QLabel('**'), 9, 2, Qt.AlignRight)

        for row in [0, 1, 2, 3, 4, 5, 6, 8, 9]:
            layout.setRowStretch(row, 1)

        self.setLayout(layout)

        # Signals
        self._group.buttonClicked.connect(self.selectionChanged)

        # Default
        self.setColorFunction(_category_color_function)

    def setColorFunction(self, func):
        if not callable(func):
            raise ValueError('Not a function')
        self._color_function = func

        # Redraw
        for widget in self._group.buttons():
            z = self._group.id(widget)
            bcolor = func(z)
            fcolor = 'white' if _calculate_brightness(bcolor) < 128 else 'black'
            sheet = 'background-color: %s; color: %s' % (bcolor.name(), fcolor)
            widget.setStyleSheet(sheet)

    def colorFunction(self):
        return self._color_function

    def setMultipleSelection(self, multiple):
        self._group.setExclusive(not multiple)

    def isMultipleSelection(self):
        return not self._group.exclusive()

    def setSelection(self, selection):
        def _uncheckedAll():
            for widget in self._group.buttons():
                widget.setChecked(False)

        if selection is None:
            _uncheckedAll()
            self.selectionChanged.emit()
            return

        if isinstance(selection, (int, six.string_types)):
            selection = [selection]

        if not self.isMultipleSelection() and len(selection) > 1:
            raise ValueError('Multiple selection mode is off. Cannot select more than one element')

        _uncheckedAll()

        for z in selection:
            if isinstance(z, six.string_types):
                z = get_atomic_number(z)
            self._group.button(z).setChecked(True)

        self.selectionChanged.emit()
#
    def selection(self):
        selection = set()
        for widget in self._group.buttons():
            if widget.isChecked():
                selection.add(self._group.id(widget))

        if self.isMultipleSelection():
            return frozenset(selection)
        else:
            if len(selection) > 0:
                return list(selection)[0]
            else:
                return None

    def selectionSymbol(self):
        selection = self.selection()
        if self.isMultipleSelection():
            return frozenset(map(get_symbol, selection))
        else:
            if selection is None:
                return None
            else:
                return get_symbol(selection)
Exemple #12
0
class WndLoadQuantitativeCalibration(SecondaryWindow):

    signal_quantitative_calibration_changed = Signal()

    def __init__(self, *, gpc, gui_vars):
        super().__init__()

        # Global processing classes
        self.gpc = gpc
        # Global GUI variables (used for control of GUI state)
        self.gui_vars = gui_vars

        self.initialize()

    def initialize(self):
        self.table_header_display_names = False

        self.setWindowTitle("PyXRF: Load Quantitative Calibration")
        self.setMinimumWidth(750)
        self.setMinimumHeight(400)
        self.resize(750, 600)

        self.pb_load_calib = QPushButton("Load Calibration ...")
        self.pb_load_calib.clicked.connect(self.pb_load_calib_clicked)

        self._changes_exist = False
        self._auto_update = True
        self.cb_auto_update = QCheckBox("Auto")
        self.cb_auto_update.setCheckState(
            Qt.Checked if self._auto_update else Qt.Unchecked)
        self.cb_auto_update.stateChanged.connect(
            self.cb_auto_update_state_changed)

        self.pb_update_plots = QPushButton("Update Plots")
        self.pb_update_plots.clicked.connect(self.pb_update_plots_clicked)

        self.grp_current_scan = QGroupBox(
            "Parameters of Currently Processed Scan")

        self._distance_to_sample = 0.0
        self.le_distance_to_sample = LineEditExtended()
        le_dist_validator = QDoubleValidator()
        le_dist_validator.setBottom(0)
        self.le_distance_to_sample.setValidator(le_dist_validator)
        self._set_distance_to_sample()
        self.le_distance_to_sample.editingFinished.connect(
            self.le_distance_to_sample_editing_finished)
        self.le_distance_to_sample.focusOut.connect(
            self.le_distance_to_sample_focus_out)

        hbox = QHBoxLayout()
        hbox.addWidget(QLabel("Distance-to-sample:"))
        hbox.addWidget(self.le_distance_to_sample)
        hbox.addStretch(1)
        self.grp_current_scan.setLayout(hbox)

        self.eline_rb_exclusive = [
        ]  # Holds the list of groups of exclusive radio buttons
        self._setup_tab_widget()

        vbox = QVBoxLayout()

        hbox = QHBoxLayout()
        hbox.addWidget(self.pb_load_calib)
        hbox.addStretch(1)
        hbox.addWidget(self.cb_auto_update)
        hbox.addWidget(self.pb_update_plots)
        vbox.addLayout(hbox)

        vbox.addWidget(self.tab_widget)

        vbox.addWidget(self.grp_current_scan)

        self.setLayout(vbox)

        # Display data
        self.update_all_data()

        self._set_tooltips()

    def _setup_tab_widget(self):

        self.tab_widget = QTabWidget()
        self.loaded_standards = QWidget()
        # self.display_loaded_standards()
        self.scroll = QScrollArea()
        self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scroll.setWidget(self.loaded_standards)
        self.tab_widget.addTab(self.scroll, "Loaded Standards")

        self.combo_set_table_header = QComboBox()
        self.combo_set_table_header.addItems(
            ["Standard Serial #", "Standard Name"])
        self.combo_set_table_header.currentIndexChanged.connect(
            self.combo_set_table_header_index_changed)

        vbox = QVBoxLayout()
        vbox.addSpacing(5)
        hbox = QHBoxLayout()
        hbox.addWidget(QLabel("Display in table header:"))
        hbox.addWidget(self.combo_set_table_header)
        hbox.addStretch(1)
        vbox.addLayout(hbox)
        self.table = QTableWidget()
        self.table.verticalHeader().hide()
        self.table.setSelectionMode(QTableWidget.NoSelection)
        self.table.horizontalHeader().setSectionResizeMode(
            QHeaderView.ResizeToContents)
        self.table.horizontalHeader().setMinimumSectionSize(150)
        vbox.addWidget(self.table)

        self.table.setStyleSheet("QTableWidget::item{color: black;}")

        frame = QFrame()
        vbox.setContentsMargins(0, 0, 0, 0)
        frame.setLayout(vbox)

        self.tab_widget.addTab(frame, "Selected Emission Lines")

    def display_loaded_standards(self):
        calib_data = self.gpc.get_quant_calibration_data()
        calib_settings = self.gpc.get_quant_calibration_settings()

        # Create the new widget (this deletes the old widget)
        self.loaded_standards = QWidget()
        self.loaded_standards.setMinimumWidth(700)

        # Also delete references to all components
        self.frames_calib_data = []
        self.pbs_view = []
        self.pbs_remove = []

        # All 'View' buttons are added to the group in order to be connected to the same slot
        self.group_view = QButtonGroup()
        self.group_view.setExclusive(False)
        self.group_view.buttonClicked.connect(self.pb_view_clicked)
        # The same for the 'Remove' buttons
        self.group_remove = QButtonGroup()
        self.group_remove.setExclusive(False)
        self.group_remove.buttonClicked.connect(self.pb_remove_clicked)

        vbox = QVBoxLayout()

        class _LabelBlack(QLabel):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                self.setStyleSheet("color: black")

        for cdata, csettings in zip(calib_data, calib_settings):
            frame = QFrame()
            frame.setFrameStyle(QFrame.StyledPanel)
            frame.setStyleSheet(
                get_background_css((200, 255, 200), widget="QFrame"))

            _vbox = QVBoxLayout()

            name = cdata["name"]  # Standard name (can be arbitrary string
            # If name is long, then print it in a separate line
            _name_is_long = len(name) > 30

            pb_view = QPushButton("View ...")
            self.group_view.addButton(pb_view)
            pb_remove = QPushButton("Remove")
            self.group_remove.addButton(pb_remove)

            # Row 1: serial, name
            serial = cdata["serial"]
            _hbox = QHBoxLayout()
            _hbox.addWidget(_LabelBlack(f"<b>Standard</b> #{serial}"))
            if not _name_is_long:
                _hbox.addWidget(_LabelBlack(f"'{name}'"))
            _hbox.addStretch(1)
            _hbox.addWidget(pb_view)
            _hbox.addWidget(pb_remove)
            _vbox.addLayout(_hbox)

            # Optional row
            if _name_is_long:
                # Wrap name if it is extemely long
                name = textwrap.fill(name, width=80)
                _hbox = QHBoxLayout()
                _hbox.addWidget(_LabelBlack("<b>Name:</b> "), 0, Qt.AlignTop)
                _hbox.addWidget(_LabelBlack(name), 0, Qt.AlignTop)
                _hbox.addStretch(1)
                _vbox.addLayout(_hbox)

            # Row 2: description
            description = textwrap.fill(cdata["description"], width=80)
            _hbox = QHBoxLayout()
            _hbox.addWidget(_LabelBlack("<b>Description:</b>"), 0, Qt.AlignTop)
            _hbox.addWidget(_LabelBlack(f"{description}"), 0, Qt.AlignTop)
            _hbox.addStretch(1)
            _vbox.addLayout(_hbox)

            # Row 3:
            incident_energy = cdata["incident_energy"]
            scaler = cdata["scaler_name"]
            detector_channel = cdata["detector_channel"]
            distance_to_sample = cdata["distance_to_sample"]
            _hbox = QHBoxLayout()
            _hbox.addWidget(
                _LabelBlack(f"<b>Incident energy, keV:</b> {incident_energy}"))
            _hbox.addWidget(_LabelBlack(f"  <b>Scaler:</b> {scaler}"))
            _hbox.addWidget(
                _LabelBlack(f"  <b>Detector channel:</b> {detector_channel}"))
            _hbox.addWidget(
                _LabelBlack(
                    f"  <b>Distance-to-sample:</b> {distance_to_sample}"))
            _hbox.addStretch(1)
            _vbox.addLayout(_hbox)

            # Row 4: file name
            fln = textwrap.fill(csettings["file_path"], width=80)
            _hbox = QHBoxLayout()
            _hbox.addWidget(_LabelBlack("<b>Source file:</b>"), 0, Qt.AlignTop)
            _hbox.addWidget(_LabelBlack(fln), 0, Qt.AlignTop)
            _hbox.addStretch(1)
            _vbox.addLayout(_hbox)

            frame.setLayout(_vbox)

            # Now the group box is added to the upper level layout
            vbox.addWidget(frame)
            vbox.addSpacing(5)
            self.frames_calib_data.append(frame)
            self.pbs_view.append(pb_view)
            self.pbs_remove.append(pb_remove)

        # Add the layout to the widget
        self.loaded_standards.setLayout(vbox)
        # ... and put the widget inside the scroll area. This will update the
        # contents of the scroll area.
        self.scroll.setWidget(self.loaded_standards)

    def display_table_header(self):
        calib_data = self.gpc.get_quant_calibration_data()
        header_by_name = self.table_header_display_names

        tbl_labels = ["Lines"]
        for n, cdata in enumerate(calib_data):
            if header_by_name:
                txt = cdata["name"]
            else:
                txt = cdata["serial"]
            txt = textwrap.fill(txt, width=20)
            tbl_labels.append(txt)

        self.table.setHorizontalHeaderLabels(tbl_labels)

    def display_standard_selection_table(self):
        calib_data = self.gpc.get_quant_calibration_data()
        self._quant_file_paths = self.gpc.get_quant_calibration_file_path_list(
        )

        brightness = 220
        table_colors = [(255, brightness, brightness),
                        (brightness, 255, brightness)]

        # Disconnect all radio button signals before clearing the table
        for bgroup in self.eline_rb_exclusive:
            bgroup.buttonToggled.disconnect(self.rb_selection_toggled)

        # This list will hold radio button groups for horizontal rows
        #   Those are exclusive groups. They are not going to be
        #   used directly, but they must be kept alive in order
        #   for the radiobuttons to work properly. Most of the groups
        #   will contain only 1 radiobutton, which will always remain checked.
        self.eline_rb_exclusive = []
        # The following list will contain the list of radio buttons for each
        #   row. If there is no radiobutton in a position, then the element is
        #   set to None.
        # N rows: the number of emission lines, N cols: the number of standards
        self.eline_rb_lists = []

        self.table.clear()

        if not calib_data:
            self.table.setRowCount(0)
            self.table.setColumnCount(0)
        else:
            # Create the sorted list of available element lines
            line_set = set()
            for cdata in calib_data:
                ks = list(cdata["element_lines"].keys())
                line_set.update(list(ks))
            self.eline_list = list(line_set)
            self.eline_list.sort()

            for n in range(len(self.eline_list)):
                self.eline_rb_exclusive.append(QButtonGroup())
                self.eline_rb_lists.append([None] * len(calib_data))

            self.table.setColumnCount(len(calib_data) + 1)
            self.table.setRowCount(len(self.eline_list))
            self.display_table_header()

            for n, eline in enumerate(self.eline_list):

                rgb = table_colors[n % 2]

                item = QTableWidgetItem(eline)
                item.setTextAlignment(Qt.AlignCenter)
                item.setFlags(item.flags() & ~Qt.ItemIsEditable)
                item.setBackground(QBrush(QColor(*rgb)))
                self.table.setItem(n, 0, item)

                for ns, cdata in enumerate(calib_data):
                    q_file_path = self._quant_file_paths[
                        ns]  # Used to identify standard
                    if eline in cdata["element_lines"]:
                        rb = QRadioButton()
                        if self.gpc.get_quant_calibration_is_eline_selected(
                                eline, q_file_path):
                            rb.setChecked(True)

                        rb.setStyleSheet("color: black")

                        self.eline_rb_lists[n][ns] = rb
                        # self.eline_rb_by_standard[ns].addButton(rb)
                        self.eline_rb_exclusive[n].addButton(rb)

                        item = QWidget()
                        item_hbox = QHBoxLayout(item)
                        item_hbox.addWidget(rb)
                        item_hbox.setAlignment(Qt.AlignCenter)
                        item_hbox.setContentsMargins(0, 0, 0, 0)

                        item.setStyleSheet(get_background_css(rgb))

                        # Generate tooltip
                        density = cdata["element_lines"][eline]["density"]
                        fluorescence = cdata["element_lines"][eline][
                            "fluorescence"]
                        ttip = f"Fluorescence (F): {fluorescence:12g}\nDensity (D): {density:12g}\n"
                        # Avoid very small values of density (probably zero)
                        if abs(density) > 1e-30:
                            ttip += f"F/D: {fluorescence/density:12g}"

                        item.setToolTip(ttip)

                        self.table.setCellWidget(n, ns + 1, item)
                    else:
                        # There is no radio button, but we still need to fill the cell
                        item = QTableWidgetItem("")
                        item.setFlags(item.flags() & ~Qt.ItemIsEditable)
                        item.setBackground(QBrush(QColor(*rgb)))
                        self.table.setItem(n, ns + 1, item)

            # Now the table is set (specifically radio buttons).
            # So we can connect the button groups with the event processing function
            for bgroup in self.eline_rb_exclusive:
                bgroup.buttonToggled.connect(self.rb_selection_toggled)

    @Slot()
    def update_all_data(self):
        self.display_loaded_standards()
        self.display_standard_selection_table()
        self._set_distance_to_sample()

    def _set_distance_to_sample(self):
        """Set 'le_distance_to_sample` without updating maps"""
        distance_to_sample = self.gpc.get_quant_calibration_distance_to_sample(
        )
        if distance_to_sample is None:
            distance_to_sample = 0.0
        self._distance_to_sample = distance_to_sample
        self._set_le_distance_to_sample(distance_to_sample)

    def _set_tooltips(self):
        set_tooltip(self.pb_load_calib,
                    "Load <b>calibration data</b> from JSON file.")
        set_tooltip(
            self.cb_auto_update,
            "Automatically <b>update the plots</b> when changes are made. "
            "If unchecked, then button <b>Update Plots</b> must be pressed "
            "to update the plots. Automatic update is often undesirable "
            "when large maps are displayed and multiple changes to parameters "
            "are made.",
        )
        set_tooltip(
            self.pb_update_plots,
            "<b>Update plots</b> based on currently selected parameters.")
        set_tooltip(
            self.le_distance_to_sample,
            "Distance between <b>the sample and the detector</b>. The ratio between of the distances "
            "during calibration and measurement is used to scale computed concentrations. "
            "If distance-to-sample is 0 for calibration or measurement, then no scaling is performed.",
        )
        set_tooltip(
            self.combo_set_table_header,
            "Use <b>Serial Number</b> or <b>Name</b> of the calibration standard in the header of the table",
        )
        set_tooltip(
            self.table,
            "Use Radio Buttons to select the <b>source of calibration data</b> for each emission line. "
            "This feature is needed if multiple loaded calibration files have data on the same "
            "emission line.",
        )

    def update_widget_state(self, condition=None):
        # Update the state of the menu bar
        state = not self.gui_vars["gui_state"]["running_computations"]
        self.setEnabled(state)

        # Hide the window if required by the program state
        state_xrf_map_exists = self.gui_vars["gui_state"][
            "state_xrf_map_exists"]
        if not state_xrf_map_exists:
            self.hide()

        if condition == "tooltips":
            self._set_tooltips()

    def cb_auto_update_state_changed(self, state):
        self._auto_update = state
        self.pb_update_plots.setEnabled(not state)
        # If changes were made, apply the changes while switching to 'auto' mode
        if state and self._changes_exist:
            self._update_maps_auto()

    def pb_update_plots_clicked(self):
        self._update_maps()

    def pb_load_calib_clicked(self):
        current_dir = self.gpc.get_current_working_directory()
        file_name = QFileDialog.getOpenFileName(
            self, "Select File with Quantitative Calibration Data",
            current_dir, "JSON (*.json);; All (*)")
        file_name = file_name[0]
        if file_name:
            try:
                logger.debug(
                    f"Loading quantitative calibration from file: '{file_name}'"
                )
                self.gpc.load_quantitative_calibration_data(file_name)
                self.update_all_data()
                self._update_maps_auto()
            except Exception:
                msg = "The selected JSON file has incorrect format. Select a different file."
                msgbox = QMessageBox(QMessageBox.Critical,
                                     "Data Loading Error",
                                     msg,
                                     QMessageBox.Ok,
                                     parent=self)
                msgbox.exec()

    def pb_view_clicked(self, button):
        try:
            n_standard = self.pbs_view.index(button)
            calib_settings = self.gpc.get_quant_calibration_settings()
            file_path = calib_settings[n_standard]["file_path"]
            calib_preview = self.gpc.get_quant_calibration_text_preview(
                file_path)
            dlg = DialogViewCalibStandard(None,
                                          file_path=file_path,
                                          calib_preview=calib_preview)
            dlg.exec()
        except ValueError:
            logger.error(
                "'View' button was pressed, but not found in the list of buttons"
            )

    def pb_remove_clicked(self, button):
        try:
            n_standard = self.pbs_remove.index(button)
            calib_settings = self.gpc.get_quant_calibration_settings()
            file_path = calib_settings[n_standard]["file_path"]
            self.gpc.quant_calibration_remove_entry(file_path)
            self.update_all_data()
            self._update_maps_auto()
        except ValueError:
            logger.error(
                "'Remove' button was pressed, but not found in the list of buttons"
            )

    def rb_selection_toggled(self, button, checked):
        if checked:
            # Find the button in 2D list 'self.eline_rb_lists'
            button_found = False
            for nr, rb_list in enumerate(self.eline_rb_lists):
                try:
                    nc = rb_list.index(button)
                    button_found = True
                    break
                except ValueError:
                    pass

            if button_found:
                eline = self.eline_list[nr]
                n_standard = nc
                file_path = self._quant_file_paths[n_standard]
                self.gpc.set_quant_calibration_select_eline(eline, file_path)
                self._update_maps_auto()
            else:
                # This should never happen
                logger.error(
                    "Selection radio button was pressed, but not found in the list"
                )

    def combo_set_table_header_index_changed(self, index):
        self.table_header_display_names = bool(index)
        self.display_table_header()

    def le_distance_to_sample_editing_finished(self):
        distance_to_sample = float(self.le_distance_to_sample.text())
        if distance_to_sample != self._distance_to_sample:
            self._distance_to_sample = distance_to_sample
            self.gpc.set_quant_calibration_distance_to_sample(
                distance_to_sample)
            self._update_maps_auto()

    def le_distance_to_sample_focus_out(self):
        try:
            float(self.le_distance_to_sample.text())
        except ValueError:
            # If the text can not be interpreted to float, then replace the text with the old value
            self._set_le_distance_to_sample(self._distance_to_sample)

    def _set_le_distance_to_sample(self, distance_to_sample):
        self.le_distance_to_sample.setText(f"{distance_to_sample:.12g}")

    def _update_maps_auto(self):
        """Update maps only if 'auto' update is ON. Used as a 'filter'
        to prevent extra plot updates."""
        self._changes_exist = True
        if self._auto_update:
            self._update_maps()

    def _update_maps(self):
        """Upload the selections (limit table) and update plot"""
        self._changes_exist = False
        self._redraw_maps()
        # Emit signal only after the maps are redrawn. This should change
        #   ranges in the respective controls for the plots
        self.signal_quantitative_calibration_changed.emit()

    def _redraw_maps(self):
        # We don't emit any signals here, but we don't really need to.
        logger.debug("Redrawing RGB XRF Maps")
        self.gpc.compute_map_ranges()
        self.gpc.redraw_maps()
        self.gpc.compute_rgb_map_ranges()
        self.gpc.redraw_rgb_maps()