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: psname_list - a list of power supplies, will be filtered based on patterns defined in the subclass; orientation - 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 # Setup the UI self.groups = self._getGroups() 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): # Get power supplies that belong to group pwrsupplies = list() pattern = re.compile(group[1]) for el in self._dev_list: if pattern.search(el): pwrsupplies.append(el) # 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) else: 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) 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) @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.sp_lineedit sp.setText(str(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 # Overloaded method 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.ctrlloop_close_act) menu.addAction(self.ctrlloop_open_act) menu.addAction(self.set_current_sp_act) if not self._dev_list[0].dev in ('FCH', 'FCV'): menu.addAction(self.set_slowref_act) menu.addAction(self.reset_act) menu.addAction(self.wfmupdate_on_act) menu.addAction(self.wfmupdate_off_act) menu.addAction(self.updparms_act) menu.addSeparator() action = menu.addAction('Show Connections...') action.triggered.connect(self.show_connections) menu.popup(self.mapToGlobal(point)) def show_connections(self, checked): """.""" _ = checked conn = ConnectionInspector(self) conn.show() def get_summary_widgets(self): """Return Summary Widgets.""" return self.findChildren(SummaryWidget)
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) 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.sp_lineedit sp.setText(str(new_value)) try: sp.send_value() except TypeError: pass def _reset_intlk(self): for dclink in self.dclink_widgets: dclink.reset() # Overloaded method def contextMenuEvent(self, event): """Overload to create a custom context menu.""" widget = self.childAt(event.pos()) 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) def show_connections(self, checked): """.""" _ = checked c = ConnectionInspector(self) c.show()