Example #1
0
class StackedCanvasView(CanvasView):
    """
    View that can display intents in their corresponding canvases.

    Currently, uses a stacked widget with several pages:
    one for tabview and others for different split views.

    Can be adapted as long as its internal widgets are CanvasDisplayWidgets.
    """
    def __init__(self, parent=None, model=None):
        super(StackedCanvasView, self).__init__(parent)
        if model is not None:
            self.setModel(model)

        self.canvas_display_widgets = [
            CanvasDisplayTabWidget(),
            SplitHorizontal(),
            SplitVertical(),
            SplitThreeView(),
            SplitGridView()
        ]

        ### Create stacked widget and fill pages with different canvas display widgets
        self.stackedwidget = QStackedWidget(self)
        # Create a visual layout section for the buttons that are used to switch the widgets
        self.buttonpanel = QHBoxLayout()
        self.buttonpanel.addStretch(10)
        # Create a logical button grouping that will:
        #   - show the currently selected view (button will be checked/pressed)
        #   - allow for switching the buttons/views in a mutually exclusive manner (only one can be pressed at a time)
        self.buttongroup = QButtonGroup()

        def add_canvas_display_widgets():
            for i in range(len(self.canvas_display_widgets)):
                # Add the view to the stacked widget
                self.stackedwidget.addWidget(self.canvas_display_widgets[i])
                # Create a button, using the view's recommended display icon
                button = QPushButton(self)
                button.setCheckable(True)
                button.setIcon(self.canvas_display_widgets[i].icon)
                # Add the button to the logical button group
                self.buttongroup.addButton(button, i)
                # Add the button to the visual layout section
                self.buttonpanel.addWidget(button)

        add_canvas_display_widgets()

        def set_default_canvas_display_widget():
            # The first button added to the buttongroup will be the currently selected button (and therefore view)
            self.buttongroup.button(0).setChecked(True)

        set_default_canvas_display_widget()

        # Whenever a button is switched, capture its id (corresponds to integer index in our case);
        # this will handle switching the view and displaying canvases.
        self.buttongroup.idToggled.connect(self.switch_view)

        # define outer layout & add stacked widget and button panel
        self.layout = QVBoxLayout()
        self.layout.addWidget(self.stackedwidget)
        self.layout.addLayout(self.buttonpanel)
        self.setLayout(self.layout)

    # INTERFACING WITH TOOLBAR (see XPCSToolbar)
    def view(self):
        # from xicam.gui.canvases import ImageIntentCanvas
        view = self.stackedwidget.currentWidget()
        return view.getView()
        # return None

    # DONE INTERFACING

    def switch_view(self, id, toggled):
        # when toggled==True, the the button is the new button that was switched to.
        # when False, the button is the previous button
        view = self.canvas_display_widgets[id]
        if not toggled:
            ...
            # TODO: is there anything we need to do here (re: cleanup)?

        else:
            self.stackedwidget.setCurrentIndex(id)
            self.show_canvases()

    def show_canvases(self):
        self.stackedwidget.currentWidget().clear_canvases()
        self.stackedwidget.currentWidget().show_canvases(
            self._canvas_manager.canvases(self.model()))
