Source code for siriushla.as_ap_injection.widgets
"""Monitoring widgets."""
from qtpy.QtGui import QPixmap, QIcon
from qtpy.QtCore import Qt, Slot, Signal, QSize, QTimer
from qtpy.QtWidgets import QWidget, QLabel, QGridLayout, QGroupBox, \
QHBoxLayout, QCheckBox, QMenu, QFrame, QSizePolicy as QSzPol, \
QPushButton
import qtawesome as qta
from pydm.widgets import PyDMPushButton
from pydm.connection_inspector import ConnectionInspector
from siriuspy.envars import VACA_PREFIX
from siriuspy.namesys import SiriusPVName
from siriuspy.devices import InjSysStandbyHandler
from siriuspy.clientarch import Time as _Time
from siriuspy.injctrl.csdev import get_status_labels, Const as _Const
from ..widgets import SiriusLedAlert, PyDMLedMultiChannel, PyDMLed, QLed, \
SiriusConnectionSignal
from ..widgets.led import MultiChannelStatusDialog
from ..widgets.dialog import StatusDetailDialog
[docs]
class MonitorSummaryWidget(QWidget):
"""Monitor Summary widget."""
def __init__(self, parent=None, prefix=VACA_PREFIX):
"""Init."""
super().__init__(parent)
self.prefix = prefix
self._inj_dev = SiriusPVName('AS-Glob:AP-InjCtrl')
self._inj_prefix = self._inj_dev.substitute(prefix=prefix)
self._setupUi()
def _setupUi(self):
monitor = QGroupBox('Monitor')
glay = QGridLayout(monitor)
glay.setAlignment(Qt.AlignTop)
glay.addWidget(QLabel('', self), 0, 0)
sec_lbls = get_status_labels()
for col, sec in enumerate(sec_lbls):
lbl = QLabel(sec, self, alignment=Qt.AlignCenter)
glay.addWidget(lbl, 0, col+1)
sub_lbls = set()
for sec in sec_lbls:
sub_lbls.update(get_status_labels(sec))
sub_lbls = sorted(sub_lbls)
for row, sub in enumerate(sub_lbls):
lbl = QLabel(sub, self, alignment=Qt.AlignCenter)
glay.addWidget(lbl, row+1, 0)
row = len(sub_lbls) - 1
line = QFrame(self)
line.setFrameShape(QFrame.HLine)
line.setFrameShadow(QFrame.Sunken)
glay.addWidget(line, row+2, 0, 1, len(sec_lbls)+1)
glay.addWidget(
QLabel('All', self, alignment=Qt.AlignCenter), row+3, 0)
for col, sec in enumerate(sec_lbls):
lbls = get_status_labels(sec)
for row, sub in enumerate(sub_lbls):
if sub not in lbls:
continue
bit = lbls.index(sub)
led = SiriusLedAlert(
self, self._inj_prefix.substitute(
propty='DiagStatus'+sec+'-Mon'), bit=bit)
glay.addWidget(led, row+1, col+1)
bit = sec_lbls.index(sec)
led = SiriusLedAlert(
self, self._inj_prefix.substitute(
propty='DiagStatus-Mon'), bit=bit)
glay.addWidget(led, row+3, col+1)
lay = QHBoxLayout(self)
lay.setContentsMargins(0, 0, 0, 0)
lay.addWidget(monitor)
[docs]
class InjDiagLed(SiriusLedAlert):
"""InjDiag Status Led."""
def __init__(self, parent, prefix=VACA_PREFIX, **kws):
init_channel = SiriusPVName(
'AS-Glob:AP-InjCtrl:InjStatus-Mon').substitute(prefix=prefix)
self.labels = get_status_labels('Inj')
super().__init__(parent, init_channel, **kws)
self.offColor = self.LightGreen
self.onColor = self.Red
[docs]
def mouseDoubleClickEvent(self, event):
"""Reimplement mouseDoubleClickEvent."""
if event.button() == Qt.LeftButton and self.labels:
self.msg = StatusDetailDialog(
parent=self.parent(), pvname=self.channel,
labels=self.labels, title='Injection Status')
self.msg.open()
super().mouseDoubleClickEvent(event)
[docs]
class InjSysStbyLed(PyDMLedMultiChannel):
"""Led to check whether several PVs are in stanby state."""
def __init__(self, parent=None, handler=None):
if not handler:
handler = InjSysStandbyHandler()
super().__init__(parent, handler.on_values)
self.stateColors = [PyDMLed.DarkGreen, PyDMLed.LightGreen,
PyDMLed.Yellow, PyDMLed.Gray]
def _update_statuses(self):
if not self._connected:
state = 3
else:
status_off = 0
for status in self._address2status.values():
if status == 'UNDEF' or not status:
status_off += 1
if status_off == len(self._address2status.values()):
state = 0
elif status_off > 0:
state = 2
else:
state = 1
self.setState(state)
[docs]
def mouseDoubleClickEvent(self, ev):
pv_groups, texts = list(), list()
pvs_err, pvs_und = set(), set()
for k, v in self._address2conn.items():
if not v:
pvs_und.add(k)
if pvs_und:
pv_groups.append(pvs_und)
texts.append('There are disconnected PVs!')
for k, v in self._address2status.items():
if not v and k not in pvs_und:
pvs_err.add(k)
if pvs_err:
pv_groups.append(pvs_err)
texts.append(
'The following PVs have value\n'
'equivalent to off status!')
if pv_groups:
msg = MultiChannelStatusDialog(
parent=self, pvs=pv_groups,
text=texts, fun_show_diff=self._show_diff)
msg.exec_()
QLed.mouseDoubleClickEvent(self, ev)
[docs]
class InjSysStbyControlWidget(QWidget):
"""Injection System Control Widget."""
expand = Signal()
def __init__(self, parent=None, prefix=VACA_PREFIX, is_summary=False,
handler=None):
"""Init."""
super().__init__(parent)
self.prefix = prefix
self._inj_prefix = SiriusPVName(
'AS-Glob:AP-InjCtrl').substitute(prefix=prefix)
self._is_summary = is_summary
self._last_comm = None
self._handler = handler or InjSysStandbyHandler()
self._icon_off = qta.icon('mdi.power-off')
self._icon_on = qta.icon('mdi.power-on')
self._icon_check = qta.icon('fa5s.check')
self._pixmap_check = self._icon_check.pixmap(
self._icon_check.actualSize(QSize(16, 16)))
self._icon_not = qta.icon('fa5s.times')
self._pixmap_not = self._icon_not.pixmap(
self._icon_not.actualSize(QSize(16, 16)))
self.menu = QMenu(self)
self.rstord_act = self.menu.addAction('Reset Commands')
self.rstord_act.triggered.connect(self._reset_commands_order)
if is_summary:
self._setupSummary()
else:
self._setupFull()
self.conn_act = self.menu.addAction('Show Connections...')
self.conn_act.triggered.connect(self._show_connections)
self._ch_cmdsts = SiriusConnectionSignal(
self._inj_prefix.substitute(propty='InjSysCmdSts-Mon'))
self._ch_off_order_sp = SiriusConnectionSignal(
self._inj_prefix.substitute(propty='InjSysTurnOffOrder-SP'))
self._ch_off_order_rb = SiriusConnectionSignal(
self._inj_prefix.substitute(propty='InjSysTurnOffOrder-RB'))
self._ch_on_order_sp = SiriusConnectionSignal(
self._inj_prefix.substitute(propty='InjSysTurnOnOrder-SP'))
self._ch_on_order_rb = SiriusConnectionSignal(
self._inj_prefix.substitute(propty='InjSysTurnOnOrder-RB'))
if not is_summary:
self._ch_cmdone = SiriusConnectionSignal(
self._inj_prefix.substitute(propty='InjSysCmdDone-Mon'))
self._ch_cmdsts.new_value_signal[int].connect(
self._handle_cmdsts_buttons_enbl)
self._ch_off_order_rb.new_value_signal[str].connect(
self._handle_buttons_color)
self._ch_on_order_rb.new_value_signal[str].connect(
self._handle_buttons_color)
self._ch_off_order_rb.new_value_signal[str].connect(
self._handle_actions_state)
self._ch_on_order_rb.new_value_signal[str].connect(
self._handle_actions_state)
if not is_summary:
self._ch_cmdone.new_value_signal[str].connect(
self._handle_cmdone_labels)
self._ch_cmdsts.new_value_signal[int].connect(
self._handle_cmdsts_labels)
def _setupSummary(self):
self._pb_off = PyDMPushButton(
self, label='', icon=self._icon_off,
init_channel=self._inj_prefix.substitute(
propty='InjSysTurnOff-Cmd'),
pressValue=0)
self._pb_off.setObjectName('bt')
self._pb_off.setStyleSheet(
'#bt{min-width:25px; max-width:25px; icon-size:20px;}')
self._pb_on = PyDMPushButton(
self, label='', icon=self._icon_on,
init_channel=self._inj_prefix.substitute(
propty='InjSysTurnOn-Cmd'),
pressValue=0)
self._pb_on.setObjectName('bt')
self._pb_on.setStyleSheet(
'#bt{min-width:25px; max-width:25px; icon-size:20px;}')
self._led_sts = InjSysStbyLed(self, handler=self._handler)
self._led_sts.setStyleSheet(
'QLed{min-width:1.29em; max-width:1.29em;}')
lay = QGridLayout(self)
lay.setAlignment(Qt.AlignCenter)
lay.addWidget(self._pb_off, 0, 0)
lay.addWidget(self._pb_on, 0, 1)
lay.addWidget(self._led_sts, 1, 0, 1, 2, alignment=Qt.AlignCenter)
# menu
for cmmtype in ['on', 'off']:
order = getattr(self._handler, cmmtype+'_order')
menu = QMenu('Select Turn '+cmmtype.upper()+' Commands', self)
setattr(self, cmmtype+'_menu', menu)
self.menu.addMenu(menu)
for cmm in order:
act = menu.addAction(self._handler.HANDLER_DESC[cmm])
act.setObjectName(cmm)
act.setCheckable(True)
def _setupFull(self):
lay = QGridLayout(self)
lay.addWidget(QLabel('Off', self, alignment=Qt.AlignCenter), 0, 1)
lay.addWidget(QLabel('On', self, alignment=Qt.AlignCenter), 0, 2)
lay.addWidget(QLabel('Status', self, alignment=Qt.AlignCenter), 0, 3)
self._checkbox_off = dict()
self._checkbox_on = dict()
self._labels_sts = dict()
for idx, name in enumerate(self._handler.DEF_ON_ORDER):
cb_off = QCheckBox(self)
cb_off.setObjectName(name)
self._checkbox_off[name] = cb_off
cb_on = QCheckBox(self)
cb_on.setObjectName(name)
self._checkbox_on[name] = cb_on
desc = self._handler.HANDLER_DESC[name]
splitd = desc.split('(')
lbl_txt = splitd[0]
tip = ''
if len(splitd) > 1:
lbl_txt, tip = splitd
lb_dsc = QLabel(lbl_txt, self, alignment=Qt.AlignLeft)
if tip:
lb_dsc.setToolTip(tip[:-1])
lb_sts = QLabel('', self, alignment=Qt.AlignCenter)
lb_sts.setObjectName(name)
self._labels_sts[name] = lb_sts
lay.addWidget(lb_dsc, idx+1, 0)
lay.addWidget(cb_off, idx+1, 1, alignment=Qt.AlignCenter)
lay.addWidget(cb_on, idx+1, 2, alignment=Qt.AlignCenter)
lay.addWidget(lb_sts, idx+1, 3)
self._pb_off = PyDMPushButton(
self, label='', icon=self._icon_off,
init_channel=self._inj_prefix.substitute(
propty='InjSysTurnOff-Cmd'),
pressValue=0)
self._pb_off.setObjectName('bt')
self._pb_off.setStyleSheet(
'#bt{min-width:25px; max-width:25px; icon-size:20px;}')
self._pb_on = PyDMPushButton(
self, label='', icon=self._icon_on,
init_channel=self._inj_prefix.substitute(
propty='InjSysTurnOn-Cmd'),
pressValue=0)
self._pb_on.setObjectName('bt')
self._pb_on.setStyleSheet(
'#bt{min-width:25px; max-width:25px; icon-size:20px;}')
self._led_sts = InjSysStbyLed(self, handler=self._handler)
lay.addWidget(self._pb_off, 6, 1)
lay.addWidget(self._pb_on, 6, 2)
lay.addWidget(self._led_sts, 6, 3)
@Slot(int)
def _handle_cmdsts_buttons_enbl(self, new_sts):
if new_sts == _Const.InjSysCmdSts.On:
self._pb_on.setEnabled(False)
self._pb_on.setIcon(qta.icon(
'fa5s.spinner', animation=qta.Spin(self._pb_on)))
self._pb_off.setEnabled(False)
elif new_sts == _Const.InjSysCmdSts.Off:
self._pb_on.setEnabled(False)
self._pb_off.setEnabled(False)
self._pb_off.setIcon(qta.icon(
'fa5s.spinner', animation=qta.Spin(self._pb_off)))
else:
if not self._pb_on.isEnabled():
self._pb_on.setEnabled(True)
self._pb_off.setEnabled(True)
self._pb_on.setIcon(self._icon_on)
self._pb_off.setIcon(self._icon_off)
@Slot(str)
def _handle_cmdone_labels(self, new_done):
for name in self._handler.DEF_ON_ORDER:
lbl = self._labels_sts[name]
if name in new_done:
lbl.setPixmap(self._pixmap_check)
elif self._ch_cmdsts.value == _Const.InjSysCmdSts.Idle:
lbl.setPixmap(self._pixmap_not)
@Slot(int)
def _handle_cmdsts_labels(self, new_sts):
if new_sts == _Const.InjSysCmdSts.On:
self._last_comm = new_sts
for name in self._handler.DEF_ON_ORDER:
if self._ch_on_order_rb.value is None:
break
lbl = self._labels_sts[name]
if name in self._ch_on_order_rb.value:
icon = qta.icon('fa5s.spinner')
pixmap = icon.pixmap(icon.actualSize(QSize(16, 16)))
lbl.setPixmap(pixmap)
else:
lbl.setPixmap(QPixmap())
elif new_sts == _Const.InjSysCmdSts.Off:
self._last_comm = new_sts
for name in self._handler.DEF_OFF_ORDER:
if self._ch_off_order_rb.value is None:
break
lbl = self._labels_sts[name]
if name in self._ch_off_order_rb.value:
icon = qta.icon('fa5s.spinner')
pixmap = icon.pixmap(icon.actualSize(QSize(16, 16)))
lbl.setPixmap(pixmap)
else:
lbl.setPixmap(QPixmap())
else:
done = self._ch_cmdone.value
for name in self._handler.DEF_ON_ORDER:
if done is None or name in done:
continue
lbl = self._labels_sts[name]
if self._last_comm == _Const.InjSysCmdSts.On and \
name in self._ch_on_order_rb.value:
lbl.setPixmap(self._pixmap_not)
elif self._last_comm == _Const.InjSysCmdSts.Off and \
name in self._ch_off_order_rb.value:
lbl.setPixmap(self._pixmap_not)
self._last_comm = None
@Slot()
def _set_commands_order(self):
if self._is_summary:
if self.sender() in self.on_menu.actions():
on_order = [
a.objectName() for a in self.on_menu.actions()
if a.isChecked()]
self._ch_on_order_sp.send_value_signal[str].emit(
','.join(on_order))
elif self.sender() in self.off_menu.actions():
off_order = [
a.objectName() for a in self.off_menu.actions()
if a.isChecked()]
self._ch_off_order_sp.send_value_signal[str].emit(
','.join(off_order))
else:
if self.sender() in self._checkbox_on.values():
checked = [
w.objectName() for w in self._checkbox_on.values()
if w.isChecked()]
on_order = [
h for h in self._handler.DEF_ON_ORDER if h in checked]
self._ch_on_order_sp.send_value_signal[str].emit(
','.join(on_order))
elif self.sender() in self._checkbox_off.values():
checked = [
w.objectName() for w in self._checkbox_off.values()
if w.isChecked()]
off_order = [
h for h in self._handler.DEF_OFF_ORDER if h in checked]
self._ch_off_order_sp.send_value_signal[str].emit(
','.join(off_order))
@Slot()
def _reset_commands_order(self):
self._ch_off_order_sp.send_value_signal[str].emit(
','.join(self._handler.DEF_OFF_ORDER))
self._ch_on_order_sp.send_value_signal[str].emit(
','.join(self._handler.DEF_ON_ORDER))
if self._is_summary:
for menu in [self.off_menu, self.on_menu]:
for act in menu.actions():
act.toggled.disconnect()
act.setChecked(True)
act.toggled.connect(self._set_commands_order)
else:
for group in [self._checkbox_off, self._checkbox_on]:
for wid in group.values():
wid.toggled.disconnect()
wid.setChecked(True)
wid.toggled.connect(self._set_commands_order)
@Slot()
def _handle_buttons_color(self):
off_color = 'yellow' if self._ch_off_order_rb.value != \
','.join(self._handler.DEF_OFF_ORDER) else 'white'
self._pb_off.setStyleSheet(
'#bt{min-width:25px; max-width:25px; icon-size:20px;'
'background-color: '+off_color+';}')
on_color = 'yellow' if self._ch_on_order_rb.value != \
','.join(self._handler.DEF_ON_ORDER) else 'white'
self._pb_on.setStyleSheet(
'#bt{min-width:25px; max-width:25px; icon-size:20px;'
'background-color: '+on_color+';}')
@Slot(str)
def _handle_actions_state(self, sts):
state = 'on' if 'On' in self.sender().address else 'off'
channel = getattr(self, '_ch_'+state+'_order_rb')
if channel.value is None:
return
if self._is_summary:
group = getattr(self, state+'_menu').actions()
else:
group = getattr(self, '_checkbox_'+state).values()
for obj in group:
obj.disconnect()
ost = obj.objectName() in sts
obj.setChecked(ost)
obj.toggled.connect(self._set_commands_order)
[docs]
def contextMenuEvent(self, event):
"""Show a custom context menu."""
self.menu.popup(self.mapToGlobal(event.pos()))
def _show_connections(self, checked):
"""Show connections action."""
_ = checked
conn = ConnectionInspector(self)
conn.show()
[docs]
class ClockLabel(QLabel):
"""Clock label."""
def __init__(self, parent=None):
"""Init."""
super().__init__(parent)
self._update_timer = QTimer(self)
self._update_timer.timeout.connect(self._update)
self._update_timer.setInterval(1000)
self._update_timer.start()
def _update(self):
text = _Time.strftime(_Time.now(), '%H:%M:%S')
self.setText(text)
[docs]
class TaskStatusLabel(QPushButton):
"""Label to show if task is running."""
def __init__(self, parent=None, init_channel=None):
super().__init__(parent)
self._wait_icon = qta.icon('fa5s.spinner', animation=qta.Spin(self))
self.setFlat(True)
self._channel = SiriusConnectionSignal(init_channel)
self._channel.new_value_signal[int].connect(self._update_icon)
self.setSizePolicy(QSzPol.Fixed, QSzPol.Fixed)
self.setMaximumSize(25, 25)
self.setStyleSheet('QPushButton:hover{border:0pt solid transparent;}')
def _update_icon(self, status):
if status:
self.setIcon(self._wait_icon)
else:
self.setIcon(QIcon())