Source code for siriushla.as_ps_control.control_widget.BasePSControlWidget

"""Base class for controlling a power supply."""
import re

from qtpy.QtCore import Qt, Slot, QLocale
from qtpy.QtWidgets import QWidget, QVBoxLayout, QGroupBox, \
    QGridLayout, QLabel, QHBoxLayout, QScrollArea, QLineEdit, QAction, \
    QMenu, QInputDialog, QFrame, QPushButton, QSplitter, \
    QSizePolicy as QSzPlcy
import qtawesome as qta
from pydm.connection_inspector import ConnectionInspector

from siriuspy.search import PSSearch
from siriuspy.namesys import SiriusPVName as PVName
from siriushla.util import connect_window, connect_newprocess
from ..PSDetailWindow import PSDetailWindow
from ..SummaryWidgets import SummaryWidget, SummaryHeader, \
    get_prop2label, sort_propties


[docs] class PSContainer(QWidget): """PSContainer.""" def __init__(self, widget, parent=None): super().__init__(parent) self._widget = widget self.name = widget.devname self.bbbname = widget.bbbname self.udcname = widget.udcname self.dclinks = list() self.dclinks_type = '' self.dclink_widgets = list() self.dclinksbbbname = set() self.dclinksudcname = set() dclinks = PSSearch.conv_psname_2_dclink(self.name) if dclinks: self.dclinks = dclinks self.dclinks_type = PSSearch.conv_psname_2_psmodel(dclinks[0]) if self.dclinks_type != 'REGATRON_DCLink': for dc in dclinks: self.dclinksbbbname.add(PSSearch.conv_psname_2_bbbname(dc)) self.dclinksudcname.add(PSSearch.conv_psname_2_udc(dc)) self.all_props = get_prop2label(PVName(dclinks[0])) self.visible_props = sort_propties([ 'detail', 'state', 'intlk', 'setpoint', 'monitor']) self._setup_ui() self._create_actions() self._enable_actions() self.setStyleSheet(""" #HideButton { min-width: 10px; max-width: 10px; } #DCLinkContainer { background-color: lightgrey; } """) def _setup_ui(self): """Setup widget UI.""" self._layout = QGridLayout() self._layout.setSpacing(10) self._layout.setContentsMargins(0, 0, 0, 0) self.setLayout(self._layout) self._dclink_container = QWidget(self) self._dclink_container.setObjectName('DCLinkContainer') self._dclink_container.setLayout(QVBoxLayout()) self._dclink_is_filled = False if self.dclinks: self._hide = QPushButton(qta.icon('mdi.plus'), '', self) else: self._hide = QPushButton('', self) self._hide.setEnabled(False) self._hide.setObjectName('HideButton') self._hide.setSizePolicy(QSzPlcy.Maximum, QSzPlcy.Maximum) self._hide.setFlat(True) self._layout.addWidget(self._hide, 0, 0, Qt.AlignCenter) self._layout.addWidget(self._widget, 0, 1) self._layout.addWidget(self._dclink_container, 1, 1) # Configure self._dclink_container.setHidden(True) self._hide.clicked.connect(self._toggle_dclink) def _toggle_dclink(self): if self._dclink_container.isHidden(): if not self._dclink_is_filled: self._fill_dclink_container() self._enable_actions() self._hide.setIcon(qta.icon('mdi.minus')) self._dclink_container.setHidden(False) else: self._hide.setIcon(qta.icon('mdi.plus')) self._dclink_container.setHidden(True) def _fill_dclink_container(self): self._dclink_is_filled = True self._dclink_container.layout().addWidget( SummaryHeader(self.dclinks[0], self.visible_props, self)) for dclink_name in self.dclinks: w = SummaryWidget(dclink_name, self.visible_props, self) if self.dclinks_type == 'REGATRON_DCLink': connect_newprocess( w.detail_bt, ['sirius-hla-as-ps-regatron-individual', '-dev', dclink_name], parent=self, is_pydm=True) else: connect_window(w.detail_bt, PSDetailWindow, self, psname=dclink_name) self._dclink_container.layout().addWidget(w) self.dclink_widgets.append(w)
[docs] def update_visible_props(self, new_value): self.visible_props = sort_propties(new_value) self._enable_actions()
# Action methods def _create_actions(self): self._turn_on_action = QAction('Turn DCLinks On', self) self._turn_on_action.triggered.connect( lambda: self._set_dclink_pwrstate(True)) self._turn_on_action.setEnabled(False) self._turn_off_action = QAction('Turn DCLinks Off', self) self._turn_off_action.triggered.connect( lambda: self._set_dclink_pwrstate(False)) self._turn_off_action.setEnabled(False) self._open_loop_action = QAction('Open DCLinks Control Loop', self) self._open_loop_action.triggered.connect( lambda: self._set_dclink_control_loop(False)) self._open_loop_action.setEnabled(False) self._close_loop_action = QAction('Close DCLinks Control Loop', self) self._close_loop_action.triggered.connect( lambda: self._set_dclink_control_loop(True)) self._close_loop_action.setEnabled(False) self._set_setpoint_action = QAction('Set DCLinks Voltage', self) self._set_setpoint_action.triggered.connect(self._set_setpoint) self._set_setpoint_action.setEnabled(False) self._reset_intlk_action = QAction('Reset DCLinks Interlocks', self) self._reset_intlk_action.triggered.connect(self._reset_intlk) self._reset_intlk_action.setEnabled(False) def _enable_actions(self): if 'state' in self.visible_props and \ not self._turn_on_action.isEnabled(): self._turn_on_action.setEnabled(True) self._turn_off_action.setEnabled(True) if 'ctrlloop' in self.visible_props and \ not self._open_loop_action.isEnabled(): self._open_loop_action.setEnabled(True) self._close_loop_action.setEnabled(True) if 'setpoint' in self.visible_props and \ not self._set_setpoint_action.isEnabled(): self._set_setpoint_action.setEnabled(True) if 'reset' in self.visible_props and \ not self._reset_intlk_action.isEnabled(): self._reset_intlk_action.setEnabled(True) def _set_dclink_pwrstate(self, value): for dclink in self.dclink_widgets: if value: dclink.turn_on() else: dclink.turn_off() def _set_dclink_control_loop(self, value): for dclink in self.dclink_widgets: btn = dclink.ctrlloop_bt if value: if btn._bit_val: btn.send_value() else: if not btn._bit_val: btn.send_value() def _set_setpoint(self): """Set current setpoint for every visible widget.""" dlg = QInputDialog(self) dlg.setLocale(QLocale(QLocale.English)) new_value, ok = dlg.getDouble(self, "New setpoint", "Value") if ok: for dclink in self.dclink_widgets: sp = dclink.setpoint.spinbox sp.value_changed(new_value) try: sp.send_value() except TypeError: pass def _reset_intlk(self): for dclink in self.dclink_widgets: dclink.reset() # Overloaded method
[docs] def contextMenuEvent(self, event): """Overload to create a custom context menu.""" widget = self.childAt(event.pos()) if not widget: return parent = widget.parent() grand_parent = parent.parent() if widget.objectName() == 'DCLinkContainer' or \ parent.objectName() == 'DCLinkContainer' or \ grand_parent.objectName() == 'DCLinkContainer': menu = QMenu(self) menu.addAction(self._turn_on_action) menu.addAction(self._turn_off_action) menu.addSeparator() menu.addAction(self._close_loop_action) menu.addAction(self._open_loop_action) menu.addSeparator() menu.addAction(self._set_setpoint_action) menu.addSeparator() menu.addAction(self._reset_intlk_action) menu.addSeparator() action = menu.addAction('Show Connections...') action.triggered.connect(self.show_connections) menu.popup(event.globalPos()) else: super().contextMenuEvent(event)
[docs] def show_connections(self, checked): """.""" _ = checked c = ConnectionInspector(self) c.show()
[docs] class BasePSControlWidget(QWidget): """Base widget class to control power supply.""" HORIZONTAL = 0 VERTICAL = 1 def __init__(self, subsection=None, orientation=0, parent=None): """Class constructor. Parameters: subsection Default to None. To be used in filters defined in subclass. orientation Default to HORIZONTAL. Define how the different groups (defined in subclasses) will be laid out. """ super(BasePSControlWidget, self).__init__(parent) self._orientation = orientation self._subsection = subsection self._dev_list = PSSearch.get_psnames(self._getFilter(subsection)) dev0 = PVName(self._dev_list[0]) if dev0.sec == 'LI': if dev0.dev == 'Slnd': idcs = [int(PVName(dev).idx) for dev in self._dev_list] self._dev_list = [ x for _, x in sorted(zip(idcs, self._dev_list))] if 'Q' in dev0.dev: all_props = dict() for dev in self._dev_list: all_props.update(get_prop2label(dev)) self.all_props = sort_propties(all_props) else: self.all_props = get_prop2label(self._dev_list[0]) else: self.all_props = get_prop2label(self._dev_list[0]) self.visible_props = self._getVisibleProps() if 'trim' in self.all_props: self.visible_props.append('trim') self.visible_props = sort_propties(self.visible_props) # Data used to filter the widgets self.ps_widgets_dict = dict() self.containers_dict = dict() self.filtered_widgets = set() # Set with key of visible widgets # Get groups and respective power supplies self.groups, self.groups2devs = list(), dict() for group in self._getGroups(): pwrsupplies = list() pattern = re.compile(group[1]) for el in self._dev_list: if pattern.search(el): pwrsupplies.append(el) if not pwrsupplies: continue self.groups.append(group) self.groups2devs[group[0]] = pwrsupplies # Setup the UI self._setup_ui() self._create_actions() self._enable_actions() if len(self.groups) in [1, 3]: self.setObjectName('cw') self.setStyleSheet('#cw{min-height: 40em;}') def _setup_ui(self): self.layout = QVBoxLayout() # Create filters self.search_le = QLineEdit(parent=self) self.search_le.setObjectName("search_lineedit") self.search_le.setPlaceholderText("Search for a power supply...") self.search_le.textEdited.connect(self._filter_pwrsupplies) self.filter_pb = QPushButton(qta.icon('mdi.view-column'), '', self) self.search_menu = QMenu(self.filter_pb) self.filter_pb.setMenu(self.search_menu) for prop, label in self.all_props.items(): act = self.search_menu.addAction(label) act.setObjectName(prop) act.setCheckable(True) act.setChecked(prop in self.visible_props) act.toggled.connect(self._set_widgets_visibility) hlay_filter = QHBoxLayout() hlay_filter.addWidget(self.search_le) hlay_filter.addWidget(self.filter_pb) self.layout.addLayout(hlay_filter) self.count_label = QLabel(parent=self) self.count_label.setSizePolicy(QSzPlcy.Maximum, QSzPlcy.Maximum) self.layout.addWidget(self.count_label) self.pwrsupplies_layout = self._getSplitter() self.layout.addWidget(self.pwrsupplies_layout) if len(self.groups) == 3: splitt_v = QSplitter(Qt.Vertical) # Build power supply Layout # Create group boxes and pop. layout for idx, group in enumerate(self.groups): pwrsupplies = self.groups2devs[group[0]] # Create header header = SummaryHeader(pwrsupplies[0], visible_props=self.visible_props, parent=self) self.containers_dict['header '+group[0]] = header self.filtered_widgets.add('header '+group[0]) # Loop power supply to create all the widgets of a groupbox group_widgets = list() for psname in pwrsupplies: ps_widget = SummaryWidget( name=psname, visible_props=self.visible_props, parent=self) pscontainer = PSContainer(ps_widget, self) group_widgets.append(pscontainer) self.containers_dict[psname] = pscontainer self.filtered_widgets.add(psname) self.ps_widgets_dict[psname] = ps_widget # Create group wid_type = 'groupbox' if group[0] else 'widget' group_wid = self._createGroupWidget( group[0], header, group_widgets, wid_type=wid_type) # Add group box to grid layout if len(self.groups) == 3: if idx in [0, 1]: splitt_v.addWidget(group_wid) else: self.pwrsupplies_layout.addWidget(splitt_v) self.pwrsupplies_layout.addWidget(group_wid) else: self.pwrsupplies_layout.addWidget(group_wid) self.count_label.setText( "Showing {} power supplies.".format( len(self.filtered_widgets)-len(self.groups))) self.setLayout(self.layout) def _createGroupWidget(self, title, header, widget_group, wid_type='groupbox'): scr_area_wid = QWidget(self) scr_area_wid.setObjectName('scr_ar_wid') scr_area_wid.setStyleSheet( '#scr_ar_wid {background-color: transparent;}') w_lay = QVBoxLayout(scr_area_wid) w_lay.setSpacing(0) w_lay.setContentsMargins(0, 0, 0, 0) for widget in widget_group: w_lay.addWidget(widget, alignment=Qt.AlignLeft) w_lay.addStretch() scr_area = QScrollArea(self) scr_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) scr_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) scr_area.setWidgetResizable(True) scr_area.setFrameShape(QFrame.NoFrame) scr_area.setWidget(scr_area_wid) wid = QGroupBox(title, self) if wid_type == 'groupbox' \ else QWidget(self) gb_lay = QVBoxLayout(wid) gb_lay.addWidget(header, alignment=Qt.AlignLeft) gb_lay.addWidget(scr_area) return wid def _getSplitter(self): if self._orientation == self.HORIZONTAL: return QSplitter(Qt.Horizontal) return QSplitter(Qt.Vertical) def _getVisibleProps(self): """Default visible properties.""" return ['detail', 'state', 'intlk', 'setpoint', 'monitor', 'strength_sp', 'strength_mon'] def _filter_pwrsupplies(self, text): """Filter power supply widgets based on text inserted at line edit.""" try: pattern = re.compile(text, re.I) except Exception: # Ignore malformed patterns? pattern = re.compile("malformed") # Clear filtered widgets and add the ones that match the new pattern self.filtered_widgets.clear() for name, container in self.containers_dict.items(): cond = 'header' in name if not cond: cond |= bool(pattern.search(name)) cond |= bool(pattern.search(container.bbbname)) cond |= bool(pattern.search(container.udcname)) for dc in container.dclinks: cond |= bool(pattern.search(dc)) for dc in container.dclinksbbbname: cond |= bool(pattern.search(dc)) for dc in container.dclinksudcname: cond |= bool(pattern.search(dc)) if cond: self.filtered_widgets.add(name) # Set widgets visibility and the number of widgets matched self._set_widgets_visibility() self.count_label.setText( "Showing {} power supplies".format( len(self.filtered_widgets)-len(self.groups))) # Scroll to top for scroll_area in self.findChildren(QScrollArea): scroll_area.verticalScrollBar().setValue(0) def _set_widgets_visibility(self): """Set visibility of the widgets.""" props = [act.objectName() for act in self.search_menu.actions() if act.isChecked()] self.visible_props = sort_propties(props) self._enable_actions() for key, wid in self.containers_dict.items(): wid.update_visible_props(props) if 'header' in key: for ob in wid.findChildren(QWidget): name = ob.objectName() ob.setVisible(name in props or 'Hidden' in name) else: vis = key in self.filtered_widgets wid.setVisible(vis) if not vis: continue objs = wid.findChildren(SummaryWidget) objs.extend(wid.findChildren(SummaryHeader)) for ob in objs: chil = ob.findChildren( QWidget, options=Qt.FindDirectChildrenOnly) for c in chil: name = c.objectName() if isinstance(ob, SummaryWidget) and name in props: ob.fillWidget(name) c.setVisible(name in props) # Actions methods def _create_actions(self): self.turn_on_act = QAction("Turn On", self) self.turn_on_act.triggered.connect(lambda: self._set_pwrstate(True)) self.turn_on_act.setEnabled(False) self.turn_off_act = QAction("Turn Off", self) self.turn_off_act.triggered.connect(lambda: self._set_pwrstate(False)) self.turn_off_act.setEnabled(False) self.ctrlloop_close_act = QAction("Close Control Loop", self) self.ctrlloop_close_act.triggered.connect( lambda: self._set_ctrlloop(True)) self.ctrlloop_close_act.setEnabled(False) self.ctrlloop_open_act = QAction("Open Control Loop", self) self.ctrlloop_open_act.triggered.connect( lambda: self._set_ctrlloop(False)) self.ctrlloop_open_act.setEnabled(False) self.set_slowref_act = QAction("Set OpMode to SlowRef", self) self.set_slowref_act.triggered.connect(self._set_slowref) self.set_slowref_act.setEnabled(False) self.set_current_sp_act = QAction("Set Current SP", self) self.set_current_sp_act.triggered.connect(self._set_current_sp) self.set_current_sp_act.setEnabled(False) self.reset_act = QAction("Reset Interlocks", self) self.reset_act.triggered.connect(self._reset_interlocks) self.reset_act.setEnabled(False) self.wfmupdate_on_act = QAction("Wfm Update Auto Enable", self) self.wfmupdate_on_act.triggered.connect( lambda: self._set_wfmupdate(True)) self.wfmupdate_on_act.setEnabled(False) self.wfmupdate_off_act = QAction("Wfm Update Auto Disable", self) self.wfmupdate_off_act.triggered.connect( lambda: self._set_wfmupdate(False)) self.wfmupdate_off_act.setEnabled(False) self.updparms_act = QAction("Update Parameters", self) self.updparms_act.triggered.connect(self._update_params) self.updparms_act.setEnabled(False) self.idffmode_on_act = QAction("Set IDFFMode On", self) self.idffmode_on_act.triggered.connect( lambda: self._set_idffmode(True)) self.idffmode_on_act.setEnabled(False) self.idffmode_off_act = QAction("Set IDFFMode Off", self) self.idffmode_off_act.triggered.connect( lambda: self._set_idffmode(False)) self.idffmode_off_act.setEnabled(False) self.set_accfreeze_frozen_act = QAction( "Set AccFreeze to frozen", self) self.set_accfreeze_frozen_act.triggered.connect( lambda: self._set_acc_freeze(True)) self.set_accfreeze_frozen_act.setEnabled(False) self.set_accfreeze_unfrozen_act = QAction( "Set AccFreeze to unfrozen", self) self.set_accfreeze_unfrozen_act.triggered.connect( lambda: self._set_acc_freeze(False)) self.set_accfreeze_unfrozen_act.setEnabled(False) self.acc_clear_act = QAction("Clear Acc", self) self.acc_clear_act.triggered.connect(self._acc_clear_cmd) self.acc_clear_act.setEnabled(False) def _enable_actions(self): if 'state' in self.visible_props and \ not self.turn_on_act.isEnabled(): self.turn_on_act.setEnabled(True) self.turn_off_act.setEnabled(True) if 'ctrlloop' in self.visible_props and \ not self.ctrlloop_close_act.isEnabled(): self.ctrlloop_close_act.setEnabled(True) self.ctrlloop_open_act.setEnabled(True) if 'opmode' in self.visible_props and \ not self.set_slowref_act.isEnabled(): self.set_slowref_act.setEnabled(True) if 'setpoint' in self.visible_props and \ not self.set_current_sp_act.isEnabled(): self.set_current_sp_act.setEnabled(True) if 'reset' in self.visible_props and \ not self.reset_act.isEnabled(): self.reset_act.setEnabled(True) if 'wfmupdate' in self.visible_props and \ not self.wfmupdate_on_act.isEnabled(): self.wfmupdate_on_act.setEnabled(True) self.wfmupdate_off_act.setEnabled(True) if 'updparms' in self.visible_props and \ not self.updparms_act.isEnabled(): self.updparms_act.setEnabled(True) if 'idffmode' in self.visible_props and \ not self.idffmode_on_act.isEnabled(): self.idffmode_on_act.setEnabled(True) self.idffmode_off_act.setEnabled(True) if 'accfreeze' in self.visible_props and \ not self.set_accfreeze_frozen_act.isEnabled(): self.set_accfreeze_frozen_act.setEnabled(True) self.set_accfreeze_unfrozen_act.setEnabled(True) if 'accclear' in self.visible_props and \ not self.acc_clear_act.isEnabled(): self.acc_clear_act.setEnabled(True) @Slot(bool) def _set_pwrstate(self, state): """Execute turn on/off actions.""" for key, widget in self.ps_widgets_dict.items(): if key in self.filtered_widgets: try: if state: widget.turn_on() else: widget.turn_off() except TypeError: pass @Slot(bool) def _set_ctrlloop(self, state): """Execute close/open control loop actions.""" for key, widget in self.ps_widgets_dict.items(): if key in self.filtered_widgets: try: if state: widget.ctrlloop_close() else: widget.ctrlloop_open() except TypeError: pass @Slot() def _set_slowref(self): """Set opmode to SlowRef for every visible widget.""" for key, widget in self.ps_widgets_dict.items(): if key in self.filtered_widgets: try: widget.set_opmode_slowref() except TypeError: pass @Slot() def _set_current_sp(self): """Set current setpoint for every visible widget.""" dlg = QInputDialog(self) dlg.setLocale(QLocale(QLocale.English)) new_value, ok = dlg.getDouble(self, "Insert current setpoint", "Value") if ok: for key, widget in self.ps_widgets_dict.items(): if key in self.filtered_widgets: sp = widget.setpoint.spinbox sp.value_changed(new_value) try: sp.send_value() except TypeError: pass @Slot() def _reset_interlocks(self): """Reset interlocks.""" for key, widget in self.ps_widgets_dict.items(): if key in self.filtered_widgets: try: widget.reset() except TypeError: pass @Slot(bool) def _set_wfmupdate(self, state): """Execute turn WfmUpdateAuto on/off actions.""" for key, widget in self.ps_widgets_dict.items(): if key in self.filtered_widgets: try: if state: widget.wfmupdate_on() else: widget.wfmupdate_off() except TypeError: pass @Slot() def _update_params(self): """Update parameters.""" for key, widget in self.ps_widgets_dict.items(): if key in self.filtered_widgets: try: widget.update_params() except TypeError: pass @Slot(bool) def _set_idffmode(self, state): """Execute turn IDFFMode on/off actions.""" for key, widget in self.ps_widgets_dict.items(): if key in self.filtered_widgets: try: if state: widget.idffmode_on() else: widget.idffmode_off() except TypeError: pass @Slot(bool) def _set_acc_freeze(self, state): """Execute turn AccFreeze frozen/unfrozen actions.""" for key, widget in self.ps_widgets_dict.items(): if key in self.filtered_widgets: try: if state: widget.set_accfreeze_frozen() else: widget.set_accfreeze_unfrozen() except TypeError: pass @Slot() def _acc_clear_cmd(self): """Reset interlocks.""" for key, widget in self.ps_widgets_dict.items(): if key in self.filtered_widgets: try: widget.acc_clear() except TypeError: pass # Overloaded method
[docs] def contextMenuEvent(self, event): """Show a custom context menu.""" point = event.pos() menu = QMenu("Actions", self) menu.addAction(self.turn_on_act) menu.addAction(self.turn_off_act) menu.addAction(self.set_current_sp_act) menu.addAction(self.reset_act) if not self._dev_list[0].dev in ('FCH', 'FCV'): menu.addAction(self.ctrlloop_close_act) menu.addAction(self.ctrlloop_open_act) menu.addAction(self.set_slowref_act) menu.addAction(self.wfmupdate_on_act) menu.addAction(self.wfmupdate_off_act) menu.addAction(self.updparms_act) else: menu.addAction(self.set_accfreeze_frozen_act) menu.addAction(self.set_accfreeze_unfrozen_act) menu.addAction(self.acc_clear_act) if PSSearch.conv_psname_2_psmodel(self._dev_list[0]) == 'FBP': menu.addAction(self.idffmode_on_act) menu.addAction(self.idffmode_off_act) menu.addSeparator() action = menu.addAction('Show Connections...') action.triggered.connect(self.show_connections) menu.popup(self.mapToGlobal(point))
[docs] def show_connections(self, checked): """.""" _ = checked conn = ConnectionInspector(self) conn.show()
[docs] def get_summary_widgets(self): """Return Summary Widgets.""" return self.findChildren(SummaryWidget)