Example #2
0
class ObjectExplorer(BaseDialog, SpyderConfigurationAccessor):
    """Object explorer main widget window."""
    CONF_SECTION = 'variable_explorer'

    def __init__(self,
                 obj,
                 name='',
                 expanded=False,
                 resize_to_contents=True,
                 parent=None,
                 attribute_columns=DEFAULT_ATTR_COLS,
                 attribute_details=DEFAULT_ATTR_DETAILS,
                 readonly=None,
                 reset=False):
        """
        Constructor

        :param name: name of the object as it will appear in the root node
        :param expanded: show the first visible root element expanded
        :param resize_to_contents: resize columns to contents ignoring width
            of the attributes
        :param obj: any Python object or variable
        :param attribute_columns: list of AttributeColumn objects that
            define which columns are present in the table and their defaults
        :param attribute_details: list of AttributeDetails objects that define
            which attributes can be selected in the details pane.
        :param reset: If true the persistent settings, such as column widths,
            are reset.
        """
        QDialog.__init__(self, parent=parent)
        self.setAttribute(Qt.WA_DeleteOnClose)

        # Options
        show_callable_attributes = self.get_conf('show_callable_attributes')
        show_special_attributes = self.get_conf('show_special_attributes')

        # Model
        self._attr_cols = attribute_columns
        self._attr_details = attribute_details
        self.readonly = readonly

        self.btn_save_and_close = None
        self.btn_close = None

        self._tree_model = TreeModel(obj,
                                     obj_name=name,
                                     attr_cols=self._attr_cols)

        self._proxy_tree_model = TreeProxyModel(
            show_callable_attributes=show_callable_attributes,
            show_special_attributes=show_special_attributes)

        self._proxy_tree_model.setSourceModel(self._tree_model)
        # self._proxy_tree_model.setSortRole(RegistryTableModel.SORT_ROLE)
        self._proxy_tree_model.setDynamicSortFilter(True)
        # self._proxy_tree_model.setSortCaseSensitivity(Qt.CaseInsensitive)

        # Tree widget
        self.obj_tree = ToggleColumnTreeView()
        self.obj_tree.setAlternatingRowColors(True)
        self.obj_tree.setModel(self._proxy_tree_model)
        self.obj_tree.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.obj_tree.setUniformRowHeights(True)
        self.obj_tree.add_header_context_menu()

        # Views
        self._setup_actions()
        self._setup_menu(show_callable_attributes=show_callable_attributes,
                         show_special_attributes=show_special_attributes)
        self._setup_views()
        if name:
            name = "{} -".format(name)
        self.setWindowTitle("{} {}".format(name, EDITOR_NAME))
        self.setWindowFlags(Qt.Window)

        self._resize_to_contents = resize_to_contents
        self._readViewSettings(reset=reset)

        # Update views with model
        self.toggle_show_special_attribute_action.setChecked(
            show_special_attributes)
        self.toggle_show_callable_action.setChecked(show_callable_attributes)

        # Select first row so that a hidden root node will not be selected.
        first_row_index = self._proxy_tree_model.firstItemIndex()
        self.obj_tree.setCurrentIndex(first_row_index)
        if self._tree_model.inspectedNodeIsVisible or expanded:
            self.obj_tree.expand(first_row_index)

    def get_value(self):
        """Get editor current object state."""
        return self._tree_model.inspectedItem.obj

    def _make_show_column_function(self, column_idx):
        """Creates a function that shows or hides a column."""
        show_column = lambda checked: self.obj_tree.setColumnHidden(
            column_idx, not checked)
        return show_column

    def _setup_actions(self):
        """Creates the main window actions."""
        # Show/hide callable objects
        self.toggle_show_callable_action = QAction(
            _("Show callable attributes"),
            self,
            checkable=True,
            shortcut=QKeySequence("Alt+C"),
            statusTip=_("Shows/hides attributes that are callable "
                        "(functions, methods, etc)"))
        self.toggle_show_callable_action.toggled.connect(
            self._proxy_tree_model.setShowCallables)
        self.toggle_show_callable_action.toggled.connect(
            self.obj_tree.resize_columns_to_contents)

        # Show/hide special attributes
        self.toggle_show_special_attribute_action = QAction(
            _("Show __special__ attributes"),
            self,
            checkable=True,
            shortcut=QKeySequence("Alt+S"),
            statusTip=_("Shows or hides __special__ attributes"))
        self.toggle_show_special_attribute_action.toggled.connect(
            self._proxy_tree_model.setShowSpecialAttributes)
        self.toggle_show_special_attribute_action.toggled.connect(
            self.obj_tree.resize_columns_to_contents)

    def _setup_menu(self,
                    show_callable_attributes=False,
                    show_special_attributes=False):
        """Sets up the main menu."""
        self.tools_layout = QHBoxLayout()

        callable_attributes = create_toolbutton(
            self,
            text=_("Show callable attributes"),
            icon=ima.icon("class"),
            toggled=self._toggle_show_callable_attributes_action)
        callable_attributes.setCheckable(True)
        callable_attributes.setChecked(show_callable_attributes)
        self.tools_layout.addWidget(callable_attributes)

        special_attributes = create_toolbutton(
            self,
            text=_("Show __special__ attributes"),
            icon=ima.icon("private2"),
            toggled=self._toggle_show_special_attributes_action)
        special_attributes.setCheckable(True)
        special_attributes.setChecked(show_special_attributes)
        self.tools_layout.addWidget(special_attributes)

        self.tools_layout.addStretch()

        self.options_button = create_toolbutton(self,
                                                text=_('Options'),
                                                icon=ima.icon('tooloptions'))
        self.options_button.setPopupMode(QToolButton.InstantPopup)

        self.show_cols_submenu = QMenu(self)
        self.options_button.setMenu(self.show_cols_submenu)
        # Don't show menu arrow and remove padding
        if is_dark_interface():
            self.options_button.setStyleSheet(
                ("QToolButton::menu-indicator{image: none;}\n"
                 "QToolButton{padding: 3px;}"))
        else:
            self.options_button.setStyleSheet(
                "QToolButton::menu-indicator{image: none;}")
        self.tools_layout.addWidget(self.options_button)

    @Slot()
    def _toggle_show_callable_attributes_action(self):
        """Toggle show callable atributes action."""
        action_checked = not self.toggle_show_callable_action.isChecked()
        self.toggle_show_callable_action.setChecked(action_checked)
        self.set_conf('show_callable_attributes', action_checked)

    @Slot()
    def _toggle_show_special_attributes_action(self):
        """Toggle show special attributes action."""
        action_checked = (
            not self.toggle_show_special_attribute_action.isChecked())
        self.toggle_show_special_attribute_action.setChecked(action_checked)
        self.set_conf('show_special_attributes', action_checked)

    def _setup_views(self):
        """Creates the UI widgets."""
        self.central_splitter = QSplitter(self, orientation=Qt.Vertical)
        layout = create_plugin_layout(self.tools_layout, self.central_splitter)
        self.setLayout(layout)

        # Stretch last column?
        # It doesn't play nice when columns are hidden and then shown again.
        obj_tree_header = self.obj_tree.header()
        obj_tree_header.setSectionsMovable(True)
        obj_tree_header.setStretchLastSection(False)
        add_actions(self.show_cols_submenu,
                    self.obj_tree.toggle_column_actions_group.actions())

        self.central_splitter.addWidget(self.obj_tree)

        # Bottom pane
        bottom_pane_widget = QWidget()
        bottom_layout = QHBoxLayout()
        bottom_layout.setSpacing(0)
        bottom_layout.setContentsMargins(5, 5, 5, 5)  # left top right bottom
        bottom_pane_widget.setLayout(bottom_layout)
        self.central_splitter.addWidget(bottom_pane_widget)

        group_box = QGroupBox(_("Details"))
        bottom_layout.addWidget(group_box)

        v_group_layout = QVBoxLayout()
        h_group_layout = QHBoxLayout()
        h_group_layout.setContentsMargins(2, 2, 2, 2)  # left top right bottom
        group_box.setLayout(v_group_layout)
        v_group_layout.addLayout(h_group_layout)

        # Radio buttons
        radio_widget = QWidget()
        radio_layout = QVBoxLayout()
        radio_layout.setContentsMargins(0, 0, 0, 0)  # left top right bottom
        radio_widget.setLayout(radio_layout)

        self.button_group = QButtonGroup(self)
        for button_id, attr_detail in enumerate(self._attr_details):
            radio_button = QRadioButton(attr_detail.name)
            radio_layout.addWidget(radio_button)
            self.button_group.addButton(radio_button, button_id)

        self.button_group.buttonClicked[int].connect(
            self._change_details_field)
        self.button_group.button(0).setChecked(True)

        radio_layout.addStretch(1)
        h_group_layout.addWidget(radio_widget)

        # Editor widget
        self.editor = SimpleCodeEditor(self)
        self.editor.setReadOnly(True)
        h_group_layout.addWidget(self.editor)

        # Save and close buttons
        btn_layout = QHBoxLayout()
        btn_layout.addStretch()

        if not self.readonly:
            self.btn_save_and_close = QPushButton(_('Save and Close'))
            self.btn_save_and_close.setDisabled(True)
            self.btn_save_and_close.clicked.connect(self.accept)
            btn_layout.addWidget(self.btn_save_and_close)

        self.btn_close = QPushButton(_('Close'))
        self.btn_close.setAutoDefault(True)
        self.btn_close.setDefault(True)
        self.btn_close.clicked.connect(self.reject)
        btn_layout.addWidget(self.btn_close)
        v_group_layout.addLayout(btn_layout)

        # Splitter parameters
        self.central_splitter.setCollapsible(0, False)
        self.central_splitter.setCollapsible(1, True)
        self.central_splitter.setSizes([500, 320])

        # Connect signals
        # Keep a temporary reference of the selection_model to prevent
        # segfault in PySide.
        # See http://permalink.gmane.org/gmane.comp.lib.qt.pyside.devel/222
        selection_model = self.obj_tree.selectionModel()
        selection_model.currentChanged.connect(self._update_details)

        # Check if the values of the model have been changed
        self._proxy_tree_model.sig_setting_data.connect(
            self.save_and_close_enable)

        self._proxy_tree_model.sig_update_details.connect(
            self._update_details_for_item)

    # End of setup_methods
    def _readViewSettings(self, reset=False):
        """
        Reads the persistent program settings.

        :param reset: If True, the program resets to its default settings.
        """
        pos = QPoint(20, 20)
        window_size = QSize(825, 650)
        details_button_idx = 0

        header = self.obj_tree.header()
        header_restored = False

        if reset:
            logger.debug("Resetting persistent view settings")
        else:
            pos = pos
            window_size = window_size
            details_button_idx = details_button_idx
            #            splitter_state = settings.value("central_splitter/state")
            splitter_state = None
            if splitter_state:
                self.central_splitter.restoreState(splitter_state)
