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()))
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
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)
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)
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()
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)