#            header_restored = self.obj_tree.read_view_settings(
#                'table/header_state',
#                settings, reset)
            header_restored = False

        if not header_restored:
            column_sizes = [col.width for col in self._attr_cols]
            column_visible = [col.col_visible for col in self._attr_cols]

            for idx, size in enumerate(column_sizes):
                if not self._resize_to_contents and size > 0:  # Just in case
                    header.resizeSection(idx, size)
                else:
                    header.resizeSections(QHeaderView.ResizeToContents)
                    break

            for idx, visible in enumerate(column_visible):
                elem = self.obj_tree.toggle_column_actions_group.actions()[idx]
                elem.setChecked(visible)

        self.resize(window_size)

        button = self.button_group.button(details_button_idx)
        if button is not None:
            button.setChecked(True)

    @Slot()
    def save_and_close_enable(self):
        """Handle the data change event to enable the save and close button."""
        if self.btn_save_and_close:
            self.btn_save_and_close.setEnabled(True)
            self.btn_save_and_close.setAutoDefault(True)
            self.btn_save_and_close.setDefault(True)

    @Slot(QModelIndex, QModelIndex)
    def _update_details(self, current_index, _previous_index):
        """Shows the object details in the editor given an index."""
        tree_item = self._proxy_tree_model.treeItem(current_index)
        self._update_details_for_item(tree_item)

    def _change_details_field(self, _button_id=None):
        """Changes the field that is displayed in the details pane."""
        # logger.debug("_change_details_field: {}".format(_button_id))
        current_index = self.obj_tree.selectionModel().currentIndex()
        tree_item = self._proxy_tree_model.treeItem(current_index)
        self._update_details_for_item(tree_item)

    @Slot(TreeItem)
    def _update_details_for_item(self, tree_item):
        """Shows the object details in the editor given an tree_item."""
        try:
            # obj = tree_item.obj
            button_id = self.button_group.checkedId()
            assert button_id >= 0, ("No radio button selected. "
                                    "Please report this bug.")
            attr_details = self._attr_details[button_id]
            data = attr_details.data_fn(tree_item)
            self.editor.setPlainText(data)
            self.editor.setWordWrapMode(attr_details.line_wrap)
            self.editor.setup_editor(
                font=get_font(font_size_delta=DEFAULT_SMALL_DELTA),
                show_blanks=False,
                color_scheme=CONF.get('appearance', 'selected'),
                scroll_past_end=False,
            )
            self.editor.set_text(data)

            if attr_details.name == 'Source code':
                self.editor.set_language('Python')
            else:
                self.editor.set_language('Rst')

        except Exception as ex:
            self.editor.setStyleSheet("color: red;")
            stack_trace = traceback.format_exc()
            self.editor.setPlainText("{}\n\n{}".format(ex, stack_trace))
            self.editor.setWordWrapMode(
                QTextOption.WrapAtWordBoundaryOrAnywhere)

    @classmethod
    def create_explorer(cls, *args, **kwargs):
        """
        Creates and shows and ObjectExplorer window.

        The *args and **kwargs will be passed to the ObjectExplorer constructor

        A (class attribute) reference to the browser window is kept to prevent
        it from being garbage-collected.
        """
        object_explorer = cls(*args, **kwargs)
        object_explorer.exec_()
        return object_explorer
Example #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)
class DataCorrectionsWidget(BaseWidget):
    """
        Widget that presents data correction options to the user.
    """
    ## Widget name
    name = "Data Corrections"

    _old_backgnd_sub = None
    _old_norm_button = None
    incident_beam_norm_grp = None

    def __init__(self, parent=None, state=None, settings=None, data_type=None):
        super(DataCorrectionsWidget, self).__init__(parent, state, settings, data_type=data_type)

        class DataCorrsFrame(QFrame):
            def __init__(self, parent=None):
                QFrame.__init__(self, parent)
                self.ui = load_ui(__file__, '../../../ui/inelastic/dgs_data_corrections.ui', baseinstance=self)

        self._content = DataCorrsFrame(self)
        self._layout.addWidget(self._content)
        self.initialize_content()
        self._instrument_name = settings.instrument_name

        if state is not None:
            self.set_state(state)
        else:
            self.set_state(DataCorrectionsScript(self._instrument_name))

    def initialize_content(self):
        # Set some validators
        self._content.monint_low_edit.setValidator(QIntValidator(self._content.monint_low_edit))
        self._content.monint_high_edit.setValidator(QIntValidator(self._content.monint_high_edit))
        self._content.tof_start_edit.setValidator(QIntValidator(self._content.tof_start_edit))
        self._content.tof_end_edit.setValidator(QIntValidator(self._content.tof_end_edit))

        # Make group for incident beam normalisation radio buttons
        self.incident_beam_norm_grp = QButtonGroup()
        self.incident_beam_norm_grp.addButton(self._content.none_rb, 0)
        self.incident_beam_norm_grp.addButton(self._content.current_rb, 1)
        self.incident_beam_norm_grp.addButton(self._content.monitor1_rb, 2)

        self._monitor_intrange_widgets_state(self._content.monitor1_rb.isChecked())
        self._content.monitor1_rb.toggled.connect(self._monitor_intrange_widgets_state)

        self._detvan_intrange_widgets_state(self._content.van_int_cb.isChecked())
        self._content.van_int_cb.toggled.connect(self._detvan_intrange_widgets_state)
        self._content.use_procdetvan_cb.toggled.connect(self._detvan_widgets_opp_state)

        self._save_detvan_widgets_state(self._content.save_procdetvan_cb.isChecked())
        self._content.save_procdetvan_cb.toggled.connect(self._save_detvan_widgets_state)

        # Connections
        self._content.van_input_browse.clicked.connect(self._detvan_browse)
        self._content.save_procdetvan_save.clicked.connect(self._save_procdetvan_save)

    def _monitor_intrange_widgets_state(self, state=False):
        self._content.monint_label.setEnabled(state)
        self._content.monint_low_edit.setEnabled(state)
        self._content.monint_high_edit.setEnabled(state)

    def _detvan_intrange_widgets_state(self, state=False):
        self._content.van_int_range_label.setEnabled(state)
        self._content.van_int_range_low_edit.setEnabled(state)
        self._content.van_int_range_high_edit.setEnabled(state)
        self._content.van_int_range_units_cb.setEnabled(state)

    def _detvan_widgets_opp_state(self, state=False):
        self._content.van_int_cb.setEnabled(not state)
        if self._content.van_int_cb.isChecked():
            self._detvan_intrange_widgets_state(not state)
            self._content.van_int_cb.setChecked(False)
        self._content.save_procdetvan_cb.setEnabled(not state)
        if self._content.save_procdetvan_cb.isChecked():
            self._content.save_procdetvan_cb.setChecked(False)

    def _save_detvan_widgets_state(self, state=False):
        self._content.save_procdetvan_label.setEnabled(state)
        self._content.save_procdetvan_edit.setEnabled(state)
        self._content.save_procdetvan_save.setEnabled(state)

    def _detvan_browse(self):
        fname = self.data_browse_dialog()
        if fname:
            self._content.van_input_edit.setText(fname)

    def _save_procdetvan_save(self):
        fname = self.data_save_dialog("*.nxs")
        if fname:
            self._content.save_procdetvan_edit.setText(fname)

    def set_state(self, state):
        """
            Populate the UI elements with the data from the given state.
            @param state: DataCorrectionsScript object
        """
        self._content.filter_bad_pulses_chkbox.setChecked(state.filter_bad_pulses)
        button_index = DataCorrectionsScript.INCIDENT_BEAM_NORM_TYPES.index(state.incident_beam_norm)
        cbutton = self.incident_beam_norm_grp.button(button_index)
        cbutton.setChecked(True)
        self._content.monint_low_edit.setText(str(state.monitor_int_low))
        self._content.monint_high_edit.setText(str(state.monitor_int_high))
        self._content.background_sub_gb.setChecked(state.tib_subtraction)
        self._content.tof_start_edit.setText(str(state.tib_tof_start))
        self._content.tof_end_edit.setText(str(state.tib_tof_end))
        self._content.correct_kikf_cb.setChecked(state.correct_kikf)
        self._content.van_input_edit.setText(state.detector_vanadium)
        self._content.van_int_cb.setChecked(state.detvan_integration)
        self._content.van_int_range_low_edit.setText(str(state.detvan_int_range_low))
        self._content.van_int_range_high_edit.setText(str(state.detvan_int_range_high))
        entry_index = self._content.van_int_range_units_cb.findText(state.detvan_int_range_units)
        self._content.van_int_range_units_cb.setCurrentIndex(entry_index)
        self._content.save_procdetvan_cb.setChecked(state.save_proc_detvan)
        self._content.save_procdetvan_edit.setText(str(state.save_proc_detvan_file))
        self._content.use_procdetvan_cb.setChecked(state.use_proc_detvan)

    def get_state(self):
        """
            Returns an object with the state of the interface
        """
        d = DataCorrectionsScript(self._instrument_name)
        d.filter_bad_pulses = self._content.filter_bad_pulses_chkbox.isChecked()
        d.incident_beam_norm = DataCorrectionsScript.INCIDENT_BEAM_NORM_TYPES[self.incident_beam_norm_grp.checkedId()]
        d.monitor_int_low = util._check_and_get_float_line_edit(self._content.monint_low_edit)
        d.monitor_int_high = util._check_and_get_float_line_edit(self._content.monint_high_edit)
        d.tib_subtraction = self._content.background_sub_gb.isChecked()
        d.tib_tof_start = util._check_and_get_float_line_edit(self._content.tof_start_edit)
        d.tib_tof_end = util._check_and_get_float_line_edit(self._content.tof_end_edit)
        d.correct_kikf = self._content.correct_kikf_cb.isChecked()
        d.detector_vanadium = self._content.van_input_edit.text()
        d.detvan_integration = self._content.van_int_cb.isChecked()
        d.detvan_int_range_low = util._check_and_get_float_line_edit(self._content.van_int_range_low_edit)
        d.detvan_int_range_high = util._check_and_get_float_line_edit(self._content.van_int_range_high_edit)
        d.detvan_int_range_units = self._content.van_int_range_units_cb.currentText()
        d.save_proc_detvan = self._content.save_procdetvan_cb.isChecked()
        d.save_proc_detvan_file = self._content.save_procdetvan_edit.text()
        d.use_proc_detvan = self._content.use_procdetvan_cb.isChecked()
        return d

    def live_button_toggled_actions(self,checked):
        if checked:
            self._old_norm_button = self.incident_beam_norm_grp.checkedId()
            self._old_backgnd_sub = self._content.background_sub_gb.isChecked()
            self._content.none_rb.setChecked(True)
            self._content.background_sub_gb.setChecked(False)
        else:
            try:
                self.incident_beam_norm_grp.button(self._old_norm_button).setChecked(True)
                self._content.background_sub_gb.setChecked(self._old_backgnd_sub)
            except:  # This is for if the live button started out checked
                pass
        self._content.incident_beam_norm_gb.setEnabled(not checked)
        self._content.background_sub_gb.setEnabled(not checked)
Example #5
0
class DataCorrectionsWidget(BaseWidget):
    """
        Widget that presents data correction options to the user.
    """
    ## Widget name
    name = "Data Corrections"

    _old_backgnd_sub = None
    _old_norm_button = None
    incident_beam_norm_grp = None

    def __init__(self, parent=None, state=None, settings=None, data_type=None):
        super(DataCorrectionsWidget, self).__init__(parent,
                                                    state,
                                                    settings,
                                                    data_type=data_type)

        class DataCorrsFrame(QFrame):
            def __init__(self, parent=None):
                QFrame.__init__(self, parent)
                self.ui = load_ui(
                    __file__,
                    '../../../ui/inelastic/dgs_data_corrections.ui',
                    baseinstance=self)

        self._content = DataCorrsFrame(self)
        self._layout.addWidget(self._content)
        self.initialize_content()
        self._instrument_name = settings.instrument_name

        if state is not None:
            self.set_state(state)
        else:
            self.set_state(DataCorrectionsScript(self._instrument_name))

    def initialize_content(self):
        # Set some validators
        self._content.monint_low_edit.setValidator(
            QIntValidator(self._content.monint_low_edit))
        self._content.monint_high_edit.setValidator(
            QIntValidator(self._content.monint_high_edit))
        self._content.tof_start_edit.setValidator(
            QIntValidator(self._content.tof_start_edit))
        self._content.tof_end_edit.setValidator(
            QIntValidator(self._content.tof_end_edit))

        # Make group for incident beam normalisation radio buttons
        self.incident_beam_norm_grp = QButtonGroup()
        self.incident_beam_norm_grp.addButton(self._content.none_rb, 0)
        self.incident_beam_norm_grp.addButton(self._content.current_rb, 1)
        self.incident_beam_norm_grp.addButton(self._content.monitor1_rb, 2)

        self._monitor_intrange_widgets_state(
            self._content.monitor1_rb.isChecked())
        self._content.monitor1_rb.toggled.connect(
            self._monitor_intrange_widgets_state)

        self._detvan_intrange_widgets_state(
            self._content.van_int_cb.isChecked())
        self._content.van_int_cb.toggled.connect(
            self._detvan_intrange_widgets_state)
        self._content.use_procdetvan_cb.toggled.connect(
            self._detvan_widgets_opp_state)

        self._save_detvan_widgets_state(
            self._content.save_procdetvan_cb.isChecked())
        self._content.save_procdetvan_cb.toggled.connect(
            self._save_detvan_widgets_state)

        # Connections
        self._content.van_input_browse.clicked.connect(self._detvan_browse)
        self._content.save_procdetvan_save.clicked.connect(
            self._save_procdetvan_save)

    def _monitor_intrange_widgets_state(self, state=False):
        self._content.monint_label.setEnabled(state)
        self._content.monint_low_edit.setEnabled(state)
        self._content.monint_high_edit.setEnabled(state)

    def _detvan_intrange_widgets_state(self, state=False):
        self._content.van_int_range_label.setEnabled(state)
        self._content.van_int_range_low_edit.setEnabled(state)
        self._content.van_int_range_high_edit.setEnabled(state)
        self._content.van_int_range_units_cb.setEnabled(state)

    def _detvan_widgets_opp_state(self, state=False):
        self._content.van_int_cb.setEnabled(not state)
        if self._content.van_int_cb.isChecked():
            self._detvan_intrange_widgets_state(not state)
            self._content.van_int_cb.setChecked(False)
        self._content.save_procdetvan_cb.setEnabled(not state)
        if self._content.save_procdetvan_cb.isChecked():
            self._content.save_procdetvan_cb.setChecked(False)

    def _save_detvan_widgets_state(self, state=False):
        self._content.save_procdetvan_label.setEnabled(state)
        self._content.save_procdetvan_edit.setEnabled(state)
        self._content.save_procdetvan_save.setEnabled(state)

    def _detvan_browse(self):
        fname = self.data_browse_dialog()
        if fname:
            self._content.van_input_edit.setText(fname)

    def _save_procdetvan_save(self):
        fname = self.data_save_dialog("*.nxs")
        if fname:
            self._content.save_procdetvan_edit.setText(fname)

    def set_state(self, state):
        """
            Populate the UI elements with the data from the given state.
            @param state: DataCorrectionsScript object
        """
        self._content.filter_bad_pulses_chkbox.setChecked(
            state.filter_bad_pulses)
        button_index = DataCorrectionsScript.INCIDENT_BEAM_NORM_TYPES.index(
            state.incident_beam_norm)
        cbutton = self.incident_beam_norm_grp.button(button_index)
        cbutton.setChecked(True)
        self._content.monint_low_edit.setText(str(state.monitor_int_low))
        self._content.monint_high_edit.setText(str(state.monitor_int_high))
        self._content.background_sub_gb.setChecked(state.tib_subtraction)
        self._content.tof_start_edit.setText(str(state.tib_tof_start))
        self._content.tof_end_edit.setText(str(state.tib_tof_end))
        self._content.correct_kikf_cb.setChecked(state.correct_kikf)
        self._content.van_input_edit.setText(state.detector_vanadium)
        self._content.van_int_cb.setChecked(state.detvan_integration)
        self._content.van_int_range_low_edit.setText(
            str(state.detvan_int_range_low))
        self._content.van_int_range_high_edit.setText(
            str(state.detvan_int_range_high))
        entry_index = self._content.van_int_range_units_cb.findText(
            state.detvan_int_range_units)
        self._content.van_int_range_units_cb.setCurrentIndex(entry_index)
        self._content.save_procdetvan_cb.setChecked(state.save_proc_detvan)
        self._content.save_procdetvan_edit.setText(
            str(state.save_proc_detvan_file))
        self._content.use_procdetvan_cb.setChecked(state.use_proc_detvan)

    def get_state(self):
        """
            Returns an object with the state of the interface
        """
        d = DataCorrectionsScript(self._instrument_name)
        d.filter_bad_pulses = self._content.filter_bad_pulses_chkbox.isChecked(
        )
        d.incident_beam_norm = DataCorrectionsScript.INCIDENT_BEAM_NORM_TYPES[
            self.incident_beam_norm_grp.checkedId()]
        d.monitor_int_low = util._check_and_get_float_line_edit(
            self._content.monint_low_edit)
        d.monitor_int_high = util._check_and_get_float_line_edit(
            self._content.monint_high_edit)
        d.tib_subtraction = self._content.background_sub_gb.isChecked()
        d.tib_tof_start = util._check_and_get_float_line_edit(
            self._content.tof_start_edit)
        d.tib_tof_end = util._check_and_get_float_line_edit(
            self._content.tof_end_edit)
        d.correct_kikf = self._content.correct_kikf_cb.isChecked()
        d.detector_vanadium = self._content.van_input_edit.text()
        d.detvan_integration = self._content.van_int_cb.isChecked()
        d.detvan_int_range_low = util._check_and_get_float_line_edit(
            self._content.van_int_range_low_edit)
        d.detvan_int_range_high = util._check_and_get_float_line_edit(
            self._content.van_int_range_high_edit)
        d.detvan_int_range_units = self._content.van_int_range_units_cb.currentText(
        )
        d.save_proc_detvan = self._content.save_procdetvan_cb.isChecked()
        d.save_proc_detvan_file = self._content.save_procdetvan_edit.text()
        d.use_proc_detvan = self._content.use_procdetvan_cb.isChecked()
        return d

    def live_button_toggled_actions(self, checked):
        if checked:
            self._old_norm_button = self.incident_beam_norm_grp.checkedId()
            self._old_backgnd_sub = self._content.background_sub_gb.isChecked()
            self._content.none_rb.setChecked(True)
            self._content.background_sub_gb.setChecked(False)
        else:
            try:
                self.incident_beam_norm_grp.button(
                    self._old_norm_button).setChecked(True)
                self._content.background_sub_gb.setChecked(
                    self._old_backgnd_sub)
            except:  # This is for if the live button started out checked
                pass
        self._content.incident_beam_norm_gb.setEnabled(not checked)
        self._content.background_sub_gb.setEnabled(not checked)
Example #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()
Example #7
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)