diff --git a/__pycache__/pvgateway.cpython-37.pyc b/__pycache__/pvgateway.cpython-37.pyc
new file mode 100644
index 0000000..af6ecc4
Binary files /dev/null and b/__pycache__/pvgateway.cpython-37.pyc differ
diff --git a/__pycache__/pvwidgets.cpython-37.pyc b/__pycache__/pvwidgets.cpython-37.pyc
new file mode 100644
index 0000000..f2890e7
Binary files /dev/null and b/__pycache__/pvwidgets.cpython-37.pyc differ
diff --git a/pvgateway.py b/pvgateway.py
index 688b49f..2d6d64d 100644
--- a/pvgateway.py
+++ b/pvgateway.py
@@ -1,242 +1,259 @@
-"""The module provides data and metadata of a process variable through PyCafe."""
+"""
+The module provides data and metadata of a process variable through
+PyCafe.
+"""
__author__ = 'Jan T. M. Chrin'
import copy
+from enum import IntEnum
import inspect
-import sys
import time
-from datetime import datetime
from distutils.version import LooseVersion
-from qtpy.QtCore import (QEvent, QObject, QMutex, QPoint, QProcess, QRect,
- QSettings, Qt, QUrl, Signal, Slot)
+from qtpy.QtCore import (QEvent, QMutex, QPoint, QProcess, QSettings, Qt, QUrl,
+ Signal)
from qtpy.QtCore import __version__ as QT_VERSION_STR
-from qtpy.QtGui import (QColor, QCursor, QDesktopServices, QFont, QPainter,
- QPalette)
-from qtpy.QtWidgets import (QAction, QApplication, QComboBox, QDialog,
- QHBoxLayout, QLabel, QLineEdit, QMenu, QMessageBox,
+from qtpy.QtGui import QCursor, QDesktopServices, QFont
+from qtpy.QtWidgets import (QAction, QApplication, QComboBox, QDialog,
+ QHBoxLayout, QLabel, QMenu, QMessageBox,
QPushButton, QSpinBox, QVBoxLayout, QWidget)
-from bdbase.readjson import Rjson
-from bdbase.enumkind import DAQState
-
def __LINE__():
return inspect.currentframe().f_back_f_lineno
-
+class DAQState(IntEnum):
+ BS = 10
+ CA = 20
+ BS_STOP = 30
+ CA_STOP = 40
+ BS_PAUSE = 50
+ CA_PAUSE = 60
+
class PVGateway(QWidget):
"""Retrieves pv metadata through PyCafe.
- The PVGateway class when subclassed by Qt widgets enables their connectivity
- to channel access.
+ The PVGateway class when subclassed by Qt widgets enables their
+ connectivity to channel access.
Attributes:
monid: (int) Monitor id
units : (str) Units associated with the pv
- trigger_monitor_ is the signal triggered by updates arising from
- monitored pvs.
- trigger_connect is the signal triggered from changes in pv connection status.
- widget_handle_dict is a dictionary mapping widgets to their pv handle.
+ trigger_monitor_ is the signal triggered by updates
+ arising from monitored pvs.
+ trigger_connect is the signal triggered from changes in pv
+ connection status.
+ widget_handle_dict is a dictionary mapping widgets to their pv
+ handle.
A pv handle may be associated to more than one widget.
"""
- trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_float = Signal(float, int, int)
trigger_monitor_int = Signal(int, int, int)
trigger_monitor_str = Signal(str, int, int)
trigger_monitor = Signal(object, int) #pvdata, status
- trigger_connect = Signal(int, str, int)
+ trigger_connect = Signal(int, str, int)
- trigger_daq = Signal(object, str, int)
+ trigger_daq = Signal(object, str, int)
trigger_daq_int = Signal(object, str, int)
trigger_daq_str = Signal(object, str, int)
-
+
#Properties, user supplied
ACT_ON_BEAM = 'actOnBeam'
NOT_ACT_ON_BEAM = 'notActOnBeam'
READBACK_ALARM = 'alarm'
- READBACK_STATIC = 'static'
-
+ READBACK_STATIC = 'static'
+
#Properties, dynamic
- DISCONNECTED = 'disconnected'
+ DISCONNECTED = 'disconnected'
ALARM_SEV_MINOR = 'alarmSevMinor'
ALARM_SEV_MAJOR = 'alarmSevMajor'
ALARM_SEV_INVALID = 'alarmSevInvalid'
ALARM_SEV_NO_ALARM = READBACK_ALARM
DAQ_STOPPED = 'stopped'
- DAQ_PAUSED = 'paused'
+ DAQ_PAUSED = 'paused'
- #ObjectName, defined by CAQ
+ #ObjectName, defined by CAQ
PV_CONTROLLER = "Controller"
PV_READBACK = "Readback"
PV_DAQ_BS = "BSRead"
PV_DAQ_CA = "CARead"
_DAQ_CAFE_SG_NAME = "gBS2CA"
-
- _alarm_severity_record_types = ["ai", "ao", "calc", "calcout", "dfanout",
- "longin", "longout", "pid", "sel",
+
+ _alarm_severity_record_types = ["ai", "ao", "calc", "calcout", "dfanout",
+ "longin", "longout", "pid", "sel",
"steppermotor", "sub"]
#parent is Gui
- def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
- pv_within_daq_group: bool = False, color_mode = None,
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode=None,
show_units: bool = False, prefix: str = "", suffix: str = "",
- connect_callback=None, msg_label: str = "",
- connect_triggers: bool = True, notify_freq_hz: int = 0,
- notify_unison: bool = False, precision: int = 0,
+ connect_callback=None, msg_label: str = "",
+ connect_triggers: bool = True, notify_freq_hz: int = 0,
+ notify_unison: bool = False, precision: int = 0,
monitor_dbr_time: bool = False):
-
- #super(PVGateway, self).__init__() # do NOT use parent
- #It turned out a widget was created with the main window as a parent, but incorrectly placed.
- #Parent must not be QMainWindow. This interferes with the toolbar!! 16 Aug. 2020
+
super().__init__()
if parent is None:
return
- if pv_name is "":
+ if not pv_name:
return
self.connect_callback = connect_callback
self.notify_freq_hz = abs(notify_freq_hz)
self.notify_freq_hz_default = self.notify_freq_hz
-
+
self.notify_milliseconds = 0 if self.notify_freq_hz == 0 else \
1000 / self.notify_freq_hz
- self.notify_unison = True if notify_unison and \
- self.notify_freq_hz > 0 else False
+ self.notify_unison = bool(notify_unison) and bool(self.notify_freq_hz)
self.parent = parent
- self.pv_name = pv_name
-
+ self.settings = self.parent.settings
+
+ self.pv_name = pv_name
+
self.color_mode = None
if color_mode is not None:
if color_mode in (self.ACT_ON_BEAM,
self.NOT_ACT_ON_BEAM,
- self.READBACK_ALARM,
+ self.READBACK_ALARM,
self.READBACK_STATIC):
- self.color_mode = color_mode
-
- self.color_mode_requested = self.color_mode
+ self.color_mode = color_mode
- if monitor_callback is not None:
+ self.color_mode_requested = self.color_mode
+
+ if monitor_callback is not None:
self.monitor_callback = monitor_callback
else:
- self.monitor_callback = None
+ self.monitor_callback = None
self.pv_within_daq_group = pv_within_daq_group
-
- self.show_units = show_units
- self.prefix = prefix
+
+ self.show_units = show_units
+ self.prefix = prefix
self.suffix = suffix
-
+
self.cafe = self.parent.cafe
self.cyca = self.parent.cyca
+
if self.parent.settings is not None:
- self.settings = self.parent.settings
+ self.url_archiver = self.parent.settings.data["url"]["archiver"]
+ self.url_databuffer = self.parent.settings.data["url"]["databuffer"]
+ self.bg_readback = self.parent.settings.data["StyleGuide"][
+ "bgReadback"]
+ self.fg_alarm_major = self.parent.settings.data["StyleGuide"][
+ "fgAlarmMajor"]
+ self.fg_alarm_minor = self.parent.settings.data["StyleGuide"][
+ "fgAlarmMinor"]
+ self.fg_alarm_invalid = self.parent.settings.data["StyleGuide"][
+ "fgAlarmInvalid"]
+ self.fg_alarm_noalarm = self.parent.settings.data["StyleGuide"][
+ "fgAlarmNoAlarm"]
else:
- self.settings = Rjson(self.parent.appname)
-
+ #self.settings = ReadJSON(self.parent.appname)
+ self.url_archiver = ("https://ui-data-api.psi.ch/prepare?channel=" +
+ "sf-archiverappliance/")
+ self.url_databuffer \
+ = "https://ui-data-api.psi.ch/prepare?channel=sf-databuffer/"
+
self.daq_group_name = self._DAQ_CAFE_SG_NAME
self.desc = None
self.handle = None
self.initialize_complete = False
self.initialize_again = False
-
+
self.msg_label = msg_label
self.msg_press_value = None
- self.msg_release_value = None
+ self.msg_release_value = None
- self.monitor_id = None
+ self.monitor_id = None
self.monitor_dbr_time = monitor_dbr_time
-
self.mutex_post_display = QMutex()
-
self.precision_user = precision
- self.has_precision_user = True if precision > 0 else False
+ self.has_precision_user = bool(precision)
self.precision_pv = 3
- self.precision = (self.precision_user if self.has_precision_user else
+ self.precision = (self.precision_user if self.has_precision_user else
self.precision_pv)
-
+
self.pvd = None
self.pv_ctrl = None
- self.pv_info = None
+ self.pv_info = None
self.record_type = None
- if self.parent.showMessage is not None:
- self.showMessage = self.parent.showMessage
- else:
- self.showMessage = None
+ #if 'show_log_message' in dir(self.parent):
+ # self.show_log_message = self.parent.show_log_message
+ #else:
+ # self.show_log_message = None
self.qt_object_name = None
-
+
self.qt_property_controller = {
- self.DISCONNECTED : False,
- self.ACT_ON_BEAM : False, self.NOT_ACT_ON_BEAM : False
+ self.DISCONNECTED: False,
+ self.ACT_ON_BEAM: False, self.NOT_ACT_ON_BEAM: False
}
self.qt_property_readback = {
- self.DISCONNECTED : False,
- self.READBACK_ALARM : False, self.READBACK_STATIC : False,
- self.ALARM_SEV_MINOR : False, self.ALARM_SEV_MAJOR : False,
- self.ALARM_SEV_INVALID : False
+ self.DISCONNECTED: False,
+ self.READBACK_ALARM: False, self.READBACK_STATIC: False,
+ self.ALARM_SEV_MINOR: False, self.ALARM_SEV_MAJOR: False,
+ self.ALARM_SEV_INVALID: False
}
-
+
self.qt_property_daq_bs = {
- self.DISCONNECTED : False,
- self.READBACK_ALARM : False, self.READBACK_STATIC : False,
- self.ALARM_SEV_MINOR : False, self.ALARM_SEV_MAJOR : False,
- self.ALARM_SEV_INVALID : False,
- self.DAQ_STOPPED : False,
- self.DAQ_PAUSED : False
+ self.DISCONNECTED: False,
+ self.READBACK_ALARM: False, self.READBACK_STATIC: False,
+ self.ALARM_SEV_MINOR: False, self.ALARM_SEV_MAJOR: False,
+ self.ALARM_SEV_INVALID: False,
+ self.DAQ_STOPPED: False, self.DAQ_PAUSED: False
}
-
+
self.qt_property_daq_ca = {
- self.DISCONNECTED : False,
- self.READBACK_ALARM : False, self.READBACK_STATIC : False,
- self.ALARM_SEV_MINOR : False, self.ALARM_SEV_MAJOR : False,
- self.ALARM_SEV_INVALID : False,
- self.DAQ_STOPPED : False,
- self.DAQ_PAUSED : False
+ self.DISCONNECTED: False,
+ self.READBACK_ALARM: False, self.READBACK_STATIC: False,
+ self.ALARM_SEV_MINOR: False, self.ALARM_SEV_MAJOR: False,
+ self.ALARM_SEV_INVALID: False,
+ self.DAQ_STOPPED: False, self.DAQ_PAUSED: False
}
-
+
self.qt_object_to_property = {
- self.PV_CONTROLLER : self.qt_property_controller,
- self.PV_READBACK : self.qt_property_readback,
- self.PV_DAQ_BS : self.qt_property_daq_bs,
- self.PV_DAQ_CA : self.qt_property_daq_ca
+ self.PV_CONTROLLER: self.qt_property_controller,
+ self.PV_READBACK: self.qt_property_readback,
+ self.PV_DAQ_BS: self.qt_property_daq_bs,
+ self.PV_DAQ_CA: self.qt_property_daq_ca
}
self._qt_property_selected = {}
-
+
self.status_tip = None
self.suggested_text = ""
self.time_monotonic = time.monotonic()
self.pvd_previous = None
self.timeout = 0.2
self.units = ""
-
+
self.widget = self
_widget_name_part = str(self.widget.__class__).split("\'")[1].split(".")
- _widget_class_part = _widget_name_part[1].split(".")
+ #_widget_class_part = _widget_name_part[1].split(".")
self.widget_class = _widget_name_part[len(_widget_name_part)-1]
if pv_within_daq_group:
self.trigger_daq_int.connect(self.receive_daq_update)
self.trigger_daq.connect(self.receive_daq_update)
- self.trigger_daq_str.connect(self.receive_daq_update)
+ self.trigger_daq_str.connect(self.receive_daq_update)
elif connect_triggers:
self.trigger_monitor.connect(self.receive_monitor_dbr_time)
- self.trigger_monitor_str.connect(self.receive_monitor_update)
+ self.trigger_monitor_str.connect(self.receive_monitor_update)
self.trigger_monitor_int.connect(self.receive_monitor_update)
self.trigger_monitor_float.connect(self.receive_monitor_update)
self.trigger_connect.connect(self.receive_connect_update)
@@ -246,7 +263,7 @@ class PVGateway(QWidget):
self.context_menu.setWindowModality(Qt.NonModal) #ApplicationModal
if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.0"):
self.context_menu.addSection("PV: {0}".format(self.pv_name))
-
+
action1 = QAction("Text Info", self)
action1.triggered.connect(self.pv_status_text)
@@ -266,46 +283,37 @@ class PVGateway(QWidget):
self.context_menu.addAction(action2)
self.context_menu.addAction(action3)
self.context_menu.addAction(action4)
-
+
action5 = QAction("Reconnect: {0}".format(self.pv_name), self)
action5.triggered.connect(self.reconnect_channel)
_font = QFont()
- _font.setPixelSize(12)
+ _font.setPixelSize(12)
action5.setFont(_font)
- if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
self.context_menu.addSection("")
- #return action6 and 5 code here eventually
+ #return action6 and 5 code here eventually
self.context_menu.addAction(action6)
self.context_menu.addAction(action5)
self.pv_message_in_a_box = QMessageBox()
self.pv_message_in_a_box.setObjectName("pvinfo")
-
- #Qt.NonModal often causes harmless QXcbConnection: XCB error: 3 (BadWindow), sequence:
- #but only if the window is closed too quickly(!)
+
#Qt.ApplicationModal not used as it blocks input to all windows
- self.pv_message_in_a_box.setWindowModality(Qt.NonModal) #Qt.ApplicationModal
+ self.pv_message_in_a_box.setWindowModality(Qt.NonModal)
self.pv_message_in_a_box.setIcon(QMessageBox.Information)
- self.pv_message_in_a_box.setStandardButtons(QMessageBox.Close) # Shows QMessageBox.Close shows Close
- self.pv_message_in_a_box.setDefaultButton(QMessageBox.Close) #Show OK
-
- self.initialize()
- '''
- #temporary code position
- if self.pv_ctrl is not None:
- if self.pv_ctrl.precision > 0:
- self.context_menu.addAction(action6)
+ self.pv_message_in_a_box.setStandardButtons(QMessageBox.Close)
+ self.pv_message_in_a_box.setDefaultButton(QMessageBox.Close)
+
+ self.initialize()
+
+ #return self - previously used by pvgateway
- self.context_menu.addAction(action5)
- '''
- return self
-
def initialize(self):
'''Initialze class attributes and connect to ca if required.'''
-
+
_handle_within_group_flag = False
if self.pv_within_daq_group:
self.handle = self.cafe.getHandleFromPVWithinGroup(
@@ -315,13 +323,11 @@ class PVGateway(QWidget):
_handle_within_group_flag = True
#Callback already invoked to emit signal here!!
_channel_info = self.cafe.getChannelInfo(self.handle)
- print(self.pv_name, self.handle)
- w = self.cafe.getWidgets(self.handle)
- print("widget list", w)
- #_channel_info.show()
-
+
+ #wgts = self.cafe.getWidgets(self.handle)
+
self.trigger_connect.emit(
- int(self.handle), str(self.pv_name),
+ int(self.handle), str(self.pv_name),
int(_channel_info.cafeConnectionState))
#In case user is misinformed
if not _handle_within_group_flag:
@@ -330,61 +336,54 @@ class PVGateway(QWidget):
self.connect_callback = self.py_connect_callback
if self.handle > 0:
-
- #The second time round, widget is gateway rather than parent, Why is that?
- self.cafe.setPyConnectCallbackFn(self.handle,
+ #The second time round, widget is gateway rather than parent,
+ #Why is that?
+ self.cafe.setPyConnectCallbackFn(self.handle,
self.connect_callback)
-
+
self.cafe.addWidget(self.handle, self.widget)
-
+
_channel_info = self.cafe.getChannelInfo(self.handle)
self.trigger_connect.emit(
- self.handle, self.pv_name,
- int(_channel_info.cafeConnectionState))
-
+ self.handle, self.pv_name,
+ int(_channel_info.cafeConnectionState))
- #print("====OLD===============", self.handle, self.widget, self.parent)
else:
-
self.cafe.openPrepare()
- self.handle = self.cafe.open(self.pv_name,
- self.connect_callback)
+ self.handle = self.cafe.open(self.pv_name,
+ self.connect_callback)
self.cafe.addWidget(self.handle, self.widget)
self.cafe.openNowAndWait(self.timeout, self.handle)
- #self.cafe.openNow()
- #_channel_info = self.cafe.getChannelInfo(self.handle)
- #self.trigger_connect.emit(int(self.handle), str(self.pv_name), int(_channel_info.cafeConnectionState))
- #print("====NEW============ ==", self.handle, self.widget)
self.initialize_meta_data()
-
- self.pv_message_in_a_box.setWindowTitle(self.pv_name)
-
+
+ self.pv_message_in_a_box.setWindowTitle(self.pv_name)
+
def initialize_meta_data(self):
-
+
_current_value = ""
-
+
if self.cafe.isConnected(self.handle) and \
self.cafe.initCallbackComplete(self.handle):
-
- if self.pvd is None:
+
+ if self.pvd is None:
self.pvd = self.cafe.getPVCache(self.handle)
-
- if self.pv_ctrl is None:
+
+ if self.pv_ctrl is None:
self.pv_ctrl = self.cafe.getCtrlCache(self.handle)
self.set_precision_and_units()
-
+
if self.pv_info is None:
self.pv_info = self.cafe.getChannelInfo(self.pv_name)
if "Not Supported" in self.pv_info.className:
_rtype = self.cafe.get(self.pv_name.split(".")[0] + ".RTYP")
- self.record_type = _rtype if _rtype is not None else self.pv_info.className
- _rtype = self.cafe.close(self.pv_name.split(".")[0] + ".RTYP")
+ self.record_type = _rtype if _rtype is not None else \
+ self.pv_info.className
+ _rtype = self.cafe.close(self.pv_name.split(".")[0] +
+ ".RTYP")
else:
self.record_type = self.pv_info.className
- #print ("record_type", self.record_type)
-
_current_value = self.cafe.getCache(self.handle)
if isinstance(_current_value, (int, float)):
@@ -392,85 +391,79 @@ class PVGateway(QWidget):
_value_form = ("{:<+.%sf}" % self.precision)
_current_value = _value_form.format(
round(_current_value, self.precision))
- #if self.desc is None:
- # self.set_desc()
-
+
#Reset
self.initialize_complete = True
-
+
#verify user input
if self.show_units is True:
if self.suffix == self.units and self.units != "":
- self.show_units = False
-
- self.suggested_text = self.prefix
- if len(self.prefix) > 0:
- self.suggested_text += " "
-
- _suggested_text_from_value = " "
-
+ self.show_units = False
+
+ self.suggested_text = self.prefix
+ if self.prefix:
+ self.suggested_text += " "
+
+ _suggested_text_from_value = " "
+
_max_control_abs = 0
-
+
if self.pv_ctrl is not None:
_lower_control_abs = abs(int(self.pv_ctrl.lowerControlLimit))
_upper_control_abs = abs(int(self.pv_ctrl.upperControlLimit))
_max_control_abs = max(_lower_control_abs, _upper_control_abs)
if _max_control_abs is None:
- _max_control_abs = 0
+ _max_control_abs = 0
_enum_list = self.pv_ctrl.enumStrings
- if len(_enum_list) > 0:
+ if _enum_list:
_enum_list_member_max_length = 0
_enum_list_member_max_index = 0
for i in range(0, len(_enum_list)):
if len(_enum_list[i]) > _enum_list_member_max_length:
- _enum_list_member_max_length = len(_enum_list[i])
- _enum_list_member_max_index = i
+ _enum_list_member_max_length = len(_enum_list[i])
+ _enum_list_member_max_index = i
_suggested_text_from_value += \
- _enum_list[_enum_list_member_max_index] + "." #Add extra space
+ _enum_list[_enum_list_member_max_index] + "."
else:
if self.pv_ctrl.lowerControlLimit < 0:
_suggested_text_from_value += "-"
_suggested_text_from_value += str(_max_control_abs) + "."
-
- #print("precision", self.precision, self.pv_name)
self.precision = min(9, self.precision) #safety net
- for i in range (0, self.precision):
+ for i in range(0, self.precision):
_suggested_text_from_value += "0"
-
- if len(_current_value) > len(_suggested_text_from_value):
+
+ if len(_current_value) > len(_suggested_text_from_value):
_suggested_text_from_value = _current_value
-
+
self.suggested_text += _suggested_text_from_value
-
+
if self.show_units:
- self.suggested_text += " " + self.units
- self.suggested_text += self.suffix
+ self.suggested_text += " " + self.units
+ self.suggested_text += self.suffix
_suggested_text_length = len(self.suggested_text)
self.suggested_text = self.suggested_text.center(
_suggested_text_length+2)
-
- self.max_control_abs_str = str(_max_control_abs)
-
-
+
+ self.max_control_abs_str = str(_max_control_abs)
+
_max_control_abs_length = len(self.max_control_abs_str)
_offset = 9
self.max_control_abs_str = self.max_control_abs_str.center(
- _max_control_abs_length + _offset)
+ _max_control_abs_length + _offset)
-
qsettings = QSettings()
qsettings.beginGroup("Widget")
qsettings.beginGroup(self.pv_name)
qsettings.beginGroup(self.widget_class)
- #_var_base = "Widget/" + self.pv_name + "/" + self.widget_class + "/"
+
_var_text = "suggested_text"
_ctrl_abs = "max_control_abs_str"
-
+
if self.cafe.isConnected(self.handle) and \
self.cafe.initCallbackComplete(self.handle):
qsettings.setValue(_var_text, self.suggested_text)
@@ -478,18 +471,17 @@ class PVGateway(QWidget):
else:
if qsettings.value(_var_text) is not None:
self.suggested_text = qsettings.value(_var_text)
- if qsettings.value(_ctrl_abs) is not None:
+ if qsettings.value(_ctrl_abs) is not None:
self.max_control_abs_str = qsettings.value(_ctrl_abs)
-
qsettings.endGroup()
qsettings.endGroup()
- qsettings.endGroup()
+ qsettings.endGroup()
def is_initialize_complete(self):
- icount = 0;
- while not self.initialize_complete :
+ icount = 0
+ while not self.initialize_complete:
time.sleep(0.01)
self.initialize_meta_data()
icount += 1
@@ -497,28 +489,26 @@ class PVGateway(QWidget):
return False
return True
- def cleanup(self, close_pv = True):
+ def cleanup(self, close_pv=True):
'''Clean up the widget.'''
- ##Check for monitors
- ##QWidget::close() is basically a combination of QWidget::closeEvent(), QWidget::hide(), and QObject::deleteLater() (if Qt::WA_DeleteOnClose is set).
#Make sure mon id is valid
if self.handle > 0:
- _monID_list = self.cafe.getMonitorIDs(self.handle)
- if self.monitor_id in _monID_list:
+ _monID_list = self.cafe.getMonitorIDs(self.handle)
+ if self.monitor_id in _monID_list:
self.cafe.monitorStop(self.handle, self.monitor_id)
- #self.cafe.monitorStop(self.handle, self.monitor_id)
+
#Do not close of there are other monitors
if self.cafe.getNoMonitors(self.handle) > 0:
if close_pv is True:
self.cafe.close(self.pv_name)
- self.widget.deleteLater()
-
+ self.widget.deleteLater()
+
def format_display_value(self, value):
-
+
if value is None:
- print(self, self.pv_name, ">>>>>>>>>>>>format_display_value is None>>>>>>")
+ print(self, self.pv_name, ">>>>format_display_value is None")
#return
if isinstance(value, str):
@@ -526,75 +516,59 @@ class PVGateway(QWidget):
elif isinstance(value, int):
_value_str = str(value)
else:
- _value_form = ("{:< .%sf}" % self.precision) #space for positive numbers
- #print("v/prec", value, self.precision, flush=True)
-
+ _value_form = ("{:< .%sf}" % self.precision)
_rounded_value = round(value, self.precision)
- #print(_rounded_value, flush=True)
_value_str = _value_form.format(_rounded_value)
- #print(_value_str, flush=True)
if self.show_units:
- _value_str += " " + self.units + " "
- if self.suffix is not "":
- _value_str += " " + self.suffix + " "
-
- if self.prefix is not "":
+ _value_str += " " + self.units + " "
+ if self.suffix:
+ _value_str += " " + self.suffix + " "
+
+ if self.prefix:
_space = ""
if self.pv_ctrl is not None:
if self.pv_ctrl.lowerDisplayLimit < 0:
_space = " "
_value_str = self.prefix + _space + _value_str
-
- return _value_str
- def post_display_value(self, value):
+ return _value_str
+
+ def post_display_value(self, value):
- #self.mutex_post_display.lock()
-
_value_str = self.format_display_value(value)
-
+
if "setText" in dir(self):
-
+
if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
self.blockSignals(True)
self.setText(_value_str)
self.blockSignals(False)
else:
- #print("value =", _value_str, flush=True)
self.setText(_value_str)
-
+
else:
- print("setText method does not exist for this widget class:\n", self.widget.__class__)
+ print("setText method does not exist for this widget class:\n",
+ self.widget.__class__)
print("sender was: ", self.sender())
-
+
def py_connect_callback(self, handle, pvname, status):
'''Callback function to be invoked on change of pv connection status.
- Checks for existence of widget. Waits up to a maximun of 100 ms.
- '''
- #print(" py_connect_callback:: START ")
- #print(" py_connect_callback:: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", pvname)
- #print(handle, pvname, status, self.cafe.getStatusCodeAsString(status))
-
+ Checks for existence of widget. Waits up to a maximun of 100 ms.
+ '''
pv_name = pvname
- _widget = None
- #_widgetList =self.cafe.getWidgets(handle)
- #for i in range(0, len(_widgetList)):
- #_widget = _widgetList[i]
- #_widget.trigger_connect.emit(int(handle), str(pv_name), int(status))
- #print (i, "widget at connect>>>>>>>>>>>>>>>>>>>>>>", _widget.__class__)
self.trigger_connect.emit(int(handle), str(pv_name), int(status))
- #print(" py_connect_callback:: END ")
-
- def receive_connect_update(self, handle, pv_name, status, post_display=True):
+ def receive_connect_update(self, handle, pv_name, status,
+ post_display=True):
'''Triggered by connect signal. For Widget to overload.'''
-
- #print(" receive _connect_callback:: START ")
- #print ("RRReceive_connect triggered for widget", self, "with status", status)
- #print ("RRReceive_connect triggered for widget", handle, pv_name, status)
- _alarm_severity = None
+
+ if pv_name is not None:
+ if pv_name != self.pv_name:
+ print(("pv_name {0} in receive_connect_update " +
+ "does not match: {1}").format(pv_name, self.pv_name))
+
if status == self.cyca.ICAFE_CS_CONN:
self.initialize_connect = True
self.pv_ctrl = self.cafe.getCtrlCache(self.handle)
@@ -602,55 +576,45 @@ class PVGateway(QWidget):
if self.pv_info is not None and self.record_type is None:
if "Not Supported" in self.pv_info.className:
_rtype = self.cafe.get(self.pv_name.split(".")[0] + ".RTYP")
- self.record_type = _rtype if _rtype is not None else self.pv_info.className
- _rtype = self.cafe.close(self.pv_name.split(".")[0] + ".RTYP")
- #print(self.pv_name)
- #print("record type====>", self.record_type)
+ self.record_type = _rtype if _rtype is not None else \
+ self.pv_info.className
+ _rtype = self.cafe.close(self.pv_name.split(".")[0] +
+ ".RTYP")
else:
self.record_type = self.pv_info.className
self.set_precision_and_units(reconnectFlag=True)
- #THis will connect to a new channel
- #if self.desc is None:
- # self.set_desc()
- #print("msg_lab", self.msg_label, "/", len(self.msg_label))
- if self.msg_label == "":
+ if not self.msg_label:
_value = self.cafe.getCache(handle, dt='native')
#Another reconnection in progress!!!
-
- if _value == None:
+
+ if _value is None:
return
else:
_value = self.msg_label
- #print("_value", _value, "/", len(_value))
-
- #print("_value", _value, "/")
+
if post_display:
- self.post_display_value(_value)
- self.qt_property_reconnect()
-
- else:
+ self.post_display_value(_value)
+ self.qt_property_reconnect()
+
+ else:
self.qt_property_disconnect()
-
-
+
+
if status == self.cyca.ICAFE_CS_CLOSED:
self.initialize_again = True
-
- elif self.initialize_again:
+
+ elif self.initialize_again:
#monitos_id informs whether or not widget has a monitor
- #CAQMessageButton for instance does not have a monitor
-
- if not self.pv_within_daq_group and self.monitor_id is not None:
+ #CAQMessageButton for instance does not have a monitor
+
+ if not self.pv_within_daq_group and self.monitor_id is not None:
self.monitor_start()
- #print("RESTART MONITOR FOR THIS WIDGET", flush=True)
-
self.initialize_again = False
-
- #print(" receive _connect_callback:: END ")
-
+
return
-
+
def receive_daq_update(self, daq_pvd, daq_mode, daq_state):
''' DAQ mode is widget specific.
@@ -663,319 +627,214 @@ class PVGateway(QWidget):
alarm_severity = daq_pvd.alarmSeverity
self.pvd = daq_pvd
- #print("BEFORE mode, object_name, daqState", daq_mode, self.qt_object_name, daq_state)
-
if daq_mode != self.qt_object_name:
self.qt_object_name = daq_mode
self.setObjectName(self.qt_object_name)
self.qt_style_polish()
- #print("AFTER mode, object_name, daqState", daq_mode, self.qt_object_name, daq_state)
-
if daq_state in (self.cyca.ICAFE_DAQ_STOPPED,):
- #if daq_state in (DAQState.CA_STOP, DAQState.BS_STOP, DAQState.CA_PAUSE,
- # DAQState.BS_PAUSE):
if _current_qt_dynamic_property != self.DAQ_STOPPED:
self.qt_property_daq_stopped()
- elif daq_state in (self.cyca.ICAFE_DAQ_PAUSED,):
+ elif daq_state in (self.cyca.ICAFE_DAQ_PAUSED,):
if _current_qt_dynamic_property != self.DAQ_PAUSED:
- self.qt_property_daq_paused()
+ self.qt_property_daq_paused()
elif daq_state in (self.cyca.ICAFE_DAQ_RUN,):
- #if _current_qt_dynamic_property != self.READBACK_ALARM:
- # self.qt_property_alarm_sev_no_alarm()
- #print ("before", daq_mode, _current_qt_dynamic_property)
-
if daq_mode == self.PV_DAQ_BS and \
_current_qt_dynamic_property != self.READBACK_STATIC:
- self.qt_property_static()
-
+ self.qt_property_static()
+
elif daq_mode == self.PV_DAQ_CA:
- #if _current_qt_dynamic_property not in (self.READBACK_STATIC,
- # self.READBACK_ALARM,):
if self.color_mode != self.color_mode_requested:
- self.color_mode == self.color_mode_requested
- #print("new colore mode")
-
+ self.color_mode = self.color_mode_requested
+
if self.cafe.isEnum(self.handle) and \
isinstance(daq_pvd.value[0], int):
- _value = self.cafe.getStringFromEnum(self.handle,
- daq_pvd.value[0])
+ _value = self.cafe.getStringFromEnum(self.handle,
+ daq_pvd.value[0])
else:
- _value = daq_pvd.value[0]
+ _value = daq_pvd.value[0]
if daq_pvd.status == self.cyca.ICAFE_NORMAL:
- if self.msg_label == "":
- self.post_display_value(_value)
-
+ if self.msg_label == "":
+ self.post_display_value(_value)
if daq_mode == self.PV_DAQ_BS:
- return
-
- #Fro DAQ when channel connects after application start-up
- #if _current_qt_dynamic_property == self.DISCONNECTED:
- # self.qt_property_initial_values(qt_object_name = self.PV_READBACK)
+ return
#Check if color settings are correct
- ##if _current_qt_dynamic_property == self.READBACK_STATIC and \
if alarm_severity > self.cyca.SEV_NO_ALARM:
self.color_mode = self.READBACK_ALARM
self.color_mode_requested = self.READBACK_ALARM
-
- if self.color_mode == self.READBACK_ALARM:
+
+ if self.color_mode == self.READBACK_ALARM:
if alarm_severity == self.cyca.SEV_MINOR:
if _current_qt_dynamic_property != self.ALARM_SEV_MINOR:
self.qt_property_alarm_sev_minor()
-
+
elif alarm_severity == self.cyca.SEV_MAJOR:
if _current_qt_dynamic_property != self.ALARM_SEV_MAJOR:
self.qt_property_alarm_sev_major()
-
+
elif alarm_severity == self.cyca.SEV_INVALID:
- if _current_qt_dynamic_property != self.ALARM_SEV_INVALID:
+ if _current_qt_dynamic_property != \
+ self.ALARM_SEV_INVALID:
self.qt_property_alarm_sev_invalid()
-
+
elif alarm_severity == self.cyca.SEV_NO_ALARM:
- if _current_qt_dynamic_property != self.ALARM_SEV_NO_ALARM:
- self.qt_property_alarm_sev_no_alarm()
-
+ if _current_qt_dynamic_property != \
+ self.ALARM_SEV_NO_ALARM:
+ self.qt_property_alarm_sev_no_alarm()
+
elif _current_qt_dynamic_property != self.READBACK_STATIC:
self.qt_property_static()
- #print ("after", daq_mode, self.qt_dynamic_property_get() )
else:
- if _current_qt_dynamic_property != self.DISCONNECTED:
- self.qt_property_disconnect()
-
+ if _current_qt_dynamic_property != self.DISCONNECTED:
+ self.qt_property_disconnect()
+
def receive_monitor_dbr_time(self, pvdata, alarm_severity):
- print("in gateway", self.pv_name)
- #pvdata.show()
+ print("called from gateway", self.pv_name, alarm_severity)
+ pvdata.show()
def receive_monitor_update(self, value, status, alarm_severity):
'''Triggered by monitor signal. For Widget to overload.'''
-
- self.mutex_post_display.lock()
- _current_qt_dynamic_property = self.qt_dynamic_property_get()
- #print(self.pv_name, value, status, alarm_severity)
- #if isinstance(value, (int, float)):
- # if value < 100:
- # print("CURRENT PROPERY VALUE", self.pv_name, _current_qt_dynamic_property, value, status, alarm_severity )
- #print ("sender //2//", self.sender(), value)
- #print("receive monitor update/1", self.pv_name, self.qt_object_name, self._qt_property_selected)
+ self.mutex_post_display.lock()
+ _current_qt_dynamic_property = self.qt_dynamic_property_get()
if status == self.cyca.ICAFE_NORMAL:
- '''
- if isinstance(value, (int, float)):
-
- if value < -20:
- alarm_severity = self.cyca.SEV_INVALID
- elif value < -1:
- alarm_severity = self.cyca.SEV_MAJOR
- elif value < 5:
- alarm_severity = self.cyca.SEV_MINOR
- else:
- alarm_severity = self.cyca.SEV_NO_ALARM
- '''
-
- if self.msg_label == "":
- self.post_display_value(value)
+
+ if self.msg_label == "":
+ self.post_display_value(value)
#For DAQ when channel connects after application start-up
if _current_qt_dynamic_property == self.DISCONNECTED:
- self.qt_property_initial_values(qt_object_name = self.PV_READBACK)
+ self.qt_property_initial_values(qt_object_name=self.PV_READBACK)
#Check if color settings are correct
elif _current_qt_dynamic_property == self.READBACK_STATIC:
- if alarm_severity > self.cyca.SEV_NO_ALARM and \
- alarm_severity < self.cyca.SEV_INVALID:
- self.color_mode = self.READBACK_ALARM
- self.status_tip = "Widget color mode is dynamic, pv with alarm limits"
+ if alarm_severity > self.cyca.SEV_NO_ALARM:
+ if alarm_severity < self.cyca.SEV_INVALID:
+ self.color_mode = self.READBACK_ALARM
+ self.status_tip = ("Widget color mode is dynamic, " +
+ "pv with alarm limits")
elif alarm_severity == self.cyca.SEV_INVALID:
if _current_qt_dynamic_property != self.ALARM_SEV_INVALID:
self.qt_property_alarm_sev_invalid()
if self.color_mode == self.READBACK_ALARM:
-
if alarm_severity == self.cyca.SEV_MINOR:
if _current_qt_dynamic_property != self.ALARM_SEV_MINOR:
self.qt_property_alarm_sev_minor()
-
+
elif alarm_severity == self.cyca.SEV_MAJOR:
if _current_qt_dynamic_property != self.ALARM_SEV_MAJOR:
self.qt_property_alarm_sev_major()
-
+
elif alarm_severity == self.cyca.SEV_INVALID:
if _current_qt_dynamic_property != self.ALARM_SEV_INVALID:
self.qt_property_alarm_sev_invalid()
-
+
elif alarm_severity == self.cyca.SEV_NO_ALARM:
if _current_qt_dynamic_property != self.ALARM_SEV_NO_ALARM:
- self.qt_property_alarm_sev_no_alarm()
-
- else:
- if _current_qt_dynamic_property != self.DISCONNECTED:
- self.qt_property_disconnect()
+ self.qt_property_alarm_sev_no_alarm()
+
+ else:
+ if _current_qt_dynamic_property != self.DISCONNECTED:
+ self.qt_property_disconnect()
+
+ self.mutex_post_display.unlock()
- self.mutex_post_display.unlock()
-
- #print("receive monitor update/2", self.pv_name, self.qt_object_name, self._qt_property_selected)
-
def py_monitor_callback(self, handle, pvname, pvdata):
-
-
+
'''Callback function to be invoked on change of pv value.
cafe.getCache and cafe.set operations permitted within callback.
'''
- '''
- if "PULSEID" in pvname:
- pass
- else:
- print ("py_monitor_callback: name/handle ",pvname, handle )
- '''
+
pv_name = pvname
- pvd = pvdata
- #print("===================================")
- #print("pvname/handle in mon callback ", pv_name, handle)
-
+ pvd = pvdata
if not hasattr(self, 'cafe'):
- print ("py_monitor_callback: name/handle self cafe is NONE =>>>>>>>>>>>> ",
- pv_name, handle)
+ print("py_monitor_callback: name/handle self cafe is NONE",
+ pv_name, handle)
return
- #pv_name = self.cafe.getPVNameFromHandle(self.handle)
- #pvd = self.cafe.getPVCache(self.handle)
-
+
self.pvd = pvd
- '''
- if pvname != pv_name:
- print ("py_monitor_callback: name/handle/monid =>>>>>>>>>>>> ",
- pv_name, handle, self.cafe.getMonitorIDInCallback(handle))
- print ("PV NAME NOT THE SAME **** WIDGET in monitor callback ", self)
- '''
- #_pvc = self.cafe.getCtrlCache(handle)
- #print("_pvc.nelem",_pvc.nelem)
- #_pvc.show()
- #print(_pvc.nelem)
-
- #_pvd = self.cafe.getPVCache(handle)
- #pvd.showMax(4000) #set no of elemets to 1 in pvctrlCache!
- #print(pvd.nelem)
- '''
- _info = self.cafe.getChannelInfo(handle)
- _info.show()
- '''
-
-
-
- #_widgetList =self.cafe.getWidgets(handle)
-
- _widget = None
+ if pvd.status == self.cyca.ICAFE_CS_NEVER_CONN:
+ print("initialize again")
+ self.initialize()
- #print ("END monid =>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ", self.cafe.getMonitorIDInCallback(handle))
- #print(self, self.pv_name)
- #print(_widgetList)
- '''
- self.mutex.lock()
-
- for _widget, _handle in self.widget_handle_dict.items():
- if _handle == handle:
- '''
- for i in range(0, 1): #len(_widgetList)):
- #_widget = _widgetList[i]
- if pvd.status == self.cyca.ICAFE_CS_NEVER_CONN:
- print("initialize again")
- self.initialize()
-
- elif pvd.status == self.cyca.ICAFE_CA_OP_CONN_DOWN:
- _alarm_severity = self.cyca.ICAFE_CA_OP_CONN_DOWN
- #print("COMPARE ALARM SEVERITIES ", _alarm_severity, pvd.alarmSeverity)
- else:
- _alarm_severity = pvd.alarmSeverity
-
-
-
- if self.monitor_dbr_time:
- self.trigger_monitor.emit(pvd, _alarm_severity)
-
- elif isinstance(pvd.value[0], str):
- self.trigger_monitor_str.emit((pvd.value[0]), pvd.status, _alarm_severity) #, _widget)
- #print("emitted str value", pvd.value[0])
- elif isinstance(pvd.value[0], int):
- self.trigger_monitor_int.emit((pvd.value[0]), pvd.status, _alarm_severity)
-
- else:
- #print(dir(self.receivers(self, self.trigger_monitor_float)))
- self.trigger_monitor_float.emit(float(pvd.value[0]), pvd.status, _alarm_severity)
- #print("emitted float value", pvd.value[0])
- pass
-
-
-
-
- #if _widget is None:
- # print("NO WIDGET FOR THIS PV!!!! pv = ", pv_name)
- #self.mutex.unlock()
+ elif pvd.status == self.cyca.ICAFE_CA_OP_CONN_DOWN:
+ _alarm_severity = self.cyca.ICAFE_CA_OP_CONN_DOWN
+ else:
+ _alarm_severity = pvd.alarmSeverity
+ if self.monitor_dbr_time:
+ self.trigger_monitor.emit(pvd, _alarm_severity)
+ elif isinstance(pvd.value[0], str):
+ self.trigger_monitor_str.emit((pvd.value[0]), pvd.status,
+ _alarm_severity)
+ elif isinstance(pvd.value[0], int):
+ self.trigger_monitor_int.emit((pvd.value[0]), pvd.status,
+ _alarm_severity)
+ else:
+ self.trigger_monitor_float.emit(float(pvd.value[0]), pvd.status,
+ _alarm_severity)
def monitor_start(self):
'''Initiate monitor on pv.'''
- #print(self, self.pv_name, "Initiate monitor on pv:", self.monitor_callback, self.py_monitor_callback)
if self.handle > 0:
#Is monitor in waiting - now deleted with monitor_stop
if self.notify_unison:
self.monitor_id = self.cafe.monitorStart(
- self.handle, dbr=self.cyca.CY_DBR_TIME)
+ self.handle, dbr=self.cyca.CY_DBR_TIME)
#start with gateway supplied monitor callback handler
- elif self.monitor_callback is None:
+ elif self.monitor_callback is None:
self.monitor_id = self.cafe.monitorStart(
- self.handle, cb=self.py_monitor_callback,
- dbr=self.cyca.CY_DBR_TIME,
+ self.handle, cb=self.py_monitor_callback,
+ dbr=self.cyca.CY_DBR_TIME,
notify_milliseconds=self.notify_milliseconds)
- else:
+ else:
self.monitor_id = self.cafe.monitorStart(
- self.handle, cb=self.monitor_callback,
+ self.handle, cb=self.monitor_callback,
dbr=self.cyca.CY_DBR_TIME,
notify_milliseconds=self.notify_milliseconds)
def monitor_stop(self):
- #print("monitor_stopped")
if self.handle > 0:
- _monID_list = self.cafe.getMonitorIDs(self.handle)
- _monID_inwaiting_list = self.cafe.getMonitorIDsInWaiting(self.handle)
+ _monID_list = self.cafe.getMonitorIDs(self.handle)
+ _monID_inwaiting_list = self.cafe.getMonitorIDsInWaiting(
+ self.handle)
_monID_all = _monID_list + _monID_inwaiting_list
-
- if self.monitor_id in _monID_all:
- #print("stopping in monitor_stop for handle", self.monitor_id)
+
+ if self.monitor_id in _monID_all:
self.cafe.monitorStop(self.handle, self.monitor_id)
- #print("stopped in monitor_stop for handle", self.monitor_id)
- #Is monitor in waiting?
- #remove monitors in waiting
-
-
+ #Is monitor in waiting?
+ #remove monitors in waiting - to do
def reconnect_channel(self):
self.cafe.reconnect([self.handle]) #list
def set_desc(self):
'''Set description of pv from pv.DESC'''
-
+
if self.cafe.hasDescription(self.handle):
- self.desc = self.cafe.getDescription(self.handle)
+ self.desc = self.cafe.getDescription(self.handle)
return
- elif self.desc is not None:
+ elif self.desc is not None:
return
- else:
- self.cafe.supplementHandle(self.handle)
+ else:
+ self.cafe.supplementHandle(self.handle)
if self.cafe.hasDescription(self.handle):
self.desc = self.cafe.getDescription(self.handle)
-
- if self.desc is not None:
+
+ if self.desc is not None:
return
-
+
###Back-up solution
_found = str(self.pv_name).find(".")
if _found != -1:
@@ -1007,14 +866,12 @@ class PVGateway(QWidget):
def set_precision_and_units(self, reconnectFlag: bool = False):
'''Set the pv precision and units.'''
if self.pv_ctrl is None or reconnectFlag is True:
- self.pv_ctrl = self.cafe.getCtrlCache(self.handle)
+ self.pv_ctrl = self.cafe.getCtrlCache(self.handle)
if self.pv_ctrl is not None:
if not self.has_precision_user:
self.precision = self.pv_ctrl.precision
if self.pv_ctrl.units is not None:
- #print(self.pv_ctrl.units)
- #print(type(self.pv_ctrl.units))
self.units = str(self.pv_ctrl.units)
else:
self.units = ""
@@ -1023,7 +880,7 @@ class PVGateway(QWidget):
#verify user input
if self.show_units is True and self.suffix is not None:
if self.suffix == self.units:
- self.show_units = False
+ self.show_units = False
def _qt_readback_color_mode(self):
@@ -1031,281 +888,232 @@ class PVGateway(QWidget):
has alarm limits (self.color_mode = 'readbackAlarm')
or is without alarm limits (self.color_mode = 'readbackStatic')
'''
-
-
+
#Already set by user
if self.color_mode is self.READBACK_ALARM:
return
-
if self.cafe.isConnected(self.handle):
pvd = self.cafe.getPVCache(self.handle)
- if pvd.alarmSeverity in (self.cyca.SEV_MINOR, self.cyca.SEV_MAJOR) or \
- self.cafe.hasAlarmStatusSeverity(self.handle):
+ if pvd.alarmSeverity in (self.cyca.SEV_MINOR, self.cyca.SEV_MAJOR) \
+ or self.cafe.hasAlarmStatusSeverity(self.handle):
self.color_mode = self.READBACK_ALARM
- self.status_tip = "Widget color mode is dynamic, pv with alarm limits"
- #print(self.pv_name, "has alarm svev", self.cafe.hasAlarmStatusSeverity(self.handle))
-
-
+ self.status_tip = ("Widget color mode is dynamic, " +
+ "pv with alarm limits")
else:
self.color_mode = self.READBACK_STATIC
- self.status_tip = "Widget color mode is static, pv without alarm limits"
-
-
- def qt_property_initial_values(self, qt_object_name: str = None, tool_tip: bool = True):
-
+ self.status_tip = ("Widget color mode is static, " +
+ "pv without alarm limits")
+
+
+ def qt_property_initial_values(self, qt_object_name: str = None,
+ tool_tip: bool = True):
+
'''Set Qt property values.'''
self.qt_object_name = qt_object_name
if tool_tip:
self.setToolTip(self.pv_name)
self.setObjectName(self.qt_object_name)
if self.qt_object_name in self.qt_object_to_property.keys():
- self._qt_property_selected = copy.deepcopy(self.qt_object_to_property[self.qt_object_name])
+ self._qt_property_selected = copy.deepcopy(
+ self.qt_object_to_property[self.qt_object_name])
else:
- print ("qt_property_initial_values: Object not found in dictionary")
+ print("qt_property_initial_values: Object not found in dictionary")
- #print("qt_property_initial_values", self.qt_object_name, self._qt_property_selected)
-
-
if self.cafe.isConnected(self.handle):
-
+
if self.qt_object_name == self.PV_READBACK:
self._qt_readback_color_mode()
#self.setStatusTip(self.status_tip)
-
+
elif self.qt_object_name == self.PV_CONTROLLER:
if self.color_mode == self.ACT_ON_BEAM:
#self.setStatusTip("PV setting acts directly on beam")
pass
- else:
+ else:
self.color_mode = self.NOT_ACT_ON_BEAM
#self.setStatusTip("PV setting does not influence beam")
-
+
elif self.qt_object_name == self.PV_DAQ_CA:
self._qt_readback_color_mode()
-
+
elif self.qt_object_name == self.PV_DAQ_BS:
self.color_mode = self.READBACK_STATIC
-
- #print("qt_property_initial_values//", self.pv_name, self.qt_object_name, self._qt_property_selected)
+
self._qt_dynamic_property_set(self.color_mode)
- #print("qt_property_initial_values///", self.pv_name, self.qt_object_name, self._qt_property_selected)
-
- else:
+
+ else:
self.qt_property_disconnect()
- #print("qt_property_initial_values", self.pv_name, self.qt_object_name, self.color_mode)
- '''
- meta_obj = self.metaObject()
- count = meta_obj.propertyCount()
- for i in range(0, count):
- meta_prop = meta_obj.property(i)
- name = meta_prop.name()
- print(i, name, self.property(name))
- '''
-
- def qt_dynamic_property_get(self, property_state : str = None):
- '''Retrieves the requested property value'''
- '''else that which is currently true'''
-
- for _property, _value in self._qt_property_selected.items(): #states.items():
+ def qt_dynamic_property_get(self, property_state: str = None):
+ '''Retrieves the requested property value
+ else that which is currently true'''
+
+ for _property, _value in self._qt_property_selected.items():
if property_state is not None:
if _property == property_state:
return _value
elif _value:
- #print(self, _property, "SELECTED")
return _property
- def _qt_dynamic_property_set(self, property_state : str = None):
- '''Set the Input property to true, and the remainder to False'''
- '''If None is given then all dynamic properties are set to False'''
+ def _qt_dynamic_property_set(self, property_state: str = None):
+ '''
+ Set the Input property to true, and the remainder to False
+ If None is given then all dynamic properties are set to False
+ '''
- #print("qt_property_set/", property_state, self.pv_name, self.qt_object_name, self.color_mode)
- #if property_state in self.qt_property_states.keys():
- for _property, _value in self._qt_property_selected.items(): #states.items():
+ for _property in self._qt_property_selected.keys():
if _property == property_state:
self.setProperty(_property, True)
- self._qt_property_selected[_property] = True
+ self._qt_property_selected[_property] = True
else:
self.setProperty(_property, False)
self._qt_property_selected[_property] = False
-
- #print("qt_property_set//", self.pv_name, self.qt_object_name, self.color_mode)
-
- #l = self.dynamicPropertyNames()
- #for i in range (0, len(l)):
- # print(i, l[i])
- #return self._qt_property_selected
- def qt_property_disconnect(self, redraw=False):
+ def qt_property_disconnect(self):
'''Set Qt disconnect property value.'''
-
- #self._qt_property_selected =
self._qt_dynamic_property_set(self.DISCONNECTED)
-
- '''
- if not self.initialize_complete:
- self.setStatusTip("PV={0} was never connected".format(self.pv_name))
- else:
- self.setStatusTip("PV={0} is presently disconnected".format(self.pv_name))
- '''
- #print("qt_property_disconnect", self.pv_name, self.qt_object_name, self.color_mode)
self.qt_style_polish()
-
- return #self._qt_property_selected
-
- def qt_property_reconnect(self, redraw=False):
+ def qt_property_reconnect(self):
'''Set Qt connected property value.'''
- #self.setObjectName("PyCafe")
- #self.setToolTip(self.pv_name)
- #l = self.dynamicPropertyNames()
- #for i in range (0, len(l)-1):
- # print(i, l[i])
- #self.setProperty(str(l[i],'utf-8'), False)
if self.qt_object_name == self.PV_READBACK:
self._qt_readback_color_mode()
#self.setStatusTip(self.status_tip)
-
+
elif self.qt_object_name == self.PV_CONTROLLER:
if self.color_mode == self.ACT_ON_BEAM:
#self.setStatusTip("PV setting acts directly on beam")
pass
- else:
+ else:
self.color_mode = self.NOT_ACT_ON_BEAM
#self.setStatusTip("PV setting does not influence beam")
- #self._qt_property_selected =
+ #self._qt_property_selected =
self._qt_dynamic_property_set(self.color_mode)
- #print("qt_property_reconnect", self.pv_name, self.qt_object_name, self.color_mode)
-
- #l = self.dynamicPropertyNames()
- #for i in range (0, len(l)):
- # print(i, l[i])
- self.qt_style_polish()
-
- def qt_property_alarm_sev_major(self, redraw=False):
- '''Set Qt MAJOR property value.'''
- #self._qt_property_selected =
- self._qt_dynamic_property_set(self.ALARM_SEV_MAJOR)
- self.setStatusTip("{0} reports value in MAJOR alarm state!".format(self.pv_name))
self.qt_style_polish()
- def qt_property_alarm_sev_minor(self, redraw=False):
- '''Set Qt MINOR property value.'''
- #self._qt_property_selected =
- self._qt_dynamic_property_set(self.ALARM_SEV_MINOR)
- self.setStatusTip("{0} reports value in MINOR alarm state!".format(self.pv_name))
- self.qt_style_polish()
-
- def qt_property_alarm_sev_no_alarm(self, redraw=False):
- '''Set Qt READBACK_ALARM property value.'''
- #self._qt_property_selected =
- self._qt_dynamic_property_set(self.READBACK_ALARM)
- self.setStatusTip("{0} reports value in normal state".format(self.pv_name))
- self.qt_style_polish()
-
- def qt_property_alarm_sev_invalid(self, redraw=False):
- '''Set Qt INVALID property value.'''
- #self._qt_property_selected =
- self._qt_dynamic_property_set(self.ALARM_SEV_INVALID)
- self.setStatusTip("PV={0} reports an INVALID value!".format(self.pv_name))
+ def qt_property_alarm_sev_major(self):
+ '''Set Qt MAJOR property value.'''
+
+ self._qt_dynamic_property_set(self.ALARM_SEV_MAJOR)
+ self.setStatusTip("{0} reports value in MAJOR alarm state!".format(
+ self.pv_name))
self.qt_style_polish()
- def qt_property_static(self, redraw=False):
- '''Set Qt STATIC property value.'''
- self._qt_dynamic_property_set(self.READBACK_STATIC)
- self.setStatusTip("PV={0} does not have an alarm state".format(self.pv_name))
- self.qt_style_polish()
-
- def qt_property_daq_stopped(self, redraw=False):
- '''Set Qt STOPPED property value.'''
- #self._qt_property_selected =
- self._qt_dynamic_property_set(self.DAQ_STOPPED)
- self.setStatusTip("PV={0} reports DAQ has stopped".format(self.pv_name))
+ def qt_property_alarm_sev_minor(self):
+ '''Set Qt MINOR property value.'''
+ self._qt_dynamic_property_set(self.ALARM_SEV_MINOR)
+ self.setStatusTip("{0} reports value in MINOR alarm state!".format(
+ self.pv_name))
self.qt_style_polish()
-
- def qt_property_daq_paused(self, redraw=False):
- '''Set Qt STOPPED property value.'''
- #self._qt_property_selected =
- self._qt_dynamic_property_set(self.DAQ_PAUSED)
- self.setStatusTip("PV={0} reports DAQ has paused".format(self.pv_name))
+ def qt_property_alarm_sev_no_alarm(self):
+ '''Set Qt READBACK_ALARM property value.'''
+ #self._qt_property_selected =
+ self._qt_dynamic_property_set(self.READBACK_ALARM)
+ self.setStatusTip("{0} reports value in normal state".format(
+ self.pv_name))
self.qt_style_polish()
-
+
+ def qt_property_alarm_sev_invalid(self):
+ '''Set Qt INVALID property value.'''
+ self._qt_dynamic_property_set(self.ALARM_SEV_INVALID)
+ self.setStatusTip("PV={0} reports an INVALID value!".format(
+ self.pv_name))
+ self.qt_style_polish()
+
+ def qt_property_static(self):
+ '''Set Qt STATIC property value.'''
+ self._qt_dynamic_property_set(self.READBACK_STATIC)
+ self.setStatusTip("PV={0} does not have an alarm state".format(
+ self.pv_name))
+ self.qt_style_polish()
+
+ def qt_property_daq_stopped(self):
+ '''Set Qt STOPPED property value.'''
+ self._qt_dynamic_property_set(self.DAQ_STOPPED)
+ self.setStatusTip("PV={0} reports DAQ has stopped".format(
+ self.pv_name))
+ self.qt_style_polish()
+
+ def qt_property_daq_paused(self):
+ '''Set Qt STOPPED property value.'''
+ self._qt_dynamic_property_set(self.DAQ_PAUSED)
+ self.setStatusTip("PV={0} reports DAQ has paused".format(
+ self.pv_name))
+ self.qt_style_polish()
+
def qt_style_polish(self, redraw=False):
if redraw:
self.style().unpolish(self)
self.style().polish(self)
- event=QEvent(QEvent.StyleChange)
- QApplication.sendEvent(self, event);
+ event = QEvent(QEvent.StyleChange)
+ QApplication.sendEvent(self, event)
self.update()
self.updateGeometry()
else:
- #self.style().unpolish(self)
self.style().polish(self)
- QApplication.processEvents()
+ QApplication.processEvents()
def pv_status_text_header(self, source="Channel Access"):
_source = source
_source_separator = "----------------------------------------"
- _text = """
+ _text = """
Widget: {0} ({1}, {2})
-
""".format(self.widget_class, self.qt_object_name, self.color_mode)
if self.msg_press_value is not None:
- _text += """
+ _text += """
On press, sends value: {0}
- """.format(self.msg_press_value, "DarkOrchid")
+ """.format(self.msg_press_value, "DarkOrchid")
if self.msg_release_value is not None:
- _text += """
+ _text += """
On release, sends value: {0}
- """.format(self.msg_release_value, "DarkOrchid")
-
+ """.format(self.msg_release_value, "DarkOrchid")
+
if self.pv_within_daq_group:
- if self.qt_object_name in (self.PV_DAQ_BS,):
+ if self.qt_object_name in self.PV_DAQ_BS:
_ds_color = "Navy Blue"
else:
_ds_color = "Black"
else:
- _ds_color = "Black"
+ _ds_color = "Black"
- _text += """
+ _text += """
{0}
Data source: {1}
{0}
- PV: {2}
+ PV: {2}
""".format(_source_separator, _source, self.pv_name, "DarkOrchid",
- _ds_color)
+ _ds_color)
if self.desc is None:
self.set_desc()
if self.desc == "":
- _text += """
- """
+ _text += """
+ """
return _text
_text += """
-
- Description: {6}
+
+ Description: {0}
- """.format(self.widget_class, self.qt_object_name, \
- self.color_mode, _source_separator, _source, \
- self.pv_name, self.desc, "DarkOrchid"
- )
+ """.format(self.desc, "DarkOrchid")
+
return _text
+
def pv_status_text_enum(self):
-
+
_val_enum = None
_value = self.pvd.value[0]
if isinstance(_value, str):
@@ -1313,45 +1121,43 @@ class PVGateway(QWidget):
elif _value is not None:
_val_enum = self.cafe.getStringFromEnum(self.handle, _value)
- _color = "Blue"
+ _color = "Blue"
#To catch case where channel is called by user
-
-
+
+
#To catch DAQ case
if self.pv_within_daq_group:
- if self.qt_object_name in (self.PV_DAQ_BS):
- if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
- self.DAQ_PAUSED,
+ if self.qt_object_name in self.PV_DAQ_BS:
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ self.DAQ_PAUSED,
self.DISCONNECTED):
- _color = "White"
- elif self.qt_object_name in (self.PV_DAQ_CA):
+ _color = "White"
+ elif self.qt_object_name in self.PV_DAQ_CA:
if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
self.DISCONNECTED):
_color = "White"
-
elif not self.cafe.isConnected(self.handle):
_color = "White"
elif self.pvd.status == self.cyca.ICAFE_CA_OP_CONN_DOWN:
- _color = "White"
+ _color = "White"
- _text = """
+ _text = """
Value: {1} [{2}]
- """.format(_color, _value , _val_enum
- )
+ """.format(_color, _value, _val_enum)
- return _text
+ return _text
def pv_status_text_data(self):
-
+
_value_str = ""
_first_end = 9
_end_range = min(self.pvd.nelem, _first_end)
if _end_range > 1:
- _value_str = "[ "
- for i in range (0, _end_range):
+ _value_str = "[ "
+ for i in range(0, _end_range):
_value = self.pvd.value[i]
if _value is None:
_value = '0'
@@ -1360,58 +1166,57 @@ class PVGateway(QWidget):
elif isinstance(_value, int):
_value_str += str(_value)
else:
- if self.pv_ctrl is not None:
- _value_form = ("{:<.%sf}" % self.pv_ctrl.precision)
+ if self.pv_ctrl is not None:
+ _value_form = ("{:<.%sf}" % self.pv_ctrl.precision)
_value_str += _value_form.format(
round(_value, self.pv_ctrl.precision))
if i < (_end_range-1):
_value_str += " "
- if self.pvd.nelem > _first_end:
+ if self.pvd.nelem > _first_end:
_value_str += " ... "
_value = self.pvd.value[self.pvd.nelem-1]
if isinstance(_value, str):
_value_str += _value
elif isinstance(_value, int):
- _value_str += str(value)
+ _value_str += str(_value)
else:
if self.pv_ctrl is not None:
- _value_form = ("{:<.%sf}" % self.pv_ctrl.precision)
+ _value_form = ("{:<.%sf}" % self.pv_ctrl.precision)
_value_str += _value_form.format(
round(_value, self.pv_ctrl.precision))
_value_str += " "
if _end_range > 1:
_value_str += "]"
- _color = "Blue"
+ _color = "Blue"
- #To catch DAQ case
+ #To catch DAQ case
if self.pv_within_daq_group:
-
- if self.qt_object_name in (self.PV_DAQ_BS):
- if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ if self.qt_object_name in self.PV_DAQ_BS:
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
self.DAQ_PAUSED,
self.DISCONNECTED):
- _color = "White"
- elif self.qt_object_name in (self.PV_DAQ_CA):
+ _color = "White"
+ elif self.qt_object_name in self.PV_DAQ_CA:
if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
self.DISCONNECTED):
_color = "White"
-
+
elif not self.cafe.isConnected(self.handle):
_color = "White"
elif self.pvd.status == self.cyca.ICAFE_CA_OP_CONN_DOWN:
_color = "White"
- _text = """
+ _text = """
- Value: {1} {2}
+ Value: {1} {2}
""".format(_color, _value_str, self.units)
return _text
-
+
def pv_status_text_timestamp(self):
_status_not_ok_color = "IndianRed"
@@ -1419,66 +1224,62 @@ class PVGateway(QWidget):
_ts_color = "Blue"
_color = _status_ok_color
-
#To catch DAQ case
if self.pv_within_daq_group:
- if self.qt_object_name in (self.PV_DAQ_BS):
- if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
- self.DAQ_PAUSED,
+ if self.qt_object_name in self.PV_DAQ_BS:
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ self.DAQ_PAUSED,
self.DISCONNECTED):
- _ts_color = "White"
+ _ts_color = "White"
_color = "White"
- elif self.qt_object_name in (self.PV_DAQ_CA):
+ elif self.qt_object_name in self.PV_DAQ_CA:
if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
self.DISCONNECTED):
_ts_color = "White"
_color = "White"
-
+
elif not self.cafe.isConnected(self.handle):
_ts_color = "White"
elif self.pvd.status == self.cyca.ICAFE_CA_OP_CONN_DOWN:
_ts_color = "White"
-
-
+
if self.pvd.status != self.cyca.ICAFE_NORMAL:
_color = _status_not_ok_color
_text = """
Timestamp: {2}
- Status: {3}
{4}
- """.format( _ts_color, _color, self.pvd.tsDateAsString, \
- self.pvd.statusAsString, \
- self.cafe.getStatusInfo(self.pvd.status))
-
- return _text
+ Status: {3}
{4}
+ """.format(_ts_color, _color, self.pvd.tsDateAsString,
+ self.pvd.statusAsString,
+ self.cafe.getStatusInfo(self.pvd.status))
+
+ return _text
+
-
def pv_status_text_alarm(self):
- _text ="""
- """
+ _text = """
+ """
_color = "DimGray"
-
-
+
#To catch DAQ case
if self.pv_within_daq_group:
-
if self.pvd.alarmSeverity == self.cyca.SEV_MINOR:
_color = "Yellow"
elif self.pvd.alarmSeverity == self.cyca.SEV_MAJOR:
_color = "Red"
elif self.pvd.alarmSeverity == self.cyca.SEV_INVALID:
- _color = "White"
+ _color = "White"
- if self.qt_object_name in (self.PV_DAQ_BS):
- if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ if self.qt_object_name in self.PV_DAQ_BS:
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
self.DAQ_PAUSED,
self.DISCONNECTED):
- _color = "White"
- elif self.qt_object_name in (self.PV_DAQ_CA):
- if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ _color = "White"
+ elif self.qt_object_name in self.PV_DAQ_CA:
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
self.DISCONNECTED):
_color = "White"
-
+
elif not self.cafe.isConnected(self.handle):
_color = "White"
@@ -1491,17 +1292,16 @@ class PVGateway(QWidget):
elif self.pvd.alarmSeverity == self.cyca.SEV_MAJOR:
_color = "Red"
elif self.pvd.alarmSeverity == self.cyca.SEV_INVALID:
- _color = "White"
-
-
+ _color = "White"
+
_text += """
- Alarm status: {1}
+ Alarm status: {1}
Alarm severity: {2}
- """.format(_color, self.pvd.alarmStatusAsString,
+ """.format(_color, self.pvd.alarmStatusAsString,
self.pvd.alarmSeverityAsString)
-
- return _text
-
+
+ return _text
+
def pv_access(self):
_accessIs = ""
if self.pv_info is None:
@@ -1510,88 +1310,85 @@ class PVGateway(QWidget):
_accessIs += "Read"
if self.pv_info.accessWrite:
_accessIs += "Write"
- return _accessIs
+ return _accessIs
def pv_status_text_enum_metadata(self):
- _text = """
- ENUM strings: {2}
+ _text = """
+ ENUM strings: {2}
Data type (native): {3}
Record type: {4}
RW Access: {5}
IOC: {6}
- """.format( "MediumBlue", "DarkOrchid", self.pvc.enumStrings,
- self.pv_info.dataTypeAsString,
- self.record_type, self.pv_access(),
- self.pv_info.hostName)
+ """.format("MediumBlue", "DarkOrchid", self.pvc.enumStrings,
+ self.pv_info.dataTypeAsString,
+ self.record_type, self.pv_access(),
+ self.pv_info.hostName)
return _text
def pv_status_text_metadata(self):
-
+
if self.pv_info is None:
self.pv_info = self.cafe.getChannelInfo(self.handle)
if self.pv_info is not None and self.record_type is None:
if "Not Supported" in self.pv_info.className:
_rtype = self.cafe.get(self.pv_name.split(".")[0] + ".RTYP")
- self.record_type = _rtype if _rtype is not None else self.pv_info.className
+ self.record_type = _rtype if _rtype is not None else \
+ self.pv_info.className
self.cafe.close(self.pv_name.split(".")[0] + ".RTYP")
else:
self.record_type = self.pv_info.className
if self.record_type in ["stringin", "stringout"]:
- _text = """
- Data type (native): {3}
- Record type: {4}
- RW Access: {5}
- IOC: {6}
- """.format("MediumBlue", self.pvd.nelem, self.pvc.precision,
- self.pv_info.dataTypeAsString,
- self.record_type, self.pv_access(),
- self.pv_info.hostName)
+ _text = """
+ Data type (native): {1}
+ Record type: {2}
+ RW Access: {3}
+ IOC: {4}
+ """.format("MediumBlue", self.pv_info.dataTypeAsString,
+ self.record_type, self.pv_access(),
+ self.pv_info.hostName)
return _text
_text = """
"""
if self.pvd.nelem > 1:
- _text += """
- Nelem: {1}
+ _text += """
+ Nelem: {1}
""".format("MediumBlue", self.pvd.nelem)
- _text += """
- Precision (PV): {1}
+ _text += """
+ Precision (PV): {1}
Data type (native): {2}
Record type: {3}
RW Access: {4}
IOC: {5}
- """.format("MediumBlue", self.pvc.precision,
- self.pv_info.dataTypeAsString,
- self.record_type, self.pv_access(),
+ """.format("MediumBlue", self.pvc.precision,
+ self.pv_info.dataTypeAsString,
+ self.record_type, self.pv_access(),
self.pv_info.hostName)
return _text
-
- def pv_status_text_alarm_limits(self, ):
+
+ def pv_status_text_alarm_limits(self):
if self.pv_info is None:
self.pv_info = self.cafe.getChannelInfo(self.handle)
if self.pv_info is not None and self.record_type is None:
if "Not Supported" in self.pv_info.className:
_rtype = self.cafe.get(self.pv_name.split(".")[0] + ".RTYP")
- self.record_type = _rtype if _rtype is not None else self.pv_info.className
+ self.record_type = _rtype if _rtype is not None else \
+ self.pv_info.className
self.cafe.close(self.pv_name.split(".")[0] + ".RTYP")
else:
self.record_type = self.pv_info.className
- _text ="""
- """
-
+ _text = """
+ """
+
#No all record types have alarms
- #className is not supported at psi since introduction of the linux ca gateway
+ #className is not supported at psi since introduction of the
+ #linux ca gateway
#Not Supported by Gateway
-
- #self.pv_info.show()
- #self.pv_ctrl.show()
- #print(self.record_type)
- #print(self._alarm_severity_record_types)
if "Not Supported" in str(self.record_type):
pass
@@ -1600,42 +1397,44 @@ class PVGateway(QWidget):
if self.pvc.lowerAlarmLimit == 0 and self.pvc.upperAlarmLimit == 0 and \
self.pvc.lowerWarningLimit == 0 and self.pvc.upperWarningLimit == 0:
-
return _text
-
- if self.cafe.hasAlarmStatusSeverity(self.handle): # or "Not Supported" in self.pv_info.className:
+
+ if self.cafe.hasAlarmStatusSeverity(self.handle):
_text = """
- Lower/Upper alarm limit: {1} / {4}
- Lower/Upper warning limit: {2} / {3}
+ Lower/Upper alarm limit:
+ {1} / {4}
+ Lower/Upper warning limit:
+ {2} / {3}
- """.format("MediumBlue",
+ """.format("MediumBlue",
self.pvc.lowerAlarmLimit, self.pvc.lowerWarningLimit,
- self.pvc.upperWarningLimit, self.pvc.upperAlarmLimit)
+ self.pvc.upperWarningLimit, self.pvc.upperAlarmLimit)
return _text
def pv_status_text_display_limits(self):
- _text ="""
- """
- if self.pvc.lowerDisplayLimit == 0 and self.pvc.upperDisplayLimit == 0 and \
+ _text = """
+ """
+ if self.pvc.lowerDisplayLimit == 0 and \
+ self.pvc.upperDisplayLimit == 0 and \
self.pvc.lowerControlLimit == 0 and self.pvc.upperControlLimit == 0:
- return _text
- _text = """
- Lower/Upper control limit: {3} / {4}
- Lower/Upper display limit: {1} / {2}
+ return _text
+ _text = """
+ Lower/Upper control limit:
+ {3} / {4}
+ Lower/Upper display limit:
+ {1} / {2}
- """.format("MediumBlue",
+ """.format("MediumBlue",
self.pvc.lowerDisplayLimit, self.pvc.upperDisplayLimit,
- self.pvc.lowerControlLimit, self.pvc.upperControlLimit)
+ self.pvc.lowerControlLimit, self.pvc.upperControlLimit)
return _text
-
-
def pv_status_text(self):
'''pv metadata to accompany widget's dialog box.'''
QApplication.processEvents()
_source = "Channel Access"
-
+
if self.pv_within_daq_group:
if self.qt_object_name == self.PV_DAQ_BS:
_source = "DAQ (Beam Synchronous)"
@@ -1643,78 +1442,79 @@ class PVGateway(QWidget):
elif self.qt_object_name == self.PV_DAQ_CA:
_source = "DAQ (Channel Access)"
self.pvd = self.cafe.getPVCache(self.handle)
- if self.pvd.pulseID > 0:
- _source += "
Pulse ID: {0}".format(self.pvd.pulseID)
- else:
+ if self.pvd.pulseID > 0:
+ _source += "
Pulse ID: {0}".format(self.pvd.pulseID)
+ else:
self.pvd = self.cafe.getPVCache(self.handle)
-
- ##For testing...
- ##self.pvd.status = self.cyca.ICAFE_CA_OP_CONN_DOWN
- ##self.pvd.statusAsString = 'ICAFE_CA_OP_CONN_DOWN'
- #i, pvd, pvc = self.cafe.getChannelDataStore(self.handle)
- self.pvc = self.cafe.getCtrlCache(self.handle)
- #self.pvc.show()
-
- _text_data ="""
- """
- if self.pvd.status == self.cyca.ECAFE_INVALID_HANDLE:
+ self.pvc = self.cafe.getCtrlCache(self.handle)
+
+ _text_data = """
+ """
+ if self.pvd.status == self.cyca.ECAFE_INVALID_HANDLE:
_text_data = """ Status: {1}
{2}
- """.format("Blue", "Channel closed while DAQ in STOP state.",
- "PV info requires DAQ to be in RUN/PAUSED state" )
+ """.format("Blue",
+ "Channel closed while DAQ in STOP state.",
+ ("PV info requires DAQ to be in " +
+ "RUN/PAUSED state"))
elif self.pvd.status == self.cyca.ICAFE_CS_NEVER_CONN:
_text_data = """ Status: {1}
{2}
- """.format("Red", self.pvd.statusAsString, self.cafe.getStatusInfo(self.pvd.status))
+ """.format("Red", self.pvd.statusAsString,
+ self.cafe.getStatusInfo(self.pvd.status))
+
+ elif self.pvc.noEnumStrings > 0:
+ _text_data = (self.pv_status_text_enum() +
+ self.pv_status_text_timestamp() +
+ self.pv_status_text_alarm() +
+ self.pv_status_text_enum_metadata())
+
+ else:
+ _text_data = (self.pv_status_text_data() +
+ self.pv_status_text_timestamp() +
+ self.pv_status_text_alarm() +
+ self.pv_status_text_metadata() +
+ self.pv_status_text_alarm_limits() +
+ self.pv_status_text_display_limits())
- elif self.pvc.noEnumStrings > 0:
- _text_data = self.pv_status_text_enum() + \
- self.pv_status_text_timestamp() + \
- self.pv_status_text_alarm() + \
- self.pv_status_text_enum_metadata()
-
- else:
- _text_data = self.pv_status_text_data()+ \
- self.pv_status_text_timestamp() + \
- self.pv_status_text_alarm() + \
- self.pv_status_text_metadata() + \
- self.pv_status_text_alarm_limits() + \
- self.pv_status_text_display_limits()
-
self.pv_message_in_a_box.setText(
self.pv_status_text_header(source=_source) + _text_data
)
QApplication.processEvents()
self.pv_message_in_a_box.exec()
-
-
+
+
def lookup_archiver(self):
'''Plot pvdata from archiver.'''
- #"https://ui-data-api.psi.ch/prepare?channel = sf-archiverappliance/"
- urlIs = self.settings.urlArchiver
+ #"https://ui-data-api.psi.ch/prepare?
+ #channel=sf-archiverappliance/"
+ urlIs = self.url_archiver
urlIs = urlIs + self.pv_name
- if not QDesktopServices.openUrl(QUrl(urlIs)): #, QUrl.TolerantMode)
- if showMessage is not None:
- self.showMessage(MsgSeverity.ERROR, __pymodule__, _line(),
- "Failed to open URL {0}".format(urlIs))
+ if not QDesktopServices.openUrl(QUrl(urlIs)):
+ print("URL FOR ARCHIVER NOT FOUND", urlIs)
+ #if self.show_log_message is not None:
+ # self.show_log_message(MsgSeverity.ERROR, __pymodule__, _line(),
+ # "Failed to open URL {0}".format(urlIs))
def lookup_databuffer(self):
'''Plot beam synchronous pvdata from databuffer.'''
#""https://ui-data-api.psi.ch/prepare?channel = sf-databuffer/"
- urlIs = self.settings.urlDatabuffer
+ urlIs = self.url_databuffer
urlIs = urlIs + self.pv_name
-
- if not QDesktopServices.openUrl(QUrl(urlIs)): #, QUrl.TolerantMode)
- if showMessage is not None:
- self.showMessage(MsgSeverity.ERROR, __pymodule__, _line(),
- "Failed to open URL {0}".format(urlIs))
+
+ if not QDesktopServices.openUrl(QUrl(urlIs)):
+ print("URL FOR DATA BUFFER NOT FOUND", urlIs)
+ #if self.show_log_message is not None:
+ # self.show_log_message(MsgSeverity.ERROR, __pymodule__, _line(),
+ # "Failed to open URL {0}".format(urlIs))
QApplication.processEvents()
-
+
def strip_chart(self):
'''PShell strip chart.'''
- configStr = "-config = [[[true,\"" + self.pv_name + "\",\"Channel\",1,1]]]"
+ configStr = ("-config = [[[true,\"" + self.pv_name +
+ "\",\"Channel\",1,1]]]")
commandStr = "/sf/op/bin/strip_chart"
argStr = ["-nlaf", "-start", configStr, "&"]
QProcess.startDetached(commandStr, argStr)
@@ -1723,29 +1523,25 @@ class PVGateway(QWidget):
def display_parameters(self):
display_wgt = QDialog(self)
- _rect = display_wgt.geometry() #get current geometry of help window
- _parentRect = self.context_menu.geometry() # QRect(100, 1000, 640, 480) #get current geometry of this window
- #print(_rect, _parentRect)
+ _rect = display_wgt.geometry() #
+ _parentRect = self.context_menu.geometry()
+
_rect.moveTo(display_wgt.mapToGlobal(
- QPoint(_parentRect.x() + _parentRect.width() - _rect.width(),
- _parentRect.y())))
-
+ QPoint(_parentRect.x() + _parentRect.width() - _rect.width(),
+ _parentRect.y())))
+
display_wgt.setGeometry(_rect)
-
- #This has no effect
- #display_wgt.setWindowModality(Qt.WindowModal) #Qt.ApplicationModal Qt.ApplicationModal
display_wgt.setWindowTitle(self.pv_name)
layout = QVBoxLayout()
- #print("sender==================>", self.sender(), self)
- #self.the_gw = self
- #print("getNativeDataType", self.cafe.getDataTypeNative(self.handle))
-
+
precision_flag = True
if self.pv_ctrl is not None:
- if self.pv_ctrl.precision <= 0:
- precision_flag = False
+ if self.pv_ctrl.precision <= 0:
+ precision_flag = False
+
if self.cafe.getDataTypeNative(self.handle) in (
- self.cyca.CY_DBR_FLOAT, self.cyca.CY_DBR_DOUBLE) and precision_flag:
+ self.cyca.CY_DBR_FLOAT,
+ self.cyca.CY_DBR_DOUBLE) and precision_flag:
#precision user
_hbox_wgt = QWidget()
_hbox = QHBoxLayout()
@@ -1757,31 +1553,31 @@ class PVGateway(QWidget):
_max = self.pv_ctrl.precision
else:
_max = 6
- self.precision_user_wgt.setMaximum(_max)
+ self.precision_user_wgt.setMaximum(_max)
self.precision_user_wgt.valueChanged.connect(
- self.precision_user_changed)
+ self.precision_user_changed)
_hbox.addWidget(precision_user_label)
_hbox.addWidget(self.precision_user_wgt)
_hbox_wgt.setLayout(_hbox)
precision_user_label.setFixedWidth(110)
- self.precision_user_wgt.setFixedWidth(35)
+ self.precision_user_wgt.setFixedWidth(35)
_hbox_wgt.setFixedWidth(160)
-
+
#precision ioc
_hbox2_wgt = QWidget()
_hbox2 = QHBoxLayout()
precision_ioc_label = QLabel("Precision (ioc): ")
- precision_ioc = QPushButton(self)
- precision_ioc.setText(" {} ".format(_max))
- precision_ioc.clicked.connect(self.precision_ioc_reset)
-
+ precision_ioc = QPushButton(self)
+ precision_ioc.setText(" {} ".format(_max))
+ precision_ioc.clicked.connect(self.precision_ioc_reset)
+
_hbox2.addWidget(precision_ioc_label)
_hbox2.addWidget(precision_ioc)
_hbox2_wgt.setLayout(_hbox2)
precision_ioc_label.setFixedWidth(110)
- precision_ioc.setFixedWidth(20)
+ precision_ioc.setFixedWidth(20)
_hbox2_wgt.setFixedWidth(145)
layout.addWidget(_hbox_wgt)
@@ -1790,89 +1586,76 @@ class PVGateway(QWidget):
#precision refresh rate
_hbox3_wgt = QWidget()
_hbox3 = QHBoxLayout()
- refresh_freq_label = QLabel("Refresh rate: ")
+ refresh_freq_label = QLabel("Refresh rate: ")
_default_refresh_val = 0 if self.notify_freq_hz_default <= 0 else \
self.notify_freq_hz_default
- self.refresh_freq_combox_idx_dict = {0:0, 1:10, 2:5, 3:2, 4:1, 5:0.5,
- 6:_default_refresh_val}
+ self.refresh_freq_combox_idx_dict = {0: 0, 1: 10, 2: 5, 3: 2, 4: 1,
+ 5: 0.5, 6: _default_refresh_val}
refresh_freq = QComboBox(self)
refresh_freq.addItem('direct')
refresh_freq.addItem('{0} Hz'.format(
- self.refresh_freq_combox_idx_dict[1]))
+ self.refresh_freq_combox_idx_dict[1]))
refresh_freq.addItem('{0} Hz'.format(
- self.refresh_freq_combox_idx_dict[2]))
+ self.refresh_freq_combox_idx_dict[2]))
refresh_freq.addItem('{0} Hz'.format(
- self.refresh_freq_combox_idx_dict[3]))
+ self.refresh_freq_combox_idx_dict[3]))
refresh_freq.addItem('{0} Hz'.format(
- self.refresh_freq_combox_idx_dict[4]))
+ self.refresh_freq_combox_idx_dict[4]))
refresh_freq.addItem('{0} Hz'.format(
- self.refresh_freq_combox_idx_dict[5]))
-
+ self.refresh_freq_combox_idx_dict[5]))
+
_default_text = 'default (direct)' if _default_refresh_val == 0 else \
'default ({0} Hz)'.format(self.refresh_freq_combox_idx_dict[6])
-
+
refresh_freq.addItem(_default_text)
-
-
+
for key, value in self.refresh_freq_combox_idx_dict.items():
- if value == self.notify_freq_hz:
+ if value == self.notify_freq_hz:
refresh_freq.setCurrentIndex(key)
break
refresh_freq.currentIndexChanged.connect(self.refresh_rate_changed)
-
_hbox3.addWidget(refresh_freq_label)
_hbox3.addWidget(refresh_freq)
_hbox3_wgt.setLayout(_hbox3)
refresh_freq_label.setFixedWidth(110)
- refresh_freq.setFixedWidth(115)
+ refresh_freq.setFixedWidth(115)
_hbox3_wgt.setFixedWidth(235)
-
+
layout.addWidget(_hbox3_wgt)
layout.setAlignment(Qt.AlignLeft)
layout.setContentsMargins(10, 0, 0, 0)
layout.setSpacing(0)
-
- display_wgt.setMinimumWidth(340)
- display_wgt.setLayout(layout)
-
+
+ display_wgt.setMinimumWidth(340)
+ display_wgt.setLayout(layout)
+
display_wgt.exec()
QApplication.processEvents()
def precision_ioc_reset(self):
- if self.pv_ctrl is not None:
+ if self.pv_ctrl is not None:
self.precision_user = self.pv_ctrl.precision
self.precision = self.pv_ctrl.precision
if self.precision is not None:
self.precision_user_wgt.setValue(self.precision)
- #self.precision_user_changed(self.precision)
- #_value = self.cafe.getCache(self.handle)
- #self.trigger_monitor_float.emit(_value, 1, 0)
def precision_user_changed(self, new_value):
self.precision_user = new_value
- self.precision = new_value
+ self.precision = new_value
+
+ _pvd = self.cafe.getPVCache(self.handle)
- _pvd = self.cafe.getPVCache(self.handle)
-
if _pvd.value[0] is not None:
- if isinstance(_pvd.value[0], float):
+ if isinstance(_pvd.value[0], float):
self.trigger_monitor_float.emit(
- _pvd.value[0], _pvd.status, _pvd.alarmSeverity)
-
- '''
- _value = self.cafe.getCache(self.handle)
- #print("widget", self.widget, self.widget.sender())
- if _value is not None:
- #self.post_display_value(_value)
- self.trigger_monitor_float.emit(_value, 1, 0)
- '''
+ _pvd.value[0], _pvd.status, _pvd.alarmSeverity)
def refresh_rate_changed(self, new_idx):
- _notify_freq_hz = self.refresh_freq_combox_idx_dict[new_idx]
+ _notify_freq_hz = self.refresh_freq_combox_idx_dict[new_idx]
self.notify_milliseconds = 0 if _notify_freq_hz == 0 else \
1000 / _notify_freq_hz
self.notify_freq_hz = _notify_freq_hz
@@ -1881,28 +1664,27 @@ class PVGateway(QWidget):
self.notify_unison = False
self.monitor_stop()
self.monitor_start()
-
+
else:
- self.cafe.updateMonitorPolicyDeltaMS(self.handle,
- self.monitor_id,
- self.notify_milliseconds)
+ self.cafe.updateMonitorPolicyDeltaMS(
+ self.handle, self.monitor_id, self.notify_milliseconds)
#https://doc.qt.io/qt-5.9/qtwidgets-mainwindows-menus-example.html
- #Since Qt5 this has to be implemented in order to avoid the Select All dialogue button appearing..
+ #Since Qt5 this has to be implemented in order to avoid the Select
+ #All dialogue button appearing..
def contextMenuEvent(self, event):
return
def showContextMenu(self):
self.context_menu.exec(QCursor.pos())
-
- def mousePressEvent(self, event):
- '''Action on mouse press event.'''
- button = event.button()
- if button == Qt.RightButton:
- #contextMenu.exec(event.globalPos())
+
+ def mousePressEvent(self, event):
+ '''Action on mouse press event.'''
+ button = event.button()
+ if button == Qt.RightButton:
self.context_menu.exec(QCursor.pos())
self.clearFocus()
-
- def mouseReleaseEvent(self, event):
- event.ignore()
+
+ def mouseReleaseEvent(self, event):
+ event.ignore()
diff --git a/pvgateway.py- b/pvgateway.py-
new file mode 100644
index 0000000..2bd8d74
--- /dev/null
+++ b/pvgateway.py-
@@ -0,0 +1,1924 @@
+"""The module provides data and metadata of a process variable through PyCafe."""
+__author__ = 'Jan T. M. Chrin'
+
+import copy
+from enum import IntEnum
+import inspect
+import sys
+import time
+
+from datetime import datetime
+from distutils.version import LooseVersion
+
+from qtpy.QtCore import (QEvent, QObject, QMutex, QPoint, QProcess, QRect,
+ QSettings, Qt, QUrl, Signal, Slot)
+from qtpy.QtCore import __version__ as QT_VERSION_STR
+from qtpy.QtGui import (QColor, QCursor, QDesktopServices, QFont, QPainter,
+ QPalette)
+from qtpy.QtWidgets import (QAction, QApplication, QComboBox, QDialog,
+ QHBoxLayout, QLabel, QLineEdit, QMenu, QMessageBox,
+ QPushButton, QSpinBox, QVBoxLayout, QWidget)
+
+from pyqtacc.bdbase.readjson import ReadJSON
+#from pyqtacc.bdbase.enumkind import DAQState
+
+def __LINE__():
+ return inspect.currentframe().f_back_f_lineno
+
+class DAQState(IntEnum):
+ BS = 10
+ CA = 20
+ BS_STOP = 30
+ CA_STOP = 40
+ BS_PAUSE = 50
+ CA_PAUSE = 60
+
+class PVGateway(QWidget):
+ """Retrieves pv metadata through PyCafe.
+
+ The PVGateway class when subclassed by Qt widgets enables their connectivity
+ to channel access.
+
+ Attributes:
+ monid: (int) Monitor id
+ units : (str) Units associated with the pv
+
+ trigger_monitor_ is the signal triggered by updates arising from
+ monitored pvs.
+ trigger_connect is the signal triggered from changes in pv connection status.
+ widget_handle_dict is a dictionary mapping widgets to their pv handle.
+ A pv handle may be associated to more than one widget.
+ """
+ trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_int = Signal(int, int, int)
+ trigger_monitor_str = Signal(str, int, int)
+ trigger_monitor = Signal(object, int) #pvdata, status
+
+
+ trigger_connect = Signal(int, str, int)
+
+ trigger_daq = Signal(object, str, int)
+ trigger_daq_int = Signal(object, str, int)
+ trigger_daq_str = Signal(object, str, int)
+
+ #Properties, user supplied
+ ACT_ON_BEAM = 'actOnBeam'
+ NOT_ACT_ON_BEAM = 'notActOnBeam'
+ READBACK_ALARM = 'alarm'
+ READBACK_STATIC = 'static'
+
+ #Properties, dynamic
+ DISCONNECTED = 'disconnected'
+ ALARM_SEV_MINOR = 'alarmSevMinor'
+ ALARM_SEV_MAJOR = 'alarmSevMajor'
+ ALARM_SEV_INVALID = 'alarmSevInvalid'
+ ALARM_SEV_NO_ALARM = READBACK_ALARM
+ DAQ_STOPPED = 'stopped'
+ DAQ_PAUSED = 'paused'
+
+ #ObjectName, defined by CAQ
+ PV_CONTROLLER = "Controller"
+ PV_READBACK = "Readback"
+ PV_DAQ_BS = "BSRead"
+ PV_DAQ_CA = "CARead"
+
+ _DAQ_CAFE_SG_NAME = "gBS2CA"
+
+ _alarm_severity_record_types = ["ai", "ao", "calc", "calcout", "dfanout",
+ "longin", "longout", "pid", "sel",
+ "steppermotor", "sub"]
+
+ #parent is Gui
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode = None,
+ show_units: bool = False, prefix: str = "", suffix: str = "",
+ connect_callback=None, msg_label: str = "",
+ connect_triggers: bool = True, notify_freq_hz: int = 0,
+ notify_unison: bool = False, precision: int = 0,
+ monitor_dbr_time: bool = False):
+
+ #super(PVGateway, self).__init__() # do NOT use parent
+ #It turned out a widget was created with the main window as a parent, but incorrectly placed.
+ #Parent must not be QMainWindow. This interferes with the toolbar!! 16 Aug. 2020
+ super().__init__()
+
+ if parent is None:
+ return
+
+ if pv_name is "":
+ return
+
+ self.connect_callback = connect_callback
+ self.notify_freq_hz = abs(notify_freq_hz)
+ self.notify_freq_hz_default = self.notify_freq_hz
+
+ self.notify_milliseconds = 0 if self.notify_freq_hz == 0 else \
+ 1000 / self.notify_freq_hz
+
+ self.notify_unison = True if notify_unison and \
+ self.notify_freq_hz > 0 else False
+
+ self.parent = parent
+ self.pv_name = pv_name
+
+ self.color_mode = None
+
+ if color_mode is not None:
+ if color_mode in (self.ACT_ON_BEAM,
+ self.NOT_ACT_ON_BEAM,
+ self.READBACK_ALARM,
+ self.READBACK_STATIC):
+ self.color_mode = color_mode
+
+ self.color_mode_requested = self.color_mode
+
+ if monitor_callback is not None:
+ self.monitor_callback = monitor_callback
+ else:
+ self.monitor_callback = None
+
+ self.pv_within_daq_group = pv_within_daq_group
+
+ self.show_units = show_units
+ self.prefix = prefix
+ self.suffix = suffix
+
+ self.cafe = self.parent.cafe
+ self.cyca = self.parent.cyca
+
+ if self.parent.settings is not None:
+ self.url_archiver = self.parent.settings.data["url"]["archiver"]
+ self.url_databuffer = self.parent.settings.data["url"]["databuffer"]
+ self.bg_readback = self.parent.settings.data["StyleGuide"]["bgReadback"]
+ self.fg_alarm_major = self.parent.settings.data["StyleGuide"]["fgAlarmMajor"]
+ self.fg_alarm_minor = self.parent.settings.data["StyleGuide"]["fgAlarmMinor"]
+ self.fg_alarm_invalid = self.parent.settings.data["StyleGuide"]["fgAlarmInvalid"]
+ self.fg_alarm_noalarm = self.parent.settings.data["StyleGuide"]["fgAlarmNoAlarm"]
+ else:
+ #self.settings = ReadJSON(self.parent.appname)
+ self.url_archiver = "https://ui-data-api.psi.ch/prepare?channel=sf-archiverappliance/"
+ self.url_databuffer = "https://ui-data-api.psi.ch/prepare?channel=sf-databuffer/"
+
+ self.daq_group_name = self._DAQ_CAFE_SG_NAME
+ self.desc = None
+ self.handle = None
+ self.initialize_complete = False
+ self.initialize_again = False
+
+ self.msg_label = msg_label
+ self.msg_press_value = None
+ self.msg_release_value = None
+
+ self.monitor_id = None
+ self.monitor_dbr_time = monitor_dbr_time
+ self.mutex_post_display = QMutex()
+
+ self.precision_user = precision
+ self.has_precision_user = True if precision > 0 else False
+ self.precision_pv = 3
+
+ self.precision = (self.precision_user if self.has_precision_user else
+ self.precision_pv)
+
+ self.pvd = None
+ self.pv_ctrl = None
+ self.pv_info = None
+ self.record_type = None
+
+ if 'showMessage' in dir(self.parent):
+ self.showMessage = self.parent.showMessage
+ else:
+ self.showMessage = None
+
+ self.qt_object_name = None
+
+ self.qt_property_controller = {
+ self.DISCONNECTED : False,
+ self.ACT_ON_BEAM : False, self.NOT_ACT_ON_BEAM : False
+ }
+
+ self.qt_property_readback = {
+ self.DISCONNECTED : False,
+ self.READBACK_ALARM : False, self.READBACK_STATIC : False,
+ self.ALARM_SEV_MINOR : False, self.ALARM_SEV_MAJOR : False,
+ self.ALARM_SEV_INVALID : False
+ }
+
+ self.qt_property_daq_bs = {
+ self.DISCONNECTED : False,
+ self.READBACK_ALARM : False, self.READBACK_STATIC : False,
+ self.ALARM_SEV_MINOR : False, self.ALARM_SEV_MAJOR : False,
+ self.ALARM_SEV_INVALID : False,
+ self.DAQ_STOPPED : False,
+ self.DAQ_PAUSED : False
+ }
+
+ self.qt_property_daq_ca = {
+ self.DISCONNECTED : False,
+ self.READBACK_ALARM : False, self.READBACK_STATIC : False,
+ self.ALARM_SEV_MINOR : False, self.ALARM_SEV_MAJOR : False,
+ self.ALARM_SEV_INVALID : False,
+ self.DAQ_STOPPED : False,
+ self.DAQ_PAUSED : False
+ }
+
+ self.qt_object_to_property = {
+ self.PV_CONTROLLER : self.qt_property_controller,
+ self.PV_READBACK : self.qt_property_readback,
+ self.PV_DAQ_BS : self.qt_property_daq_bs,
+ self.PV_DAQ_CA : self.qt_property_daq_ca
+ }
+
+ self._qt_property_selected = {}
+
+ self.status_tip = None
+ self.suggested_text = ""
+ self.time_monotonic = time.monotonic()
+ self.pvd_previous = None
+ self.timeout = 0.2
+ self.units = ""
+
+ self.widget = self
+
+ _widget_name_part = str(self.widget.__class__).split("\'")[1].split(".")
+ _widget_class_part = _widget_name_part[1].split(".")
+ self.widget_class = _widget_name_part[len(_widget_name_part)-1]
+
+ if pv_within_daq_group:
+ self.trigger_daq_int.connect(self.receive_daq_update)
+ self.trigger_daq.connect(self.receive_daq_update)
+ self.trigger_daq_str.connect(self.receive_daq_update)
+
+ elif connect_triggers:
+ self.trigger_monitor.connect(self.receive_monitor_dbr_time)
+ self.trigger_monitor_str.connect(self.receive_monitor_update)
+ self.trigger_monitor_int.connect(self.receive_monitor_update)
+ self.trigger_monitor_float.connect(self.receive_monitor_update)
+ self.trigger_connect.connect(self.receive_connect_update)
+
+ self.context_menu = QMenu()
+ self.context_menu.setObjectName("contextMenu")
+ self.context_menu.setWindowModality(Qt.NonModal) #ApplicationModal
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.0"):
+ self.context_menu.addSection("PV: {0}".format(self.pv_name))
+
+ action1 = QAction("Text Info", self)
+ action1.triggered.connect(self.pv_status_text)
+
+ action2 = QAction("Lookup in Archiver", self)
+ action2.triggered.connect(self.lookup_archiver)
+
+ action3 = QAction("Lookup in Databuffer", self)
+ action3.triggered.connect(self.lookup_databuffer)
+
+ action4 = QAction("Strip Chart (PShell)", self)
+ action4.triggered.connect(self.strip_chart)
+
+ action6 = QAction("Configure Display Parameters", self)
+ action6.triggered.connect(self.display_parameters)
+
+ self.context_menu.addAction(action1)
+ self.context_menu.addAction(action2)
+ self.context_menu.addAction(action3)
+ self.context_menu.addAction(action4)
+
+ action5 = QAction("Reconnect: {0}".format(self.pv_name), self)
+ action5.triggered.connect(self.reconnect_channel)
+ _font = QFont()
+ _font.setPixelSize(12)
+ action5.setFont(_font)
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.context_menu.addSection("")
+
+ #return action6 and 5 code here eventually
+ self.context_menu.addAction(action6)
+ self.context_menu.addAction(action5)
+
+ self.pv_message_in_a_box = QMessageBox()
+ self.pv_message_in_a_box.setObjectName("pvinfo")
+
+ #Qt.NonModal often causes harmless QXcbConnection: XCB error: 3 (BadWindow), sequence:
+ #but only if the window is closed too quickly(!)
+ #Qt.ApplicationModal not used as it blocks input to all windows
+ self.pv_message_in_a_box.setWindowModality(Qt.NonModal) #Qt.ApplicationModal
+ self.pv_message_in_a_box.setIcon(QMessageBox.Information)
+ self.pv_message_in_a_box.setStandardButtons(QMessageBox.Close) # Shows QMessageBox.Close shows Close
+ self.pv_message_in_a_box.setDefaultButton(QMessageBox.Close) #Show OK
+
+ self.initialize()
+ '''
+ #temporary code position
+ if self.pv_ctrl is not None:
+ if self.pv_ctrl.precision > 0:
+ self.context_menu.addAction(action6)
+
+ self.context_menu.addAction(action5)
+ '''
+ return self
+
+
+ def initialize(self):
+ '''Initialze class attributes and connect to ca if required.'''
+
+ _handle_within_group_flag = False
+ if self.pv_within_daq_group:
+ self.handle = self.cafe.getHandleFromPVWithinGroup(
+ self.pv_name, self.daq_group_name)
+ if self.handle > 0:
+ self.cafe.addWidget(self.handle, self.widget)
+ _handle_within_group_flag = True
+ #Callback already invoked to emit signal here!!
+ _channel_info = self.cafe.getChannelInfo(self.handle)
+ ##print(self.pv_name, self.handle)
+ w = self.cafe.getWidgets(self.handle)
+ ##print("widget list", w)
+ #_channel_info.show()
+
+ self.trigger_connect.emit(
+ int(self.handle), str(self.pv_name),
+ int(_channel_info.cafeConnectionState))
+ #In case user is misinformed
+ if not _handle_within_group_flag:
+ self.handle = self.cafe.getHandleFromPV(self.pv_name)
+ if self.connect_callback is None:
+ self.connect_callback = self.py_connect_callback
+
+ if self.handle > 0:
+
+ #The second time round, widget is gateway rather than parent, Why is that?
+ self.cafe.setPyConnectCallbackFn(self.handle,
+ self.connect_callback)
+
+ self.cafe.addWidget(self.handle, self.widget)
+
+ _channel_info = self.cafe.getChannelInfo(self.handle)
+ self.trigger_connect.emit(
+ self.handle, self.pv_name,
+ int(_channel_info.cafeConnectionState))
+
+
+ #print("====OLD===============", self.handle, self.widget, self.parent)
+ else:
+
+ self.cafe.openPrepare()
+ self.handle = self.cafe.open(self.pv_name,
+ self.connect_callback)
+ self.cafe.addWidget(self.handle, self.widget)
+ self.cafe.openNowAndWait(self.timeout, self.handle)
+ #self.cafe.openNow()
+ #_channel_info = self.cafe.getChannelInfo(self.handle)
+ #self.trigger_connect.emit(int(self.handle), str(self.pv_name), int(_channel_info.cafeConnectionState))
+ #print("====NEW============ ==", self.handle, self.widget)
+
+ self.initialize_meta_data()
+
+ self.pv_message_in_a_box.setWindowTitle(self.pv_name)
+
+
+ def initialize_meta_data(self):
+
+ _current_value = ""
+
+ if self.cafe.isConnected(self.handle) and \
+ self.cafe.initCallbackComplete(self.handle):
+
+ if self.pvd is None:
+ self.pvd = self.cafe.getPVCache(self.handle)
+
+ if self.pv_ctrl is None:
+ self.pv_ctrl = self.cafe.getCtrlCache(self.handle)
+ self.set_precision_and_units()
+
+ if self.pv_info is None:
+ self.pv_info = self.cafe.getChannelInfo(self.pv_name)
+ if "Not Supported" in self.pv_info.className:
+ _rtype = self.cafe.get(self.pv_name.split(".")[0] + ".RTYP")
+ self.record_type = _rtype if _rtype is not None else self.pv_info.className
+ _rtype = self.cafe.close(self.pv_name.split(".")[0] + ".RTYP")
+ else:
+ self.record_type = self.pv_info.className
+ #print ("record_type", self.record_type)
+
+
+ _current_value = self.cafe.getCache(self.handle)
+ if isinstance(_current_value, (int, float)):
+ #space for positive numbers
+ _value_form = ("{:<+.%sf}" % self.precision)
+ _current_value = _value_form.format(
+ round(_current_value, self.precision))
+ #if self.desc is None:
+ # self.set_desc()
+
+ #Reset
+ self.initialize_complete = True
+
+ #verify user input
+ if self.show_units is True:
+ if self.suffix == self.units and self.units != "":
+ self.show_units = False
+
+ self.suggested_text = self.prefix
+ if len(self.prefix) > 0:
+ self.suggested_text += " "
+
+ _suggested_text_from_value = " "
+
+ _max_control_abs = 0
+
+ if self.pv_ctrl is not None:
+ _lower_control_abs = abs(int(self.pv_ctrl.lowerControlLimit))
+ _upper_control_abs = abs(int(self.pv_ctrl.upperControlLimit))
+ _max_control_abs = max(_lower_control_abs, _upper_control_abs)
+ if _max_control_abs is None:
+ _max_control_abs = 0
+
+ _enum_list = self.pv_ctrl.enumStrings
+
+ if len(_enum_list) > 0:
+ _enum_list_member_max_length = 0
+ _enum_list_member_max_index = 0
+
+ for i in range(0, len(_enum_list)):
+ if len(_enum_list[i]) > _enum_list_member_max_length:
+ _enum_list_member_max_length = len(_enum_list[i])
+ _enum_list_member_max_index = i
+ _suggested_text_from_value += \
+ _enum_list[_enum_list_member_max_index] + "." #Add extra space
+ else:
+ if self.pv_ctrl.lowerControlLimit < 0:
+ _suggested_text_from_value += "-"
+ _suggested_text_from_value += str(_max_control_abs) + "."
+
+
+ #print("precision", self.precision, self.pv_name)
+ self.precision = min(9, self.precision) #safety net
+ for i in range (0, self.precision):
+ _suggested_text_from_value += "0"
+
+ if len(_current_value) > len(_suggested_text_from_value):
+ _suggested_text_from_value = _current_value
+
+ self.suggested_text += _suggested_text_from_value
+
+ if self.show_units:
+ self.suggested_text += " " + self.units
+ self.suggested_text += self.suffix
+
+ _suggested_text_length = len(self.suggested_text)
+ self.suggested_text = self.suggested_text.center(
+ _suggested_text_length+2)
+
+ self.max_control_abs_str = str(_max_control_abs)
+
+
+ _max_control_abs_length = len(self.max_control_abs_str)
+ _offset = 9
+ self.max_control_abs_str = self.max_control_abs_str.center(
+ _max_control_abs_length + _offset)
+
+
+ qsettings = QSettings()
+ qsettings.beginGroup("Widget")
+ qsettings.beginGroup(self.pv_name)
+ qsettings.beginGroup(self.widget_class)
+ #_var_base = "Widget/" + self.pv_name + "/" + self.widget_class + "/"
+ _var_text = "suggested_text"
+ _ctrl_abs = "max_control_abs_str"
+
+ if self.cafe.isConnected(self.handle) and \
+ self.cafe.initCallbackComplete(self.handle):
+ qsettings.setValue(_var_text, self.suggested_text)
+ qsettings.setValue(_ctrl_abs, self.max_control_abs_str)
+ else:
+ if qsettings.value(_var_text) is not None:
+ self.suggested_text = qsettings.value(_var_text)
+ if qsettings.value(_ctrl_abs) is not None:
+ self.max_control_abs_str = qsettings.value(_ctrl_abs)
+
+
+ qsettings.endGroup()
+ qsettings.endGroup()
+ qsettings.endGroup()
+
+
+ def is_initialize_complete(self):
+ icount = 0;
+ while not self.initialize_complete :
+ time.sleep(0.01)
+ self.initialize_meta_data()
+ icount += 1
+ if icount > 50:
+ return False
+ return True
+
+ def cleanup(self, close_pv = True):
+ '''Clean up the widget.'''
+ ##Check for monitors
+ ##QWidget::close() is basically a combination of QWidget::closeEvent(), QWidget::hide(), and QObject::deleteLater() (if Qt::WA_DeleteOnClose is set).
+
+ #Make sure mon id is valid
+ if self.handle > 0:
+ _monID_list = self.cafe.getMonitorIDs(self.handle)
+ if self.monitor_id in _monID_list:
+ self.cafe.monitorStop(self.handle, self.monitor_id)
+ #self.cafe.monitorStop(self.handle, self.monitor_id)
+ #Do not close of there are other monitors
+ if self.cafe.getNoMonitors(self.handle) > 0:
+ if close_pv is True:
+ self.cafe.close(self.pv_name)
+ self.widget.deleteLater()
+
+
+ def format_display_value(self, value):
+
+ if value is None:
+ print(self, self.pv_name, ">>>>>>>>>>>>format_display_value is None>>>>>>")
+ #return
+
+ if isinstance(value, str):
+ _value_str = value
+ elif isinstance(value, int):
+ _value_str = str(value)
+ else:
+ _value_form = ("{:< .%sf}" % self.precision) #space for positive numbers
+ #print("v/prec", value, self.precision, flush=True)
+
+ _rounded_value = round(value, self.precision)
+ #print(_rounded_value, flush=True)
+ _value_str = _value_form.format(_rounded_value)
+ #print(_value_str, flush=True)
+
+ if self.show_units:
+ _value_str += " " + self.units + " "
+ if self.suffix is not "":
+ _value_str += " " + self.suffix + " "
+
+ if self.prefix is not "":
+ _space = ""
+ if self.pv_ctrl is not None:
+ if self.pv_ctrl.lowerDisplayLimit < 0:
+ _space = " "
+ _value_str = self.prefix + _space + _value_str
+
+ return _value_str
+
+ def post_display_value(self, value):
+
+ #self.mutex_post_display.lock()
+
+ _value_str = self.format_display_value(value)
+ #if 'deg' in _value_str:
+ # print(_value_str, len(_value_str))
+
+ if "setText" in dir(self):
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+ self.setText(_value_str)
+ self.blockSignals(False)
+ else:
+ #print("value =", _value_str, flush=True)
+ self.setText(_value_str)
+
+ else:
+ print("setText method does not exist for this widget class:\n", self.widget.__class__)
+ print("sender was: ", self.sender())
+
+
+ def py_connect_callback(self, handle, pvname, status):
+ '''Callback function to be invoked on change of pv connection status.
+ Checks for existence of widget. Waits up to a maximun of 100 ms.
+ '''
+ #print(" py_connect_callback:: START ")
+ #print(" py_connect_callback:: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", pvname)
+ #print(handle, pvname, status, self.cafe.getStatusCodeAsString(status))
+
+ pv_name = pvname
+ _widget = None
+ #_widgetList =self.cafe.getWidgets(handle)
+ #for i in range(0, len(_widgetList)):
+ #_widget = _widgetList[i]
+ #_widget.trigger_connect.emit(int(handle), str(pv_name), int(status))
+ #print (i, "widget at connect>>>>>>>>>>>>>>>>>>>>>>", _widget.__class__)
+ self.trigger_connect.emit(int(handle), str(pv_name), int(status))
+ #print(" py_connect_callback:: END ")
+
+
+ def receive_connect_update(self, handle, pv_name, status, post_display=True):
+ '''Triggered by connect signal. For Widget to overload.'''
+
+ #print(" receive _connect_callback:: START ")
+ #print ("RRReceive_connect triggered for widget", self, "with status", status)
+ #print ("RRReceive_connect triggered for widget", handle, pv_name, status)
+ _alarm_severity = None
+ if status == self.cyca.ICAFE_CS_CONN:
+ self.initialize_connect = True
+ self.pv_ctrl = self.cafe.getCtrlCache(self.handle)
+ self.pv_info = self.cafe.getChannelInfo(self.handle)
+ if self.pv_info is not None and self.record_type is None:
+ if "Not Supported" in self.pv_info.className:
+ _rtype = self.cafe.get(self.pv_name.split(".")[0] + ".RTYP")
+ self.record_type = _rtype if _rtype is not None else self.pv_info.className
+ _rtype = self.cafe.close(self.pv_name.split(".")[0] + ".RTYP")
+ #print(self.pv_name)
+ #print("record type====>", self.record_type)
+ else:
+ self.record_type = self.pv_info.className
+
+ self.set_precision_and_units(reconnectFlag=True)
+ #THis will connect to a new channel
+ #if self.desc is None:
+ # self.set_desc()
+ #print("msg_lab", self.msg_label, "/", len(self.msg_label))
+
+ if self.msg_label == "":
+ _value = self.cafe.getCache(handle, dt='native')
+ #Another reconnection in progress!!!
+
+ if _value == None:
+ return
+ else:
+ _value = self.msg_label
+ #print("_value", _value, "/", len(_value))
+
+ #print("_value", _value, "/")
+ if post_display:
+ self.post_display_value(_value)
+ self.qt_property_reconnect()
+
+ else:
+ self.qt_property_disconnect()
+
+
+ if status == self.cyca.ICAFE_CS_CLOSED:
+ self.initialize_again = True
+
+ elif self.initialize_again:
+ #monitos_id informs whether or not widget has a monitor
+ #CAQMessageButton for instance does not have a monitor
+
+ if not self.pv_within_daq_group and self.monitor_id is not None:
+ self.monitor_start()
+ #print("RESTART MONITOR FOR THIS WIDGET", flush=True)
+
+ self.initialize_again = False
+
+ #print(" receive _connect_callback:: END ")
+
+ return
+
+
+ def receive_daq_update(self, daq_pvd, daq_mode, daq_state):
+ ''' DAQ mode is widget specific.
+ DAQ may be in BS mode, but channels within DAQ stream that
+ are not BS enabled will be flagged as CA Mode, i.e., CARead
+ '''
+
+ _current_qt_dynamic_property = self.qt_dynamic_property_get()
+
+ alarm_severity = daq_pvd.alarmSeverity
+ self.pvd = daq_pvd
+
+ #print("BEFORE mode, object_name, daqState", daq_mode, self.qt_object_name, daq_state)
+
+ if daq_mode != self.qt_object_name:
+ self.qt_object_name = daq_mode
+ self.setObjectName(self.qt_object_name)
+ self.qt_style_polish()
+
+ #print("AFTER mode, object_name, daqState", daq_mode, self.qt_object_name, daq_state)
+
+ if daq_state in (self.cyca.ICAFE_DAQ_STOPPED,):
+ #if daq_state in (DAQState.CA_STOP, DAQState.BS_STOP, DAQState.CA_PAUSE,
+ # DAQState.BS_PAUSE):
+ if _current_qt_dynamic_property != self.DAQ_STOPPED:
+ self.qt_property_daq_stopped()
+
+ elif daq_state in (self.cyca.ICAFE_DAQ_PAUSED,):
+ if _current_qt_dynamic_property != self.DAQ_PAUSED:
+ self.qt_property_daq_paused()
+
+ elif daq_state in (self.cyca.ICAFE_DAQ_RUN,):
+ #if _current_qt_dynamic_property != self.READBACK_ALARM:
+ # self.qt_property_alarm_sev_no_alarm()
+ #print ("before", daq_mode, _current 22993_qt_dynamic_property)
+
+ if daq_mode == self.PV_DAQ_BS and \
+ _current_qt_dynamic_property != self.READBACK_STATIC:
+ self.qt_property_static()
+
+ elif daq_mode == self.PV_DAQ_CA:
+ #if _current_qt_dynamic_property not in (self.READBACK_STATIC,
+ # self.READBACK_ALARM,):
+ if self.color_mode != self.color_mode_requested:
+ self.color_mode == self.color_mode_requested
+ #print("new colore mode")
+
+ if self.cafe.isEnum(self.handle) and \
+ isinstance(daq_pvd.value[0], int):
+ _value = self.cafe.getStringFromEnum(self.handle,
+ daq_pvd.value[0])
+ else:
+ _value = daq_pvd.value[0]
+
+ if daq_pvd.status == self.cyca.ICAFE_NORMAL:
+ if self.msg_label == "":
+ self.post_display_value(_value)
+ if daq_mode == self.PV_DAQ_BS:
+ return
+
+ #Fro DAQ when channel connects after application start-up
+ #if _current_qt_dynamic_property == self.DISCONNECTED:
+ # self.qt_property_initial_values(qt_object_name = self.PV_READBACK)
+
+ #Check if color settings are correct
+ ##if _current_qt_dynamic_property == self.READBACK_STATIC and \
+ if alarm_severity > self.cyca.SEV_NO_ALARM:
+ self.color_mode = self.READBACK_ALARM
+ self.color_mode_requested = self.READBACK_ALARM
+
+ if self.color_mode == self.READBACK_ALARM:
+ if alarm_severity == self.cyca.SEV_MINOR:
+ if _current_qt_dynamic_property != self.ALARM_SEV_MINOR:
+ self.qt_property_alarm_sev_minor()
+
+ elif alarm_severity == self.cyca.SEV_MAJOR:
+ if _current_qt_dynamic_property != self.ALARM_SEV_MAJOR:
+ self.qt_property_alarm_sev_major()
+
+ elif alarm_severity == self.cyca.SEV_INVALID:
+ if _current_qt_dynamic_property != self.ALARM_SEV_INVALID:
+ self.qt_property_alarm_sev_invalid()
+
+ elif alarm_severity == self.cyca.SEV_NO_ALARM:
+ if _current_qt_dynamic_property != self.ALARM_SEV_NO_ALARM:
+ self.qt_property_alarm_sev_no_alarm()
+
+ elif _current_qt_dynamic_property != self.READBACK_STATIC:
+ self.qt_property_static()
+
+ #print ("after", daq_mode, self.qt_dynamic_property_get() )
+ else:
+ if _current_qt_dynamic_property != self.DISCONNECTED:
+ self.qt_property_disconnect()
+
+
+ def receive_monitor_dbr_time(self, pvdata, alarm_severity):
+ print("in gateway", self.pv_name)
+ #pvdata.show()
+
+ def receive_monitor_update(self, value, status, alarm_severity):
+ '''Triggered by monitor signal. For Widget to overload.'''
+
+ self.mutex_post_display.lock()
+ _current_qt_dynamic_property = self.qt_dynamic_property_get()
+ #print(self.pv_name, value, status, alarm_severity)
+ #if isinstance(value, (int, float)):
+ # if value < 100:
+ # print("CURRENT PROPERY VALUE", self.pv_name, _current_qt_dynamic_property, value, status, alarm_severity )
+ #print ("sender //2//", self.sender(), value)
+
+ #print("receive monitor update/1", self.pv_name, self.qt_object_name, self._qt_property_selected)
+
+ if status == self.cyca.ICAFE_NORMAL:
+ '''
+ if isinstance(value, (int, float)):
+
+ if value < -20:
+ alarm_severity = self.cyca.SEV_INVALID
+ elif value < -1:
+ alarm_severity = self.cyca.SEV_MAJOR
+ elif value < 5:
+ alarm_severity = self.cyca.SEV_MINOR
+ else:
+ alarm_severity = self.cyca.SEV_NO_ALARM
+ '''
+
+ if self.msg_label == "":
+ self.post_display_value(value)
+
+ #For DAQ when channel connects after application start-up
+ if _current_qt_dynamic_property == self.DISCONNECTED:
+ self.qt_property_initial_values(qt_object_name = self.PV_READBACK)
+
+ #Check if color settings are correct
+ elif _current_qt_dynamic_property == self.READBACK_STATIC:
+ if alarm_severity > self.cyca.SEV_NO_ALARM and \
+ alarm_severity < self.cyca.SEV_INVALID:
+ self.color_mode = self.READBACK_ALARM
+ self.status_tip = "Widget color mode is dynamic, pv with alarm limits"
+ elif alarm_severity == self.cyca.SEV_INVALID:
+ if _current_qt_dynamic_property != self.ALARM_SEV_INVALID:
+ self.qt_property_alarm_sev_invalid()
+
+ if self.color_mode == self.READBACK_ALARM:
+
+ if alarm_severity == self.cyca.SEV_MINOR:
+ if _current_qt_dynamic_property != self.ALARM_SEV_MINOR:
+ self.qt_property_alarm_sev_minor()
+
+ elif alarm_severity == self.cyca.SEV_MAJOR:
+ if _current_qt_dynamic_property != self.ALARM_SEV_MAJOR:
+ self.qt_property_alarm_sev_major()
+
+ elif alarm_severity == self.cyca.SEV_INVALID:
+ if _current_qt_dynamic_property != self.ALARM_SEV_INVALID:
+ self.qt_property_alarm_sev_invalid()
+
+ elif alarm_severity == self.cyca.SEV_NO_ALARM:
+ if _current_qt_dynamic_property != self.ALARM_SEV_NO_ALARM:
+ self.qt_property_alarm_sev_no_alarm()
+
+ else:
+ if _current_qt_dynamic_property != self.DISCONNECTED:
+ self.qt_property_disconnect()
+
+ self.mutex_post_display.unlock()
+
+ #print("receive monitor update/2", self.pv_name, self.qt_object_name, self._qt_property_selected)
+
+ def py_monitor_callback(self, handle, pvname, pvdata):
+
+
+ '''Callback function to be invoked on change of pv value.
+ cafe.getCache and cafe.set operations permitted within callback.
+ '''
+ '''
+ if "PULSEID" in pvname:
+ pass
+ else:
+ print ("py_monitor_callback: name/handle ",pvname, handle )
+ '''
+ pv_name = pvname
+ pvd = pvdata
+ #print("===================================")
+ #print("pvname/handle in mon callback ", pv_name, handle)
+
+
+ if not hasattr(self, 'cafe'):
+ print ("py_monitor_callback: name/handle self cafe is NONE =>>>>>>>>>>>> ",
+ pv_name, handle)
+ return
+ #pv_name = self.cafe.getPVNameFromHandle(self.handle)
+ #pvd = self.cafe.getPVCache(self.handle)
+
+ self.pvd = pvd
+ '''
+ if pvname != pv_name:
+ print ("py_monitor_callback: name/handle/monid =>>>>>>>>>>>> ",
+ pv_name, handle, self.cafe.getMonitorIDInCallback(handle))
+ print ("PV NAME NOT THE SAME **** WIDGET in monitor callback ", self)
+ '''
+ #_pvc = self.cafe.getCtrlCache(handle)
+ #print("_pvc.nelem",_pvc.nelem)
+ #_pvc.show()
+ #print(_pvc.nelem)
+
+ #_pvd = self.cafe.getPVCache(handle)
+ #pvd.showMax(4000) #set no of elemets to 1 in pvctrlCache!
+ #print(pvd.nelem)
+
+ '''
+ _info = self.cafe.getChannelInfo(handle)
+ _info.show()
+ '''
+
+
+
+ #_widgetList =self.cafe.getWidgets(handle)
+
+ _widget = None
+
+ #print ("END monid =>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ", self.cafe.getMonitorIDInCallback(handle))
+ #print(self, self.pv_name)
+ #print(_widgetList)
+ '''
+ self.mutex.lock()
+
+ for _widget, _handle in self.widget_handle_dict.items():
+ if _handle == handle:
+ '''
+ for i in range(0, 1): #len(_widgetList)):
+ #_widget = _widgetList[i]
+ if pvd.status == self.cyca.ICAFE_CS_NEVER_CONN:
+ print("initialize again")
+ self.initialize()
+
+ elif pvd.status == self.cyca.ICAFE_CA_OP_CONN_DOWN:
+ _alarm_severity = self.cyca.ICAFE_CA_OP_CONN_DOWN
+ #print("COMPARE ALARM SEVERITIES ", _alarm_severity, pvd.alarmSeverity)
+ else:
+ _alarm_severity = pvd.alarmSeverity
+
+
+
+ if self.monitor_dbr_time:
+ self.trigger_monitor.emit(pvd, _alarm_severity)
+
+ elif isinstance(pvd.value[0], str):
+ self.trigger_monitor_str.emit((pvd.value[0]), pvd.status, _alarm_severity) #, _widget)
+ #print("emitted str value", pvd.value[0])
+ elif isinstance(pvd.value[0], int):
+ self.trigger_monitor_int.emit((pvd.value[0]), pvd.status, _alarm_severity)
+
+ else:
+ #print(dir(self.receivers(self, self.trigger_monitor_float)))
+ self.trigger_monitor_float.emit(float(pvd.value[0]), pvd.status, _alarm_severity)
+ #print("emitted float value", pvd.value[0])
+ pass
+
+
+
+
+ #if _widget is None:
+ # print("NO WIDGET FOR THIS PV!!!! pv = ", pv_name)
+ #self.mutex.unlock()
+
+
+
+ def monitor_start(self):
+ '''Initiate monitor on pv.'''
+ #print(self, self.pv_name, "Initiate monitor on pv:", self.monitor_callback, self.py_monitor_callback)
+ if self.handle > 0:
+ #Is monitor in waiting - now deleted with monitor_stop
+ if self.notify_unison:
+ self.monitor_id = self.cafe.monitorStart(
+ self.handle, dbr=self.cyca.CY_DBR_TIME)
+ #start with gateway supplied monitor callback handler
+ elif self.monitor_callback is None:
+ self.monitor_id = self.cafe.monitorStart(
+ self.handle, cb=self.py_monitor_callback,
+ dbr=self.cyca.CY_DBR_TIME,
+ notify_milliseconds=self.notify_milliseconds)
+ else:
+ self.monitor_id = self.cafe.monitorStart(
+ self.handle, cb=self.monitor_callback,
+ dbr=self.cyca.CY_DBR_TIME,
+ notify_milliseconds=self.notify_milliseconds)
+
+ def monitor_stop(self):
+ #print("monitor_stopped")
+ if self.handle > 0:
+ _monID_list = self.cafe.getMonitorIDs(self.handle)
+ _monID_inwaiting_list = self.cafe.getMonitorIDsInWaiting(self.handle)
+ _monID_all = _monID_list + _monID_inwaiting_list
+
+ if self.monitor_id in _monID_all:
+ #print("stopping in monitor_stop for handle", self.monitor_id)
+ self.cafe.monitorStop(self.handle, self.monitor_id)
+ #print("stopped in monitor_stop for handle", self.monitor_id)
+ #Is monitor in waiting?
+ #remove monitors in waiting
+
+
+
+ def reconnect_channel(self):
+ self.cafe.reconnect([self.handle]) #list
+
+ def set_desc(self):
+ '''Set description of pv from pv.DESC'''
+
+ if self.cafe.hasDescription(self.handle):
+ self.desc = self.cafe.getDescription(self.handle)
+ return
+ elif self.desc is not None:
+ return
+ else:
+ self.cafe.supplementHandle(self.handle)
+ if self.cafe.hasDescription(self.handle):
+ self.desc = self.cafe.getDescription(self.handle)
+
+ if self.desc is not None:
+ return
+
+ ###Back-up solution
+ _found = str(self.pv_name).find(".")
+ if _found != -1:
+ _pv_desc = str(self.pv_name)[0:_found] +".DESC"
+ else:
+ _pv_desc = self.pv_name +".DESC"
+ _handle_desc = self.cafe.getHandleFromPVName(_pv_desc)
+
+ _handle_desc_already_open = False
+
+ if _handle_desc == 0:
+ self.cafe.openPrepare()
+ _handle_desc = self.cafe.open(_pv_desc)
+ self.cafe.openNowAndWait(self.timeout, _handle_desc)
+ time.sleep(0.001)
+ else:
+ _handle_desc_already_open = True
+
+ if self.cafe.isConnected(_handle_desc):
+ self.desc = self.cafe.getCache(_handle_desc, 'str')
+ if self.desc is None:
+ self.desc = self.cafe.get(_handle_desc, 'str')
+ else:
+ self.desc = None
+
+ if not _handle_desc_already_open:
+ self.cafe.close(_handle_desc)
+
+ def set_precision_and_units(self, reconnectFlag: bool = False):
+ '''Set the pv precision and units.'''
+ if self.pv_ctrl is None or reconnectFlag is True:
+ self.pv_ctrl = self.cafe.getCtrlCache(self.handle)
+
+ if self.pv_ctrl is not None:
+ if not self.has_precision_user:
+ self.precision = self.pv_ctrl.precision
+ if self.pv_ctrl.units is not None:
+ #print(self.pv_ctrl.units)
+ #print(type(self.pv_ctrl.units))
+ self.units = str(self.pv_ctrl.units)
+ else:
+ self.units = ""
+
+ if reconnectFlag is True:
+ #verify user input
+ if self.show_units is True and self.suffix is not None:
+ if self.suffix == self.units:
+ self.show_units = False
+
+
+ def _qt_readback_color_mode(self):
+ '''Color mode is determined from CAFE and depends on whether the pv:
+ has alarm limits (self.color_mode = 'readbackAlarm')
+ or is without alarm limits (self.color_mode = 'readbackStatic')
+ '''
+
+
+ #Already set by user
+ if self.color_mode is self.READBACK_ALARM:
+ return
+
+
+ if self.cafe.isConnected(self.handle):
+ pvd = self.cafe.getPVCache(self.handle)
+ if pvd.alarmSeverity in (self.cyca.SEV_MINOR, self.cyca.SEV_MAJOR) or \
+ self.cafe.hasAlarmStatusSeverity(self.handle):
+ self.color_mode = self.READBACK_ALARM
+ self.status_tip = "Widget color mode is dynamic, pv with alarm limits"
+ #print(self.pv_name, "has alarm svev", self.cafe.hasAlarmStatusSeverity(self.handle))
+
+
+ else:
+ self.color_mode = self.READBACK_STATIC
+ self.status_tip = "Widget color mode is static, pv without alarm limits"
+
+
+ def qt_property_initial_values(self, qt_object_name: str = None, tool_tip: bool = True):
+
+ '''Set Qt property values.'''
+ self.qt_object_name = qt_object_name
+ if tool_tip:
+ self.setToolTip(self.pv_name)
+ self.setObjectName(self.qt_object_name)
+ if self.qt_object_name in self.qt_object_to_property.keys():
+ self._qt_property_selected = copy.deepcopy(self.qt_object_to_property[self.qt_object_name])
+ else:
+ print ("qt_property_initial_values: Object not found in dictionary")
+
+ #print("qt_property_initial_values", self.qt_object_name, self._qt_property_selected)
+
+
+ if self.cafe.isConnected(self.handle):
+
+ if self.qt_object_name == self.PV_READBACK:
+ self._qt_readback_color_mode()
+ #self.setStatusTip(self.status_tip)
+
+ elif self.qt_object_name == self.PV_CONTROLLER:
+ if self.color_mode == self.ACT_ON_BEAM:
+ #self.setStatusTip("PV setting acts directly on beam")
+ pass
+ else:
+ self.color_mode = self.NOT_ACT_ON_BEAM
+ #self.setStatusTip("PV setting does not influence beam")
+
+ elif self.qt_object_name == self.PV_DAQ_CA:
+ self._qt_readback_color_mode()
+
+ elif self.qt_object_name == self.PV_DAQ_BS:
+ self.color_mode = self.READBACK_STATIC
+
+ #print("qt_property_initial_values//", self.pv_name, self.qt_object_name, self._qt_property_selected)
+ self._qt_dynamic_property_set(self.color_mode)
+ #print("qt_property_initial_values///", self.pv_name, self.qt_object_name, self._qt_property_selected)
+
+ else:
+ self.qt_property_disconnect()
+
+ #print("qt_property_initial_values", self.pv_name, self.qt_object_name, self.color_mode)
+
+ '''
+ meta_obj = self.metaObject()
+ count = meta_obj.propertyCount()
+ for i in range(0, count):
+ meta_prop = meta_obj.property(i)
+ name = meta_prop.name()
+ print(i, name, self.property(name))
+ '''
+
+ def qt_dynamic_property_get(self, property_state : str = None):
+ '''Retrieves the requested property value'''
+ '''else that which is currently true'''
+
+ for _property, _value in self._qt_property_selected.items(): #states.items():
+ if property_state is not None:
+ if _property == property_state:
+ return _value
+ elif _value:
+ #print(self, _property, "SELECTED")
+ return _property
+
+ def _qt_dynamic_property_set(self, property_state : str = None):
+ '''Set the Input property to true, and the remainder to False'''
+ '''If None is given then all dynamic properties are set to False'''
+
+ #print("qt_property_set/", property_state, self.pv_name, self.qt_object_name, self.color_mode)
+ #if property_state in self.qt_property_states.keys():
+ for _property, _value in self._qt_property_selected.items(): #states.items():
+ if _property == property_state:
+ self.setProperty(_property, True)
+ self._qt_property_selected[_property] = True
+ else:
+ self.setProperty(_property, False)
+ self._qt_property_selected[_property] = False
+
+ #print("qt_property_set//", self.pv_name, self.qt_object_name, self.color_mode)
+
+ #l = self.dynamicPropertyNames()
+ #for i in range (0, len(l)):
+ # print(i, l[i])
+ #return self._qt_property_selected
+
+ def qt_property_disconnect(self, redraw=False):
+ '''Set Qt disconnect property value.'''
+
+ #self._qt_property_selected =
+ self._qt_dynamic_property_set(self.DISCONNECTED)
+
+ '''
+ if not self.initialize_complete:
+ self.setStatusTip("PV={0} was never connected".format(self.pv_name))
+ else:
+ self.setStatusTip("PV={0} is presently disconnected".format(self.pv_name))
+ '''
+ #print("qt_property_disconnect", self.pv_name, self.qt_object_name, self.color_mode)
+ self.qt_style_polish()
+
+ return #self._qt_property_selected
+
+
+ def qt_property_reconnect(self, redraw=False):
+ '''Set Qt connected property value.'''
+ #self.setObjectName("PyCafe")
+ #self.setToolTip(self.pv_name)
+ #l = self.dynamicPropertyNames()
+ #for i in range (0, len(l)-1):
+ # print(i, l[i])
+ #self.setProperty(str(l[i],'utf-8'), False)
+
+ if self.qt_object_name == self.PV_READBACK:
+ self._qt_readback_color_mode()
+ #self.setStatusTip(self.status_tip)
+
+
+ elif self.qt_object_name == self.PV_CONTROLLER:
+ if self.color_mode == self.ACT_ON_BEAM:
+ #self.setStatusTip("PV setting acts directly on beam")
+ pass
+ else:
+ self.color_mode = self.NOT_ACT_ON_BEAM
+ #self.setStatusTip("PV setting does not influence beam")
+
+
+ #self._qt_property_selected =
+ self._qt_dynamic_property_set(self.color_mode)
+
+ #print("qt_property_reconnect", self.pv_name, self.qt_object_name, self.color_mode)
+
+ #l = self.dynamicPropertyNames()
+ #for i in range (0, len(l)):
+ # print(i, l[i])
+ self.qt_style_polish()
+
+ def qt_property_alarm_sev_major(self, redraw=False):
+ '''Set Qt MAJOR property value.'''
+ #self._qt_property_selected =
+ self._qt_dynamic_property_set(self.ALARM_SEV_MAJOR)
+ self.setStatusTip("{0} reports value in MAJOR alarm state!".format(self.pv_name))
+ self.qt_style_polish()
+
+ def qt_property_alarm_sev_minor(self, redraw=False):
+ '''Set Qt MINOR property value.'''
+ #self._qt_property_selected =
+ self._qt_dynamic_property_set(self.ALARM_SEV_MINOR)
+ self.setStatusTip("{0} reports value in MINOR alarm state!".format(self.pv_name))
+ self.qt_style_polish()
+
+ def qt_property_alarm_sev_no_alarm(self, redraw=False):
+ '''Set Qt READBACK_ALARM property value.'''
+ #self._qt_property_selected =
+ self._qt_dynamic_property_set(self.READBACK_ALARM)
+ self.setStatusTip("{0} reports value in normal state".format(self.pv_name))
+ self.qt_style_polish()
+
+ def qt_property_alarm_sev_invalid(self, redraw=False):
+ '''Set Qt INVALID property value.'''
+ #self._qt_property_selected =
+ self._qt_dynamic_property_set(self.ALARM_SEV_INVALID)
+ self.setStatusTip("PV={0} reports an INVALID value!".format(self.pv_name))
+ self.qt_style_polish()
+
+ def qt_property_static(self, redraw=False):
+ '''Set Qt STATIC property value.'''
+ self._qt_dynamic_property_set(self.READBACK_STATIC)
+ self.setStatusTip("PV={0} does not have an alarm state".format(self.pv_name))
+ self.qt_style_polish()
+
+ def qt_property_daq_stopped(self, redraw=False):
+ '''Set Qt STOPPED property value.'''
+ #self._qt_property_selected =
+ self._qt_dynamic_property_set(self.DAQ_STOPPED)
+ self.setStatusTip("PV={0} reports DAQ has stopped".format(self.pv_name))
+ self.qt_style_polish()
+
+
+ def qt_property_daq_paused(self, redraw=False):
+ '''Set Qt STOPPED property value.'''
+ #self._qt_property_selected =
+ self._qt_dynamic_property_set(self.DAQ_PAUSED)
+ self.setStatusTip("PV={0} reports DAQ has paused".format(self.pv_name))
+ self.qt_style_polish()
+
+ def qt_style_polish(self, redraw=False):
+ if redraw:
+ self.style().unpolish(self)
+ self.style().polish(self)
+ event=QEvent(QEvent.StyleChange)
+ QApplication.sendEvent(self, event);
+ self.update()
+ self.updateGeometry()
+ else:
+ #self.style().unpolish(self)
+ self.style().polish(self)
+ QApplication.processEvents()
+
+ def pv_status_text_header(self, source="Channel Access"):
+ _source = source
+ _source_separator = "----------------------------------------"
+ _text = """
+
+ Widget: {0} ({1}, {2})
+
+ """.format(self.widget_class, self.qt_object_name, self.color_mode)
+
+ if self.msg_press_value is not None:
+ _text += """
+ On press, sends value: {0}
+ """.format(self.msg_press_value, "DarkOrchid")
+
+ if self.msg_release_value is not None:
+ _text += """
+ On release, sends value: {0}
+ """.format(self.msg_release_value, "DarkOrchid")
+
+ if self.pv_within_daq_group:
+ if self.qt_object_name in (self.PV_DAQ_BS,):
+ _ds_color = "Navy Blue"
+ else:
+ _ds_color = "Black"
+ else:
+ _ds_color = "Black"
+
+ _text += """
+ {0}
+ Data source: {1}
+ {0}
+ PV: {2}
+ """.format(_source_separator, _source, self.pv_name, "DarkOrchid",
+ _ds_color)
+
+ if self.desc is None:
+ self.set_desc()
+
+ if self.desc == "":
+ _text += """
+ """
+ return _text
+
+ _text += """
+
+ Description: {6}
+
+ """.format(self.widget_class, self.qt_object_name, \
+ self.color_mode, _source_separator, _source, \
+ self.pv_name, self.desc, "DarkOrchid"
+ )
+ return _text
+
+ def pv_status_text_enum(self):
+
+ _val_enum = None
+ _value = self.pvd.value[0]
+ if isinstance(_value, str):
+ _val_enum = self.cafe.getEnumFromString(self.handle, _value)
+ elif _value is not None:
+ _val_enum = self.cafe.getStringFromEnum(self.handle, _value)
+
+ _color = "Blue"
+
+ #To catch case where channel is called by user
+
+
+ #To catch DAQ case
+ if self.pv_within_daq_group:
+ if self.qt_object_name in (self.PV_DAQ_BS):
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ self.DAQ_PAUSED,
+ self.DISCONNECTED):
+ _color = "White"
+ elif self.qt_object_name in (self.PV_DAQ_CA):
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ self.DISCONNECTED):
+ _color = "White"
+
+
+ elif not self.cafe.isConnected(self.handle):
+ _color = "White"
+ elif self.pvd.status == self.cyca.ICAFE_CA_OP_CONN_DOWN:
+ _color = "White"
+
+ _text = """
+
+ Value: {1} [{2}]
+ """.format(_color, _value , _val_enum
+ )
+
+ return _text
+
+ def pv_status_text_data(self):
+
+ _value_str = ""
+ _first_end = 9
+ _end_range = min(self.pvd.nelem, _first_end)
+ if _end_range > 1:
+ _value_str = "[ "
+ for i in range (0, _end_range):
+ _value = self.pvd.value[i]
+ if _value is None:
+ _value = '0'
+ if isinstance(_value, str):
+ _value_str += _value
+ elif isinstance(_value, int):
+ _value_str += str(_value)
+ else:
+ if self.pv_ctrl is not None:
+ _value_form = ("{:<.%sf}" % self.pv_ctrl.precision)
+ _value_str += _value_form.format(
+ round(_value, self.pv_ctrl.precision))
+ if i < (_end_range-1):
+ _value_str += " "
+
+ if self.pvd.nelem > _first_end:
+ _value_str += " ... "
+ _value = self.pvd.value[self.pvd.nelem-1]
+ if isinstance(_value, str):
+ _value_str += _value
+ elif isinstance(_value, int):
+ _value_str += str(value)
+ else:
+ if self.pv_ctrl is not None:
+ _value_form = ("{:<.%sf}" % self.pv_ctrl.precision)
+ _value_str += _value_form.format(
+ round(_value, self.pv_ctrl.precision))
+ _value_str += " "
+ if _end_range > 1:
+ _value_str += "]"
+
+ _color = "Blue"
+
+
+ #To catch DAQ case
+ if self.pv_within_daq_group:
+
+
+ if self.qt_object_name in (self.PV_DAQ_BS):
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ self.DAQ_PAUSED,
+ self.DISCONNECTED):
+ _color = "White"
+ elif self.qt_object_name in (self.PV_DAQ_CA):
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ self.DISCONNECTED):
+ _color = "White"
+
+ elif not self.cafe.isConnected(self.handle):
+ _color = "White"
+ elif self.pvd.status == self.cyca.ICAFE_CA_OP_CONN_DOWN:
+ _color = "White"
+
+ _text = """
+
+ Value: {1} {2}
+ """.format(_color, _value_str, self.units)
+
+ return _text
+
+
+ def pv_status_text_timestamp(self):
+ _status_not_ok_color = "IndianRed"
+ _status_ok_color = "DimGray"
+ _ts_color = "Blue"
+ _color = _status_ok_color
+
+
+ #To catch DAQ case
+ if self.pv_within_daq_group:
+ if self.qt_object_name in (self.PV_DAQ_BS):
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ self.DAQ_PAUSED,
+ self.DISCONNECTED):
+ _ts_color = "White"
+ _color = "White"
+ elif self.qt_object_name in (self.PV_DAQ_CA):
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ self.DISCONNECTED):
+ _ts_color = "White"
+ _color = "White"
+
+ elif not self.cafe.isConnected(self.handle):
+ _ts_color = "White"
+ elif self.pvd.status == self.cyca.ICAFE_CA_OP_CONN_DOWN:
+ _ts_color = "White"
+
+
+ if self.pvd.status != self.cyca.ICAFE_NORMAL:
+ _color = _status_not_ok_color
+ _text = """
+ Timestamp: {2}
+ Status: {3}
{4}
+ """.format( _ts_color, _color, self.pvd.tsDateAsString, \
+ self.pvd.statusAsString, \
+ self.cafe.getStatusInfo(self.pvd.status))
+
+ return _text
+
+
+ def pv_status_text_alarm(self):
+ _text ="""
+ """
+ _color = "DimGray"
+
+
+ #To catch DAQ case
+ if self.pv_within_daq_group:
+
+
+ if self.pvd.alarmSeverity == self.cyca.SEV_MINOR:
+ _color = "Yellow"
+ elif self.pvd.alarmSeverity == self.cyca.SEV_MAJOR:
+ _color = "Red"
+ elif self.pvd.alarmSeverity == self.cyca.SEV_INVALID:
+ _color = "White"
+
+ if self.qt_object_name in (self.PV_DAQ_BS):
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ self.DAQ_PAUSED,
+ self.DISCONNECTED):
+ _color = "White"
+ elif self.qt_object_name in (self.PV_DAQ_CA):
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ self.DISCONNECTED):
+ _color = "White"
+
+
+ elif not self.cafe.isConnected(self.handle):
+ _color = "White"
+
+ elif self.pvd.status == self.cyca.ICAFE_CA_OP_CONN_DOWN:
+ _color = "White"
+
+ elif self.pvd.alarmSeverity == self.cyca.SEV_MINOR:
+ _color = "Yellow"
+ elif self.pvd.alarmSeverity == self.cyca.SEV_MAJOR:
+ _color = "Red"
+ elif self.pvd.alarmSeverity == self.cyca.SEV_INVALID:
+ _color = "White"
+
+
+ _text += """
+ Alarm status: {1}
+ Alarm severity: {2}
+ """.format(_color, self.pvd.alarmStatusAsString,
+ self.pvd.alarmSeverityAsString)
+
+ return _text
+
+ def pv_access(self):
+ _accessIs = ""
+ if self.pv_info is None:
+ self.pv_info = self.cafe.getChannelInfo(self.handle)
+ if self.pv_info.accessRead:
+ _accessIs += "Read"
+ if self.pv_info.accessWrite:
+ _accessIs += "Write"
+ return _accessIs
+
+ def pv_status_text_enum_metadata(self):
+ _text = """
+ ENUM strings: {2}
+ Data type (native): {3}
+ Record type: {4}
+ RW Access: {5}
+ IOC: {6}
+ """.format( "MediumBlue", "DarkOrchid", self.pvc.enumStrings,
+ self.pv_info.dataTypeAsString,
+ self.record_type, self.pv_access(),
+ self.pv_info.hostName)
+ return _text
+
+ def pv_status_text_metadata(self):
+
+ if self.pv_info is None:
+ self.pv_info = self.cafe.getChannelInfo(self.handle)
+ if self.pv_info is not None and self.record_type is None:
+ if "Not Supported" in self.pv_info.className:
+ _rtype = self.cafe.get(self.pv_name.split(".")[0] + ".RTYP")
+ self.record_type = _rtype if _rtype is not None else self.pv_info.className
+ self.cafe.close(self.pv_name.split(".")[0] + ".RTYP")
+ else:
+ self.record_type = self.pv_info.className
+
+ if self.record_type in ["stringin", "stringout"]:
+ _text = """
+ Data type (native): {3}
+ Record type: {4}
+ RW Access: {5}
+ IOC: {6}
+ """.format("MediumBlue", self.pvd.nelem, self.pvc.precision,
+ self.pv_info.dataTypeAsString,
+ self.record_type, self.pv_access(),
+ self.pv_info.hostName)
+ return _text
+
+ _text = """
+ """
+ if self.pvd.nelem > 1:
+ _text += """
+ Nelem: {1}
+ """.format("MediumBlue", self.pvd.nelem)
+
+ _text += """
+ Precision (PV): {1}
+ Data type (native): {2}
+ Record type: {3}
+ RW Access: {4}
+ IOC: {5}
+ """.format("MediumBlue", self.pvc.precision,
+ self.pv_info.dataTypeAsString,
+ self.record_type, self.pv_access(),
+ self.pv_info.hostName)
+ return _text
+
+
+ def pv_status_text_alarm_limits(self, ):
+
+ if self.pv_info is None:
+ self.pv_info = self.cafe.getChannelInfo(self.handle)
+ if self.pv_info is not None and self.record_type is None:
+ if "Not Supported" in self.pv_info.className:
+ _rtype = self.cafe.get(self.pv_name.split(".")[0] + ".RTYP")
+ self.record_type = _rtype if _rtype is not None else self.pv_info.className
+ self.cafe.close(self.pv_name.split(".")[0] + ".RTYP")
+ else:
+ self.record_type = self.pv_info.className
+
+ _text ="""
+ """
+
+ #No all record types have alarms
+ #className is not supported at psi since introduction of the linux ca gateway
+ #Not Supported by Gateway
+
+ #self.pv_info.show()
+ #self.pv_ctrl.show()
+ #print(self.record_type)
+ #print(self._alarm_severity_record_types)
+
+ if "Not Supported" in str(self.record_type):
+ pass
+ elif self.record_type not in self._alarm_severity_record_types:
+ return _text
+
+ if self.pvc.lowerAlarmLimit == 0 and self.pvc.upperAlarmLimit == 0 and \
+ self.pvc.lowerWarningLimit == 0 and self.pvc.upperWarningLimit == 0:
+
+ return _text
+
+ if self.cafe.hasAlarmStatusSeverity(self.handle): # or "Not Supported" in self.pv_info.className:
+ _text = """
+ Lower/Upper alarm limit: {1} / {4}
+ Lower/Upper warning limit: {2} / {3}
+
+ """.format("MediumBlue",
+ self.pvc.lowerAlarmLimit, self.pvc.lowerWarningLimit,
+ self.pvc.upperWarningLimit, self.pvc.upperAlarmLimit)
+ return _text
+
+ def pv_status_text_display_limits(self):
+ _text ="""
+ """
+ if self.pvc.lowerDisplayLimit == 0 and self.pvc.upperDisplayLimit == 0 and \
+ self.pvc.lowerControlLimit == 0 and self.pvc.upperControlLimit == 0:
+ return _text
+ _text = """
+ Lower/Upper control limit: {3} / {4}
+ Lower/Upper display limit: {1} / {2}
+
+ """.format("MediumBlue",
+ self.pvc.lowerDisplayLimit, self.pvc.upperDisplayLimit,
+ self.pvc.lowerControlLimit, self.pvc.upperControlLimit)
+ return _text
+
+
+
+
+ def pv_status_text(self):
+ '''pv metadata to accompany widget's dialog box.'''
+ QApplication.processEvents()
+ _source = "Channel Access"
+
+ if self.pv_within_daq_group:
+ if self.qt_object_name == self.PV_DAQ_BS:
+ _source = "DAQ (Beam Synchronous)"
+ #self.pvd written to in receive_daq_update
+ elif self.qt_object_name == self.PV_DAQ_CA:
+ _source = "DAQ (Channel Access)"
+ self.pvd = self.cafe.getPVCache(self.handle)
+ if self.pvd.pulseID > 0:
+ _source += "
Pulse ID: {0}".format(self.pvd.pulseID)
+ else:
+ self.pvd = self.cafe.getPVCache(self.handle)
+
+ ##For testing...
+ ##self.pvd.status = self.cyca.ICAFE_CA_OP_CONN_DOWN
+ ##self.pvd.statusAsString = 'ICAFE_CA_OP_CONN_DOWN'
+ #i, pvd, pvc = self.cafe.getChannelDataStore(self.handle)
+ self.pvc = self.cafe.getCtrlCache(self.handle)
+ #self.pvc.show()
+
+ _text_data ="""
+ """
+
+ if self.pvd.status == self.cyca.ECAFE_INVALID_HANDLE:
+ _text_data = """ Status: {1}
{2}
+ """.format("Blue", "Channel closed while DAQ in STOP state.",
+ "PV info requires DAQ to be in RUN/PAUSED state" )
+
+
+ elif self.pvd.status == self.cyca.ICAFE_CS_NEVER_CONN:
+ _text_data = """ Status: {1}
{2}
+ """.format("Red", self.pvd.statusAsString, self.cafe.getStatusInfo(self.pvd.status))
+
+ elif self.pvc.noEnumStrings > 0:
+ _text_data = self.pv_status_text_enum() + \
+ self.pv_status_text_timestamp() + \
+ self.pv_status_text_alarm() + \
+ self.pv_status_text_enum_metadata()
+
+ else:
+ _text_data = self.pv_status_text_data()+ \
+ self.pv_status_text_timestamp() + \
+ self.pv_status_text_alarm() + \
+ self.pv_status_text_metadata() + \
+ self.pv_status_text_alarm_limits() + \
+ self.pv_status_text_display_limits()
+
+ self.pv_message_in_a_box.setText(
+ self.pv_status_text_header(source=_source) + _text_data
+ )
+ QApplication.processEvents()
+ self.pv_message_in_a_box.exec()
+
+
+ def lookup_archiver(self):
+ '''Plot pvdata from archiver.'''
+ #"https://ui-data-api.psi.ch/prepare?channel = sf-archiverappliance/"
+ urlIs = self.url_archiver
+ urlIs = urlIs + self.pv_name
+
+ if not QDesktopServices.openUrl(QUrl(urlIs)): #, QUrl.TolerantMode)
+ if self.showMessage is not None:
+ self.showMessage(MsgSeverity.ERROR, __pymodule__, _line(),
+ "Failed to open URL {0}".format(urlIs))
+
+ def lookup_databuffer(self):
+ '''Plot beam synchronous pvdata from databuffer.'''
+ #""https://ui-data-api.psi.ch/prepare?channel = sf-databuffer/"
+ urlIs = self.url_databuffer
+ urlIs = urlIs + self.pv_name
+
+ if not QDesktopServices.openUrl(QUrl(urlIs)): #, QUrl.TolerantMode)
+ if self.showMessage is not None:
+ self.showMessage(MsgSeverity.ERROR, __pymodule__, _line(),
+ "Failed to open URL {0}".format(urlIs))
+ QApplication.processEvents()
+
+ def strip_chart(self):
+ '''PShell strip chart.'''
+ configStr = "-config = [[[true,\"" + self.pv_name + "\",\"Channel\",1,1]]]"
+ commandStr = "/sf/op/bin/strip_chart"
+ argStr = ["-nlaf", "-start", configStr, "&"]
+ QProcess.startDetached(commandStr, argStr)
+
+
+ def display_parameters(self):
+ display_wgt = QDialog(self)
+
+ _rect = display_wgt.geometry() #get current geometry of help window
+ _parentRect = self.context_menu.geometry() # QRect(100, 1000, 640, 480) #get current geometry of this window
+ #print(_rect, _parentRect)
+ _rect.moveTo(display_wgt.mapToGlobal(
+ QPoint(_parentRect.x() + _parentRect.width() - _rect.width(),
+ _parentRect.y())))
+
+ display_wgt.setGeometry(_rect)
+
+ #This has no effect
+ #display_wgt.setWindowModality(Qt.WindowModal) #Qt.ApplicationModal Qt.ApplicationModal
+ display_wgt.setWindowTitle(self.pv_name)
+ layout = QVBoxLayout()
+ #print("sender==================>", self.sender(), self)
+ #self.the_gw = self
+ #print("getNativeDataType", self.cafe.getDataTypeNative(self.handle))
+
+ precision_flag = True
+ if self.pv_ctrl is not None:
+ if self.pv_ctrl.precision <= 0:
+ precision_flag = False
+ if self.cafe.getDataTypeNative(self.handle) in (
+ self.cyca.CY_DBR_FLOAT, self.cyca.CY_DBR_DOUBLE) and precision_flag:
+ #precision user
+ _hbox_wgt = QWidget()
+ _hbox = QHBoxLayout()
+ precision_user_label = QLabel("Precision (user):")
+ self.precision_user_wgt = QSpinBox(self)
+ self.precision_user_wgt.setFocusPolicy(Qt.NoFocus)
+ self.precision_user_wgt.setValue(int(self.precision))
+ if self.pv_ctrl is not None:
+ _max = self.pv_ctrl.precision
+ else:
+ _max = 6
+ self.precision_user_wgt.setMaximum(_max)
+ self.precision_user_wgt.valueChanged.connect(
+ self.precision_user_changed)
+ _hbox.addWidget(precision_user_label)
+ _hbox.addWidget(self.precision_user_wgt)
+ _hbox_wgt.setLayout(_hbox)
+
+ precision_user_label.setFixedWidth(110)
+ self.precision_user_wgt.setFixedWidth(35)
+ _hbox_wgt.setFixedWidth(160)
+
+ #precision ioc
+ _hbox2_wgt = QWidget()
+ _hbox2 = QHBoxLayout()
+ precision_ioc_label = QLabel("Precision (ioc): ")
+ precision_ioc = QPushButton(self)
+ precision_ioc.setText(" {} ".format(_max))
+ precision_ioc.clicked.connect(self.precision_ioc_reset)
+
+ _hbox2.addWidget(precision_ioc_label)
+ _hbox2.addWidget(precision_ioc)
+ _hbox2_wgt.setLayout(_hbox2)
+
+ precision_ioc_label.setFixedWidth(110)
+ precision_ioc.setFixedWidth(20)
+ _hbox2_wgt.setFixedWidth(145)
+
+ layout.addWidget(_hbox_wgt)
+ layout.addWidget(_hbox2_wgt)
+
+ #precision refresh rate
+ _hbox3_wgt = QWidget()
+ _hbox3 = QHBoxLayout()
+ refresh_freq_label = QLabel("Refresh rate: ")
+ _default_refresh_val = 0 if self.notify_freq_hz_default <= 0 else \
+ self.notify_freq_hz_default
+
+ self.refresh_freq_combox_idx_dict = {0:0, 1:10, 2:5, 3:2, 4:1, 5:0.5,
+ 6:_default_refresh_val}
+ refresh_freq = QComboBox(self)
+ refresh_freq.addItem('direct')
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[1]))
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[2]))
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[3]))
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[4]))
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[5]))
+
+ _default_text = 'default (direct)' if _default_refresh_val == 0 else \
+ 'default ({0} Hz)'.format(self.refresh_freq_combox_idx_dict[6])
+
+ refresh_freq.addItem(_default_text)
+
+
+ for key, value in self.refresh_freq_combox_idx_dict.items():
+ if value == self.notify_freq_hz:
+ refresh_freq.setCurrentIndex(key)
+ break
+ refresh_freq.currentIndexChanged.connect(self.refresh_rate_changed)
+
+
+ _hbox3.addWidget(refresh_freq_label)
+ _hbox3.addWidget(refresh_freq)
+ _hbox3_wgt.setLayout(_hbox3)
+
+ refresh_freq_label.setFixedWidth(110)
+ refresh_freq.setFixedWidth(115)
+ _hbox3_wgt.setFixedWidth(235)
+
+ layout.addWidget(_hbox3_wgt)
+
+ layout.setAlignment(Qt.AlignLeft)
+ layout.setContentsMargins(10, 0, 0, 0)
+ layout.setSpacing(0)
+
+ display_wgt.setMinimumWidth(340)
+ display_wgt.setLayout(layout)
+
+ display_wgt.exec()
+ QApplication.processEvents()
+
+ def precision_ioc_reset(self):
+ if self.pv_ctrl is not None:
+ self.precision_user = self.pv_ctrl.precision
+ self.precision = self.pv_ctrl.precision
+ if self.precision is not None:
+ self.precision_user_wgt.setValue(self.precision)
+ #self.precision_user_changed(self.precision)
+ #_value = self.cafe.getCache(self.handle)
+ #self.trigger_monitor_float.emit(_value, 1, 0)
+
+ def precision_user_changed(self, new_value):
+ self.precision_user = new_value
+ self.precision = new_value
+
+ _pvd = self.cafe.getPVCache(self.handle)
+
+ if _pvd.value[0] is not None:
+ if isinstance(_pvd.value[0], float):
+ self.trigger_monitor_float.emit(
+ _pvd.value[0], _pvd.status, _pvd.alarmSeverity)
+
+ '''
+ _value = self.cafe.getCache(self.handle)
+ #print("widget", self.widget, self.widget.sender())
+ if _value is not None:
+ #self.post_display_value(_value)
+ self.trigger_monitor_float.emit(_value, 1, 0)
+ '''
+
+ def refresh_rate_changed(self, new_idx):
+ _notify_freq_hz = self.refresh_freq_combox_idx_dict[new_idx]
+ self.notify_milliseconds = 0 if _notify_freq_hz == 0 else \
+ 1000 / _notify_freq_hz
+ self.notify_freq_hz = _notify_freq_hz
+
+ if self.notify_unison:
+ self.notify_unison = False
+ self.monitor_stop()
+ self.monitor_start()
+
+ else:
+ self.cafe.updateMonitorPolicyDeltaMS(self.handle,
+ self.monitor_id,
+ self.notify_milliseconds)
+
+ #https://doc.qt.io/qt-5.9/qtwidgets-mainwindows-menus-example.html
+ #Since Qt5 this has to be implemented in order to avoid the Select All dialogue button appearing..
+ def contextMenuEvent(self, event):
+ return
+
+ def showContextMenu(self):
+ self.context_menu.exec(QCursor.pos())
+
+ def mousePressEvent(self, event):
+ '''Action on mouse press event.'''
+ button = event.button()
+ if button == Qt.RightButton:
+ #contextMenu.exec(event.globalPos())
+ self.context_menu.exec(QCursor.pos())
+ self.clearFocus()
+
+ def mouseReleaseEvent(self, event):
+ event.ignore()
+
diff --git a/pvgateway.py-- b/pvgateway.py--
new file mode 100644
index 0000000..2d6d64d
--- /dev/null
+++ b/pvgateway.py--
@@ -0,0 +1,1690 @@
+"""
+The module provides data and metadata of a process variable through
+PyCafe.
+"""
+__author__ = 'Jan T. M. Chrin'
+
+import copy
+from enum import IntEnum
+import inspect
+import time
+
+from distutils.version import LooseVersion
+
+from qtpy.QtCore import (QEvent, QMutex, QPoint, QProcess, QSettings, Qt, QUrl,
+ Signal)
+from qtpy.QtCore import __version__ as QT_VERSION_STR
+from qtpy.QtGui import QCursor, QDesktopServices, QFont
+from qtpy.QtWidgets import (QAction, QApplication, QComboBox, QDialog,
+ QHBoxLayout, QLabel, QMenu, QMessageBox,
+ QPushButton, QSpinBox, QVBoxLayout, QWidget)
+
+def __LINE__():
+ return inspect.currentframe().f_back_f_lineno
+
+class DAQState(IntEnum):
+ BS = 10
+ CA = 20
+ BS_STOP = 30
+ CA_STOP = 40
+ BS_PAUSE = 50
+ CA_PAUSE = 60
+
+class PVGateway(QWidget):
+ """Retrieves pv metadata through PyCafe.
+
+ The PVGateway class when subclassed by Qt widgets enables their
+ connectivity to channel access.
+
+ Attributes:
+ monid: (int) Monitor id
+ units : (str) Units associated with the pv
+
+ trigger_monitor_ is the signal triggered by updates
+ arising from monitored pvs.
+ trigger_connect is the signal triggered from changes in pv
+ connection status.
+ widget_handle_dict is a dictionary mapping widgets to their pv
+ handle.
+ A pv handle may be associated to more than one widget.
+ """
+ trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_int = Signal(int, int, int)
+ trigger_monitor_str = Signal(str, int, int)
+ trigger_monitor = Signal(object, int) #pvdata, status
+
+
+ trigger_connect = Signal(int, str, int)
+
+ trigger_daq = Signal(object, str, int)
+ trigger_daq_int = Signal(object, str, int)
+ trigger_daq_str = Signal(object, str, int)
+
+ #Properties, user supplied
+ ACT_ON_BEAM = 'actOnBeam'
+ NOT_ACT_ON_BEAM = 'notActOnBeam'
+ READBACK_ALARM = 'alarm'
+ READBACK_STATIC = 'static'
+
+ #Properties, dynamic
+ DISCONNECTED = 'disconnected'
+ ALARM_SEV_MINOR = 'alarmSevMinor'
+ ALARM_SEV_MAJOR = 'alarmSevMajor'
+ ALARM_SEV_INVALID = 'alarmSevInvalid'
+ ALARM_SEV_NO_ALARM = READBACK_ALARM
+ DAQ_STOPPED = 'stopped'
+ DAQ_PAUSED = 'paused'
+
+ #ObjectName, defined by CAQ
+ PV_CONTROLLER = "Controller"
+ PV_READBACK = "Readback"
+ PV_DAQ_BS = "BSRead"
+ PV_DAQ_CA = "CARead"
+
+ _DAQ_CAFE_SG_NAME = "gBS2CA"
+
+ _alarm_severity_record_types = ["ai", "ao", "calc", "calcout", "dfanout",
+ "longin", "longout", "pid", "sel",
+ "steppermotor", "sub"]
+
+ #parent is Gui
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode=None,
+ show_units: bool = False, prefix: str = "", suffix: str = "",
+ connect_callback=None, msg_label: str = "",
+ connect_triggers: bool = True, notify_freq_hz: int = 0,
+ notify_unison: bool = False, precision: int = 0,
+ monitor_dbr_time: bool = False):
+
+ super().__init__()
+
+ if parent is None:
+ return
+
+ if not pv_name:
+ return
+
+ self.connect_callback = connect_callback
+ self.notify_freq_hz = abs(notify_freq_hz)
+ self.notify_freq_hz_default = self.notify_freq_hz
+
+ self.notify_milliseconds = 0 if self.notify_freq_hz == 0 else \
+ 1000 / self.notify_freq_hz
+
+ self.notify_unison = bool(notify_unison) and bool(self.notify_freq_hz)
+
+ self.parent = parent
+ self.settings = self.parent.settings
+
+ self.pv_name = pv_name
+
+ self.color_mode = None
+
+ if color_mode is not None:
+ if color_mode in (self.ACT_ON_BEAM,
+ self.NOT_ACT_ON_BEAM,
+ self.READBACK_ALARM,
+ self.READBACK_STATIC):
+ self.color_mode = color_mode
+
+ self.color_mode_requested = self.color_mode
+
+ if monitor_callback is not None:
+ self.monitor_callback = monitor_callback
+ else:
+ self.monitor_callback = None
+
+ self.pv_within_daq_group = pv_within_daq_group
+
+ self.show_units = show_units
+ self.prefix = prefix
+ self.suffix = suffix
+
+ self.cafe = self.parent.cafe
+ self.cyca = self.parent.cyca
+
+ if self.parent.settings is not None:
+ self.url_archiver = self.parent.settings.data["url"]["archiver"]
+ self.url_databuffer = self.parent.settings.data["url"]["databuffer"]
+ self.bg_readback = self.parent.settings.data["StyleGuide"][
+ "bgReadback"]
+ self.fg_alarm_major = self.parent.settings.data["StyleGuide"][
+ "fgAlarmMajor"]
+ self.fg_alarm_minor = self.parent.settings.data["StyleGuide"][
+ "fgAlarmMinor"]
+ self.fg_alarm_invalid = self.parent.settings.data["StyleGuide"][
+ "fgAlarmInvalid"]
+ self.fg_alarm_noalarm = self.parent.settings.data["StyleGuide"][
+ "fgAlarmNoAlarm"]
+ else:
+ #self.settings = ReadJSON(self.parent.appname)
+ self.url_archiver = ("https://ui-data-api.psi.ch/prepare?channel=" +
+ "sf-archiverappliance/")
+ self.url_databuffer \
+ = "https://ui-data-api.psi.ch/prepare?channel=sf-databuffer/"
+
+ self.daq_group_name = self._DAQ_CAFE_SG_NAME
+ self.desc = None
+ self.handle = None
+ self.initialize_complete = False
+ self.initialize_again = False
+
+ self.msg_label = msg_label
+ self.msg_press_value = None
+ self.msg_release_value = None
+
+ self.monitor_id = None
+ self.monitor_dbr_time = monitor_dbr_time
+ self.mutex_post_display = QMutex()
+
+ self.precision_user = precision
+ self.has_precision_user = bool(precision)
+ self.precision_pv = 3
+
+ self.precision = (self.precision_user if self.has_precision_user else
+ self.precision_pv)
+
+ self.pvd = None
+ self.pv_ctrl = None
+ self.pv_info = None
+ self.record_type = None
+
+ #if 'show_log_message' in dir(self.parent):
+ # self.show_log_message = self.parent.show_log_message
+ #else:
+ # self.show_log_message = None
+
+ self.qt_object_name = None
+
+ self.qt_property_controller = {
+ self.DISCONNECTED: False,
+ self.ACT_ON_BEAM: False, self.NOT_ACT_ON_BEAM: False
+ }
+
+ self.qt_property_readback = {
+ self.DISCONNECTED: False,
+ self.READBACK_ALARM: False, self.READBACK_STATIC: False,
+ self.ALARM_SEV_MINOR: False, self.ALARM_SEV_MAJOR: False,
+ self.ALARM_SEV_INVALID: False
+ }
+
+ self.qt_property_daq_bs = {
+ self.DISCONNECTED: False,
+ self.READBACK_ALARM: False, self.READBACK_STATIC: False,
+ self.ALARM_SEV_MINOR: False, self.ALARM_SEV_MAJOR: False,
+ self.ALARM_SEV_INVALID: False,
+ self.DAQ_STOPPED: False, self.DAQ_PAUSED: False
+ }
+
+ self.qt_property_daq_ca = {
+ self.DISCONNECTED: False,
+ self.READBACK_ALARM: False, self.READBACK_STATIC: False,
+ self.ALARM_SEV_MINOR: False, self.ALARM_SEV_MAJOR: False,
+ self.ALARM_SEV_INVALID: False,
+ self.DAQ_STOPPED: False, self.DAQ_PAUSED: False
+ }
+
+ self.qt_object_to_property = {
+ self.PV_CONTROLLER: self.qt_property_controller,
+ self.PV_READBACK: self.qt_property_readback,
+ self.PV_DAQ_BS: self.qt_property_daq_bs,
+ self.PV_DAQ_CA: self.qt_property_daq_ca
+ }
+
+ self._qt_property_selected = {}
+
+ self.status_tip = None
+ self.suggested_text = ""
+ self.time_monotonic = time.monotonic()
+ self.pvd_previous = None
+ self.timeout = 0.2
+ self.units = ""
+
+ self.widget = self
+
+ _widget_name_part = str(self.widget.__class__).split("\'")[1].split(".")
+ #_widget_class_part = _widget_name_part[1].split(".")
+ self.widget_class = _widget_name_part[len(_widget_name_part)-1]
+
+ if pv_within_daq_group:
+ self.trigger_daq_int.connect(self.receive_daq_update)
+ self.trigger_daq.connect(self.receive_daq_update)
+ self.trigger_daq_str.connect(self.receive_daq_update)
+
+ elif connect_triggers:
+ self.trigger_monitor.connect(self.receive_monitor_dbr_time)
+ self.trigger_monitor_str.connect(self.receive_monitor_update)
+ self.trigger_monitor_int.connect(self.receive_monitor_update)
+ self.trigger_monitor_float.connect(self.receive_monitor_update)
+ self.trigger_connect.connect(self.receive_connect_update)
+
+ self.context_menu = QMenu()
+ self.context_menu.setObjectName("contextMenu")
+ self.context_menu.setWindowModality(Qt.NonModal) #ApplicationModal
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.0"):
+ self.context_menu.addSection("PV: {0}".format(self.pv_name))
+
+ action1 = QAction("Text Info", self)
+ action1.triggered.connect(self.pv_status_text)
+
+ action2 = QAction("Lookup in Archiver", self)
+ action2.triggered.connect(self.lookup_archiver)
+
+ action3 = QAction("Lookup in Databuffer", self)
+ action3.triggered.connect(self.lookup_databuffer)
+
+ action4 = QAction("Strip Chart (PShell)", self)
+ action4.triggered.connect(self.strip_chart)
+
+ action6 = QAction("Configure Display Parameters", self)
+ action6.triggered.connect(self.display_parameters)
+
+ self.context_menu.addAction(action1)
+ self.context_menu.addAction(action2)
+ self.context_menu.addAction(action3)
+ self.context_menu.addAction(action4)
+
+ action5 = QAction("Reconnect: {0}".format(self.pv_name), self)
+ action5.triggered.connect(self.reconnect_channel)
+ _font = QFont()
+ _font.setPixelSize(12)
+ action5.setFont(_font)
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.context_menu.addSection("")
+
+ #return action6 and 5 code here eventually
+ self.context_menu.addAction(action6)
+ self.context_menu.addAction(action5)
+
+ self.pv_message_in_a_box = QMessageBox()
+ self.pv_message_in_a_box.setObjectName("pvinfo")
+
+ #Qt.ApplicationModal not used as it blocks input to all windows
+ self.pv_message_in_a_box.setWindowModality(Qt.NonModal)
+ self.pv_message_in_a_box.setIcon(QMessageBox.Information)
+ self.pv_message_in_a_box.setStandardButtons(QMessageBox.Close)
+ self.pv_message_in_a_box.setDefaultButton(QMessageBox.Close)
+
+ self.initialize()
+
+ #return self - previously used by pvgateway
+
+
+ def initialize(self):
+ '''Initialze class attributes and connect to ca if required.'''
+
+ _handle_within_group_flag = False
+ if self.pv_within_daq_group:
+ self.handle = self.cafe.getHandleFromPVWithinGroup(
+ self.pv_name, self.daq_group_name)
+ if self.handle > 0:
+ self.cafe.addWidget(self.handle, self.widget)
+ _handle_within_group_flag = True
+ #Callback already invoked to emit signal here!!
+ _channel_info = self.cafe.getChannelInfo(self.handle)
+
+ #wgts = self.cafe.getWidgets(self.handle)
+
+ self.trigger_connect.emit(
+ int(self.handle), str(self.pv_name),
+ int(_channel_info.cafeConnectionState))
+ #In case user is misinformed
+ if not _handle_within_group_flag:
+ self.handle = self.cafe.getHandleFromPV(self.pv_name)
+ if self.connect_callback is None:
+ self.connect_callback = self.py_connect_callback
+
+ if self.handle > 0:
+ #The second time round, widget is gateway rather than parent,
+ #Why is that?
+ self.cafe.setPyConnectCallbackFn(self.handle,
+ self.connect_callback)
+
+ self.cafe.addWidget(self.handle, self.widget)
+
+ _channel_info = self.cafe.getChannelInfo(self.handle)
+ self.trigger_connect.emit(
+ self.handle, self.pv_name,
+ int(_channel_info.cafeConnectionState))
+
+ else:
+ self.cafe.openPrepare()
+ self.handle = self.cafe.open(self.pv_name,
+ self.connect_callback)
+ self.cafe.addWidget(self.handle, self.widget)
+ self.cafe.openNowAndWait(self.timeout, self.handle)
+
+ self.initialize_meta_data()
+
+ self.pv_message_in_a_box.setWindowTitle(self.pv_name)
+
+
+ def initialize_meta_data(self):
+
+ _current_value = ""
+
+ if self.cafe.isConnected(self.handle) and \
+ self.cafe.initCallbackComplete(self.handle):
+
+ if self.pvd is None:
+ self.pvd = self.cafe.getPVCache(self.handle)
+
+ if self.pv_ctrl is None:
+ self.pv_ctrl = self.cafe.getCtrlCache(self.handle)
+ self.set_precision_and_units()
+
+ if self.pv_info is None:
+ self.pv_info = self.cafe.getChannelInfo(self.pv_name)
+ if "Not Supported" in self.pv_info.className:
+ _rtype = self.cafe.get(self.pv_name.split(".")[0] + ".RTYP")
+ self.record_type = _rtype if _rtype is not None else \
+ self.pv_info.className
+ _rtype = self.cafe.close(self.pv_name.split(".")[0] +
+ ".RTYP")
+ else:
+ self.record_type = self.pv_info.className
+
+ _current_value = self.cafe.getCache(self.handle)
+ if isinstance(_current_value, (int, float)):
+ #space for positive numbers
+ _value_form = ("{:<+.%sf}" % self.precision)
+ _current_value = _value_form.format(
+ round(_current_value, self.precision))
+
+ #Reset
+ self.initialize_complete = True
+
+ #verify user input
+ if self.show_units is True:
+ if self.suffix == self.units and self.units != "":
+ self.show_units = False
+
+ self.suggested_text = self.prefix
+ if self.prefix:
+ self.suggested_text += " "
+
+ _suggested_text_from_value = " "
+
+ _max_control_abs = 0
+
+ if self.pv_ctrl is not None:
+ _lower_control_abs = abs(int(self.pv_ctrl.lowerControlLimit))
+ _upper_control_abs = abs(int(self.pv_ctrl.upperControlLimit))
+ _max_control_abs = max(_lower_control_abs, _upper_control_abs)
+ if _max_control_abs is None:
+ _max_control_abs = 0
+
+ _enum_list = self.pv_ctrl.enumStrings
+
+ if _enum_list:
+ _enum_list_member_max_length = 0
+ _enum_list_member_max_index = 0
+
+ for i in range(0, len(_enum_list)):
+ if len(_enum_list[i]) > _enum_list_member_max_length:
+ _enum_list_member_max_length = len(_enum_list[i])
+ _enum_list_member_max_index = i
+ _suggested_text_from_value += \
+ _enum_list[_enum_list_member_max_index] + "."
+ else:
+ if self.pv_ctrl.lowerControlLimit < 0:
+ _suggested_text_from_value += "-"
+ _suggested_text_from_value += str(_max_control_abs) + "."
+
+ self.precision = min(9, self.precision) #safety net
+ for i in range(0, self.precision):
+ _suggested_text_from_value += "0"
+
+ if len(_current_value) > len(_suggested_text_from_value):
+ _suggested_text_from_value = _current_value
+
+ self.suggested_text += _suggested_text_from_value
+
+ if self.show_units:
+ self.suggested_text += " " + self.units
+ self.suggested_text += self.suffix
+
+ _suggested_text_length = len(self.suggested_text)
+ self.suggested_text = self.suggested_text.center(
+ _suggested_text_length+2)
+
+ self.max_control_abs_str = str(_max_control_abs)
+
+ _max_control_abs_length = len(self.max_control_abs_str)
+ _offset = 9
+ self.max_control_abs_str = self.max_control_abs_str.center(
+ _max_control_abs_length + _offset)
+
+ qsettings = QSettings()
+ qsettings.beginGroup("Widget")
+ qsettings.beginGroup(self.pv_name)
+ qsettings.beginGroup(self.widget_class)
+
+ _var_text = "suggested_text"
+ _ctrl_abs = "max_control_abs_str"
+
+ if self.cafe.isConnected(self.handle) and \
+ self.cafe.initCallbackComplete(self.handle):
+ qsettings.setValue(_var_text, self.suggested_text)
+ qsettings.setValue(_ctrl_abs, self.max_control_abs_str)
+ else:
+ if qsettings.value(_var_text) is not None:
+ self.suggested_text = qsettings.value(_var_text)
+ if qsettings.value(_ctrl_abs) is not None:
+ self.max_control_abs_str = qsettings.value(_ctrl_abs)
+
+ qsettings.endGroup()
+ qsettings.endGroup()
+ qsettings.endGroup()
+
+
+ def is_initialize_complete(self):
+ icount = 0
+ while not self.initialize_complete:
+ time.sleep(0.01)
+ self.initialize_meta_data()
+ icount += 1
+ if icount > 50:
+ return False
+ return True
+
+ def cleanup(self, close_pv=True):
+ '''Clean up the widget.'''
+
+ #Make sure mon id is valid
+ if self.handle > 0:
+ _monID_list = self.cafe.getMonitorIDs(self.handle)
+ if self.monitor_id in _monID_list:
+ self.cafe.monitorStop(self.handle, self.monitor_id)
+
+ #Do not close of there are other monitors
+ if self.cafe.getNoMonitors(self.handle) > 0:
+ if close_pv is True:
+ self.cafe.close(self.pv_name)
+ self.widget.deleteLater()
+
+
+ def format_display_value(self, value):
+
+ if value is None:
+ print(self, self.pv_name, ">>>>format_display_value is None")
+ #return
+
+ if isinstance(value, str):
+ _value_str = value
+ elif isinstance(value, int):
+ _value_str = str(value)
+ else:
+ _value_form = ("{:< .%sf}" % self.precision)
+ _rounded_value = round(value, self.precision)
+ _value_str = _value_form.format(_rounded_value)
+
+ if self.show_units:
+ _value_str += " " + self.units + " "
+ if self.suffix:
+ _value_str += " " + self.suffix + " "
+
+ if self.prefix:
+ _space = ""
+ if self.pv_ctrl is not None:
+ if self.pv_ctrl.lowerDisplayLimit < 0:
+ _space = " "
+ _value_str = self.prefix + _space + _value_str
+
+ return _value_str
+
+ def post_display_value(self, value):
+
+ _value_str = self.format_display_value(value)
+
+ if "setText" in dir(self):
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+ self.setText(_value_str)
+ self.blockSignals(False)
+ else:
+ self.setText(_value_str)
+
+ else:
+ print("setText method does not exist for this widget class:\n",
+ self.widget.__class__)
+ print("sender was: ", self.sender())
+
+
+ def py_connect_callback(self, handle, pvname, status):
+ '''Callback function to be invoked on change of pv connection status.
+ Checks for existence of widget. Waits up to a maximun of 100 ms.
+ '''
+ pv_name = pvname
+ self.trigger_connect.emit(int(handle), str(pv_name), int(status))
+
+ def receive_connect_update(self, handle, pv_name, status,
+ post_display=True):
+ '''Triggered by connect signal. For Widget to overload.'''
+
+ if pv_name is not None:
+ if pv_name != self.pv_name:
+ print(("pv_name {0} in receive_connect_update " +
+ "does not match: {1}").format(pv_name, self.pv_name))
+
+ if status == self.cyca.ICAFE_CS_CONN:
+ self.initialize_connect = True
+ self.pv_ctrl = self.cafe.getCtrlCache(self.handle)
+ self.pv_info = self.cafe.getChannelInfo(self.handle)
+ if self.pv_info is not None and self.record_type is None:
+ if "Not Supported" in self.pv_info.className:
+ _rtype = self.cafe.get(self.pv_name.split(".")[0] + ".RTYP")
+ self.record_type = _rtype if _rtype is not None else \
+ self.pv_info.className
+ _rtype = self.cafe.close(self.pv_name.split(".")[0] +
+ ".RTYP")
+ else:
+ self.record_type = self.pv_info.className
+
+ self.set_precision_and_units(reconnectFlag=True)
+
+ if not self.msg_label:
+ _value = self.cafe.getCache(handle, dt='native')
+ #Another reconnection in progress!!!
+
+ if _value is None:
+ return
+ else:
+ _value = self.msg_label
+
+ if post_display:
+ self.post_display_value(_value)
+ self.qt_property_reconnect()
+
+ else:
+ self.qt_property_disconnect()
+
+
+ if status == self.cyca.ICAFE_CS_CLOSED:
+ self.initialize_again = True
+
+ elif self.initialize_again:
+ #monitos_id informs whether or not widget has a monitor
+ #CAQMessageButton for instance does not have a monitor
+
+ if not self.pv_within_daq_group and self.monitor_id is not None:
+ self.monitor_start()
+ self.initialize_again = False
+
+ return
+
+
+ def receive_daq_update(self, daq_pvd, daq_mode, daq_state):
+ ''' DAQ mode is widget specific.
+ DAQ may be in BS mode, but channels within DAQ stream that
+ are not BS enabled will be flagged as CA Mode, i.e., CARead
+ '''
+
+ _current_qt_dynamic_property = self.qt_dynamic_property_get()
+
+ alarm_severity = daq_pvd.alarmSeverity
+ self.pvd = daq_pvd
+
+ if daq_mode != self.qt_object_name:
+ self.qt_object_name = daq_mode
+ self.setObjectName(self.qt_object_name)
+ self.qt_style_polish()
+
+ if daq_state in (self.cyca.ICAFE_DAQ_STOPPED,):
+ if _current_qt_dynamic_property != self.DAQ_STOPPED:
+ self.qt_property_daq_stopped()
+
+ elif daq_state in (self.cyca.ICAFE_DAQ_PAUSED,):
+ if _current_qt_dynamic_property != self.DAQ_PAUSED:
+ self.qt_property_daq_paused()
+
+ elif daq_state in (self.cyca.ICAFE_DAQ_RUN,):
+ if daq_mode == self.PV_DAQ_BS and \
+ _current_qt_dynamic_property != self.READBACK_STATIC:
+ self.qt_property_static()
+
+ elif daq_mode == self.PV_DAQ_CA:
+ if self.color_mode != self.color_mode_requested:
+ self.color_mode = self.color_mode_requested
+
+ if self.cafe.isEnum(self.handle) and \
+ isinstance(daq_pvd.value[0], int):
+ _value = self.cafe.getStringFromEnum(self.handle,
+ daq_pvd.value[0])
+ else:
+ _value = daq_pvd.value[0]
+
+ if daq_pvd.status == self.cyca.ICAFE_NORMAL:
+ if self.msg_label == "":
+ self.post_display_value(_value)
+ if daq_mode == self.PV_DAQ_BS:
+ return
+
+ #Check if color settings are correct
+ if alarm_severity > self.cyca.SEV_NO_ALARM:
+ self.color_mode = self.READBACK_ALARM
+ self.color_mode_requested = self.READBACK_ALARM
+
+ if self.color_mode == self.READBACK_ALARM:
+ if alarm_severity == self.cyca.SEV_MINOR:
+ if _current_qt_dynamic_property != self.ALARM_SEV_MINOR:
+ self.qt_property_alarm_sev_minor()
+
+ elif alarm_severity == self.cyca.SEV_MAJOR:
+ if _current_qt_dynamic_property != self.ALARM_SEV_MAJOR:
+ self.qt_property_alarm_sev_major()
+
+ elif alarm_severity == self.cyca.SEV_INVALID:
+ if _current_qt_dynamic_property != \
+ self.ALARM_SEV_INVALID:
+ self.qt_property_alarm_sev_invalid()
+
+ elif alarm_severity == self.cyca.SEV_NO_ALARM:
+ if _current_qt_dynamic_property != \
+ self.ALARM_SEV_NO_ALARM:
+ self.qt_property_alarm_sev_no_alarm()
+
+ elif _current_qt_dynamic_property != self.READBACK_STATIC:
+ self.qt_property_static()
+
+ else:
+ if _current_qt_dynamic_property != self.DISCONNECTED:
+ self.qt_property_disconnect()
+
+
+ def receive_monitor_dbr_time(self, pvdata, alarm_severity):
+ print("called from gateway", self.pv_name, alarm_severity)
+ pvdata.show()
+
+ def receive_monitor_update(self, value, status, alarm_severity):
+ '''Triggered by monitor signal. For Widget to overload.'''
+
+ self.mutex_post_display.lock()
+ _current_qt_dynamic_property = self.qt_dynamic_property_get()
+
+ if status == self.cyca.ICAFE_NORMAL:
+
+ if self.msg_label == "":
+ self.post_display_value(value)
+
+ #For DAQ when channel connects after application start-up
+ if _current_qt_dynamic_property == self.DISCONNECTED:
+ self.qt_property_initial_values(qt_object_name=self.PV_READBACK)
+
+ #Check if color settings are correct
+ elif _current_qt_dynamic_property == self.READBACK_STATIC:
+ if alarm_severity > self.cyca.SEV_NO_ALARM:
+ if alarm_severity < self.cyca.SEV_INVALID:
+ self.color_mode = self.READBACK_ALARM
+ self.status_tip = ("Widget color mode is dynamic, " +
+ "pv with alarm limits")
+ elif alarm_severity == self.cyca.SEV_INVALID:
+ if _current_qt_dynamic_property != self.ALARM_SEV_INVALID:
+ self.qt_property_alarm_sev_invalid()
+
+ if self.color_mode == self.READBACK_ALARM:
+ if alarm_severity == self.cyca.SEV_MINOR:
+ if _current_qt_dynamic_property != self.ALARM_SEV_MINOR:
+ self.qt_property_alarm_sev_minor()
+
+ elif alarm_severity == self.cyca.SEV_MAJOR:
+ if _current_qt_dynamic_property != self.ALARM_SEV_MAJOR:
+ self.qt_property_alarm_sev_major()
+
+ elif alarm_severity == self.cyca.SEV_INVALID:
+ if _current_qt_dynamic_property != self.ALARM_SEV_INVALID:
+ self.qt_property_alarm_sev_invalid()
+
+ elif alarm_severity == self.cyca.SEV_NO_ALARM:
+ if _current_qt_dynamic_property != self.ALARM_SEV_NO_ALARM:
+ self.qt_property_alarm_sev_no_alarm()
+
+ else:
+ if _current_qt_dynamic_property != self.DISCONNECTED:
+ self.qt_property_disconnect()
+
+ self.mutex_post_display.unlock()
+
+ def py_monitor_callback(self, handle, pvname, pvdata):
+
+ '''Callback function to be invoked on change of pv value.
+ cafe.getCache and cafe.set operations permitted within callback.
+ '''
+
+ pv_name = pvname
+ pvd = pvdata
+
+ if not hasattr(self, 'cafe'):
+ print("py_monitor_callback: name/handle self cafe is NONE",
+ pv_name, handle)
+ return
+
+ self.pvd = pvd
+
+ if pvd.status == self.cyca.ICAFE_CS_NEVER_CONN:
+ print("initialize again")
+ self.initialize()
+
+ elif pvd.status == self.cyca.ICAFE_CA_OP_CONN_DOWN:
+ _alarm_severity = self.cyca.ICAFE_CA_OP_CONN_DOWN
+ else:
+ _alarm_severity = pvd.alarmSeverity
+
+ if self.monitor_dbr_time:
+ self.trigger_monitor.emit(pvd, _alarm_severity)
+ elif isinstance(pvd.value[0], str):
+ self.trigger_monitor_str.emit((pvd.value[0]), pvd.status,
+ _alarm_severity)
+ elif isinstance(pvd.value[0], int):
+ self.trigger_monitor_int.emit((pvd.value[0]), pvd.status,
+ _alarm_severity)
+ else:
+ self.trigger_monitor_float.emit(float(pvd.value[0]), pvd.status,
+ _alarm_severity)
+
+
+ def monitor_start(self):
+ '''Initiate monitor on pv.'''
+ if self.handle > 0:
+ #Is monitor in waiting - now deleted with monitor_stop
+ if self.notify_unison:
+ self.monitor_id = self.cafe.monitorStart(
+ self.handle, dbr=self.cyca.CY_DBR_TIME)
+ #start with gateway supplied monitor callback handler
+ elif self.monitor_callback is None:
+ self.monitor_id = self.cafe.monitorStart(
+ self.handle, cb=self.py_monitor_callback,
+ dbr=self.cyca.CY_DBR_TIME,
+ notify_milliseconds=self.notify_milliseconds)
+ else:
+ self.monitor_id = self.cafe.monitorStart(
+ self.handle, cb=self.monitor_callback,
+ dbr=self.cyca.CY_DBR_TIME,
+ notify_milliseconds=self.notify_milliseconds)
+
+ def monitor_stop(self):
+ if self.handle > 0:
+ _monID_list = self.cafe.getMonitorIDs(self.handle)
+ _monID_inwaiting_list = self.cafe.getMonitorIDsInWaiting(
+ self.handle)
+ _monID_all = _monID_list + _monID_inwaiting_list
+
+ if self.monitor_id in _monID_all:
+ self.cafe.monitorStop(self.handle, self.monitor_id)
+ #Is monitor in waiting?
+ #remove monitors in waiting - to do
+
+ def reconnect_channel(self):
+ self.cafe.reconnect([self.handle]) #list
+
+ def set_desc(self):
+ '''Set description of pv from pv.DESC'''
+
+ if self.cafe.hasDescription(self.handle):
+ self.desc = self.cafe.getDescription(self.handle)
+ return
+ elif self.desc is not None:
+ return
+ else:
+ self.cafe.supplementHandle(self.handle)
+ if self.cafe.hasDescription(self.handle):
+ self.desc = self.cafe.getDescription(self.handle)
+
+ if self.desc is not None:
+ return
+
+ ###Back-up solution
+ _found = str(self.pv_name).find(".")
+ if _found != -1:
+ _pv_desc = str(self.pv_name)[0:_found] +".DESC"
+ else:
+ _pv_desc = self.pv_name +".DESC"
+ _handle_desc = self.cafe.getHandleFromPVName(_pv_desc)
+
+ _handle_desc_already_open = False
+
+ if _handle_desc == 0:
+ self.cafe.openPrepare()
+ _handle_desc = self.cafe.open(_pv_desc)
+ self.cafe.openNowAndWait(self.timeout, _handle_desc)
+ time.sleep(0.001)
+ else:
+ _handle_desc_already_open = True
+
+ if self.cafe.isConnected(_handle_desc):
+ self.desc = self.cafe.getCache(_handle_desc, 'str')
+ if self.desc is None:
+ self.desc = self.cafe.get(_handle_desc, 'str')
+ else:
+ self.desc = None
+
+ if not _handle_desc_already_open:
+ self.cafe.close(_handle_desc)
+
+ def set_precision_and_units(self, reconnectFlag: bool = False):
+ '''Set the pv precision and units.'''
+ if self.pv_ctrl is None or reconnectFlag is True:
+ self.pv_ctrl = self.cafe.getCtrlCache(self.handle)
+
+ if self.pv_ctrl is not None:
+ if not self.has_precision_user:
+ self.precision = self.pv_ctrl.precision
+ if self.pv_ctrl.units is not None:
+ self.units = str(self.pv_ctrl.units)
+ else:
+ self.units = ""
+
+ if reconnectFlag is True:
+ #verify user input
+ if self.show_units is True and self.suffix is not None:
+ if self.suffix == self.units:
+ self.show_units = False
+
+
+ def _qt_readback_color_mode(self):
+ '''Color mode is determined from CAFE and depends on whether the pv:
+ has alarm limits (self.color_mode = 'readbackAlarm')
+ or is without alarm limits (self.color_mode = 'readbackStatic')
+ '''
+
+ #Already set by user
+ if self.color_mode is self.READBACK_ALARM:
+ return
+
+ if self.cafe.isConnected(self.handle):
+ pvd = self.cafe.getPVCache(self.handle)
+ if pvd.alarmSeverity in (self.cyca.SEV_MINOR, self.cyca.SEV_MAJOR) \
+ or self.cafe.hasAlarmStatusSeverity(self.handle):
+ self.color_mode = self.READBACK_ALARM
+ self.status_tip = ("Widget color mode is dynamic, " +
+ "pv with alarm limits")
+ else:
+ self.color_mode = self.READBACK_STATIC
+ self.status_tip = ("Widget color mode is static, " +
+ "pv without alarm limits")
+
+
+ def qt_property_initial_values(self, qt_object_name: str = None,
+ tool_tip: bool = True):
+
+ '''Set Qt property values.'''
+ self.qt_object_name = qt_object_name
+ if tool_tip:
+ self.setToolTip(self.pv_name)
+ self.setObjectName(self.qt_object_name)
+ if self.qt_object_name in self.qt_object_to_property.keys():
+ self._qt_property_selected = copy.deepcopy(
+ self.qt_object_to_property[self.qt_object_name])
+ else:
+ print("qt_property_initial_values: Object not found in dictionary")
+
+ if self.cafe.isConnected(self.handle):
+
+ if self.qt_object_name == self.PV_READBACK:
+ self._qt_readback_color_mode()
+ #self.setStatusTip(self.status_tip)
+
+ elif self.qt_object_name == self.PV_CONTROLLER:
+ if self.color_mode == self.ACT_ON_BEAM:
+ #self.setStatusTip("PV setting acts directly on beam")
+ pass
+ else:
+ self.color_mode = self.NOT_ACT_ON_BEAM
+ #self.setStatusTip("PV setting does not influence beam")
+
+ elif self.qt_object_name == self.PV_DAQ_CA:
+ self._qt_readback_color_mode()
+
+ elif self.qt_object_name == self.PV_DAQ_BS:
+ self.color_mode = self.READBACK_STATIC
+
+ self._qt_dynamic_property_set(self.color_mode)
+
+ else:
+ self.qt_property_disconnect()
+
+
+ def qt_dynamic_property_get(self, property_state: str = None):
+ '''Retrieves the requested property value
+ else that which is currently true'''
+
+ for _property, _value in self._qt_property_selected.items():
+ if property_state is not None:
+ if _property == property_state:
+ return _value
+ elif _value:
+ return _property
+
+ def _qt_dynamic_property_set(self, property_state: str = None):
+ '''
+ Set the Input property to true, and the remainder to False
+ If None is given then all dynamic properties are set to False
+ '''
+
+ for _property in self._qt_property_selected.keys():
+ if _property == property_state:
+ self.setProperty(_property, True)
+ self._qt_property_selected[_property] = True
+ else:
+ self.setProperty(_property, False)
+ self._qt_property_selected[_property] = False
+
+ def qt_property_disconnect(self):
+ '''Set Qt disconnect property value.'''
+ self._qt_dynamic_property_set(self.DISCONNECTED)
+ self.qt_style_polish()
+
+ def qt_property_reconnect(self):
+ '''Set Qt connected property value.'''
+
+ if self.qt_object_name == self.PV_READBACK:
+ self._qt_readback_color_mode()
+ #self.setStatusTip(self.status_tip)
+
+
+ elif self.qt_object_name == self.PV_CONTROLLER:
+ if self.color_mode == self.ACT_ON_BEAM:
+ #self.setStatusTip("PV setting acts directly on beam")
+ pass
+ else:
+ self.color_mode = self.NOT_ACT_ON_BEAM
+ #self.setStatusTip("PV setting does not influence beam")
+
+
+ #self._qt_property_selected =
+ self._qt_dynamic_property_set(self.color_mode)
+
+ self.qt_style_polish()
+
+ def qt_property_alarm_sev_major(self):
+ '''Set Qt MAJOR property value.'''
+
+ self._qt_dynamic_property_set(self.ALARM_SEV_MAJOR)
+ self.setStatusTip("{0} reports value in MAJOR alarm state!".format(
+ self.pv_name))
+ self.qt_style_polish()
+
+ def qt_property_alarm_sev_minor(self):
+ '''Set Qt MINOR property value.'''
+ self._qt_dynamic_property_set(self.ALARM_SEV_MINOR)
+ self.setStatusTip("{0} reports value in MINOR alarm state!".format(
+ self.pv_name))
+ self.qt_style_polish()
+
+ def qt_property_alarm_sev_no_alarm(self):
+ '''Set Qt READBACK_ALARM property value.'''
+ #self._qt_property_selected =
+ self._qt_dynamic_property_set(self.READBACK_ALARM)
+ self.setStatusTip("{0} reports value in normal state".format(
+ self.pv_name))
+ self.qt_style_polish()
+
+ def qt_property_alarm_sev_invalid(self):
+ '''Set Qt INVALID property value.'''
+ self._qt_dynamic_property_set(self.ALARM_SEV_INVALID)
+ self.setStatusTip("PV={0} reports an INVALID value!".format(
+ self.pv_name))
+ self.qt_style_polish()
+
+ def qt_property_static(self):
+ '''Set Qt STATIC property value.'''
+ self._qt_dynamic_property_set(self.READBACK_STATIC)
+ self.setStatusTip("PV={0} does not have an alarm state".format(
+ self.pv_name))
+ self.qt_style_polish()
+
+ def qt_property_daq_stopped(self):
+ '''Set Qt STOPPED property value.'''
+ self._qt_dynamic_property_set(self.DAQ_STOPPED)
+ self.setStatusTip("PV={0} reports DAQ has stopped".format(
+ self.pv_name))
+ self.qt_style_polish()
+
+ def qt_property_daq_paused(self):
+ '''Set Qt STOPPED property value.'''
+ self._qt_dynamic_property_set(self.DAQ_PAUSED)
+ self.setStatusTip("PV={0} reports DAQ has paused".format(
+ self.pv_name))
+ self.qt_style_polish()
+
+ def qt_style_polish(self, redraw=False):
+ if redraw:
+ self.style().unpolish(self)
+ self.style().polish(self)
+ event = QEvent(QEvent.StyleChange)
+ QApplication.sendEvent(self, event)
+ self.update()
+ self.updateGeometry()
+ else:
+ self.style().polish(self)
+ QApplication.processEvents()
+
+ def pv_status_text_header(self, source="Channel Access"):
+ _source = source
+ _source_separator = "----------------------------------------"
+ _text = """
+
+ Widget: {0} ({1}, {2})
+ """.format(self.widget_class, self.qt_object_name, self.color_mode)
+
+ if self.msg_press_value is not None:
+ _text += """
+ On press, sends value: {0}
+ """.format(self.msg_press_value, "DarkOrchid")
+
+ if self.msg_release_value is not None:
+ _text += """
+ On release, sends value: {0}
+ """.format(self.msg_release_value, "DarkOrchid")
+
+ if self.pv_within_daq_group:
+ if self.qt_object_name in self.PV_DAQ_BS:
+ _ds_color = "Navy Blue"
+ else:
+ _ds_color = "Black"
+ else:
+ _ds_color = "Black"
+
+ _text += """
+ {0}
+ Data source: {1}
+ {0}
+ PV: {2}
+ """.format(_source_separator, _source, self.pv_name, "DarkOrchid",
+ _ds_color)
+
+ if self.desc is None:
+ self.set_desc()
+
+ if self.desc == "":
+ _text += """
+ """
+ return _text
+
+ _text += """
+
+ Description: {0}
+
+ """.format(self.desc, "DarkOrchid")
+
+ return _text
+
+
+ def pv_status_text_enum(self):
+
+ _val_enum = None
+ _value = self.pvd.value[0]
+ if isinstance(_value, str):
+ _val_enum = self.cafe.getEnumFromString(self.handle, _value)
+ elif _value is not None:
+ _val_enum = self.cafe.getStringFromEnum(self.handle, _value)
+
+ _color = "Blue"
+
+ #To catch case where channel is called by user
+
+
+ #To catch DAQ case
+ if self.pv_within_daq_group:
+ if self.qt_object_name in self.PV_DAQ_BS:
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ self.DAQ_PAUSED,
+ self.DISCONNECTED):
+ _color = "White"
+ elif self.qt_object_name in self.PV_DAQ_CA:
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ self.DISCONNECTED):
+ _color = "White"
+
+ elif not self.cafe.isConnected(self.handle):
+ _color = "White"
+ elif self.pvd.status == self.cyca.ICAFE_CA_OP_CONN_DOWN:
+ _color = "White"
+
+ _text = """
+
+ Value: {1} [{2}]
+ """.format(_color, _value, _val_enum)
+
+ return _text
+
+ def pv_status_text_data(self):
+
+ _value_str = ""
+ _first_end = 9
+ _end_range = min(self.pvd.nelem, _first_end)
+ if _end_range > 1:
+ _value_str = "[ "
+ for i in range(0, _end_range):
+ _value = self.pvd.value[i]
+ if _value is None:
+ _value = '0'
+ if isinstance(_value, str):
+ _value_str += _value
+ elif isinstance(_value, int):
+ _value_str += str(_value)
+ else:
+ if self.pv_ctrl is not None:
+ _value_form = ("{:<.%sf}" % self.pv_ctrl.precision)
+ _value_str += _value_form.format(
+ round(_value, self.pv_ctrl.precision))
+ if i < (_end_range-1):
+ _value_str += " "
+
+ if self.pvd.nelem > _first_end:
+ _value_str += " ... "
+ _value = self.pvd.value[self.pvd.nelem-1]
+ if isinstance(_value, str):
+ _value_str += _value
+ elif isinstance(_value, int):
+ _value_str += str(_value)
+ else:
+ if self.pv_ctrl is not None:
+ _value_form = ("{:<.%sf}" % self.pv_ctrl.precision)
+ _value_str += _value_form.format(
+ round(_value, self.pv_ctrl.precision))
+ _value_str += " "
+ if _end_range > 1:
+ _value_str += "]"
+
+ _color = "Blue"
+
+
+ #To catch DAQ case
+ if self.pv_within_daq_group:
+
+ if self.qt_object_name in self.PV_DAQ_BS:
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ self.DAQ_PAUSED,
+ self.DISCONNECTED):
+ _color = "White"
+ elif self.qt_object_name in self.PV_DAQ_CA:
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ self.DISCONNECTED):
+ _color = "White"
+
+ elif not self.cafe.isConnected(self.handle):
+ _color = "White"
+ elif self.pvd.status == self.cyca.ICAFE_CA_OP_CONN_DOWN:
+ _color = "White"
+
+ _text = """
+
+ Value: {1} {2}
+ """.format(_color, _value_str, self.units)
+
+ return _text
+
+
+ def pv_status_text_timestamp(self):
+ _status_not_ok_color = "IndianRed"
+ _status_ok_color = "DimGray"
+ _ts_color = "Blue"
+ _color = _status_ok_color
+
+ #To catch DAQ case
+ if self.pv_within_daq_group:
+ if self.qt_object_name in self.PV_DAQ_BS:
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ self.DAQ_PAUSED,
+ self.DISCONNECTED):
+ _ts_color = "White"
+ _color = "White"
+ elif self.qt_object_name in self.PV_DAQ_CA:
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ self.DISCONNECTED):
+ _ts_color = "White"
+ _color = "White"
+
+ elif not self.cafe.isConnected(self.handle):
+ _ts_color = "White"
+ elif self.pvd.status == self.cyca.ICAFE_CA_OP_CONN_DOWN:
+ _ts_color = "White"
+
+ if self.pvd.status != self.cyca.ICAFE_NORMAL:
+ _color = _status_not_ok_color
+ _text = """
+ Timestamp: {2}
+ Status: {3}
{4}
+ """.format(_ts_color, _color, self.pvd.tsDateAsString,
+ self.pvd.statusAsString,
+ self.cafe.getStatusInfo(self.pvd.status))
+
+ return _text
+
+
+ def pv_status_text_alarm(self):
+ _text = """
+ """
+ _color = "DimGray"
+
+ #To catch DAQ case
+ if self.pv_within_daq_group:
+
+ if self.pvd.alarmSeverity == self.cyca.SEV_MINOR:
+ _color = "Yellow"
+ elif self.pvd.alarmSeverity == self.cyca.SEV_MAJOR:
+ _color = "Red"
+ elif self.pvd.alarmSeverity == self.cyca.SEV_INVALID:
+ _color = "White"
+
+ if self.qt_object_name in self.PV_DAQ_BS:
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ self.DAQ_PAUSED,
+ self.DISCONNECTED):
+ _color = "White"
+ elif self.qt_object_name in self.PV_DAQ_CA:
+ if self.qt_dynamic_property_get() in (self.DAQ_STOPPED,
+ self.DISCONNECTED):
+ _color = "White"
+
+
+ elif not self.cafe.isConnected(self.handle):
+ _color = "White"
+
+ elif self.pvd.status == self.cyca.ICAFE_CA_OP_CONN_DOWN:
+ _color = "White"
+
+ elif self.pvd.alarmSeverity == self.cyca.SEV_MINOR:
+ _color = "Yellow"
+ elif self.pvd.alarmSeverity == self.cyca.SEV_MAJOR:
+ _color = "Red"
+ elif self.pvd.alarmSeverity == self.cyca.SEV_INVALID:
+ _color = "White"
+
+ _text += """
+ Alarm status: {1}
+ Alarm severity: {2}
+ """.format(_color, self.pvd.alarmStatusAsString,
+ self.pvd.alarmSeverityAsString)
+
+ return _text
+
+ def pv_access(self):
+ _accessIs = ""
+ if self.pv_info is None:
+ self.pv_info = self.cafe.getChannelInfo(self.handle)
+ if self.pv_info.accessRead:
+ _accessIs += "Read"
+ if self.pv_info.accessWrite:
+ _accessIs += "Write"
+ return _accessIs
+
+ def pv_status_text_enum_metadata(self):
+ _text = """
+ ENUM strings: {2}
+ Data type (native): {3}
+ Record type: {4}
+ RW Access: {5}
+ IOC: {6}
+ """.format("MediumBlue", "DarkOrchid", self.pvc.enumStrings,
+ self.pv_info.dataTypeAsString,
+ self.record_type, self.pv_access(),
+ self.pv_info.hostName)
+ return _text
+
+ def pv_status_text_metadata(self):
+
+ if self.pv_info is None:
+ self.pv_info = self.cafe.getChannelInfo(self.handle)
+ if self.pv_info is not None and self.record_type is None:
+ if "Not Supported" in self.pv_info.className:
+ _rtype = self.cafe.get(self.pv_name.split(".")[0] + ".RTYP")
+ self.record_type = _rtype if _rtype is not None else \
+ self.pv_info.className
+ self.cafe.close(self.pv_name.split(".")[0] + ".RTYP")
+ else:
+ self.record_type = self.pv_info.className
+
+ if self.record_type in ["stringin", "stringout"]:
+ _text = """
+ Data type (native): {1}
+ Record type: {2}
+ RW Access: {3}
+ IOC: {4}
+ """.format("MediumBlue", self.pv_info.dataTypeAsString,
+ self.record_type, self.pv_access(),
+ self.pv_info.hostName)
+ return _text
+
+ _text = """
+ """
+ if self.pvd.nelem > 1:
+ _text += """
+ Nelem: {1}
+ """.format("MediumBlue", self.pvd.nelem)
+
+ _text += """
+ Precision (PV): {1}
+ Data type (native): {2}
+ Record type: {3}
+ RW Access: {4}
+ IOC: {5}
+ """.format("MediumBlue", self.pvc.precision,
+ self.pv_info.dataTypeAsString,
+ self.record_type, self.pv_access(),
+ self.pv_info.hostName)
+ return _text
+
+
+ def pv_status_text_alarm_limits(self):
+
+ if self.pv_info is None:
+ self.pv_info = self.cafe.getChannelInfo(self.handle)
+ if self.pv_info is not None and self.record_type is None:
+ if "Not Supported" in self.pv_info.className:
+ _rtype = self.cafe.get(self.pv_name.split(".")[0] + ".RTYP")
+ self.record_type = _rtype if _rtype is not None else \
+ self.pv_info.className
+ self.cafe.close(self.pv_name.split(".")[0] + ".RTYP")
+ else:
+ self.record_type = self.pv_info.className
+
+ _text = """
+ """
+
+ #No all record types have alarms
+ #className is not supported at psi since introduction of the
+ #linux ca gateway
+ #Not Supported by Gateway
+
+ if "Not Supported" in str(self.record_type):
+ pass
+ elif self.record_type not in self._alarm_severity_record_types:
+ return _text
+
+ if self.pvc.lowerAlarmLimit == 0 and self.pvc.upperAlarmLimit == 0 and \
+ self.pvc.lowerWarningLimit == 0 and self.pvc.upperWarningLimit == 0:
+ return _text
+
+ if self.cafe.hasAlarmStatusSeverity(self.handle):
+ _text = """
+ Lower/Upper alarm limit:
+ {1} / {4}
+ Lower/Upper warning limit:
+ {2} / {3}
+
+ """.format("MediumBlue",
+ self.pvc.lowerAlarmLimit, self.pvc.lowerWarningLimit,
+ self.pvc.upperWarningLimit, self.pvc.upperAlarmLimit)
+ return _text
+
+ def pv_status_text_display_limits(self):
+ _text = """
+ """
+ if self.pvc.lowerDisplayLimit == 0 and \
+ self.pvc.upperDisplayLimit == 0 and \
+ self.pvc.lowerControlLimit == 0 and self.pvc.upperControlLimit == 0:
+ return _text
+ _text = """
+ Lower/Upper control limit:
+ {3} / {4}
+ Lower/Upper display limit:
+ {1} / {2}
+
+ """.format("MediumBlue",
+ self.pvc.lowerDisplayLimit, self.pvc.upperDisplayLimit,
+ self.pvc.lowerControlLimit, self.pvc.upperControlLimit)
+ return _text
+
+
+ def pv_status_text(self):
+ '''pv metadata to accompany widget's dialog box.'''
+ QApplication.processEvents()
+ _source = "Channel Access"
+
+ if self.pv_within_daq_group:
+ if self.qt_object_name == self.PV_DAQ_BS:
+ _source = "DAQ (Beam Synchronous)"
+ #self.pvd written to in receive_daq_update
+ elif self.qt_object_name == self.PV_DAQ_CA:
+ _source = "DAQ (Channel Access)"
+ self.pvd = self.cafe.getPVCache(self.handle)
+ if self.pvd.pulseID > 0:
+ _source += "
Pulse ID: {0}".format(self.pvd.pulseID)
+ else:
+ self.pvd = self.cafe.getPVCache(self.handle)
+
+ self.pvc = self.cafe.getCtrlCache(self.handle)
+
+ _text_data = """
+ """
+ if self.pvd.status == self.cyca.ECAFE_INVALID_HANDLE:
+ _text_data = """ Status: {1}
{2}
+ """.format("Blue",
+ "Channel closed while DAQ in STOP state.",
+ ("PV info requires DAQ to be in " +
+ "RUN/PAUSED state"))
+
+
+ elif self.pvd.status == self.cyca.ICAFE_CS_NEVER_CONN:
+ _text_data = """ Status: {1}
{2}
+ """.format("Red", self.pvd.statusAsString,
+ self.cafe.getStatusInfo(self.pvd.status))
+
+ elif self.pvc.noEnumStrings > 0:
+ _text_data = (self.pv_status_text_enum() +
+ self.pv_status_text_timestamp() +
+ self.pv_status_text_alarm() +
+ self.pv_status_text_enum_metadata())
+
+ else:
+ _text_data = (self.pv_status_text_data() +
+ self.pv_status_text_timestamp() +
+ self.pv_status_text_alarm() +
+ self.pv_status_text_metadata() +
+ self.pv_status_text_alarm_limits() +
+ self.pv_status_text_display_limits())
+
+ self.pv_message_in_a_box.setText(
+ self.pv_status_text_header(source=_source) + _text_data
+ )
+ QApplication.processEvents()
+ self.pv_message_in_a_box.exec()
+
+
+ def lookup_archiver(self):
+ '''Plot pvdata from archiver.'''
+ #"https://ui-data-api.psi.ch/prepare?
+ #channel=sf-archiverappliance/"
+ urlIs = self.url_archiver
+ urlIs = urlIs + self.pv_name
+
+ if not QDesktopServices.openUrl(QUrl(urlIs)):
+ print("URL FOR ARCHIVER NOT FOUND", urlIs)
+ #if self.show_log_message is not None:
+ # self.show_log_message(MsgSeverity.ERROR, __pymodule__, _line(),
+ # "Failed to open URL {0}".format(urlIs))
+
+ def lookup_databuffer(self):
+ '''Plot beam synchronous pvdata from databuffer.'''
+ #""https://ui-data-api.psi.ch/prepare?channel = sf-databuffer/"
+ urlIs = self.url_databuffer
+ urlIs = urlIs + self.pv_name
+
+ if not QDesktopServices.openUrl(QUrl(urlIs)):
+ print("URL FOR DATA BUFFER NOT FOUND", urlIs)
+ #if self.show_log_message is not None:
+ # self.show_log_message(MsgSeverity.ERROR, __pymodule__, _line(),
+ # "Failed to open URL {0}".format(urlIs))
+ QApplication.processEvents()
+
+ def strip_chart(self):
+ '''PShell strip chart.'''
+ configStr = ("-config = [[[true,\"" + self.pv_name +
+ "\",\"Channel\",1,1]]]")
+ commandStr = "/sf/op/bin/strip_chart"
+ argStr = ["-nlaf", "-start", configStr, "&"]
+ QProcess.startDetached(commandStr, argStr)
+
+
+ def display_parameters(self):
+ display_wgt = QDialog(self)
+
+ _rect = display_wgt.geometry() #
+ _parentRect = self.context_menu.geometry()
+
+ _rect.moveTo(display_wgt.mapToGlobal(
+ QPoint(_parentRect.x() + _parentRect.width() - _rect.width(),
+ _parentRect.y())))
+
+ display_wgt.setGeometry(_rect)
+ display_wgt.setWindowTitle(self.pv_name)
+ layout = QVBoxLayout()
+
+ precision_flag = True
+ if self.pv_ctrl is not None:
+ if self.pv_ctrl.precision <= 0:
+ precision_flag = False
+
+ if self.cafe.getDataTypeNative(self.handle) in (
+ self.cyca.CY_DBR_FLOAT,
+ self.cyca.CY_DBR_DOUBLE) and precision_flag:
+ #precision user
+ _hbox_wgt = QWidget()
+ _hbox = QHBoxLayout()
+ precision_user_label = QLabel("Precision (user):")
+ self.precision_user_wgt = QSpinBox(self)
+ self.precision_user_wgt.setFocusPolicy(Qt.NoFocus)
+ self.precision_user_wgt.setValue(int(self.precision))
+ if self.pv_ctrl is not None:
+ _max = self.pv_ctrl.precision
+ else:
+ _max = 6
+ self.precision_user_wgt.setMaximum(_max)
+ self.precision_user_wgt.valueChanged.connect(
+ self.precision_user_changed)
+ _hbox.addWidget(precision_user_label)
+ _hbox.addWidget(self.precision_user_wgt)
+ _hbox_wgt.setLayout(_hbox)
+
+ precision_user_label.setFixedWidth(110)
+ self.precision_user_wgt.setFixedWidth(35)
+ _hbox_wgt.setFixedWidth(160)
+
+ #precision ioc
+ _hbox2_wgt = QWidget()
+ _hbox2 = QHBoxLayout()
+ precision_ioc_label = QLabel("Precision (ioc): ")
+ precision_ioc = QPushButton(self)
+ precision_ioc.setText(" {} ".format(_max))
+ precision_ioc.clicked.connect(self.precision_ioc_reset)
+
+ _hbox2.addWidget(precision_ioc_label)
+ _hbox2.addWidget(precision_ioc)
+ _hbox2_wgt.setLayout(_hbox2)
+
+ precision_ioc_label.setFixedWidth(110)
+ precision_ioc.setFixedWidth(20)
+ _hbox2_wgt.setFixedWidth(145)
+
+ layout.addWidget(_hbox_wgt)
+ layout.addWidget(_hbox2_wgt)
+
+ #precision refresh rate
+ _hbox3_wgt = QWidget()
+ _hbox3 = QHBoxLayout()
+ refresh_freq_label = QLabel("Refresh rate: ")
+ _default_refresh_val = 0 if self.notify_freq_hz_default <= 0 else \
+ self.notify_freq_hz_default
+
+ self.refresh_freq_combox_idx_dict = {0: 0, 1: 10, 2: 5, 3: 2, 4: 1,
+ 5: 0.5, 6: _default_refresh_val}
+ refresh_freq = QComboBox(self)
+ refresh_freq.addItem('direct')
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[1]))
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[2]))
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[3]))
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[4]))
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[5]))
+
+ _default_text = 'default (direct)' if _default_refresh_val == 0 else \
+ 'default ({0} Hz)'.format(self.refresh_freq_combox_idx_dict[6])
+
+ refresh_freq.addItem(_default_text)
+
+ for key, value in self.refresh_freq_combox_idx_dict.items():
+ if value == self.notify_freq_hz:
+ refresh_freq.setCurrentIndex(key)
+ break
+ refresh_freq.currentIndexChanged.connect(self.refresh_rate_changed)
+
+ _hbox3.addWidget(refresh_freq_label)
+ _hbox3.addWidget(refresh_freq)
+ _hbox3_wgt.setLayout(_hbox3)
+
+ refresh_freq_label.setFixedWidth(110)
+ refresh_freq.setFixedWidth(115)
+ _hbox3_wgt.setFixedWidth(235)
+
+ layout.addWidget(_hbox3_wgt)
+
+ layout.setAlignment(Qt.AlignLeft)
+ layout.setContentsMargins(10, 0, 0, 0)
+ layout.setSpacing(0)
+
+ display_wgt.setMinimumWidth(340)
+ display_wgt.setLayout(layout)
+
+ display_wgt.exec()
+ QApplication.processEvents()
+
+ def precision_ioc_reset(self):
+ if self.pv_ctrl is not None:
+ self.precision_user = self.pv_ctrl.precision
+ self.precision = self.pv_ctrl.precision
+ if self.precision is not None:
+ self.precision_user_wgt.setValue(self.precision)
+
+ def precision_user_changed(self, new_value):
+ self.precision_user = new_value
+ self.precision = new_value
+
+ _pvd = self.cafe.getPVCache(self.handle)
+
+ if _pvd.value[0] is not None:
+ if isinstance(_pvd.value[0], float):
+ self.trigger_monitor_float.emit(
+ _pvd.value[0], _pvd.status, _pvd.alarmSeverity)
+
+ def refresh_rate_changed(self, new_idx):
+ _notify_freq_hz = self.refresh_freq_combox_idx_dict[new_idx]
+ self.notify_milliseconds = 0 if _notify_freq_hz == 0 else \
+ 1000 / _notify_freq_hz
+ self.notify_freq_hz = _notify_freq_hz
+
+ if self.notify_unison:
+ self.notify_unison = False
+ self.monitor_stop()
+ self.monitor_start()
+
+ else:
+ self.cafe.updateMonitorPolicyDeltaMS(
+ self.handle, self.monitor_id, self.notify_milliseconds)
+
+ #https://doc.qt.io/qt-5.9/qtwidgets-mainwindows-menus-example.html
+ #Since Qt5 this has to be implemented in order to avoid the Select
+ #All dialogue button appearing..
+ def contextMenuEvent(self, event):
+ return
+
+ def showContextMenu(self):
+ self.context_menu.exec(QCursor.pos())
+
+ def mousePressEvent(self, event):
+ '''Action on mouse press event.'''
+ button = event.button()
+ if button == Qt.RightButton:
+ self.context_menu.exec(QCursor.pos())
+ self.clearFocus()
+
+ def mouseReleaseEvent(self, event):
+ event.ignore()
+
diff --git a/pvwidgets.py b/pvwidgets.py
index 29f50a9..3e05f6d 100644
--- a/pvwidgets.py
+++ b/pvwidgets.py
@@ -2,50 +2,83 @@
__author__ = 'Jan T. M. Chrin'
import re
-import sys
import time
import collections
import numpy as np
from sklearn.linear_model import LinearRegression
-from sklearn.metrics import mean_squared_error, r2_score
from distutils.version import LooseVersion
from functools import reduce as func_reduce
-from qtpy.QtCore import (qVersion, QEvent, QEventLoop, QObject, QPoint, QSize,
- Qt, QThread, QTimer, Signal, Slot)
+from qtpy.QtCore import QEventLoop, QPoint, Qt, QThread, QTimer, Signal, Slot
from qtpy.QtGui import (QCloseEvent, QColor, QCursor, QFont, QFontMetricsF,
QIcon, QKeySequence)
from qtpy.QtCore import __version__ as QT_VERSION_STR
from qtpy.QtWidgets import (QAbstractItemView, QAbstractSpinBox, QAction,
- QApplication, QCheckBox, QComboBox, QDialog,
- QDockWidget, QDoubleSpinBox, QFrame, QGroupBox,
- QHeaderView, QHBoxLayout, QLabel, QLineEdit,
+ QApplication, QBoxLayout, QCheckBox, QComboBox,
+ QDialog, QDockWidget, QDoubleSpinBox, QFrame,
+ QGroupBox, QHBoxLayout, QLabel, QLineEdit,
QListWidget, QMenu, QMessageBox, QPushButton,
QSpinBox, QStyle, QStyleOptionSpinBox, QTableWidget,
QTableWidgetItem, QVBoxLayout, QWidget)
import pyqtgraph as pg
from pyqtgraph import PlotWidget
-
from caqtwidgets.pvgateway import PVGateway
+class QTaggedLineEdit(QWidget):
+ def __init__(self, label_text=str(""), value="",
+ position="LEFT", parent=None):
+ super(QTaggedLineEdit, self).__init__(parent)
+ self.parameter = str(value)
+ self.label = QLabel(label_text)
+ self.label.setObjectName("Tagged")
+ self.label.setFixedHeight(24)
+ self.label.setContentsMargins(10, 0, 0, 0)
+ #self.label.setFixedWidth(80)
+ self.line_edit = QLineEdit(self.parameter)
+ self.line_edit.setObjectName("Write")
+ self.line_edit.setFixedHeight(24)
+ font = QFont("sans serif", 16)
+ fm = QFontMetricsF(font)
+ self.line_edit.setMaximumWidth(fm.width(self.parameter)+20)
+ self.label.setBuddy(self.line_edit)
+ layout = QBoxLayout(
+ QBoxLayout.LeftToRight if position == "LEFT" else \
+ QBoxLayout.TopToBottom)
+ layout.addWidget(self.label)
+ layout.addWidget(self.line_edit)
+ layout.addStretch()
+ layout.setSpacing(2)
+ layout.setContentsMargins(0, 0, 0, 0)
+ self.setLayout(layout)
+
+class QHLine(QFrame):
+ def __init__(self):
+ super(QHLine, self).__init__()
+ self.setFrameShape(QFrame.HLine)
+ self.setFrameShadow(QFrame.Sunken)
+
+class QVLine(QFrame):
+ def __init__(self):
+ super(QVLine, self).__init__()
+ self.setFrameShape(QFrame.VLine)
+ self.setFrameShadow(QFrame.Sunken)
class AppQLineEdit(QLineEdit):
- def __init__(self, parent=None):
- super().__init__(parent)
-
+ def __init__(self, parent=None):
+ #super().__init__(parent)
+ pass
def leaveEvent(self, event):
self.clearFocus()
del event
-
-
class CAQLineEdit(QLineEdit, PVGateway):
'''Channel access enabled QLineEdit widget'''
trigger_monitor_float = Signal(float, int, int)
trigger_monitor_int = Signal(int, int, int)
trigger_monitor_str = Signal(str, int, int)
+ trigger_monitor = Signal(object, int)
trigger_connect = Signal(int, str, int)
trigger_daq = Signal(object, str, int)
@@ -53,15 +86,14 @@ class CAQLineEdit(QLineEdit, PVGateway):
trigger_daq_str = Signal(object, str, int)
def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
- pv_within_daq_group: bool = False, color_mode = None,
+ pv_within_daq_group: bool = False, color_mode=None,
show_units: bool = False, prefix: str = "", suffix: str = "",
- notify_freq_hz: int = 0):
- #super(CAQLineEdit, self).__init__(parent)
+ notify_freq_hz: int = 0, precision: int = 0):
- super().__init__(parent, pv_name, monitor_callback,
+ super().__init__(parent, pv_name, monitor_callback,
pv_within_daq_group, color_mode, show_units, prefix,
suffix, connect_callback=self.py_connect_callback,
- notify_freq_hz=notify_freq_hz )
+ notify_freq_hz=notify_freq_hz, precision=precision)
self.is_initialize_complete()
self.configure_widget()
@@ -69,20 +101,11 @@ class CAQLineEdit(QLineEdit, PVGateway):
if not self.pv_within_daq_group:
self.monitor_start()
- '''
- print("fixed width==========>", self.width())
- print ("size", self.size()) # (100, 30)
- print ("sizeHint", self.sizeHint()) # (190, 37)
- print ("min Size", self.minimumSize()) #(0, 0)
- print ("min SizeHint", self.minimumSizeHint()) # (26, 37)
- print ("sizePolicy", self.sizePolicy()) #Object
- '''
-
def py_connect_callback(self, handle, pvname, status):
'''Callback function to be invoked on change of pv connection status.
'''
self.trigger_connect.emit(int(handle), str(pvname), int(status))
-
+
@Slot(object, str, int)
def receive_daq_update(self, daq_pvd, daq_mode, daq_state):
PVGateway.receive_daq_update(self, daq_pvd, daq_mode, daq_state)
@@ -97,20 +120,20 @@ class CAQLineEdit(QLineEdit, PVGateway):
def receive_connect_update(self, handle: int, pv_name: str, status: int):
'''Triggered by connect signal'''
PVGateway.receive_connect_update(self, handle, pv_name, status)
-
+
def configure_widget(self):
self.setFocusPolicy(Qt.NoFocus)
-
- fm = QFontMetricsF(QFont("Sans Serif", 12))
+
+ fm = QFontMetricsF(QFont("Sans Serif", 10))
qrect = fm.boundingRect(self.suggested_text)
_width_scaling_factor = 1.15
self.setFixedHeight((fm.lineSpacing()*1.8))
self.setFixedWidth(((qrect.width()) * _width_scaling_factor))
if self.pv_within_daq_group:
- self.qt_property_initial_values(qt_object_name = self.PV_DAQ_CA)
+ self.qt_property_initial_values(qt_object_name=self.PV_DAQ_CA)
else:
- self.qt_property_initial_values(qt_object_name = self.PV_READBACK)
+ self.qt_property_initial_values(qt_object_name=self.PV_READBACK)
#renove highlighting which persists after mouse leaves
def mouseMoveEvent(self, event):
@@ -123,42 +146,42 @@ class CAQLineEdit(QLineEdit, PVGateway):
class CAQLabel(QLabel, PVGateway):
'''Channel access enabled QLabel widget'''
- trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_float = Signal(float, int, int)
trigger_monitor_int = Signal(int, int, int)
trigger_monitor_str = Signal(str, int, int)
trigger_monitor = Signal(object, int)
- trigger_connect = Signal(int, str, int)
-
- trigger_daq = Signal(object, str, int)
+ trigger_connect = Signal(int, str, int)
+
+ trigger_daq = Signal(object, str, int)
trigger_daq_int = Signal(object, str, int)
trigger_daq_str = Signal(object, str, int)
-
-
- def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
- pv_within_daq_group: bool = False, color_mode = None,
- show_units: bool = False, prefix: str = "", suffix: str = "",
- notify_freq_hz: int = 0):
- #super(CAQLabel, self).__init__(parent)
-
- super().__init__(parent, pv_name, monitor_callback,
- pv_within_daq_group, color_mode, show_units, prefix,
- suffix, connect_callback=self.py_connect_callback,
- notify_freq_hz= notify_freq_hz)
-
- self.is_initialize_complete()
- self.configure_widget()
-
+
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode=None,
+ show_units: bool = False, prefix: str = "", suffix: str = "",
+ notify_freq_hz: int = 0, precision: int = 0):
+
+ super().__init__(parent, pv_name, monitor_callback, pv_within_daq_group,
+ color_mode, show_units, prefix, suffix,
+ connect_callback=self.py_connect_callback,
+ notify_freq_hz=notify_freq_hz, precision=precision)
+
+ self.is_initialize_complete()
+
+ self.configure_widget()
+
if self.pv_within_daq_group is False:
self.monitor_start()
def py_connect_callback(self, handle, pvname, status):
- '''Callback function to be invoked on change of pv connection status.
- '''
+ '''Callback function to be invoked on change of
+ pv connection status.
+ '''
self.trigger_connect.emit(int(handle), str(pvname), int(status))
-
- @Slot(object, str, int)
+
+ @Slot(object, str, int)
def receive_daq_update(self, daq_pvd, daq_mode, daq_state):
PVGateway.receive_daq_update(self, daq_pvd, daq_mode, daq_state)
@@ -166,38 +189,27 @@ class CAQLabel(QLabel, PVGateway):
@Slot(int, int, int)
@Slot(float, int, int)
def receive_monitor_update(self, value, status, alarm_severity):
-
- #print(self, self.pv_name, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
- # if (time.monotonic() - self.time_monotonic) < 0.9945:
- # return
- #self.time_monotonic = time.monotonic()
- #self.lock.acquire()
PVGateway.receive_monitor_update(self, value, status, alarm_severity)
- #self.lock.release()
- #QApplication.processEvents()
-
-
+
@Slot(int, str, int)
def receive_connect_update(self, handle: int, pv_name: str, status: int):
'''Triggered by connect signal'''
PVGateway.receive_connect_update(self, handle, pv_name, status)
-
-
+
def configure_widget(self):
self.setFocusPolicy(Qt.NoFocus)
- fm = QFontMetricsF(QFont("Sans Serif", 12))
- qrect = fm.boundingRect(self.suggested_text)
+ fm = QFontMetricsF(QFont("Sans Serif", 10))
+ qrect = fm.boundingRect(self.suggested_text)
_width_scaling_factor = 1.15
- self.setFixedHeight((fm.lineSpacing()*1.8))
+ self.setFixedHeight((fm.lineSpacing()*1.8))
self.setFixedWidth((qrect.width() * _width_scaling_factor))
- #self.setFixedWidth(140)
-
+
if self.pv_within_daq_group:
- self.qt_property_initial_values(qt_object_name = self.PV_DAQ_CA)
+ self.qt_property_initial_values(qt_object_name=self.PV_DAQ_CA)
else:
- self.qt_property_initial_values(qt_object_name = self.PV_READBACK)
+ self.qt_property_initial_values(qt_object_name=self.PV_READBACK)
#For use with CAQMenu
class QLineEditExtended(QLineEdit):
@@ -206,418 +218,386 @@ class QLineEditExtended(QLineEdit):
self.parent = parent
def mousePressEvent(self, event):
- button = event.button()
- if button == Qt.RightButton:
+ button = event.button()
+ if button == Qt.RightButton:
self.parent.showContextMenu()
- elif button == Qt.LeftButton:
+ elif button == Qt.LeftButton:
self.parent.mousePressEvent(event)
-
+
class CAQMenu(QComboBox, PVGateway):
'''Channel access enabled QMenu widget'''
- trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_float = Signal(float, int, int)
trigger_monitor_int = Signal(int, int, int)
trigger_monitor_str = Signal(str, int, int)
trigger_monitor = Signal(object, int)
- trigger_connect = Signal(int, str, int)
-
- def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
- pv_within_daq_group: bool = False, color_mode = None,
- show_units = False, prefix: str = "", suffix: str = ""):
- #super(CAQMenu, self)
- super().__init__(parent, pv_name, monitor_callback,
- pv_within_daq_group, color_mode, show_units, prefix,
+ trigger_connect = Signal(int, str, int)
+
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode=None,
+ show_units=False, prefix: str = "", suffix: str = ""):
+
+ super().__init__(parent, pv_name, monitor_callback,
+ pv_within_daq_group, color_mode, show_units, prefix,
suffix, connect_callback=self.py_connect_callback)
-
- self.is_initialize_complete()
-
+
+ self.is_initialize_complete()
+
self.configure_widget()
- #After configure:widget
+ #After configure:widget
self.currentIndexChanged.connect(self.value_change)
if self.pv_within_daq_group is False:
self.monitor_start()
-
+
def py_connect_callback(self, handle, pvname, status):
- '''Callback function to be invoked on change of pv connection status.
- '''
+ '''Callback function to be invoked on change of
+ pv connection status.
+ '''
self.trigger_connect.emit(int(handle), str(pvname), int(status))
-
def configure_widget(self):
-
+
self.previousIndex = None
-
+
self.setFocusPolicy(Qt.NoFocus)
self.setEditable(True)
self.setLineEdit(QLineEditExtended(self))
self.lineEdit().setReadOnly(True)
self.lineEdit().setAlignment(Qt.AlignCenter)
-
- #self.lineEdit().setMouseTracking(True)
- #self.setAttribute(Qt.WA_MouseNoMask)
- #self.lineEdit().setAttribute(Qt.WA_NoMousePropagation)
- #self.lineEdit().setAttribute(Qt.WA_WindowPropagation)
-
+
enumStringList = self.cafe.getEnumStrings(self.handle)
-
+
self.addItems(enumStringList)
for i in range(0, self.count()):
- self.setItemData(i, Qt.AlignCenter, Qt.TextAlignmentRole);
+ self.setItemData(i, Qt.AlignCenter, Qt.TextAlignmentRole)
+
+ fm = QFontMetricsF(QFont("Sans Serif", 10))
+ qrect = fm.boundingRect(self.suggested_text)
- '''
- self.ensurePolished()
- print(dir(self.style().property("font")))
- f=self.style().property("font")
- self.style().unpolish(self);
- self.style().polish(self);
- self.update()
- '''
-
- fm = QFontMetricsF(QFont("Sans Serif", 12))
- qrect = fm.boundingRect(self.suggested_text)
-
_width_scaling_factor = 1.1
- self.setFixedHeight(fm.lineSpacing()*1.8)
+ self.setFixedHeight(fm.lineSpacing()*1.8)
self.setFixedWidth((qrect.width()+40) * _width_scaling_factor)
-
+
self.qt_property_initial_values(qt_object_name=self.PV_CONTROLLER)
-
+
def post_display_value(self, value):
'''Convert value to index'''
if "setCurrentIndex" in dir(self):
-
+
if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
self.blockSignals(True)
-
+
if isinstance(value, str):
- self.setCurrentIndex(self.cafe.getEnumFromString(self.handle,
+ self.setCurrentIndex(self.cafe.getEnumFromString(self.handle,
value))
-
- elif isinstance(value, int):
+
+ elif isinstance(value, int):
self.setCurrentIndex(value)
#Should not happen
- elif isinstance(value, float):
- self.setCurrentIndex(int(value))
+ elif isinstance(value, float):
+ self.setCurrentIndex(int(value))
if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
self.blockSignals(False)
-
- #self.previousIndex = self.currentIndex()
- return
+
+ #self.previousIndex = self.currentIndex()
+ return
else:
print(("ERROR: overloaded post_display_value: 'setCurrentIndex' "
- "method does not exist!"))
+ "method does not exist!"))
def value_change(self, indx):
-
+
status = self.cafe.set(self.handle, indx)
-
+
if status != self.cyca.ICAFE_NORMAL:
#self.showSetErrorMsg(status)
value = self.cafe.getCache(self.handle, 'int')
-
+
if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
self.blockSignals(True)
-
+
if value is not None:
self.setCurrentIndex(value)
else:
if self.previousIndex is not None:
self.setCurrentIndex(self.previousIndex)
-
+
if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
self.blockSignals(False)
-
+
self.pv_message_in_a_box.setText(
- ("CAQMenu set operation reports error:\n{0}"
- .format(self.cafe.getStatusCodeAsString(status))))
- self.pv_message_in_a_box.exec()
+ "CAQMenu set operation reports error:\n{0}".format(
+ self.cafe.getStatusCodeAsString(status)))
+ self.pv_message_in_a_box.exec()
+ def mousePressEvent(self, event):
- def mousePressEvent(self, event):
-
- button = event.button()
- if button == Qt.RightButton:
+ button = event.button()
+ if button == Qt.RightButton:
PVGateway.mousePressEvent(self, event)
elif self.pv_info is not None:
- if self.pv_info.accessWrite == 0:
+ if self.pv_info.accessWrite == 0:
event.ignore()
return
- else:
- QComboBox.mousePressEvent(self, event)
-
+ else:
+ QComboBox.mousePressEvent(self, event)
+
self.previousIndex = self.currentIndex()
-
+
def enterEvent(self, event):
if self.pv_info is not None:
- if self.pv_info.accessWrite == 0:
+ if self.pv_info.accessWrite == 0:
for i in range(0, self.count()):
- self.setItemIcon(i, QIcon(":/forbidden.png"))
- self.setStyleSheet(("QComboBox {background: transparent}" +
- "QComboBox::drop-down {image: url(:/forbidden.png)}"))
-
- def leaveEvent(self, event):
- if self.pv_info is not None:
- if self.pv_info.accessWrite == 0:
+ self.setItemIcon(i, QIcon(":/forbidden.png"))
+ self.setStyleSheet(
+ ("QComboBox {background: transparent}" +
+ "QComboBox::drop-down {image: url(:/forbidden.png)}"))
+
+ def leaveEvent(self, event):
+ if self.pv_info is not None:
+ if self.pv_info.accessWrite == 0:
for i in range(0, self.count()):
- self.setItemIcon(i, QIcon())
- self.setStyleSheet("QComboBox::drop-down {background: transparent}")
-
+ self.setItemIcon(i, QIcon())
+ self.setStyleSheet(
+ "QComboBox::drop-down {background: transparent}")
+
#The widget should not gain focus by using the mouse wheel.
#This is accomplished by setting the focus policy to Qt.StrongFocus.
- #The widget should only accept wheel events if it already has the focus.
- #This is accomplished by reimplementing QWidget.wheelEvent within a QSpinBox subclass:
+ #The widget should only accept wheel events if it already has the
+ #focus. This is accomplished by reimplementing QWidget.wheelEvent
+ #within a QSpinBox subclass:
def wheelEvent(self, event):
if self.hasFocus() is False:
event.ignore()
else:
- QComboBox.wheelEvent(self, event)
-
+ QComboBox.wheelEvent(self, event)
+
@Slot(str, int, int)
@Slot(int, int, int)
@Slot(float, int, int)
def receive_monitor_update(self, value, status, alarm_severity):
'''Triggered by monitor signal'''
-
+
PVGateway.receive_monitor_update(self, value, status, alarm_severity)
-
+
@Slot(int, str, int)
def receive_connect_update(self, handle: int, pv_name: str, status: int):
'''Triggered by connect signal'''
PVGateway.receive_connect_update(self, handle, pv_name, status)
-
+
class CAQMessageButton(QPushButton, PVGateway):
- trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_float = Signal(float, int, int)
trigger_monitor_int = Signal(int, int, int)
trigger_monitor_str = Signal(str, int, int)
trigger_monitor = Signal(object, int)
- trigger_connect = Signal(int, str, int)
-
+ trigger_connect = Signal(int, str, int)
+
def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
notify_freq_hz: int = 0,
- pv_within_daq_group: bool = False, color_mode = None,
- show_units = False, msg_label: str = "",
- msg_press_value = None, msg_release_value =None,
+ pv_within_daq_group: bool = False, color_mode=None,
+ show_units=False, msg_label: str = "",
+ msg_press_value=None, msg_release_value=None,
start_monitor=False):
- super().__init__(parent=parent, pv_name=pv_name,
+ super().__init__(parent=parent, pv_name=pv_name,
monitor_callback=monitor_callback,
- notify_freq_hz=\
- notify_freq_hz,
- pv_within_daq_group=pv_within_daq_group,
- color_mode=color_mode, show_units=show_units,
- msg_label=msg_label,
+ notify_freq_hz=notify_freq_hz,
+ pv_within_daq_group=pv_within_daq_group,
+ color_mode=color_mode, show_units=show_units,
+ msg_label=msg_label,
connect_callback=self.py_connect_callback)
-
-
+
self.msg_press_value = msg_press_value
self.msg_release_value = msg_release_value
-
+
if self.msg_press_value is not None:
self.pressed.connect(self.act_on_pressed)
- if self.msg_release_value is not None:
- self.released.connect(self.act_on_released)
-
+ if self.msg_release_value is not None:
+ self.released.connect(self.act_on_released)
+
self.msg_label = msg_label
self.suggested_text = self.msg_label
_suggested_text_length = len(self.suggested_text)+3
- self.suggested_text = self.suggested_text.rjust(
- _suggested_text_length,"^")
+ self.suggested_text = self.suggested_text.rjust(_suggested_text_length,
+ "^")
+
+ self.configure_widget()
- self.configure_widget()
-
self.msg_press_status = self.cyca.ICAFE_NORMAL
self.msg_release_status = self.cyca.ICAFE_NORMAL
self.msg_report_status = "PV={0}\n".format(self.pv_name)
self.msg_has_error = False
-
+
if not self.pv_within_daq_group and start_monitor:
self.monitor_start()
def py_connect_callback(self, handle, pvname, status):
- '''Callback function to be invoked on change of pv connection status.
- '''
+ '''Callback function to be invoked on change of
+ pv connection status.
+ '''
self.trigger_connect.emit(int(handle), str(pvname), int(status))
-
-
+
+
@Slot(str, int, int)
@Slot(int, int, int)
@Slot(float, int, int)
def receive_monitor_update(self, value, status, alarm_severity):
- #print(self, self.pv_name, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
PVGateway.receive_monitor_update(self, value, status, alarm_severity)
-
-
+
@Slot(int, str, int)
def receive_connect_update(self, handle: int, pv_name: str, status: int):
'''Triggered by connect signal'''
PVGateway.receive_connect_update(self, handle, pv_name, status)
-
- def configure_widget(self):
- self.setFocusPolicy(Qt.StrongFocus)
- #self.setAutoDefault(True)
- self.setCheckable(True) #Recognizes press and release states
- #self.setChecked(True)
- #self.setFlat(True)
- fm = QFontMetricsF(QFont("Sans Serif", 12))
- qrect = fm.boundingRect(self.suggested_text)
-
+ def configure_widget(self):
+ self.setFocusPolicy(Qt.StrongFocus)
+ self.setCheckable(True) #Recognizes press and release states
+
+ fm = QFontMetricsF(QFont("Sans Serif", 12))
+ qrect = fm.boundingRect(self.suggested_text)
+
_width_scaling_factor = 1.0
self.setText(self.msg_label)
- self.setFixedHeight((fm.lineSpacing()*2.0))
+ self.setFixedHeight((fm.lineSpacing()*2.0))
self.setFixedWidth((qrect.width() * _width_scaling_factor))
-
- self.qt_property_initial_values(qt_object_name = self.PV_CONTROLLER)
+
+ self.qt_property_initial_values(qt_object_name=self.PV_CONTROLLER)
-
def enterEvent(self, event):
if self.pv_info is not None:
if self.pv_info.accessWrite == 0:
self.setProperty("readOnly", True)
self.qt_style_polish()
-
+
def leaveEvent(self, event):
- #if self.pv_info.accessWrite == 0:
- if self.property("readOnly"):
+ if self.property("readOnly"):
self.setProperty(self.qt_dynamic_property_get(), True)
- self.qt_style_polish()
- '''
- def enterEvent(self, event):
- if self.pv_info.accessWrite == 0:
- self.setEnabled(False)
-
- def leaveEvent(self, event):
- if not self.isEnabled():
- self.setEnabled(True)
- '''
+ self.qt_style_polish()
def mouseReleaseEvent(self, event):
- #print("LOCAL mouseRelease = = > This Event is required so that clicked is activated!!!!!!")
if self.msg_release_value is not None:
time.sleep(0.1)
QPushButton.mouseReleaseEvent(self, event)
- def mousePressEvent(self, event):
+ def mousePressEvent(self, event):
if self.pv_info is not None:
if self.pv_info.accessWrite == 1:
QPushButton.mousePressEvent(self, event)
- if event.button() == Qt.RightButton:
+ if event.button() == Qt.RightButton:
PVGateway.mousePressEvent(self, event)
-
+
def act_on_pressed(self):
- #print("caQPushButton press ValueChanged--> ")
- if self.msg_press_value is not None:
- self.msg_press_status = self.cafe.set(self.handle, self.msg_press_value)
+ if self.msg_press_value is not None:
+ self.msg_press_status = self.cafe.set(self.handle,
+ self.msg_press_value)
if self.msg_press_status != self.cyca.ICAFE_NORMAL:
- self.msg_report_status += ("Error in set operation (at press button):\n{0}\n"
- .format(self.cafe.getStatusCodeAsString(self.msg_press_status)))
+ self.msg_report_status += (
+ "Error in set operation (at press button):\n{0}\n".format(
+ self.cafe.getStatusCodeAsString(self.msg_press_status)))
self.msg_has_error = True
qm = QMessageBox()
- qm.setText(self.msg_report_status)
+ qm.setText(self.msg_report_status)
qm.exec()
QApplication.processEvents()
-
+
def act_on_released(self):
- #print("caQPushButton release ValueChanged--> ")
- if self.msg_release_value is not None:
- self.msg_release_status = self.cafe.set(self.handle, self.msg_release_value)
+ if self.msg_release_value is not None:
+ self.msg_release_status = self.cafe.set(self.handle,
+ self.msg_release_value)
if self.msg_release_status != self.cyca.ICAFE_NORMAL:
- self.msg_report_status += ("Error in set operation (at release button):\n{0}\n"
- .format(self.cafe.getStatusCodeAsString(self.msg_release_status)))
+ self.msg_report_status += (
+ "Error in set operation (at release button):\n{0}\n".format(
+ self.cafe.getStatusCodeAsString(self.msg_release_status)))
self.msg_has_error = True
if self.msg_has_error:
self.msg_has_error = False
self.pv_message_in_a_box.setText(self.msg_report_status)
- self.pv_message_in_a_box.exec()
+ self.pv_message_in_a_box.exec()
self.msg_report_status = "PV={0}\n".format(self.pv_name)
qm = QMessageBox()
- qm.setText(self.msg_report_status)
+ qm.setText(self.msg_report_status)
qm.exec()
QApplication.processEvents()
class CAQTextEntry(QLineEdit, PVGateway):
'''Channel access enabled QTextEntry widget'''
- trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_float = Signal(float, int, int)
trigger_monitor_int = Signal(int, int, int)
trigger_monitor_str = Signal(str, int, int)
trigger_monitor = Signal(object, int)
- trigger_connect = Signal(int, str, int)
-
+ trigger_connect = Signal(int, str, int)
+
def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
- pv_within_daq_group: bool = False, color_mode = None,
- show_units = False, prefix: str = "", suffix: str = ""):
- super().__init__(parent, pv_name, monitor_callback,
- pv_within_daq_group, color_mode, show_units, prefix,
- suffix, connect_callback=self.py_connect_callback)
-
+ pv_within_daq_group: bool = False, color_mode=None,
+ show_units=False, prefix: str = "", suffix: str = ""):
+ super().__init__(parent, pv_name, monitor_callback, pv_within_daq_group,
+ color_mode, show_units, prefix, suffix,
+ connect_callback=self.py_connect_callback)
+
self.is_initialize_complete() #waits a fraction of a second
-
- self.currentText =""
+
+ self.currentText = ""
self.returnPressed.connect(self.valuechange)
- self.configure_widget()
+ self.configure_widget()
if self.pv_within_daq_group is False:
self.monitor_start()
def py_connect_callback(self, handle, pvname, status):
- '''Callback function to be invoked on change of pv connection status.
- '''
+ '''Callback function to be invoked on change of
+ pv connection status.
+ '''
self.trigger_connect.emit(int(handle), str(pvname), int(status))
-
@Slot(str, int, int)
@Slot(int, int, int)
@Slot(float, int, int)
def receive_monitor_update(self, value, status, alarm_severity):
- #print(self, self.pv_name, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
PVGateway.receive_monitor_update(self, value, status, alarm_severity)
-
-
+
@Slot(int, str, int)
def receive_connect_update(self, handle: int, pv_name: str, status: int):
'''Triggered by connect signal'''
PVGateway.receive_connect_update(self, handle, pv_name, status)
-
- def configure_widget(self):
+
+ def configure_widget(self):
self.setFocusPolicy(Qt.StrongFocus)
-
- fm = QFontMetricsF(QFont("Sans Serif", 12))
- qrect = fm.boundingRect(self.suggested_text)
-
+
+ fm = QFontMetricsF(QFont("Sans Serif", 12))
+ qrect = fm.boundingRect(self.suggested_text)
+
_width_scaling_factor = 1.15
- self.setFixedHeight((fm.lineSpacing()*1.8))
+ self.setFixedHeight((fm.lineSpacing()*1.8))
self.setFixedWidth(((qrect.width()+10) * _width_scaling_factor))
-
- self.qt_property_initial_values(qt_object_name = self.PV_CONTROLLER)
+
+ self.qt_property_initial_values(qt_object_name=self.PV_CONTROLLER)
def valuechange(self):
- print(self.handle, self.text())
status = self.cafe.set(self.handle, self.text())
if status != self.cyca.ICAFE_NORMAL:
- #elf.showSetErrorMsg(status)
if self.cafe.getNoMonitors(self.handle) == 0:
val = self.cafe.get(self.handle, 'native')
else:
val = self.cafe.getCache(self.handle, 'native')
- #print("val", val)
+
if val is not None:
if isinstance(val, str):
strText = val
else:
valStr = ("{: .%sf}" % self.precision)
- strText = valStr.format(round(val, self.precision)) ### + " " + self.units
+ strText = valStr.format(round(val, self.precision))
print(strText, " precision ", self.precision)
self.setText(strText)
else:
@@ -626,15 +606,8 @@ class CAQTextEntry(QLineEdit, PVGateway):
val = self.cafe.get(self.handle, 'native')
def setText(self, value):
-
QLineEdit.setText(self, value)
-
- #QLineEdit.setFixedWidth(self, QLineEdit.width(self)+10)
self.currentText = self.text()
-
- #status = self.cafe.set(self.handle, value)
- #if status ! = self.cyca.ICAFE_NORMAL:
- #self.showSetErrorMsg(status)
def enterEvent(self, event):
if self.pv_info is not None:
@@ -643,328 +616,73 @@ class CAQTextEntry(QLineEdit, PVGateway):
self.qt_style_polish()
self.setReadOnly(True)
self.setFocusPolicy(Qt.StrongFocus)
-
+
def leaveEvent(self, event):
-
+
if self.isReadOnly():
self.setReadOnly(False)
self.setProperty(self.qt_dynamic_property_get(), True)
- self.qt_style_polish()
+ self.qt_style_polish()
if self.text() != self.currentText:
- QLineEdit.setText(self, self.currentText)
-
+ QLineEdit.setText(self, self.currentText)
+
self.setCursorPosition(100)
self.clearFocus()
self.setFocusPolicy(Qt.NoFocus)
- del event
+ del event
- #def mouseMoveEvent(self, event):
- # print("mouseMoveEvent", event.type())
-
- def mousePressEvent(self, event):
+ def mousePressEvent(self, event):
if event.button() == Qt.RightButton:
- PVGateway.mousePressEvent(self, event)
+ PVGateway.mousePressEvent(self, event)
self.clearFocus()
return
local_event_position = QPoint(event.x(), event.y())
local_cursor_position = self.cursorPositionAt(local_event_position)
self.setCursorPosition(local_cursor_position)
-
-
-
class CAQSpinBox(QSpinBox, PVGateway):
'''Channel access enabled QTextEntry widget'''
- trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_float = Signal(float, int, int)
trigger_monitor_int = Signal(int, int, int)
trigger_monitor_str = Signal(str, int, int)
trigger_monitor = Signal(object, int)
- trigger_connect = Signal(int, str, int)
-
- def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
- pv_within_daq_group: bool = False, color_mode = None,
- show_units = False, prefix: str = "", suffix: str = ""):
- super().__init__(parent, pv_name, monitor_callback,
- pv_within_daq_group, color_mode, show_units, prefix,
- suffix, connect_callback=self.py_connect_callback)
- #super().__init__(parent=parent, pv_name=pv_name, monitor_callback=monitor_callback,
- # pv_within_daq_group= pv_within_daq_group, color_mode=color_mode, show_units=show_units, prefix=prefix,
- # suffix=suffix, connect_callback=self.py_connect_callback)
-
-
- self.is_initialize_complete()
+ trigger_connect = Signal(int, str, int)
+
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode=None,
+ show_units=False, prefix: str = "", suffix: str = ""):
+ super().__init__(parent, pv_name, monitor_callback, pv_within_daq_group,
+ color_mode, show_units, prefix, suffix,
+ connect_callback=self.py_connect_callback)
+
+ self.is_initialize_complete()
self.valueChanged.connect(self.value_change)
self.configure_widget()
if not self.pv_within_daq_group:
self.monitor_start()
-
+
def py_connect_callback(self, handle, pvname, status):
- '''Callback function to be invoked on change of pv connection status.
- '''
- #print(" py_connect_callback::::::::::::::::::::::::::::::::::::::::::::::::::::::::::..: START ")
+ '''
+ Callback function to be invoked on change of pv connection
+ status.
+ '''
self.trigger_connect.emit(int(handle), str(pvname), int(status))
- #print(" py_connect_callback:: END ")
-
@Slot(str, int, int)
@Slot(int, int, int)
@Slot(float, int, int)
def receive_monitor_update(self, value, status, alarm_severity):
PVGateway.receive_monitor_update(self, value, status, alarm_severity)
-
-
- @Slot(int, str, int)
- def receive_connect_update(self, handle: int, pv_name: str, status: int):
- '''Triggered by connect signal'''
- PVGateway.receive_connect_update(self, handle, pv_name, status)
-
- def configure_widget(self):
- self.previousValue = None
- self.currentValue = None
- self.setFocusPolicy(Qt.StrongFocus)
- self.setButtonSymbols(QAbstractSpinBox.UpDownArrows)
- self.setAccelerated(False)
- self.setLineEdit(QLineEditExtended(self))
- self.lineEdit().setEnabled(True)
- self.lineEdit().setReadOnly(False)
- self.lineEdit().setAlignment(Qt.AlignLeft)
- self.lineEdit().setFont(QFont("Sans Serif", 16))
-
- fm = QFontMetricsF(QFont("Sans Serif", 12))
-
- _suggested_text = self.max_control_abs_str
- _added_text = ""
-
- if self.show_units:
- _added_text += " " + self.units
- _suggested_text += self.units
- if len(self.suffix) > 0:
- _added_text += " " + self.suffix
- _suggested_text += self.suffix
-
- self.setSuffix(_added_text)
-
- qrect = fm.boundingRect(_suggested_text)
- _width_scaling_factor = 1.0
-
- self.setFixedHeight((fm.lineSpacing()*1.8))
- self.setFixedWidth(((qrect.width()) * _width_scaling_factor))
-
- self.qt_property_initial_values(qt_object_name = self.PV_CONTROLLER)
-
- if self.pv_ctrl is not None:
- self.setRange(int(self.pv_ctrl.lowerControlLimit),
- int(self.pv_ctrl.upperControlLimit))
-
-
- def post_display_value(self, value):
- '''Convert value to index'''
- #print ("MON VALUE IN QSPINBOX ", value, " //////////// ", int(value))
-
- if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
- self.blockSignals(True)
- self.setValue(int(round(value)))
- self.blockSignals(False)
- else:
- self.setValue(int(round(value)))
-
-
- def mousePressEvent(self, event):
- _opt = QStyleOptionSpinBox()
- self.initStyleOption(_opt)
- _rect_up = self.style().subControlRect(QStyle.CC_SpinBox, _opt,
- QStyle.SC_SpinBoxUp, self)
- _rect_down = self.style().subControlRect(QStyle.CC_SpinBox, _opt,
- QStyle.SC_SpinBoxDown, self)
-
- #print(_rect_up.x(), _rect_down.x(), event.x(), event.y(), event.localPos(), event.pos())
-
- self.previousValue = self.value()
-
- if event.button() == Qt.LeftButton:
- if _rect_up.contains(event.pos(), proper=True) or \
- _rect_down.contains(event.pos(), proper=True):
-
- if not self.cafe.isConnected(self.handle):
- self.pv_message_in_a_box.setText(("Spinbox change value "
- "events currently suspended\n"
- "as channel {0} is disconnected.".format(self.pv_name)))
- self.pv_message_in_a_box.exec()
-
- return
-
- QSpinBox.mousePressEvent(self, event)
- #Clear Focus: only one step per mouse click.
- self.clearFocus()
-
- local_event_position = QPoint(event.x(), event.y())
- local_cursor_position = self.lineEdit().cursorPositionAt(local_event_position)
-
- #print(local_event_position, " QSPINBOX VALUE POS ", local_cursor_position)
- self.lineEdit().setCursorPosition(local_cursor_position)
-
-
- PVGateway.mousePressEvent(self, event)
-
- #Clear Focus: only one step per mouse click.
- #self.clearFocus()
-
- def setValue(self, intVal):
- #print( QSpinBox.value(self), intVal)
- #print( "setValue called//1//", intVal)
- QSpinBox.setValue(self, intVal)
- self.currentValue = self.value()
- #print( "setValue called//2//", intVal)
-
-
- def value_change(self, intVal):
- #print("valuechange called", intVal, QSpinBox.value(self))
- status = self.cafe.set(self.handle, intVal)
-
- if status != self.cyca.ICAFE_NORMAL:
-
- #print("previous current", self.previousValue, self.currentValue)
-
- if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
- self.blockSignals(True)
-
- if self.previousValue is not None:
- self.setValue(self.previousValue)
- else:
- _value = self.cafe.getCache(self.handle, 'int')
-
- if _value is not None:
- self.setValue(_value)
-
- if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
- self.blockSignals(False)
-
- self.pv_message_in_a_box.setText(
- ("Spinbox set operation reports error:\n{0}"
- .format(self.cafe.getStatusCodeAsString(status))))
- self.pv_message_in_a_box.exec()
-
- else:
- if self.previousValue is not None:
- self.setValue(self.previousValue)
- else:
- _value = self.cafe.getCache(self.handle, 'int')
-
- if _value is not None:
- self.setValue(_value)
-
- self.parent.statusbar.showMessage(
- (self.widget_class + " " +
- self.cafe.getStatusCodeAsString(status)))
-
-
-
- def enterEvent(self, event):
- if self.pv_info is not None:
- if self.pv_info.accessWrite == 0:
- self.setProperty("readOnly", True)
- self.qt_style_polish()
- self.setReadOnly(True)
- self.setFocusPolicy(Qt.StrongFocus)
-
- def leaveEvent(self, event):
- #if self.value() != self.currentValue:
- # self.setValue(self.currentValue)
- if self.isReadOnly():
- self.setReadOnly(False)
- self.setProperty(self.qt_dynamic_property_get(), True)
- self.qt_style_polish()
-
- self.clearFocus()
- self.setFocusPolicy(Qt.NoFocus)
- del event
-
-
- def keyPressEvent(self, event):
- #print("key press event", event.type(), hex(event.key()))
- if event.key() in (Qt.Key_Return, Qt.Key_Enter):
- QSpinBox.keyPressEvent(self, event)
- self.clearFocus()
- elif event.key() in (Qt.Key_Up, Qt.Key_Down):
- QSpinBox.keyPressEvent(self, event)
- else:
- if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
- self.blockSignals(True)
- QSpinBox.keyPressEvent(self, event)
- if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
- self.blockSignals(False)
-
-
- # The spin box should not gain focus by using the mouse wheel.
- # This is accomplished by setting the focus policy to Qt.StrongFocus.
- # The spin box should only accept wheel events if it already has the focus.
- # This is accomplished by reimplementing QWidget.wheelEvent within a QSpinBox subclass:
- def wheelEvent(self, event):
- #print("wheelEvent", self.hasFocus())
- if self.hasFocus() is False:
- event.ignore()
- else:
- QSpinBox.wheelEvent(self, event)
-
- # def enterEvent(self,event):
- # print("EnterEvent set focusPolicy to Strong")
- # self.setFocusPolicy(Qt.StrongFocus)
-
-
-
-class CAQDoubleSpinBox(QDoubleSpinBox, PVGateway):
- '''Channel access enabled QDoubleSpinBox widget'''
- trigger_monitor_float = Signal(float, int, int)
- trigger_monitor_int = Signal(int, int, int)
- trigger_monitor_str = Signal(str, int, int)
- trigger_monitor = Signal(object, int)
- trigger_connect = Signal(int, str, int)
-
- def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
- pv_within_daq_group: bool = False, color_mode = None,
- show_units: bool = False, prefix: str = "", suffix: str = ""):
- super().__init__(parent=parent, pv_name=pv_name,
- monitor_callback=monitor_callback,
- pv_within_daq_group= pv_within_daq_group,
- color_mode=color_mode, show_units=show_units,
- prefix=prefix, suffix=suffix,
- connect_callback=self.py_connect_callback)
-
- self.is_initialize_complete()
- self.valueChanged.connect(self.valuechange)
- self.configure_widget()
-
- if self.pv_within_daq_group is False:
- self.monitor_start()
-
-
- def py_connect_callback(self, handle, pvname, status):
- '''Callback function to be invoked on change of pv connection status.
- '''
- #print(" py_connect_callback::::::::::::::::::::::::::::::::::::::::::::::::::::::::::..: START ")
- self.trigger_connect.emit(int(handle), str(pvname), int(status))
- #print(" py_connect_callback:: END ")
-
-
- @Slot(str, int, int)
- @Slot(int, int, int)
- @Slot(float, int, int)
- def receive_monitor_update(self, value, status, alarm_severity):
- PVGateway.receive_monitor_update(self, value, status, alarm_severity)
-
@Slot(int, str, int)
def receive_connect_update(self, handle: int, pv_name: str, status: int):
'''Triggered by connect signal'''
PVGateway.receive_connect_update(self, handle, pv_name, status)
- #self.configure_widget()
- #self.setSingleStep(0.1)
-
-
+
def configure_widget(self):
self.previousValue = None
self.currentValue = None
@@ -972,132 +690,106 @@ class CAQDoubleSpinBox(QDoubleSpinBox, PVGateway):
self.setButtonSymbols(QAbstractSpinBox.UpDownArrows)
self.setAccelerated(False)
self.setLineEdit(QLineEditExtended(self))
- #self.lineEdit().setObjectName("Controller")
- #self.lineEdit().setProperty("notActOnBeam", True)
- #self.lineEdit().setEnabled(False)
+ self.lineEdit().setEnabled(True)
self.lineEdit().setReadOnly(False)
- self.lineEdit().setAlignment(Qt.AlignRight)
- self.lineEdit().setFont(QFont("Sans Serif", 12))
-
- _stepsize = 10**(self.precision * -1)
- self.setSingleStep(_stepsize)
- self.setDecimals(self.precision)
+ self.lineEdit().setAlignment(Qt.AlignLeft)
+ self.lineEdit().setFont(QFont("Sans Serif", 16))
fm = QFontMetricsF(QFont("Sans Serif", 12))
- _suggested_text = self.suggested_text
+ _suggested_text = self.max_control_abs_str
_added_text = ""
if self.show_units:
- _added_text += " " + self.units
- _suggested_text += self.units
- if len(self.suffix) > 0:
- _added_text += " " + self.suffix
- _suggested_text += self.suffix
-
- self.setSuffix(_added_text)
-
- qrect = fm.boundingRect(_suggested_text)
-
- _width_scaling_factor = 1.15
-
- self.setFixedHeight((fm.lineSpacing()*1.8))
+ _added_text += " " + self.units
+ _suggested_text += self.units
+ if self.suffix:
+ _added_text += " " + self.suffix
+ _suggested_text += self.suffix
+
+ self.setSuffix(_added_text)
+
+ qrect = fm.boundingRect(_suggested_text)
+ _width_scaling_factor = 1.0
+
+ self.setFixedHeight((fm.lineSpacing()*1.8))
self.setFixedWidth(((qrect.width()) * _width_scaling_factor))
- self.qt_property_initial_values(qt_object_name = self.PV_CONTROLLER)
+ self.qt_property_initial_values(qt_object_name=self.PV_CONTROLLER)
if self.pv_ctrl is not None:
- self.setRange(int(self.pv_ctrl.lowerControlLimit),
+ self.setRange(int(self.pv_ctrl.lowerControlLimit),
int(self.pv_ctrl.upperControlLimit))
-
- #if self.cafe.isConnected(self.handle):
- # self.setButtonSymbols(QAbstractSpinBox.UpDownArrows)
- #else:
- # self.setButtonSymbols(QAbstractSpinBox.NoButtons)
def post_display_value(self, value):
- '''set value from monitor'''
- #print("value: : ", value)
+ '''Convert value to index'''
if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
self.blockSignals(True)
-
- self.setValue(value)
+ self.setValue(int(round(value)))
self.blockSignals(False)
else:
- self.setValue(value)
+ self.setValue(int(round(value)))
+
def mousePressEvent(self, event):
-
_opt = QStyleOptionSpinBox()
- self.initStyleOption(_opt)
- _rect_up = self.style().subControlRect(QStyle.CC_SpinBox, _opt,
- QStyle.SC_SpinBoxUp, self)
- _rect_down = self.style().subControlRect(QStyle.CC_SpinBox, _opt,
+ self.initStyleOption(_opt)
+ _rect_up = self.style().subControlRect(QStyle.CC_SpinBox, _opt,
+ QStyle.SC_SpinBoxUp, self)
+ _rect_down = self.style().subControlRect(QStyle.CC_SpinBox, _opt,
QStyle.SC_SpinBoxDown, self)
- self.previousValue = self.value()
-
- if event.button() == Qt.LeftButton:
- if _rect_up.contains(event.pos(), proper=False) or \
- _rect_down.contains(event.pos(), proper=False):
-
- if not self.cafe.isConnected(self.handle):
- self.pv_message_in_a_box.setText(("Spinbox change value "
- "events currently suspended\n"
- "as channel {0} is disconnected.".format(self.pv_name)))
- self.pv_message_in_a_box.exec()
- return
- QDoubleSpinBox.mousePressEvent(self, event)
+ self.previousValue = self.value()
+
+ if event.button() == Qt.LeftButton:
+ if _rect_up.contains(event.pos(), proper=True) or \
+ _rect_down.contains(event.pos(), proper=True):
+
+ if not self.cafe.isConnected(self.handle):
+ self.pv_message_in_a_box.setText(
+ ("Spinbox change value events currently suspended\n" +
+ "as channel {0} is disconnected.").format(
+ self.pv_name))
+ self.pv_message_in_a_box.exec()
+ return
+
+ QSpinBox.mousePressEvent(self, event)
+ #Clear Focus: only one step per mouse click.
+ self.clearFocus()
local_event_position = QPoint(event.x(), event.y())
local_cursor_position = self.lineEdit().cursorPositionAt(
local_event_position)
- #print(local_event_position, " POS ", local_cursor_position)
+
self.lineEdit().setCursorPosition(local_cursor_position)
-
PVGateway.mousePressEvent(self, event)
-
- #Clear Focus: only one step per mouse click.
- #Not wanted for floats
- #self.clearFocus()
- def mouseReleaseEvent(self, event):
- self.clearFocus()
-
- def setValue(self, value):
- #print("setValue called (B)", value, self.value())
+ def setValue(self, intVal):
+ QSpinBox.setValue(self, intVal)
self.currentValue = self.value()
- QDoubleSpinBox.setValue(self, value)
- #time.sleep(0.01)
- #print("setValue called (A)", value, self.value())
-
- def valuechange(self, fval):
- #print("valuechange called, fval, value(), previous", fval, self.value(), self.previousValue)
- status = self.cafe.set(self.handle, fval)
-
+ def value_change(self, intVal):
+ status = self.cafe.set(self.handle, intVal)
if status != self.cyca.ICAFE_NORMAL:
-
- #print("previous current", self.previousValue, self.currentValue)
-
-
+
if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
self.blockSignals(True)
+
if self.previousValue is not None:
self.setValue(self.previousValue)
else:
- _value = self.cafe.getCache(self.handle, 'float')
-
+ _value = self.cafe.getCache(self.handle, 'int')
+
if _value is not None:
- self.setValue(_value)
-
+ self.setValue(_value)
+
if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
self.blockSignals(False)
-
+
self.pv_message_in_a_box.setText(
("Spinbox set operation reports error:\n{0}"
.format(self.cafe.getStatusCodeAsString(status))))
@@ -1107,19 +799,238 @@ class CAQDoubleSpinBox(QDoubleSpinBox, PVGateway):
if self.previousValue is not None:
self.setValue(self.previousValue)
else:
- _value = self.cafe.getCache(self.handle, 'float')
-
+ _value = self.cafe.getCache(self.handle, 'int')
+
if _value is not None:
- self.setValue(_value)
-
- self.parent.statusbar.showMessage(
- (self.widget_class + " " +
+ self.setValue(_value)
+
+ self.parent.statusbar.showMessage(
+ (self.widget_class + " " +
self.cafe.getStatusCodeAsString(status)))
-
-
+
+
+ def enterEvent(self, event):
+ if self.pv_info is not None:
+ if self.pv_info.accessWrite == 0:
+ self.setProperty("readOnly", True)
+ self.qt_style_polish()
+ self.setReadOnly(True)
+ self.setFocusPolicy(Qt.StrongFocus)
+
+ def leaveEvent(self, event):
+ if self.isReadOnly():
+ self.setReadOnly(False)
+ self.setProperty(self.qt_dynamic_property_get(), True)
+ self.qt_style_polish()
+
+ self.clearFocus()
+ self.setFocusPolicy(Qt.NoFocus)
+ del event
+
+
+ def keyPressEvent(self, event):
+ if event.key() in (Qt.Key_Return, Qt.Key_Enter):
+ QSpinBox.keyPressEvent(self, event)
+ self.clearFocus()
+ elif event.key() in (Qt.Key_Up, Qt.Key_Down):
+ QSpinBox.keyPressEvent(self, event)
+ else:
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+ QSpinBox.keyPressEvent(self, event)
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(False)
+
+
+ # The spin box should not gain focus by using the mouse wheel.
+ # This is accomplished by setting the focus policy to Qt.StrongFocus.
+ # The spin box should only accept wheel events if it already has the focus.
+ # This is accomplished by reimplementing QWidget.wheelEvent within a
+ # QSpinBox subclass:
+ def wheelEvent(self, event):
+ #print("wheelEvent", self.hasFocus())
+ if self.hasFocus() is False:
+ event.ignore()
+ else:
+ QSpinBox.wheelEvent(self, event)
+
+
+class CAQDoubleSpinBox(QDoubleSpinBox, PVGateway):
+ '''Channel access enabled QDoubleSpinBox widget'''
+ trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_int = Signal(int, int, int)
+ trigger_monitor_str = Signal(str, int, int)
+ trigger_monitor = Signal(object, int)
+ trigger_connect = Signal(int, str, int)
+
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode=None,
+ show_units: bool = False, prefix: str = "", suffix: str = ""):
+ super().__init__(parent=parent, pv_name=pv_name,
+ monitor_callback=monitor_callback,
+ pv_within_daq_group=pv_within_daq_group,
+ color_mode=color_mode, show_units=show_units,
+ prefix=prefix, suffix=suffix,
+ connect_callback=self.py_connect_callback)
+
+ self.is_initialize_complete()
+ self.valueChanged.connect(self.valuechange)
+ self.configure_widget()
+
+ if self.pv_within_daq_group is False:
+ self.monitor_start()
+
+
+ def py_connect_callback(self, handle, pvname, status):
+ '''
+ Callback function to be invoked on change of pv connection
+ status.
+ '''
+ self.trigger_connect.emit(int(handle), str(pvname), int(status))
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+ PVGateway.receive_monitor_update(self, value, status, alarm_severity)
+
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ PVGateway.receive_connect_update(self, handle, pv_name, status)
+
+ def configure_widget(self):
+ self.previousValue = None
+ self.currentValue = None
+ self.setFocusPolicy(Qt.StrongFocus)
+ self.setButtonSymbols(QAbstractSpinBox.UpDownArrows)
+ self.setAccelerated(False)
+ self.setLineEdit(QLineEditExtended(self))
+ self.lineEdit().setReadOnly(False)
+ self.lineEdit().setAlignment(Qt.AlignRight)
+ self.lineEdit().setFont(QFont("Sans Serif", 12))
+
+ _stepsize = 10**(self.precision * -1)
+ self.setSingleStep(_stepsize)
+ self.setDecimals(self.precision)
+
+ fm = QFontMetricsF(QFont("Sans Serif", 12))
+
+ _suggested_text = self.suggested_text
+ _added_text = ""
+
+ if self.show_units:
+ _added_text += " " + self.units
+ _suggested_text += self.units
+ if self.suffix:
+ _added_text += " " + self.suffix
+ _suggested_text += self.suffix
+
+ self.setSuffix(_added_text)
+
+ qrect = fm.boundingRect(_suggested_text)
+
+ _width_scaling_factor = 1.15
+
+ self.setFixedHeight((fm.lineSpacing()*1.8))
+ self.setFixedWidth(((qrect.width()) * _width_scaling_factor))
+
+ self.qt_property_initial_values(qt_object_name=self.PV_CONTROLLER)
+
+ if self.pv_ctrl is not None:
+ self.setRange(int(self.pv_ctrl.lowerControlLimit),
+ int(self.pv_ctrl.upperControlLimit))
+
+
+ def post_display_value(self, value):
+ '''set value from monitor'''
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+ self.setValue(value)
+ self.blockSignals(False)
+ else:
+ self.setValue(value)
+
+ def mousePressEvent(self, event):
+
+ _opt = QStyleOptionSpinBox()
+ self.initStyleOption(_opt)
+ _rect_up = self.style().subControlRect(QStyle.CC_SpinBox, _opt,
+ QStyle.SC_SpinBoxUp, self)
+ _rect_down = self.style().subControlRect(QStyle.CC_SpinBox, _opt,
+ QStyle.SC_SpinBoxDown, self)
+ self.previousValue = self.value()
+
+ if event.button() == Qt.LeftButton:
+ if _rect_up.contains(event.pos(), proper=False) or \
+ _rect_down.contains(event.pos(), proper=False):
+
+ if not self.cafe.isConnected(self.handle):
+ self.pv_message_in_a_box.setText(
+ ("Spinbox change value events currently suspended\n" +
+ "as channel {0} is disconnected.").format(
+ self.pv_name))
+ self.pv_message_in_a_box.exec()
+ return
+
+ QDoubleSpinBox.mousePressEvent(self, event)
+
+ local_event_position = QPoint(event.x(), event.y())
+ local_cursor_position = self.lineEdit().cursorPositionAt(
+ local_event_position)
+
+ self.lineEdit().setCursorPosition(local_cursor_position)
+
+ PVGateway.mousePressEvent(self, event)
+
+ def mouseReleaseEvent(self, event):
+ self.clearFocus()
+
+ def setValue(self, value):
+ self.currentValue = self.value()
+ QDoubleSpinBox.setValue(self, value)
+
+ def valuechange(self, fval):
+ status = self.cafe.set(self.handle, fval)
+
+ if status != self.cyca.ICAFE_NORMAL:
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+ if self.previousValue is not None:
+ self.setValue(self.previousValue)
+ else:
+ _value = self.cafe.getCache(self.handle, 'float')
+
+ if _value is not None:
+ self.setValue(_value)
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(False)
+
+ self.pv_message_in_a_box.setText(
+ ("Spinbox set operation reports error:\n{0}"
+ .format(self.cafe.getStatusCodeAsString(status))))
+ self.pv_message_in_a_box.exec()
+
+ else:
+ if self.previousValue is not None:
+ self.setValue(self.previousValue)
+ else:
+ _value = self.cafe.getCache(self.handle, 'float')
+
+ if _value is not None:
+ self.setValue(_value)
+
+ self.parent.statusbar.showMessage(
+ (self.widget_class + " " +
+ self.cafe.getStatusCodeAsString(status)))
+
+
def enterEvent(self, event):
self.setFocusPolicy(Qt.StrongFocus)
- #print("eventtype", event.type())
if self.pv_info is not None:
if self.pv_info.accessWrite == 0:
self.setProperty("readOnly", True)
@@ -1127,44 +1038,35 @@ class CAQDoubleSpinBox(QDoubleSpinBox, PVGateway):
self.setReadOnly(True)
def leaveEvent(self, event):
- #if self.value() != self.currentValue:
- # self.setValue(self.currentValue)
if self.isReadOnly():
self.setReadOnly(False)
self.setProperty(self.qt_dynamic_property_get(), True)
- self.qt_style_polish()
-
+ self.qt_style_polish()
+
self.clearFocus()
self.setFocusPolicy(Qt.NoFocus)
del event
def keyPressEvent(self, event):
- #print("key press event", event.type(), hex(event.key()))
- if event.key() in (Qt.Key_Return, Qt.Key_Enter):
+
+ if event.key() in (Qt.Key_Return, Qt.Key_Enter):
QDoubleSpinBox.keyPressEvent(self, event)
self.clearFocus()
- elif event.key() in (Qt.Key_Up, Qt.Key_Down):
+ elif event.key() in (Qt.Key_Up, Qt.Key_Down):
QDoubleSpinBox.keyPressEvent(self, event)
- else:
+ else:
if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
self.blockSignals(True)
QDoubleSpinBox.keyPressEvent(self, event)
if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
self.blockSignals(False)
-
-
-
- #def keyReleaseEvent(self, event):
- # print("key release")
- # QDoubleSpinBox.keyReleaseEvent(self, event)
-
# The spin box should not gain focus by using the mouse wheel.
# This is accomplished by setting the focus policy to Qt.StrongFocus.
# The spin box should only accept wheel events if it already has the focus.
- # This is accomplished by reimplementing QWidget.wheelEvent within a QSpinBox subclass:
+ # This is accomplished by reimplementing QWidget.wheelEvent within a
+ # QSpinBox subclass:
def wheelEvent(self, event):
- #print("wheelEvent", self.hasFocus())
if self.hasFocus() is False:
event.ignore()
else:
@@ -1176,88 +1078,60 @@ class reconnectQPushButton(QPushButton, QThread):
super().__init__()
self.parent = parent
self.clicked.connect(self.onClicked)
- self.isdirty = False
- self._handles_to_reconnect = []
+ self.isdirty = False
+ self._handles_to_reconnect = []
self.reconnectThread = None
- #def __del__(self):
- # print("D Called")
- # self.reconnectThread.wait()
-
def onClicked(self, event):
-
- self._handles_to_reconnect = []
+
+ self._handles_to_reconnect = []
for i in range(0, len(self.parent.pv_gateway)):
- if self.parent.item(i, self.parent.no_columns-1).checkState() == Qt.Checked:
- #print("handle", self.parent.pv_gateway[i].handle)
- #self.parent.cafe.monitorStop(self.parent.pv_gateway[1].handle)
- #self.parent.cafe.reconnect([self.parent.pv_gateway[1].handle])
- self._handles_to_reconnect.append(self.parent.pv_gateway[i].handle)
-
- #print("isConnected ", self.parent.cafe.isConnected(self.parent.pv_gateway[i].handle))
-
- #self.reconnectThread = ReconnectThread(self.parent, self._handles_to_reconnect ) # QThread(self).create(self.reconnect)
- #self.reconnectThread.start()
- self.reconnect() #,self._handles_to_reconnect)
+ if self.parent.item(
+ i, self.parent.no_columns-1).checkState() == Qt.Checked:
+ self._handles_to_reconnect.append(
+ self.parent.pv_gateway[i].handle)
+
+ self.reconnect()
QApplication.processEvents()
- #self.reconnectThread.wait()
def reconnect(self):
QApplication.processEvents()
- #self.parent.cafe.printHandles()
- #print(self._handles_to_reconnect)
+
self.isdirty = True
- if len(self._handles_to_reconnect) > 0:
- status=self.parent.cafe.reconnect(self._handles_to_reconnect)
- self.isdirty = False
+ if self._handles_to_reconnect:
+ self.parent.cafe.reconnect(self._handles_to_reconnect)
+ self.isdirty = False
#Uncheck reconnected channels
for i in range(0, len(self.parent.pv_gateway)):
- if self.parent.item(i, self.parent.no_columns-1).checkState() == Qt.Checked:
- if self.parent.cafe.isConnected(self.parent.pv_gateway[i].handle):
- self.parent.item(i, self.parent.no_columns-1).setCheckState(False)
+ if self.parent.item(
+ i, self.parent.no_columns-1).checkState() == Qt.Checked:
+ if self.parent.cafe.isConnected(
+ self.parent.pv_gateway[i].handle):
+ self.parent.item(
+ i, self.parent.no_columns-1).setCheckState(False)
-
+ #Uncheck global reconnect check box
+ self.parent.cb_item_all.setCheckState(Qt.Unchecked)
-'''
-class ReconnectThread(QThread):
-
- def __init__(self, parent, handles):
- QThread.__init__(self)
- self.parent=parent
- self._handles = handles
- print("Initialized")
-
- def __del__(self):
- self.wait()
-
- def run(self):
- print("reconnect")
- self.isdirty = True
- if len(self._handles) > 0:
- #status=self.parent.cafe.reconnect(self._handles)
- print("status")
- self.isdirty = False
- print("still running")
-'''
class CAQTableWidget(QTableWidget):
'''Channel access enabled QTableWidget widget'''
- #trigger_monitor_float = Signal(float, int, int)
+ #trigger_monitor_float = Signal(float, int, int)
#trigger_monitor_int = Signal(int, int, int)
#trigger_monitor_str = Signal(str, int, int)
- #trigger_connect = Signal(int, str, int)
-
+ #trigger_connect = Signal(int, str, int)
+
def hasNewData(self, _row, pv_data):
-
+
if self.pv_gateway[_row].pvd_previous is None:
return True
-
- newDataFlag = False
- if self.pv_gateway[_row].pvd_previous.ts[1] != pv_data.ts[1]:
+ newDataFlag = False
+
+ if self.pv_gateway[_row].pvd_previous.ts[1] != pv_data.ts[1]:
newDataFlag = True
- elif self.pv_gateway[_row].pvd_previous.ts[0] != pv_data.ts[0]:
+ elif self.pv_gateway[_row].pvd_previous.ts[0] != pv_data.ts[0]:
newDataFlag = True
# Catch disconnect events(!!) and set newDataFlag only
elif self.pv_gateway[_row].pvd_previous.status != pv_data.status:
@@ -1265,44 +1139,86 @@ class CAQTableWidget(QTableWidget):
return newDataFlag
+ def paint_rows(self, row_range: list = [], reset=False, last_row=[" ", " "],
+ columns=[0]):
+
+ _qcolor_last_line = QColor("#d1e8e9")
+ self.font_pts11 = QTableWidgetItem().font()
+ self.font_pts11.setPixelSize(11)
+ if reset:
+ _qcolor = self.item(0, self.columnCount()-1).background()
+ _start = 0
+ _end = self.rowCount()-1
+ else:
+ _qcolor = _qcolor_last_line
+ _start = row_range[0]
+ _end = row_range[1]
+
+ for _row in range(_start, _end):
+ _cell = QTableWidgetItem("{0}".format(_row+1))
+ if not reset:
+ _cell.setFont(self.font_pts11)
+ _cell.setBackground(_qcolor)
+
+ if 1 in columns:
+ self.item(_row, 0).setBackground(_qcolor)
+ self.item(_row, 0).setFont(self.font_pts11)
+ if 0 in columns:
+ self.setVerticalHeaderItem(_row, _cell)
+
+
+ #last row
+
+ if reset and 0 in columns:
+ _cell = QTableWidgetItem("{0}".format(last_row[0]))
+ _cell.setFont(self.font_pts11)
+ self.setVerticalHeaderItem(self.rowCount()-1, _cell)
+
+ self.item(self.rowCount()-1, 0).setTextAlignment(Qt.AlignCenter)
+ self.item(self.rowCount()-1, 0).setText(str(last_row[1]))
+ self.item(self.rowCount()-1, 0).setBackground(_qcolor)
+ self.item(self.rowCount()-1, 0).setFont(self.font_pts11)
+ elif last_row[0] != " ":
+ _cell = QTableWidgetItem("{0}".format(last_row[0]))
+ _cell.setBackground(_qcolor_last_line)
+ _cell.setFont(self.font_pts11)
+ self.setVerticalHeaderItem(self.rowCount()-1, _cell)
+
+ if columns:
+ self.item(self.rowCount()-1, 0).setTextAlignment(Qt.AlignCenter)
+ self.item(self.rowCount()-1, 0).setText(str(last_row[1]))
+ self.item(self.rowCount()-1, 0).setBackground(_qcolor_last_line)
+ self.item(self.rowCount()-1, 0).setFont(self.font_pts11)
+
+
def widget_update(self):
-
+
for _row, pvgate in enumerate(self.pv_gateway):
#for _row in range(0, len(self.pv_gateway)):
if not pvgate.notify_unison:
continue
- _handle = pvgate.handle
+ _handle = pvgate.handle
_pvd = pvgate.cafe.getPVCache(_handle)
- #Only if unison flag is set else return
-
- #Does not cater for reconnections
- #print(_row, self.pv_gateway[_row].pv_name, _pvd.status)
- #if not self.hasNewData(_row, _pvd) and _pvd.status == \
- # self.cyca.ICAFE_NORMAL:
- #print(_row, " has no new data")
- #continue
-
- if _pvd.status in (self.cyca.ICAFE_CS_NEVER_CONN,
- self.cyca.ICAFE_CA_OP_CONN_DOWN):
- pvgate.pvd_previous = _pvd
+ if _pvd.status in (self.cyca.ICAFE_CS_NEVER_CONN,
+ self.cyca.ICAFE_CA_OP_CONN_DOWN):
+ pvgate.pvd_previous = _pvd
continue
pvgate.pvd_previous = _pvd
-
- #if timestamps the same - then skip
+
+ #if timestamps the same - then skip
_value = _pvd.value[0]
- _value = pvgate.format_display_value(_value)
+ _value = pvgate.format_display_value(_value)
qtwi = QTableWidgetItem(str(_value)+ " ")
f = qtwi.font()
f.setPointSize(8)
qtwi.setFont(f)
-
- self.setItem(_row, self.no_columns-3,
- QTableWidgetItem(qtwi))
- self.item(_row, self.no_columns-3).setTextAlignment(Qt.AlignRight |
+ self.setItem(_row, self.no_columns-3,
+ QTableWidgetItem(qtwi))
+ self.item(_row, self.no_columns-3).setTextAlignment(Qt.AlignRight |
Qt.AlignVCenter)
_ts_date = _pvd.tsDateAsString
@@ -1313,19 +1229,18 @@ class CAQTableWidget(QTableWidget):
_ts_date += "0"
_ilength_target = _ilength_target - 1
_ts_str_len = len(_ts_date)
- _ts_str = _ts_date[0:_ts_str_len - (
- self.format_ts_nano-self.format_ts_decimal_part)]
- _ts_str_len = len(_ts_str)
-
+ _ts_str = _ts_date[0: _ts_str_len - (self.format_ts_nano -
+ self.format_ts_decimal_part)]
+ _ts_str_len = len(_ts_str)
_ilength_target = self.format_ts_decimal_part
- if self.format_ts_decimal_part == self.format_ts_deci:
- if _ts_str_len == self.format_ts_sec :
- _ts_str += "."
- while _ts_str_len < _ilength_target :
+ if self.format_ts_decimal_part == self.format_ts_deci:
+ if _ts_str_len == self.format_ts_sec:
+ _ts_str += "."
+ while _ts_str_len < _ilength_target:
_ts_str += "0"
- _ilength_target = _ilength_target -1
+ _ilength_target = _ilength_target -1
- qtwi = QTableWidgetItem( _ts_str)
+ qtwi = QTableWidgetItem(_ts_str)
f = qtwi.font()
f.setPointSize(8)
qtwi.setFont(f)
@@ -1333,194 +1248,187 @@ class CAQTableWidget(QTableWidget):
self.setItem(_row, self.no_columns-2, QTableWidgetItem(qtwi))
self.item(_row, self.no_columns-2).setTextAlignment(Qt.AlignCenter)
- _prop = pvgate.qt_dynamic_property_get()
-
+ _prop = pvgate.qt_dynamic_property_get()
+
alarm_severity = _pvd.alarmSeverity
-
+
if _prop == pvgate.READBACK_ALARM:
-
+
if alarm_severity == pvgate.cyca.SEV_MAJOR:
- _bgcolor = pvgate.settings.fgAlarmMajor
+ _bgcolor = pvgate.fg_alarm_major
_fgcolor = "black"
elif alarm_severity == pvgate.cyca.SEV_MINOR:
- _bgcolor = pvgate.settings.fgAlarmMinor
+ _bgcolor = pvgate.fg_alarm_minor
_fgcolor = "black"
elif alarm_severity == pvgate.cyca.SEV_INVALID:
- _bgcolor = pvgate.settings.fgAlarmInvalid
+ _bgcolor = pvgate.fg_alarm_invalid
_fgcolor = "#777777"
else:
- _bgcolor = pvgate.settings.fgAlarmNoAlarm
- #_bgcolor = pvgate.settings.bgReadbackAlarm
+ _bgcolor = pvgate.fg_alarm_noalarm
_fgcolor = "black"
#Colors for bg/fg reversed as is the old norm
self.item(_row, self.no_columns-3).setBackground(
- QColor(_bgcolor))
+ QColor(_bgcolor))
self.item(_row, self.no_columns-2).setBackground(
- QColor(_bgcolor))
+ QColor(_bgcolor))
self.item(_row, self.no_columns-3).setForeground(
- QColor(_fgcolor))
+ QColor(_fgcolor))
self.item(_row, self.no_columns-2).setForeground(
- QColor(_fgcolor))
+ QColor(_fgcolor))
elif _prop == pvgate.READBACK_STATIC:
-
+
self.item(_row, self.no_columns-3).setBackground(
- QColor(pvgate.settings.bgReadback))
+ QColor(pvgate.bg_readback))
self.item(_row, self.no_columns-2).setBackground(
- QColor(pvgate.settings.bgReadback))
-
+ QColor(pvgate.bg_readback))
+
elif _prop == pvgate.DISCONNECTED:
self.item(_row, self.no_columns-3).setBackground(
QColor("#ffffff"))
- self.item(_row, self.no_columns-2).setBackground(QColor(
- "#ffffff"))
- self.item(_row, self.no_columns-3).setForeground(QColor(
- "#777777"))
- self.item(_row, self.no_columns-2).setForeground(QColor(
- "#777777"))
+ self.item(_row, self.no_columns-2).setBackground(
+ QColor("#ffffff"))
+ self.item(_row, self.no_columns-3).setForeground(
+ QColor("#777777"))
+ self.item(_row, self.no_columns-2).setForeground(
+ QColor("#777777"))
else:
- print (_prop, "widget_update unknown in element/row", _row,
- _row+1)
-
- QApplication.processEvents()
+ print(_prop, "widget_update unknown in element/row", _row,
+ _row+1)
+
+ QApplication.processEvents()
def __init__(self, parent=None, pv_list: list = ["PV_NAME_NOT_GIVEN"],
- monitor_callback=None, pv_within_daq_group: bool = False,
- color_mode = None, show_units: bool = True, prefix: str = "",
+ monitor_callback=None, pv_within_daq_group: bool = False,
+ color_mode=None, show_units: bool = True, prefix: str = "",
suffix: str = "", ts_res: str = "milli",
init_column: bool = False, init_list: list = [],
notify_freq_hz: int = 0, notify_unison: bool = True,
- precision: int = 0, scale_factor: float = 1,
+ precision: int = 0, scale_factor: float = 1,
show_timestamp: bool = True, pv_list_show: list = None):
-
+
super().__init__()
self.columns_dict = {}
_column_dict_value = 0
self.columns_dict['PV'] = _column_dict_value
if init_column:
- _column_dict_value += 1
+ _column_dict_value += 1
self.columns_dict['Init'] = _column_dict_value
- _column_dict_value += 1
+ _column_dict_value += 1
self.columns_dict['Value'] = _column_dict_value
if show_timestamp:
- _column_dict_value += 1
+ _column_dict_value += 1
self.columns_dict['Timestamp'] = _column_dict_value
- _column_dict_value += 1
+ _column_dict_value += 1
self.columns_dict['Reconnect'] = _column_dict_value
- self.setWindowModality(Qt.ApplicationModal)
+ self.setWindowModality(Qt.ApplicationModal)
self.no_columns = _column_dict_value + 1
-
- self.init_column = init_column
-
+
+ self.init_column = init_column
+
self.init_list = init_list
if self.init_column and not self.init_list:
self.init_list = pv_list
-
+
self.icount = 0
- self.notify_freq_hz = abs(notify_freq_hz)
+ self.notify_freq_hz = abs(notify_freq_hz)
self.notify_freq_hz_default = self.notify_freq_hz
self.notify_milliseconds = 0 if self.notify_freq_hz == 0 else \
1000 / self.notify_freq_hz
- self.notify_unison = True if notify_unison and \
- self.notify_freq_hz > 0 else False
+ self.notify_unison = bool(notify_unison) and bool(self.notify_freq_hz)
- self.precision = precision
+ self.precision = precision
self.scale_factor = scale_factor
self.show_timestamp = show_timestamp
self.format_ts_nano = 31 #max length of date
- self.format_ts_micro = 28
+ self.format_ts_micro = 28
self.format_ts_milli = 25
- self.format_ts_deci = 23 #-8
+ self.format_ts_deci = 23 #-8
self.format_ts_sec = 21
if "nano" in ts_res.lower():
self.format_ts_decimal_part = self.format_ts_nano
elif "micro" in ts_res.lower():
self.format_ts_decimal_part = self.format_ts_micro
elif "milli" in ts_res.lower():
- self.format_ts_decimal_part = self.format_ts_milli
+ self.format_ts_decimal_part = self.format_ts_milli
elif "deci" in ts_res.lower():
- self.format_ts_decimal_part = self.format_ts_deci
+ self.format_ts_decimal_part = self.format_ts_deci
elif "sec" in ts_res.lower():
- self.format_ts_decimal_part = self.format_ts_sec
+ self.format_ts_decimal_part = self.format_ts_sec
else:
self.format_ts_decimal_part = self.format_ts_milli
self.pv2item_dict = {}
- self.pv_list = pv_list
+ self.pv_list = pv_list
self.pv_gateway = [None] * len(self.pv_list)
-
+
self.pv_list_show = pv_list_show
if self.pv_list_show is None:
- self.pv_list_show = self.pv_list
+ self.pv_list_show = self.pv_list
_color_mode = [None] * len(self.pv_list)
if isinstance(color_mode, list):
- for i in range (0, len(color_mode)):
- _color_mode[i] = color_mode[i]
+ for i in range(0, len(color_mode)):
+ _color_mode[i] = color_mode[i]
- for i in range (0, len(self.pv_list)):
+ for i in range(0, len(self.pv_list)):
- self.pv_gateway[i] = PVGateway().__init__(
- parent, self.pv_list[i], monitor_callback,
+ self.pv_gateway[i] = PVGateway(
+ parent, self.pv_list[i], monitor_callback,
pv_within_daq_group, _color_mode[i], show_units, prefix, suffix,
connect_triggers=False, notify_freq_hz=self.notify_freq_hz,
notify_unison=self.notify_unison, precision=self.precision)
-
- self.pv_gateway[i].is_initialize_complete()
+
+ self.pv_gateway[i].is_initialize_complete()
self.pv_gateway[i].trigger_connect.connect(
self.receive_connect_update)
self.pv_gateway[i].trigger_monitor_str.connect(
- self.receive_monitor_update)
+ self.receive_monitor_update)
self.pv_gateway[i].trigger_monitor_int.connect(
self.receive_monitor_update)
self.pv_gateway[i].trigger_monitor_float.connect(
self.receive_monitor_update)
-
- self.pv_gateway[i].widget_class = "QTableWidgetItem"
+
+ self.pv_gateway[i].widget_class = "QTableWidgetItem"
self.pv_gateway[i].qt_property_initial_values(
- qt_object_name = self.pv_gateway[i].PV_READBACK,
- tool_tip=False)
- #cant do this - sender will be a qspinbox
- #self.pv_gateway[i].post_display_value = self.post_display_value
-
-
+ qt_object_name=self.pv_gateway[i].PV_READBACK, tool_tip=False)
+
#required for reconnect
self.cafe = self.pv_gateway[0].cafe
self.cyca = self.pv_gateway[0].cyca
self.timer = None
if self.notify_unison:
- self.timer = QTimer()
+ self.timer = QTimer()
self.timer.timeout.connect(self.widget_update)
self.timer.singleShot(0, self.widget_update)
self.timer.start(self.notify_milliseconds)
-
- self.configure_widget()
-
+
+ self.configure_widget()
+
#Connect only deals with colours - only helps on reconnect
- # In any case monitors take over
+ # In any case monitors take over
#Got to do this earlier or emit immediately after!!
- for i in range(0, len(self.pv_gateway)):
+ for i in range(0, len(self.pv_gateway)):
if self.cafe.isConnected(self.pv_gateway[i].pv_name):
- self.pv_gateway[i].trigger_connect.emit(
- self.pv_gateway[i].handle, str(self.pv_gateway[i].pv_name),
- self.pv_gateway[i].cyca.ICAFE_CS_CONN)
-
-
+ self.pv_gateway[i].trigger_connect.emit(
+ self.pv_gateway[i].handle, str(self.pv_gateway[i].pv_name),
+ self.pv_gateway[i].cyca.ICAFE_CS_CONN)
+
for i in range(0, len(self.pv_gateway)):
if not self.pv_gateway[i].pv_within_daq_group:
self.pv_gateway[i].monitor_start()
-
+
self.update_init_values()
-
+
self.configure_context_menu()
@@ -1528,70 +1436,78 @@ class CAQTableWidget(QTableWidget):
self.table_context_menu = QMenu()
self.table_context_menu.setObjectName("contextMenu")
self.table_context_menu.setWindowModality(Qt.NonModal)
-
- if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
self.table_context_menu.addSection("---")
action1 = QAction("Configure Table PVs", self)
action1.triggered.connect(self.display_table_parameters)
self.table_context_menu.addAction(action1)
- if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
self.table_context_menu.addSection("---")
-
+
QApplication.processEvents()
- def restore_init_values(self):
+ def restore_init_values(self, pv_list: list = []):
_set_values_dict = self.get_init_values()
- #print("set val dict", _set_values_dict))
- #print("same as init vals", self.is_same_as_init_values())
- _pvs_to_set, _values_to_set = zip(*_set_values_dict.items())
- #zip returns tuples
- #print(_pvs_to_set)
- #print(_values_to_set)
- _pvs_to_set = list(_pvs_to_set)
- _values_to_set = list(_values_to_set)
+ if not pv_list:
+ _pvs_to_set, _values_to_set = zip(*_set_values_dict.items())
+ #zip returns tuples
+ _pvs_to_set = list(_pvs_to_set)
+ _values_to_set = list(_values_to_set)
+ else:
+ _pvs_to_set = []
+ _values_to_set = []
+ for pv in pv_list:
+ if pv in _set_values_dict.keys():
+ _pvs_to_set.append(pv)
+ _values_to_set.append(_set_values_dict[pv])
- status, status_list = self.cafe.setScalarList(_pvs_to_set,
+ status, status_list = self.cafe.setScalarList(_pvs_to_set,
_values_to_set)
if status != self.cyca.ICAFE_NORMAL:
- _mess = ("The following device(s) reported an error " +
- "in set operation:")
+ _mess = ("The following device(s) reported an error " +
+ "in 'set' operation:")
for i, status_value in enumerate(status_list):
if status_value != self.cyca.ICAFE_NORMAL:
_mess += ("\n" + _pvs_to_set[i] + " has status = " +
- str(status_value) + " " +
+ str(status_value) + " " +
self.cafe.getStatusCodeAsString(status_value) +
- " " + self.cafe.getStatusInfo(status_value) )
+ " " + self.cafe.getStatusInfo(status_value))
qm = QMessageBox()
qm.setText(_mess)
-
+
qm.exec()
QApplication.processEvents()
-
- self.init_value_button.setEnabled(True)
-
+
+ self.init_value_button.setEnabled(True)
+
def is_same_as_init_values(self):
_init_values_dict = self.get_column_values(self.columns_dict['Init'])
_pvs, _init_values = zip(*_init_values_dict.items())
- _current_values_dict = self.get_column_values(self.columns_dict['Value'])
+ _current_values_dict = self.get_column_values(
+ self.columns_dict['Value'])
_pvs, _current_values = zip(*_current_values_dict.items())
#zip returns tuples
-
- if func_reduce(lambda i, j : i and j, map(
- lambda m, k: m == k, _init_values, _current_values), True):
- return True
- else:
- return False
+
+ return bool(func_reduce(lambda i, j: i and j, map(
+ lambda m, k: m == k, _init_values, _current_values), True))
+
+ #if func_reduce(lambda i, j: i and j, map(
+ # lambda m, k: m == k, _init_values, _current_values), True):
+ # return True
+ #else:
+ # return False
- def get_column_values(self, column_no):
+ def get_column_values(self, column_no):
_values_dict = {}
- _start = 0
+ _start = 0
_end = len(self.pv_gateway)
_pvs = [None] * _end
_values_str = [None] * _end
@@ -1600,86 +1516,83 @@ class CAQTableWidget(QTableWidget):
for _row in range(_start, _end):
_values_str[_row] = self.item(_row, column_no).text()
_pvs[_row] = self.item(_row, 0).text()
-
+
_value_list = [float(_value_list) for _value_list in re.findall(
- r'-?\d+\.?\d*', _values_str[_row])]
-
+ r'-?\d+\.?\d*', _values_str[_row])]
+
if not _value_list:
- print("row", _row, "values", _values_str[_row], _pvs[_row])
+ print("row", _row, "values", _values_str[_row], _pvs[_row])
_values[_row] = _values_str[_row] #Can be enum string
else:
_values[_row] = _value_list[0]
-
if _pvs[_row] in self.pv_list_show:
- #_values_dict[_pvs[_row]] = _values[_row]
_values_dict[self.pv_gateway[_row].pv_name] = _values[_row]
-
- #print("_pvs", _pvs)
- #print("show", self.pv_list_show)
- return _values_dict #_pvs_to_set, _values_to_set
-
-
+
+ return _values_dict #_pvs_to_set, _values_to_set
+
+
def get_init_values(self):
return self.get_column_values(self.columns_dict['Init'])
def get_init_values_previous(self):
_set_values_dict = {}
- _start = 0
+ _start = 0
_end = len(self.pv_gateway)
_pvs_to_set = [None] * _end
_values_to_set_str = [None] * _end
_values_to_set = [None] * _end
for _row in range(_start, _end):
- _values_to_set_str[_row] = self.item(_row, self.columns_dict['Init']).text()
+ _values_to_set_str[_row] = self.item(
+ _row, self.columns_dict['Init']).text()
_pvs_to_set[_row] = self.item(_row, self.columns_dict['PV']).text()
-
+
_value_list = [float(_value_list) for _value_list in re.findall(
- r'-?\d+\.?\d*', _values_to_set_str[_row])]
-
+ r'-?\d+\.?\d*', _values_to_set_str[_row])]
+
if not _value_list:
- print("//row", _row, "values", _values_to_set_str[_row],
+ print("//row", _row, "values", _values_to_set_str[_row],
_pvs_to_set[_row])
- _values_to_set[_row] = _values_to_set_str[_row] #Can be enum string
- else:
+ _values_to_set[_row] = _values_to_set_str[_row] #Can be enum str
+ else:
_values_to_set[_row] = _value_list[0]
if _pvs_to_set[_row] in self.init_list:
- #_set_values_dict[_pvs_to_set[_row]] = _values_to_set[_row]
- _set_values_dict[self.pv_gateway[_row].pv_name] = _values_to_set[_row]
- #print(_set_values_dict)
- return _set_values_dict #_pvs_to_set, _values_to_set
+ _set_values_dict[
+ self.pv_gateway[_row].pv_name] = _values_to_set[_row]
+
+ return _set_values_dict
+
-
def update_init_values(self):
- _start = 0
- _end=len(self.pv_gateway)
+ _start = 0
+ _end = len(self.pv_gateway)
+
for _row in range(_start, _end):
- _handle = self.pv_gateway[_row].handle
+ _handle = self.pv_gateway[_row].handle
_value = self.pv_gateway[_row].cafe.getCache(_handle)
-
+
if _value is not None:
- if self.scale_factor != 1:
+ if self.scale_factor != 1:
_value = _value * self.scale_factor
- _value = self.pv_gateway[_row].format_display_value(_value)
- #print("value from update", _value)
+ _value = self.pv_gateway[_row].format_display_value(_value)
+
qtwi = QTableWidgetItem(str(_value)+ " ")
_f = qtwi.font()
_f.setPointSize(8)
qtwi.setFont(_f)
- self.setItem(_row, 1, qtwi)
- self.item(_row, 1).setTextAlignment(
- Qt.AlignRight | Qt.AlignVCenter)
-
-
+ self.setItem(_row, 1, qtwi)
+ self.item(_row, 1).setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
+
+
def configure_widget(self):
- _column_width_pvname = 210
- _column_width_value = 110
- _column_width_timestamp = 240
- _column_width_checkbox = 25
-
+ _column_width_pvname = 180
+ _column_width_value = 90
+ _column_width_timestamp = 210
+ _column_width_checkbox = 22
+
self.setRowCount(len(self.pv_gateway)+1)
self.setColumnCount(self.no_columns)
self.setEditTriggers(QAbstractItemView.NoEditTriggers)
@@ -1687,44 +1600,46 @@ class CAQTableWidget(QTableWidget):
self.resizeRowsToContents()
#self.horizontalHeader().setStretchLastSection(True);
self.setColumnWidth(self.columns_dict['PV'], _column_width_pvname)
-
+
self.setColumnWidth(self.columns_dict['Value'], _column_width_value)
if 'Init' in self.columns_dict.keys():
self.setColumnWidth(self.columns_dict['Init'], _column_width_value)
if 'Timestamp' in self.columns_dict.keys():
- self.setColumnWidth(self.columns_dict['Timestamp'], _column_width_timestamp)
- self.setColumnWidth(self.columns_dict['Reconnect'], _column_width_checkbox)
+ self.setColumnWidth(self.columns_dict['Timestamp'],
+ _column_width_timestamp)
+ self.setColumnWidth(self.columns_dict['Reconnect'],
+ _column_width_checkbox)
_pv_column = self.columns_dict['PV']
+
for i in range(0, len(self.pv_gateway)):
- #self.setItem(i, _pv_column, QTableWidgetItem(self.pv_gateway[i].pv_name))
qtwt = QTableWidgetItem(self.pv_list_show[i])
f = qtwt.font()
f.setPointSize(8)
- qtwt.setFont(f)
- #qtwt.setTextAlignment(Qt.AlignLeft)
+ qtwt.setFont(f)
+
self.setItem(i, _pv_column, qtwt)
- self.item(i, _pv_column).setTextAlignment(Qt.AlignCenter | Qt.AlignVCenter)
+ self.item(i, _pv_column).setTextAlignment(Qt.AlignHCenter |
+ Qt.AlignVCenter)
for i_column in range(1, self.no_columns-1):
self.setItem(i, i_column, QTableWidgetItem(str("")))
- self.item(i, i_column).setTextAlignment(Qt.AlignCenter |
- Qt.AlignVCenter)
+ self.item(i, i_column).setTextAlignment(Qt.AlignHCenter |
+ Qt.AlignVCenter)
self.pv2item_dict[self.pv_gateway[i]] = i
-
+
cb_item = QTableWidgetItem()
cb_item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
- cb_item.setCheckState(Qt.Unchecked)
- cb_item.setTextAlignment(Qt.AlignCenter)
+ cb_item.setCheckState(Qt.Unchecked)
+ cb_item.setTextAlignment(Qt.AlignCenter)
cb_item.setToolTip(self.pv_gateway[i].pv_name)
-
+
self.setItem(i, self.no_columns-1, cb_item)
- self.item(i, self.no_columns-1).setTextAlignment(Qt.AlignCenter |
- Qt.AlignVCenter)
-
+ self.item(i, self.no_columns-1).setTextAlignment(Qt.AlignCenter)
+
if self.init_column:
self.init_widget = QWidget()
- _init_layout = QHBoxLayout(self.init_widget)
- self.init_value_button = QPushButton()
+ _init_layout = QHBoxLayout(self.init_widget)
+ self.init_value_button = QPushButton()
self.init_value_button.setText("Update")
_f = self.init_value_button.font()
_f.setPointSize(8)
@@ -1732,21 +1647,19 @@ class CAQTableWidget(QTableWidget):
self.init_value_button.setFixedWidth(80)
self.init_value_button.clicked.connect(self.update_init_values)
self.init_value_button.setToolTip(
- ("Stores initial, pre-measurement value. Update is also " +
- "executed automatically before each measurement."))
+ ("Stores initial, pre-measurement value. Update is also " +
+ "typically executed automatically before new optics are set."))
_init_layout.addWidget(self.init_value_button)
_init_layout.setAlignment(Qt.AlignRight)
- _init_layout.setContentsMargins(1,1,0,0) #Required
+ _init_layout.setContentsMargins(1, 1, 0, 0) #Required
self.init_widget.setLayout(_init_layout)
- self.setCellWidget(len(self.pv_gateway), 1, self.init_widget)
-
+ self.setCellWidget(len(self.pv_gateway), 1, self.init_widget)
+
_restore_widget = QWidget()
- _restore_layout = QHBoxLayout(_restore_widget)
- self.restore_value_button = QPushButton()
- #self.restore_value_button.setObjectName("Controller")
- #self.restore_value_button.setProperty('actOnBeam', True)
+ _restore_layout = QHBoxLayout(_restore_widget)
+ self.restore_value_button = QPushButton()
self.restore_value_button.setStyleSheet(
- "QPushButton{background-color: rgb(212, 219, 157);}")
+ "QPushButton{background-color: rgb(212, 219, 157);}")
self.restore_value_button.setText("Restore")
_f = self.restore_value_button.font()
_f.setPointSize(8)
@@ -1757,20 +1670,19 @@ class CAQTableWidget(QTableWidget):
("Restore devices to their pre-measurement values"))
_restore_layout.addWidget(self.restore_value_button)
_restore_layout.setAlignment(Qt.AlignRight)
- _restore_layout.setContentsMargins(1,1,0,0) #Required
+ _restore_layout.setContentsMargins(1, 1, 0, 0)
_restore_widget.setLayout(_restore_layout)
- self.setCellWidget(len(self.pv_gateway), 2, _restore_widget)
-
+ self.setCellWidget(len(self.pv_gateway), 2, _restore_widget)
+
#Do not display no for last row (Reconnect button)
_row_digit_last_cell = QTableWidgetItem(str(""))
- self.setVerticalHeaderItem(len(self.pv_gateway), _row_digit_last_cell)
+ self.setVerticalHeaderItem(len(self.pv_gateway), _row_digit_last_cell)
self.setItem(len(self.pv_gateway), 0, QTableWidgetItem(str("")))
-
+
_qwb = QWidget()
-
+
self.reconnect_button = reconnectQPushButton(self) #self required
- #self.reconnect_button.setFont(self.font12);QFont("Sans Serif", 12)
-
+
f = self.reconnect_button.font()
if 'Timestamp' in self.columns_dict.keys():
@@ -1780,109 +1692,90 @@ class CAQTableWidget(QTableWidget):
f.setPointSize(6)
self.reconnect_button.setFixedWidth(58)
- self.reconnect_button.setFont(f)
-
+ self.reconnect_button.setFont(f)
self.reconnect_button.setText("Reconnect")
-
-
-
_layout = QHBoxLayout(_qwb)
_layout.addWidget(self.reconnect_button)
_layout.setAlignment(Qt.AlignCenter)
- _layout.setContentsMargins(0,0,0,0) #Required
- _qwb.setLayout(_layout)
+ _layout.setContentsMargins(0, 0, 0, 0) #Required
#_reconnect_button
- self.setCellWidget(len(self.pv_gateway), self.no_columns-2, _qwb)
-
- _qwc = QWidget()
+ self.setCellWidget(len(self.pv_gateway), self.no_columns-2, _qwb)
- self.cb_item_all = QCheckBox(self)
- self.cb_item_all.setCheckState(Qt.Unchecked)
+ self.cb_item_all = QCheckBox()
+ self.cb_item_all.setCheckState(Qt.Unchecked)
self.cb_item_all.stateChanged.connect(self.reconnectStateChanged)
+ self.cb_item_all.setObjectName("Reconnect")
- _layout_cb = QHBoxLayout(_qwc)
- _layout_cb.addWidget(self.cb_item_all)
- _layout_cb.setAlignment(Qt.AlignLeft)
- _layout_cb.setContentsMargins(3,2,0,1) #Required LTRB
- _qwc.setLayout(_layout_cb)
-
- self.setCellWidget(len(self.pv_gateway), self.no_columns-1, _qwc)
-
+ self.setCellWidget(len(self.pv_gateway), self.no_columns-1,
+ self.cb_item_all)
header_item = QTableWidgetItem("Process Variable")
-
- '''
- header_item.setText("Process Variable")
- f= header_item.font()
- f.setPixelSize(12)
- header_item.setFont(f)
- '''
-
+
self.setHorizontalHeaderItem(self.columns_dict['PV'], header_item)
-
+
if 'Init' in self.columns_dict.keys():
- self.setHorizontalHeaderItem(self.columns_dict['Init'],
+ self.setHorizontalHeaderItem(self.columns_dict['Init'],
QTableWidgetItem("Initial Value"))
-
- self.setHorizontalHeaderItem(self.columns_dict['Value'],
+
+ self.setHorizontalHeaderItem(self.columns_dict['Value'],
QTableWidgetItem("Value"))
-
- if 'Timestamp' in self.columns_dict.keys():
- self.setHorizontalHeaderItem(self.columns_dict['Timestamp'],
+
+ if 'Timestamp' in self.columns_dict.keys():
+ self.setHorizontalHeaderItem(self.columns_dict['Timestamp'],
QTableWidgetItem("Timestamp"))
- self.setHorizontalHeaderItem(self.columns_dict['Reconnect'],
+ self.setHorizontalHeaderItem(self.columns_dict['Reconnect'],
QTableWidgetItem("R"))
self.setFocusPolicy(Qt.NoFocus)
self.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.setSelectionMode(QAbstractItemView.NoSelection)
self.verticalHeader().setDefaultAlignment(Qt.AlignRight)
- self.verticalHeader().setFixedWidth(22)
-
-
- #self.verticalHeader().setVisible(False)
- #self.horizontalHeader().setSectionResizeMode( 0, QHeaderView.Stretch )
+ self.verticalHeader().setFixedWidth(22)
_fm_font = QFont("Sans Serif")
_fm_font.setPointSize(12)
- fm = QFontMetricsF(_fm_font) #QFont("Sans Serif", 12))
-
+ fm = QFontMetricsF(_fm_font)
+
_factor = 1
if LooseVersion(QT_VERSION_STR) < LooseVersion("5.0"):
_factor = 1.18
- self.setFixedHeight(int(fm.lineSpacing() * _factor *
- (len(self.pv_gateway)+3)))
+ self.setFixedHeight(
+ int(fm.lineSpacing() * _factor * (len(self.pv_gateway)+3)))
_min_table_width = 620 if not self.init_column else 650
self.setMinimumWidth(_min_table_width)
-
for _row in range(0, len(self.pv_gateway)):
- #print("name/row/columns", self.pv_gateway[_row].pv_name, _row, self.no_columns)
- self.item(_row, _pv_column).setForeground(QColor("#000000"))
- ##self.item(_row, _pv_column).setTextAlignment(Qt.AlignCenter)
-
+ self.item(_row, _pv_column).setForeground(QColor("#000000"))
+
for i_column in range(1, self.no_columns-2):
- self.item(_row, i_column).setForeground(QColor("#000000"))
- self.item(_row, i_column).setTextAlignment(Qt.AlignRight |
- Qt.AlignVCenter)
-
- self.item(_row, self.columns_dict['Value']).setBackground(QColor("#ffffff"))
+ self.item(_row, i_column).setForeground(QColor("#000000"))
+ self.item(_row, i_column).setTextAlignment(Qt.AlignRight |
+ Qt.AlignVCenter)
+
+ self.item(_row, self.columns_dict['Value']).setBackground(
+ QColor("#ffffff"))
if 'Timestamp' in self.columns_dict.keys():
- self.item(_row, self.columns_dict['Timestamp']).setTextAlignment(Qt.AlignCenter)
- self.item(_row, self.columns_dict['Timestamp']).setBackground(QColor("#ffffff"))
-
+ self.item(_row,
+ self.columns_dict['Timestamp']).setTextAlignment(
+ Qt.AlignCenter)
+ self.item(_row,
+ self.columns_dict['Timestamp']).setBackground(
+ QColor("#ffffff"))
+
@Slot(int)
def reconnectStateChanged(self, state):
if state == Qt.Unchecked:
- for i in range(0, len(self.pv_gateway)):
- self.item(i, self.columns_dict['Reconnect']).setCheckState(Qt.Unchecked)
+ for i in range(0, len(self.pv_gateway)):
+ self.item(i, self.columns_dict['Reconnect']).setCheckState(
+ Qt.Unchecked)
else:
- for i in range(0, len(self.pv_gateway)):
- self.item(i, self.columns_dict['Reconnect']).setCheckState(Qt.Checked)
+ for i in range(0, len(self.pv_gateway)):
+ self.item(i, self.columns_dict['Reconnect']).setCheckState(
+ Qt.Checked)
@Slot(str, int, int)
@Slot(int, int, int)
@@ -1890,79 +1783,56 @@ class CAQTableWidget(QTableWidget):
def receive_monitor_update(self, value, status, alarm_severity):
_row = self.pv2item_dict[self.sender()]
- '''
- if _row in (3,):
- print("receive mon update before post_display, value/row==", value, _row, self.pv_gateway[_row].pv_name)
- print(self, _row, self.pv_gateway[_row].pv_name, ">>>>>>>from gateway>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
- print("sender", self.sender())
- '''
-
- #print(self.pv2item_dict)
- #print( value, status, alarm_severity)
- #Timing on CAQTableWidget basis! Miss last events if many events
- #First trigger should always happen
- #if self.update_hz is not None:
- # print(time.monotonic(), self.pv_gateway[_row].time_monotonic, (1/self.update_hz))
- # if (time.monotonic() - self.pv_gateway[_row].time_monotonic) < (1/self.update_hz):
- # return
- # self.pv_gateway[_row].receive_monitor_update(value, status, alarm_severity)
self.pv_gateway[_row].time_monotonic = time.monotonic()
- if self.scale_factor != 1:
+ if self.scale_factor != 1:
value = value * self.scale_factor
- _value = self.pv_gateway[_row].format_display_value(value)
-
- #print("row no//", _row)
- qtwi = QTableWidgetItem(str(_value) + " ")
+ _value = self.pv_gateway[_row].format_display_value(value)
+
+ qtwi = QTableWidgetItem(str(_value) + " ")
f = qtwi.font()
f.setPointSize(8)
- qtwi.setFont(f)
- self.setItem(_row, self.columns_dict['Value'], qtwi)
+ qtwi.setFont(f)
+ self.setItem(_row, self.columns_dict['Value'], qtwi)
self.item(_row, self.columns_dict['Value']).setTextAlignment(
Qt.AlignRight | Qt.AlignVCenter)
-
+
if 'Timestamp' in self.columns_dict.keys():
-
_handle = self.pv_gateway[_row].handle
- #print("post_display HANDLE/val/row", _handle, _value, _row)
- #_status = self.pv_gateway[_row].cafe.getStatus(_handle)
_pvd = self.pv_gateway[_row].cafe.getPVCache(_handle)
-
-
_ts_date = _pvd.tsDateAsString
_ts_str_len = len(_ts_date)
_ilength_target = self.format_ts_nano
- while _ts_str_len < _ilength_target:
+ while _ts_str_len < _ilength_target:
_ts_date += "0"
- _ilength_target = _ilength_target -1
+ _ilength_target = _ilength_target -1
- ts_str_len = len(_ts_date)
- _ts_str = _ts_date[0:_ts_str_len-(
+ ##ts_str_len = len(_ts_date)
+ _ts_str = _ts_date[0: _ts_str_len-(
self.format_ts_nano-self.format_ts_decimal_part)]
- _ts_str_len = len(_ts_str)
- #print(_ts_date)
- #print(_ts_str)
- #print("length of timestamp string ", _ts_str_len)
- _ilength_target = self.format_ts_decimal_part
- if self.format_ts_decimal_part == self.format_ts_deci:
- if _ts_str_len == self.format_ts_sec:
- _ts_str += "."
- while _ts_str_len < _ilength_target :
- _ts_str += "0"
- _ilength_target = _ilength_target -1
+ _ts_str_len = len(_ts_str)
- qtwi = QTableWidgetItem( _ts_str)
+ _ilength_target = self.format_ts_decimal_part
+ if self.format_ts_decimal_part == self.format_ts_deci:
+ if _ts_str_len == self.format_ts_sec:
+ _ts_str += "."
+ while _ts_str_len < _ilength_target:
+ _ts_str += "0"
+ _ilength_target = _ilength_target -1
+
+ qtwi = QTableWidgetItem(_ts_str)
f = qtwi.font()
f.setPointSize(8)
- qtwi.setFont(f)
+ qtwi.setFont(f)
self.setItem(_row, self.columns_dict['Timestamp'], qtwi)
- self.item(_row, self.columns_dict['Timestamp']).setTextAlignment(Qt.AlignCenter)
+ self.item(_row, self.columns_dict['Timestamp']).setTextAlignment(
+ Qt.AlignCenter)
+
+ _prop = self.pv_gateway[_row].qt_dynamic_property_get()
- _prop = self.pv_gateway[_row].qt_dynamic_property_get()
-
if _prop == self.pv_gateway[_row].READBACK_ALARM:
-
+
if alarm_severity == self.pv_gateway[_row].cyca.SEV_MAJOR:
_bgcolor = self.pv_gateway[_row].settings.fgAlarmMajor
_fgcolor = "black"
@@ -1972,19 +1842,22 @@ class CAQTableWidget(QTableWidget):
elif alarm_severity == self.pv_gateway[_row].cyca.SEV_INVALID:
_bgcolor = self.pv_gateway[_row].settings.fgAlarmInvalid
_fgcolor = "#777777"
- else:
+ else:
_bgcolor = self.pv_gateway[_row].settings.fgAlarmNoAlarm
- #_bgcolor = self.pv_gateway[_row].settings.bgReadbackAlarm
_fgcolor = "black"
#Colors for bg/fg reversed as is the old norm
- self.item(_row, self.columns_dict['Value']).setBackground(QColor(_bgcolor))
- self.item(_row, self.columns_dict['Value']).setForeground(QColor(_fgcolor))
+ self.item(_row, self.columns_dict['Value']).setBackground(
+ QColor(_bgcolor))
+ self.item(_row, self.columns_dict['Value']).setForeground(
+ QColor(_fgcolor))
if 'Timestamp' in self.columns_dict.keys():
- self.item(_row, self.columns_dict['Timestamp']).setBackground(QColor(_bgcolor))
- self.item(_row, self.columns_dict['Timestamp']).setForeground(QColor(_fgcolor))
-
-
+ self.item(_row, self.columns_dict['Timestamp']).setBackground(
+ QColor(_bgcolor))
+ self.item(_row, self.columns_dict['Timestamp']).setForeground(
+ QColor(_fgcolor))
+
+
elif _prop == self.pv_gateway[_row].DISCONNECTED or \
alarm_severity == self.pv_gateway[_row].cyca.SEV_INVALID:
self.item(_row, self.columns_dict['Value']).setBackground(
@@ -1996,227 +1869,137 @@ class CAQTableWidget(QTableWidget):
self.item(_row, self.columns_dict['Timestamp']).setBackground(
QColor("#ffffff"))
self.item(_row, self.columns_dict['Timestamp']).setForeground(
- QColor("#777777"))
+ QColor("#777777"))
elif _prop == self.pv_gateway[_row].READBACK_STATIC:
self.item(_row, self.columns_dict['Value']).setBackground(
- QColor(self.pv_gateway[_row].settings.bgReadback))
+ QColor(self.pv_gateway[_row].bg_readback))
if 'Timestamp' in self.columns_dict.keys():
self.item(_row, self.columns_dict['Timestamp']).setBackground(
- QColor(self.pv_gateway[_row].settings.bgReadback))
+ QColor(self.pv_gateway[_row].bg_readback))
else:
-
- print (_prop, self.pv_gateway[_row].DISCONNECTED, "(in monitor) unknown in element/row no.", _row, _row+1)
- #TRZ SET PROPERTY
- QApplication.processEvents(QEventLoop.AllEvents, 10)
- #self.post_display_value(value)
-
-
+ print(_prop, self.pv_gateway[_row].DISCONNECTED,
+ "(in monitor) unknown in element/row no.", _row, _row+1)
+
+ QApplication.processEvents(QEventLoop.AllEvents, 10)
+
+
@Slot(int, str, int)
def receive_connect_update(self, handle: int, pv_name: str, status: int):
'''Triggered by connect signal'''
_row = self.pv2item_dict[self.sender()]
-
- #print(self, self.pv_gateway[_row].pv_name, ">>>>>>>receive connect >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
- #print("sender", self.sender())
- #print("MY RECEIVE receive_connect_update for row = ", _row, " status=", status)
- #print(handle, pv_name)
- self.pv_gateway[_row].receive_connect_update(handle, pv_name, status,
+
+ self.pv_gateway[_row].receive_connect_update(handle, pv_name, status,
post_display=False)
- #print("after gateway connect update")
+
_prop = self.pv_gateway[_row].qt_dynamic_property_get()
-
- #self.post_display_value(status)
+
+ #self.post_display_value(status)
if _prop == self.pv_gateway[_row].DISCONNECTED:
- #self.item(_row,0).setBackground(QColor("#ffffff"))
- self.item(_row, self.columns_dict['Value']).setBackground(QColor("#ffffff"))
- #self.item(_row,0).setForeground(QColor("#777777"))
- self.item(_row, self.columns_dict['Value']).setForeground(QColor("#777777"))
+ self.item(_row, self.columns_dict['Value']).setBackground(
+ QColor("#ffffff"))
+ self.item(_row, self.columns_dict['Value']).setForeground(
+ QColor("#777777"))
if 'Timestamp' in self.columns_dict.keys():
- self.item(_row, self.columns_dict['Timestamp']).setBackground(QColor("#ffffff"))
- self.item(_row, self.columns_dict['Timestamp']).setForeground(QColor("#777777"))
-
- QApplication.processEvents()
+ self.item(_row, self.columns_dict['Timestamp']).setBackground(
+ QColor("#ffffff"))
+ self.item(_row, self.columns_dict['Timestamp']).setForeground(
+ QColor("#777777"))
- '''
- def post_display_value(self, value):
-
-#IS CLEAN BEFORE EXIT
- self.icount += 1;
- print("recursion limit", sys.getrecursionlimit(), self.icount)
- _row = self.pv2item_dict[self.sender()]
- print("row no", _row)
- _value = self.pv_gateway[_row].format_display_value(value)
- print("row no//", _row)
- self.setItem(_row, self.no_columns-3,
- QTableWidgetItem(str(_value)+ " "))
- self.item(_row, self.no_columns-3).setTextAlignment(Qt.AlignRight)
-
- _handle = self.pv_gateway[_row].handle
- #print("post_display HANDLE/val/row", _handle, _value, _row)
- #_status = self.pv_gateway[_row].cafe.getStatus(_handle)
- _pvd = self.pv_gateway[_row].cafe.getPVCache(_handle)
+ QApplication.processEvents()
- _ts_date = _pvd.tsDateAsString
- _ts_str_len = len(_ts_date)
- _ilength_target = self.format_ts_nano
-
- while _ts_str_len < _ilength_target:
- _ts_date += "0"
- _ilength_target = _ilength_target -1
- _ts_str_len = len(_ts_date)
- _ts_str = _ts_date[0:_ts_str_len - (self.format_ts_nano -
- self.format_ts_decimal_part)]
- _ts_str_len = len(_ts_str)
- #print(_ts_date)
- #print(_ts_str)
- #print("length of timestamp string ", _ts_str_len)
- _ilength_target = self.format_ts_decimal_part
- if self.format_ts_decimal_part == self.format_ts_deci:
- if _ts_str_len == self.format_ts_sec :
- _ts_str += "."
- while _ts_str_len < _ilength_target :
- _ts_str += "0"
- _ilength_target = _ilength_target -1
-
- self.setItem(_row, self.no_columns-2, QTableWidgetItem( _ts_str))
- self.item(_row, self.no_columns-2).setTextAlignment(Qt.AlignCenter)
-
- _prop = self.pv_gateway[_row].qt_dynamic_property_get()
-
- if _prop == self.pv_gateway[_row].READBACK_ALARM:
- #self.item(_row,0).setBackground(QColor("#c8c8c8"))
- self.item(_row, self.no_columns-3).setBackground(QColor("#c8c8c8"))
- self.item(_row, self.no_columns-2).setBackground(QColor("#c8c8c8"))
-
- elif _prop == self.pv_gateway[_row].READBACK_STATIC:
- #self.item(_row,0).setBackground(QColor("#ffffe0"))
- self.item(_row, self.no_columns-3).setBackground(QColor("#ffffe0"))
- self.item(_row, self.no_columns-2).setBackground(QColor("#ffffe0"))
-
- elif _prop == self.pv_gateway[_row].DISCONNECTED:
- self.item(_row, self.no_columns-3).setBackground(QColor("#ffffff"))
- self.item(_row, self.no_columns-2).setBackground(QColor("#ffffff"))
- self.item(_row, self.no_columns-3).setForeground(QColor("#777777"))
- self.item(_row, self.no_columns-2).setForeground(QColor("#777777"))
-
- else:
- print (_prop, "unknown in element/row ==>", _row, _row+1)
- #TRZ SET PROPERTY
-
- QApplication.processEvents()
- #self.setStyleSheet("QTableWidget::item {margin-right: 5 }");
-
- #self.setStyleSheet("QHeaderView::section:horizontal {margin-right: 2; border: 1px solid}");
- '''
def table_precision_user_changed(self, new_value):
self.pvgateway_precision = new_value
-
+
for pvgate in self.pv_gateway:
if pvgate.pv_ctrl is not None:
- self.pvgateway_precision = min(pvgate.pv_ctrl.precision,
+ self.pvgateway_precision = min(pvgate.pv_ctrl.precision,
new_value)
-
- pvgate.precision_user = self.pvgateway_precision
- pvgate.precision = self.pvgateway_precision
- _pvd = self.cafe.getPVCache(pvgate.handle)
-
+ pvgate.precision_user = self.pvgateway_precision
+ pvgate.precision = self.pvgateway_precision
+
+ _pvd = self.cafe.getPVCache(pvgate.handle)
+
if _pvd.value[0] is not None:
- if isinstance(_pvd.value[0], float):
+ if isinstance(_pvd.value[0], float):
pvgate.trigger_monitor_float.emit(
- _pvd.value[0], _pvd.status, _pvd.alarmSeverity)
+ _pvd.value[0], _pvd.status, _pvd.alarmSeverity)
def table_precision_ioc_reset(self):
- '''
- _max_current_precision_value = -1
- for i, pvgate in enumerate(self.pv_gateway):
- if pvgate.pv_ctrl is not None:
-
- _max_current_precision_value = max(
- pvgate.precision_user, _max_current_precision_value)
-
- if _max_current_precision_value == -1:
- _max_current_precision_value = self.max_precision_value
- '''
if self.max_precision_value == self.table_precision_user_wgt.value():
self.table_precision_user_changed(self.max_precision_value)
else:
self.table_precision_user_wgt.setValue(self.max_precision_value)
def table_refresh_rate_changed(self, new_idx):
-
- _notify_freq_hz = self.refresh_freq_combox_idx_dict[new_idx]
+
+ _notify_freq_hz = self.refresh_freq_combox_idx_dict[new_idx]
_notify_milliseconds = 0 if _notify_freq_hz == 0 else \
1000 / _notify_freq_hz
-
- self.notify_freq_hz = _notify_freq_hz
-
- if _notify_milliseconds == 0:
- for pvgate in self.pv_gateway:
+
+ self.notify_freq_hz = _notify_freq_hz
+
+ if _notify_milliseconds == 0:
+ for pvgate in self.pv_gateway:
pvgate.notify_unison = False
pvgate.notify_milliseconds = _notify_milliseconds
pvgate.notify_freq_hz = self.notify_freq_hz
pvgate.monitor_stop()
time.sleep(0.01)
- for pvgate in self.pv_gateway:
- pvgate.monitor_start()
+ for pvgate in self.pv_gateway:
+ pvgate.monitor_start()
- else:
+ else:
for pvgate in self.pv_gateway:
if not pvgate.notify_unison:
pvgate.monitor_stop()
-
+
for pvgate in self.pv_gateway:
pvgate.notify_milliseconds = _notify_milliseconds
pvgate.notify_freq_hz = self.notify_freq_hz
if not pvgate.notify_unison:
pvgate.notify_unison = True
- pvgate.monitor_start()
- else:
-
+ pvgate.monitor_start()
+ else:
+
self.cafe.updateMonitorPolicyDeltaMS(
- pvgate.handle, pvgate.monitor_id,
+ pvgate.handle, pvgate.monitor_id,
pvgate.notify_milliseconds)
-
- #for pvgate in self.pv_gateway:
- # pvgate.monitor_start()
- # print("pvgate / mon started", pvgate)
-
- if self.timer is not None:
- self.timer.stop()
+
+ if self.timer is not None:
+ self.timer.stop()
else:
- self.timer = QTimer()
+ self.timer = QTimer()
self.timer.timeout.connect(self.widget_update)
self.timer.singleShot(0, self.widget_update)
-
- if _notify_milliseconds > 0:
- self.timer.start(_notify_milliseconds)
-
+
+ if _notify_milliseconds > 0:
+ self.timer.start(_notify_milliseconds)
+
def table_ts_resolution_changed(self, new_idx):
-
- for i, (key, ts_res) in enumerate(self.ts_combox_idx_dict.items()):
+
+ for i, ts_res in enumerate(self.ts_combox_idx_dict.values()):
if i == new_idx:
self.format_ts_decimal_part = ts_res
- break;
+ break
- for pvgate in self.pv_gateway:
- _pvd = self.cafe.getPVCache(pvgate.handle)
+ for pvgate in self.pv_gateway:
+ _pvd = self.cafe.getPVCache(pvgate.handle)
if _pvd.value[0] is not None:
if isinstance(_pvd.value[0], float):
pvgate.trigger_monitor_float.emit(
- _pvd.value[0], _pvd.status, _pvd.alarmSeverity)
+ _pvd.value[0], _pvd.status, _pvd.alarmSeverity)
elif isinstance(_pvd.value[0], int):
- pvgate.trigger_monitor_int.emit(
+ pvgate.trigger_monitor_int.emit(
_pvd.value[0], _pvd.status, _pvd.alarmSeverity)
else:
pvgate.trigger_monitor_str.emit(
- str(_pvd.value[0]), _pvd.status,
- _pvd.alarmSeverity)
+ str(_pvd.value[0]), _pvd.status, _pvd.alarmSeverity)
def display_table_parameters(self):
@@ -2233,11 +2016,11 @@ class CAQTableWidget(QTableWidget):
if pvgate.pv_ctrl is not None:
if pvgate.pv_ctrl.precision > 0:
self.max_precision_value = max(self.max_precision_value,
- pvgate.pv_ctrl.precision)
- self.initial_value = max(self.initial_value,
+ pvgate.pv_ctrl.precision)
+ self.initial_value = max(self.initial_value,
pvgate.precision)
-
- if self.max_precision_value > 0:
+
+ if self.max_precision_value > 0:
#precision user
_hbox_wgt = QWidget()
_hbox = QHBoxLayout()
@@ -2245,9 +2028,9 @@ class CAQTableWidget(QTableWidget):
self.table_precision_user_wgt = QSpinBox(self)
self.table_precision_user_wgt.setFocusPolicy(Qt.NoFocus)
self.table_precision_user_wgt.setValue(self.initial_value)
- self.table_precision_user_wgt.setMaximum(self.max_precision_value)
+ self.table_precision_user_wgt.setMaximum(self.max_precision_value)
self.table_precision_user_wgt.valueChanged.connect(
- self.table_precision_user_changed)
+ self.table_precision_user_changed)
precision_user_label.setAlignment(Qt.AlignLeft)
self.table_precision_user_wgt.setAlignment(Qt.AlignLeft)
_hbox.addWidget(precision_user_label)
@@ -2256,18 +2039,17 @@ class CAQTableWidget(QTableWidget):
_hbox_wgt.setLayout(_hbox)
precision_user_label.setFixedWidth(common_label_width)
- self.table_precision_user_wgt.setFixedWidth(40)
+ self.table_precision_user_wgt.setFixedWidth(40)
_hbox_wgt.setFixedWidth(common_hbox_width)
-
+
#precision ioc
_hbox2_wgt = QWidget()
_hbox2 = QHBoxLayout()
precision_ioc_label = QLabel("Precision (ioc): ")
- precision_ioc = QPushButton(self)
- precision_ioc.setText("Reset")
- precision_ioc.clicked.connect(self.table_precision_ioc_reset)
+ precision_ioc = QPushButton(self)
+ precision_ioc.setText("Reset")
+ precision_ioc.clicked.connect(self.table_precision_ioc_reset)
precision_ioc_label.setAlignment(Qt.AlignLeft)
-
_hbox2.addWidget(precision_ioc_label)
_hbox2.addWidget(precision_ioc)
@@ -2276,40 +2058,38 @@ class CAQTableWidget(QTableWidget):
_hbox2_wgt.setLayout(_hbox2)
precision_ioc_label.setFixedWidth(common_label_width)
- precision_ioc.setFixedWidth(50)
+ precision_ioc.setFixedWidth(50)
+
+ _hbox2_wgt.setFixedWidth(common_hbox_width)
- _hbox2_wgt.setFixedWidth(common_hbox_width)
-
-
layout.addWidget(_hbox_wgt)
layout.addWidget(_hbox2_wgt)
-
if 'Timestamp' in self.columns_dict.keys():
#time-stamp
_hbox4_wgt = QWidget()
_hbox4 = QHBoxLayout()
ts_label = QLabel("Timestamp: ")
- self.ts_combox_idx_dict = {'second (s)':self.format_ts_sec,
- 'decisecond (ds)':self.format_ts_deci,
- 'millisecond (ms)':self.format_ts_milli,
- 'microsecond (\u03bcs)':self.format_ts_micro,
- 'nanosecond (ns)':self.format_ts_nano}
+ self.ts_combox_idx_dict = {
+ 'second (s)': self.format_ts_sec,
+ 'decisecond (ds)': self.format_ts_deci,
+ 'millisecond (ms)': self.format_ts_milli,
+ 'microsecond (\u03bcs)': self.format_ts_micro,
+ 'nanosecond (ns)': self.format_ts_nano}
ts_resolution = QComboBox(self)
- for (key, ts_res) in (self.ts_combox_idx_dict.items()):
+ for key, ts_res in self.ts_combox_idx_dict.items():
ts_resolution.addItem(key)
+ _current_idx = 0
- _current_idx = 0
-
for i, (key, ts_res) in enumerate(self.ts_combox_idx_dict.items()):
if ts_res == self.format_ts_decimal_part:
_current_idx = i
break
- ts_resolution.setCurrentIndex(_current_idx)
+ ts_resolution.setCurrentIndex(_current_idx)
ts_resolution.currentIndexChanged.connect(
self.table_ts_resolution_changed)
@@ -2318,98 +2098,89 @@ class CAQTableWidget(QTableWidget):
_hbox4_wgt.setLayout(_hbox4)
ts_label.setFixedWidth(common_label_width)
- ts_resolution.setFixedWidth(common_wgt_width)
+ ts_resolution.setFixedWidth(common_wgt_width)
_hbox4_wgt.setFixedWidth(common_hbox_width)
-
+
layout.addWidget(_hbox4_wgt)
#precision refresh rate
_hbox3_wgt = QWidget()
_hbox3 = QHBoxLayout()
- refresh_freq_label = QLabel("Refresh rate: ")
+ refresh_freq_label = QLabel("Refresh rate: ")
#_default_refresh_val = 0 if self.notify_freq_hz <= 0 else \
# self.notify_freq_hz
_default_refresh_val = 0 if self.notify_freq_hz_default <= 0 else \
self.notify_freq_hz_default
- self.refresh_freq_combox_idx_dict = {0:0, 1:10, 2:5, 3:2, 4:1, 5:0.5,
- 6:_default_refresh_val}
+ self.refresh_freq_combox_idx_dict = {0: 0, 1: 10, 2: 5, 3: 2, 4: 1,
+ 5: 0.5, 6: _default_refresh_val}
refresh_freq = QComboBox(self)
refresh_freq.addItem('direct')
refresh_freq.addItem('{0} Hz'.format(
- self.refresh_freq_combox_idx_dict[1]))
+ self.refresh_freq_combox_idx_dict[1]))
refresh_freq.addItem('{0} Hz'.format(
- self.refresh_freq_combox_idx_dict[2]))
+ self.refresh_freq_combox_idx_dict[2]))
refresh_freq.addItem('{0} Hz'.format(
- self.refresh_freq_combox_idx_dict[3]))
+ self.refresh_freq_combox_idx_dict[3]))
refresh_freq.addItem('{0} Hz'.format(
- self.refresh_freq_combox_idx_dict[4]))
+ self.refresh_freq_combox_idx_dict[4]))
refresh_freq.addItem('{0} Hz'.format(
- self.refresh_freq_combox_idx_dict[5]))
-
+ self.refresh_freq_combox_idx_dict[5]))
+
_default_text = 'default (direct)' if _default_refresh_val == 0 else \
'default ({0} Hz)'.format(self.refresh_freq_combox_idx_dict[6])
-
+
refresh_freq.addItem(_default_text)
-
+
for key, value in self.refresh_freq_combox_idx_dict.items():
- if value == self.notify_freq_hz:
+ if value == self.notify_freq_hz:
refresh_freq.setCurrentIndex(key)
break
-
refresh_freq.currentIndexChanged.connect(
self.table_refresh_rate_changed)
-
_hbox3.addWidget(refresh_freq_label)
_hbox3.addWidget(refresh_freq)
_hbox3_wgt.setLayout(_hbox3)
refresh_freq_label.setFixedWidth(common_label_width)
- refresh_freq.setFixedWidth(common_wgt_width)
+ refresh_freq.setFixedWidth(common_wgt_width)
_hbox3_wgt.setFixedWidth(common_hbox_width)
-
+
layout.addWidget(_hbox3_wgt)
layout.setAlignment(Qt.AlignLeft)
layout.setContentsMargins(10, 0, 0, 0)
layout.setSpacing(0)
-
- display_wgt.setMinimumWidth(340)
- display_wgt.setLayout(layout)
+
+ display_wgt.setMinimumWidth(340)
+ display_wgt.setLayout(layout)
display_wgt.exec()
-
-
+
+
def mousePressEvent(self, event):
- #print(event.pos())
row = self.indexAt(event.pos()).row()
- #print("current item", row)
- if row < len(self.pv_list) and row > -1:
- self.pv_gateway[row].mousePressEvent(event)
- #elif row == -1:
- # self.display_table_parameters()
- else:
- button = event.button()
- #print("button", button, Qt.RightButton)
- if button == Qt.RightButton:
+
+ if row > -1:
+ if row < len(self.pv_list):
+ self.pv_gateway[row].mousePressEvent(event)
+ else:
+ button = event.button()
+ if button == Qt.RightButton:
self.table_context_menu.exec(QCursor.pos())
self.clearFocus()
-
-
#remove highlighting which persists after mouse leaves
def mouseMoveEvent(self, event):
- #event.ignore()
pass
def leaveEvent(self, event):
self.clearSelection()
- self.clearFocus()
+ self.clearFocus()
del event
-
-
+
class QMessageWidget(QListWidget):
"""Log message window."""
@@ -2418,18 +2189,16 @@ class QMessageWidget(QListWidget):
self.myItem = None
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.setFocusPolicy(Qt.StrongFocus)
-
-
-
+
def leaveEvent(self, event):
if self.myItem:
self.clearSelection()
self.clearFocus()
del event
-
+
def mousePressEvent(self, event):
item = self.itemAt(event.x(), event.y())
- if item:
+ if item:
self.myItem = item
self.setCurrentItem(self.myItem)
@@ -2439,19 +2208,14 @@ class QMessageWidget(QListWidget):
if nitem:
if self.myItem is not None:
_str = self.myItem.text()
- #beg = mystr.find("file = ")
- #end = mystr.find("'", beg+6)
- #newstr = "\""+mystr[beg+6:end]+"\""
QApplication.clipboard().setText(_str)
-
-
class QResultsWidget:
"""Results table"""
def __init__(self, summary_dict=None, table_dict=None):
-
+
self.summary_dict = summary_dict
self.table_dict = table_dict
self._group_box = None
@@ -2468,22 +2232,22 @@ class QResultsWidget:
longest_str_item1 = ""
longest_str_item2 = ""
-
+
for i, (label, text) in enumerate(self.summary_dict.items()):
if len(str(label)) > len(longest_str_item1):
longest_str_item1 = str(label)
if len(str(text)) > len(longest_str_item2):
- longest_str_item2 = str(text)
-
- fm = QFontMetricsF(_font)
-
+ longest_str_item2 = str(text)
+
+ fm = QFontMetricsF(_font)
+
_factor = 1.15
if LooseVersion(QT_VERSION_STR) < LooseVersion("5.0"):
_factor = 1.18
-
- qrect1 = fm.boundingRect(longest_str_item1)
- qrect2 = fm.boundingRect(longest_str_item2)
+
+ qrect1 = fm.boundingRect(longest_str_item1)
+ qrect2 = fm.boundingRect(longest_str_item2)
_width_scaling_factor = 1.5
_width_scaling_factor_le = 1.15
_widget_height = 25
@@ -2492,16 +2256,16 @@ class QResultsWidget:
qlabel = QLabel(label)
qle = QLineEdit(text)
qlabel.setFont(_font)
- qlabel.setStyleSheet(("QLabel{color:black;" +
- "margin:0px; padding:2px;}"))
+ qlabel.setStyleSheet(("QLabel{color:black;" +
+ "margin:0px; padding:2px;}"))
qlabel.setFixedWidth(qrect1.width() * _width_scaling_factor)
qlabel.setFixedHeight(_widget_height)
qle.setFocusPolicy(Qt.NoFocus)
qle.setFont(_font)
- qle.setStyleSheet(("QLineEdit{color:blue;" +
- "background-color: lightgray;" +
- "qproperty-readOnly: true;" +
+ qle.setStyleSheet(("QLineEdit{color:blue;" +
+ "background-color: lightgray;" +
+ "qproperty-readOnly: true;" +
"margin:0px; padding:2px;}"))
qle.setFixedWidth(qrect2.width() * _width_scaling_factor_le)
qle.setFixedHeight(_widget_height)
@@ -2511,25 +2275,25 @@ class QResultsWidget:
_hbox = QHBoxLayout()
_hbox.addWidget(qlabel)
_hbox.addWidget(qle)
- _hbox_widget.setLayout(_hbox)
+ _hbox_widget.setLayout(_hbox)
_hbox.setAlignment(Qt.AlignCenter)
_hbox.setContentsMargins(0, 2, 0, 0)
_vbox.addWidget(_hbox_widget)
-
+
_vbox.setContentsMargins(0, 0, 0, 0)
_vbox.setAlignment(Qt.AlignCenter|Qt.AlignTop)
_vbox2_widget = QWidget()
_vbox2 = QVBoxLayout()
_vbox2.setContentsMargins(0, 20, 0, 40)
- table = QTableWidget(len(self.table_dict)-1, 2)
- table.verticalHeader().setVisible(False)
+ table = QTableWidget(len(self.table_dict)-1, 2)
+ table.verticalHeader().setVisible(False)
table.setFocusPolicy(Qt.NoFocus)
#table.setFont(_font)
longest_str_item1 = ""
longest_str_item2 = ""
-
+
for i, (label, text) in enumerate(self.table_dict.items()):
item1 = QTableWidgetItem(str(label))
item2 = QTableWidgetItem(str(text))
@@ -2538,214 +2302,157 @@ class QResultsWidget:
item1.setForeground(QColor("black"))
item2.setForeground(QColor("black"))
if i%2 == 0:
- item1.setBackground(QColor("lightgray"))
- item2.setBackground(QColor("lightgray"))
+ item1.setBackground(QColor("lightgray"))
+ item2.setBackground(QColor("lightgray"))
if len(str(label)) > len(longest_str_item1):
longest_str_item1 = str(label)
if len(str(text)) > len(longest_str_item2):
- longest_str_item2 = str(text)
+ longest_str_item2 = str(text)
if i == 0:
#item1.setFont(_font)
#item2.setFont(_font)
table.setHorizontalHeaderItem(0, item1)
table.setHorizontalHeaderItem(1, item2)
- else:
+ else:
table.setItem(i-1, 0, item1)
- table.setItem(i-1, 1, item2)
+ table.setItem(i-1, 1, item2)
+ fm = QFontMetricsF(_font)
- fm = QFontMetricsF(_font)
-
_factor = 1.2
if LooseVersion(QT_VERSION_STR) < LooseVersion("5.0"):
_factor = 1.18
-
- qrect = fm.boundingRect(longest_str_item1 + longest_str_item2 )
-
+
+ qrect = fm.boundingRect(longest_str_item1 + longest_str_item2)
+
_width_scaling_factor = 1.04
table.resizeColumnsToContents()
table.resizeRowsToContents()
- #print(fm.lineSpacing())
- table.setFixedHeight((fm.lineSpacing() * _factor * len(self.table_dict))
- + fm.lineSpacing()*2)
-
- #table.setColumnWidth(0, fm.boundingRect(longest_str_item1).width() * _width_scaling_factor)
- #table.setColumnWidth(1, fm.boundingRect(longest_str_item2).width() * _width_scaling_factor)
- table.setFixedWidth(((qrect.width()) * _width_scaling_factor))
- #table.setFixedWidth(220)
+
+ table.setFixedHeight((fm.lineSpacing() * _factor * len(
+ self.table_dict)) + fm.lineSpacing()*2)
+
+ table.setFixedWidth(((qrect.width()) * _width_scaling_factor))
+
_vbox2.addWidget(table)
_vbox2.setAlignment(Qt.AlignCenter|Qt.AlignTop)
_vbox2_widget.setLayout(_vbox2)
-
- _vbox.addWidget(_vbox2_widget)
-
+
+ _vbox.addWidget(_vbox2_widget)
+
self._group_box.setLayout(_vbox)
self._group_box.setContentsMargins(20, 20, 20, 20)
self._group_box.setAlignment(Qt.AlignTop)
- self._group_box.setFixedHeight(table.height() + (
- _widget_height*len(self.summary_dict))) #_vbox2_widget.height() -50)
+ self._group_box.setFixedHeight(
+ table.height() + (_widget_height*len(self.summary_dict)))
self._group_box.setFixedWidth(table.width() + 20)
return self._group_box
-
+
class QResultsTableWidget():
"""Results table"""
def __init__(self, column_headings=None):
-
+
self.column_headings = column_headings
self._group_box = None
def group_box(self, title="Table of Results"):
self._group_box = QGroupBox(title)
self._group_box.setObjectName("OUTER")
-
+
_font = QFont("Sans Serif", 10)
_vbox2_widget = QWidget()
_vbox2 = QVBoxLayout()
_vbox2.setContentsMargins(0, 20, 0, 40)
- table = QTableWidget(1, len(self.column_headings))
- table.verticalHeader().setVisible(True)
+ table = QTableWidget(1, len(self.column_headings))
+ table.verticalHeader().setVisible(True)
table.setFocusPolicy(Qt.NoFocus)
table.setFont(_font)
- longest_str_item1 = ""
- longest_str_item2 = ""
-
for i, heading in enumerate(self.column_headings):
_item = QTableWidgetItem(str(heading))
table.setHorizontalHeaderItem(i, _item)
-
-
+
table.resizeColumnsToContents()
table.resizeRowsToContents()
- #print(fm.lineSpacing())
- table.setFixedHeight(400)
-
+ table.setFixedHeight(400)
+
_vbox2.addWidget(table)
_vbox2.setAlignment(Qt.AlignCenter|Qt.AlignTop)
_vbox2_widget.setLayout(_vbox2)
-
-
+
self._group_box.setLayout(_vbox2)
self._group_box.setContentsMargins(20, 20, 20, 20)
self._group_box.setAlignment(Qt.AlignTop)
-
+
self._group_box.setFixedWidth(table.width() + 20)
return self._group_box
-
+
class QHDFDockWidget(QDockWidget):
-
- def __init__(self, title=None, parent=None):
+
+ def __init__(self, title=None, parent=None):
super().__init__(title, parent)
self.parent = parent
self.is_docked = True
- self.geometry_from_qsettings = self.parent.getGeometry()
- self.topLevelChanged.connect(self._top_level_changed)
- self.setVisible(False)
- self.setFloating(False)
+ self.geometry_from_qsettings = self.parent.application_geometry
+ self.topLevelChanged.connect(self._top_level_changed)
+ self.setVisible(False)
+ self.setFloating(False)
self.geometry_from_qsettings = self.parent.geometry()
- print( "START GEOEMTRY ",self.geometry_from_qsettings)
-
def closeEvent(self, event: QCloseEvent):
- ######################print("Super ClosingEvent....")
super().closeEvent(event)
- print("Super ClosedEvent....")
- print("float/1", self.isFloating())
- print("visible/1", self.isVisible())
- #print("isDocked/1", self.is_docked)
- print("before", self.parent.geometry(), self.geometry_from_qsettings)
- #self.parent.setGeometry(self.geometry_from_qsettings)
- #self.setGeometry(self.geometry_from_qsettings)
- #QApplication.processEvents()
- print("after", self.parent.geometry())
-
- def changeEvent(self, event):
- print("event Type", event.type())
- print("Sender", self.sender())
- #This implies that one of restore/quit button of the widget was clicked
- #if self.senderSignalIndex() == 34:
- # self.close()
- print("before//", self.parent.geometry(), self.geometry_from_qsettings)
- #self.parent.setGeometry(self.geometry_from_qsettings)
- #Generic
- #if "QAbstractButton" in str(self.sender()):
- # self.geometry_from_qsettings = self.parent.geometry()
- print("after", self.parent.geometry())
+ self.parent.setGeometry(self.geometry_from_qsettings)
+ self.setGeometry(self.geometry_from_qsettings)
+ QApplication.processEvents()
+
+ self.parent.setGeometry(self.geometry_from_qsettings)
+
+ def changeEvent(self, event):
+ pass
def _top_level_changed(self, is_floating):
- #Need MUTEX
- print("is_floating", is_floating)
- #self.setVisible(False)
- #self.setFloating(True)
- #ResetGeometry
- #self.parent.setGeometry(self.geometry_from_qsettings)
- #QApplication.processEvents()
-
+ pass
+
+
class QNoDockWidget(QDockWidget):
-
- def __init__(self, title=None, parent=None):
+
+ def __init__(self, title=None, parent=None):
super().__init__(title, parent)
self.parent = parent
self.is_docked = True
- self.geometry_from_qsettings = self.parent.getGeometry()
- self.topLevelChanged.connect(self._top_level_changed)
- self.setVisible(False)
- self.setFloating(True)
-
- def changeEvent(self, event):
- #print("event Type", event.type())
- #print("Sender", self.sender())
- #This implies that one of restore/quit button of the widget was clicked
- #if self.senderSignalIndex() == 34:
- # self.close()
- #Generic
- if "QAbstractButton" in str(self.sender()):
- self.geometry_from_qsettings = self.parent.geometry()
-
-
- #def closeEvent(self, event: QCloseEvent):
- ######################print("Super ClosingEvent....")
- #super().closeEvent(event)
- #print("Super ClosedEvent....")
- #print("float/1", self.isFloating())
- #print("visible/1", self.isVisible())
- #print("isDocked/1", self.is_docked)
-
-
-
- #This is for the quit button
- #if not self.isVisible():
- # self.topLevelChanged.emit(False)
- # print("emitting..")
- # self.topLevelChanged.emit(False)
-
-
- def _top_level_changed(self, is_floating):
- #Need MUTEX
-
- self.setVisible(False)
+ self.geometry_from_qsettings = self.parent.application_geometry
+ self.topLevelChanged.connect(self._top_level_changed)
+ self.setVisible(False)
self.setFloating(True)
- #ResetGeometry
+
+ def changeEvent(self, event):
+ if "QAbstractButton" in str(self.sender()):
+ self.geometry_from_qsettings = self.parent.geometry()
+
+ def _top_level_changed(self): #, is_floating):
+ self.setVisible(False)
+ self.setFloating(True)
+ #ResetGeometry
self.parent.setGeometry(self.geometry_from_qsettings)
QApplication.processEvents()
-
-
+
+
class CAQStripChart(PlotWidget):
'''Channel access enabled pyqtgraph.PlotWidget'''
-
- def __init__(self, parent=None, pv_list: list = ['PV_NAME_NOT_GIVEN'],
- monitor_callback=None, pv_within_daq_group: bool = False,
- color_mode = None, show_units: bool = False, prefix: str = "",
- suffix: str = "", notify_freq_hz: int = 0, title: str = "",
- ylabel: str = "", force_ts_align = True):
+
+ def __init__(self, parent=None, pv_list: list = ['PV_NAME_NOT_GIVEN'],
+ monitor_callback=None, pv_within_daq_group: bool = False,
+ color_mode=None, show_units: bool = False, prefix: str = "",
+ suffix: str = "", notify_freq_hz: int = 0, title: str = "",
+ ylabel: str = ""):
super().__init__()
self.no_channels = len(pv_list)
@@ -2753,33 +2460,32 @@ class CAQStripChart(PlotWidget):
self.found = False
self.time_zero = [0] * self.no_channels
self.time_delta = [0] * self.no_channels
- self.pv_list = pv_list
+ self.pv_list = pv_list
self.pv2item_dict = {}
self.pv_gateway = [None] * self.no_channels
-
+
self.pvd_previous_list = [None] * self.no_channels
self.val_previous = [None] * self.no_channels
self.curve = [None] * self.no_channels
- for i in range (0, len(self.pv_list)):
- self.pv_gateway[i] = PVGateway().__init__(
- parent, pv_list[i], monitor_callback, pv_within_daq_group,
- color_mode, show_units, prefix, suffix,
+ for i in range(0, len(self.pv_list)):
+ self.pv_gateway[i] = PVGateway(
+ parent, pv_list[i], monitor_callback, pv_within_daq_group,
+ color_mode, show_units, prefix, suffix,
#connect_callback=self.py_connect_callback,
connect_triggers=False, notify_freq_hz=notify_freq_hz,
- monitor_dbr_time = True)
+ monitor_dbr_time=True)
self.pv_gateway[i].is_initialize_complete()
-
-
+
self.pvd_previous_list[i] = self.pv_gateway[i].pvd
self.pv_gateway[i].trigger_connect.connect(
self.receive_connect_update)
-
+
self.pv_gateway[i].trigger_monitor_str.connect(
- self.receive_monitor_update)
+ self.receive_monitor_update)
self.pv_gateway[i].trigger_monitor_int.connect(
self.receive_monitor_update)
self.pv_gateway[i].trigger_monitor_float.connect(
@@ -2787,40 +2493,38 @@ class CAQStripChart(PlotWidget):
self.pv_gateway[i].trigger_monitor.connect(
self.receive_monitor_dbr_time)
- self.pv_gateway[i].widget_class = "PlotWidget"
-
+ self.pv_gateway[i].widget_class = "PlotWidget"
self.pv2item_dict[self.pv_gateway[i]] = i
self.cafe = self.pv_gateway[0].cafe
self.cyca = self.pv_gateway[0].cyca
- for i in range(0, len(self.pv_gateway)):
+ for i in range(0, len(self.pv_gateway)):
if self.cafe.isConnected(self.pv_gateway[i].pv_name):
self.pv_gateway[i].trigger_connect.emit(
self.pv_gateway[i].handle, str(self.pv_gateway[i].pv_name),
self.pv_gateway[i].cyca.ICAFE_CS_CONN)
-
for i in range(0, len(self.pv_gateway)):
if not self.pv_gateway[i].pv_within_daq_group:
self.pv_gateway[i].monitor_start()
-
- sampleinterval = 0.2
- timewindow = 1800.0
+
+ sampleinterval = 0.2
+ ##timewindow = 1800.0
self.ts_delta_max = 0.6
-
+
# Data stuff
self._interval = int(sampleinterval*1000)
self._bufsize = 9000 #int(timewindow/0.33)
self._bufsize2 = 9000 # int(timewindow/1.33)
self.databuffer = [None] * self.no_channels
self.timebuffer = [None] * self.no_channels
- self.x = [None] * self.no_channels
- self.y = [None] * self.no_channels
+ self.x = [None] * self.no_channels
+ self.y = [None] * self.no_channels
self.x_shifted = [None] * self.no_channels
self.idx = [0] * self.no_channels
-
+
for i in range(0, self.no_channels):
bsize = self._bufsize if i == 0 else self._bufsize2
self.databuffer[i] = collections.deque([None]*bsize, bsize)
@@ -2828,9 +2532,9 @@ class CAQStripChart(PlotWidget):
self.x[i] = np.zeros(bsize, dtype=np.float)
self.y[i] = np.zeros(bsize, dtype=np.float)
- _long_size=20
+ ##_long_size=20
#self.data_series_buffer = collections.deque([0]*_long_size, _long_size)
- #self.time_series_buffer = collections.deque([0]*_long_size, _long_size)
+ #self.time_series_buffer = collections.deque([0]*_long_size, _long_size)
#self.data_series = [] * self.no_channels
#self.time_series = [] * self.no_channels
@@ -2841,104 +2545,98 @@ class CAQStripChart(PlotWidget):
#self.x_series = np.zeros(_long_size, dtype=np.float)
#self.y_series = np.zeros(_long_size, dtype=np.float)
if title is not None:
- self.setTitle(str(title)) #self.pv_gateway[0].pv_name)
+ self.setTitle(str(title)) #self.pv_gateway[0].pv_name)
self.showGrid(x=True, y=True)
self.setLabel('left', ylabel, self.pv_gateway[0].units)
- self.setLabel('bottom', 'time', 's')
+ self.setLabel('bottom', 'time', 's')
self.setBackground((60, 60, 60)) #247, 236, 249))
self.setLimits(yMin=-0.11)
self.plotItem.setMouseEnabled(y=False) # Only allow zoom in X-axis
self.plotItem.setMouseEnabled(x=True) # Only allow zoom in Y-axis
- pen_list = [(125, 249, 255), (255,255,0) ]
-
+ pen_list = [(125, 249, 255), (255, 255, 0)]
+
for i in range(0, len(self.pv_gateway)):
- self.curve[i] = self.plot(self.x[0], self.y[0], pen=pen_list[i]) # (0, 253, 235))
- #self.curve[1] = self.plot(self.x[1], self.y[1], pen=(255,255,0))
-
- l=pg.LegendItem(offset=(0., 0.5), colCount=1)
+ self.curve[i] = self.plot(self.x[0], self.y[0], pen=pen_list[i])
+
+ l = pg.LegendItem(offset=(0., 0.5), colCount=1)
l.setParentItem(self.graphicsItem())
l.setLabelTextColor((255, 255, 255))
- #l.setOffset(-60)
+
for curv, pv in zip(self.curve, self.pv_gateway):
- l.addItem(curv, pv.pv_name)
-
- #self.daq_stop()
- #print(self._bufsize)
- #print(len(self.x), len(self.y))
+ l.addItem(curv, pv.pv_name)
+
QApplication.processEvents()
-
- @Slot(object, int)
+
+ @Slot(object, int)
def receive_monitor_dbr_time(self, pvdata, alarm_severity):
+
+ #Check on alarm_severity??
+
_row = self.pv2item_dict[self.sender()]
- #print("row, value from pvdata==>", _row, pvdata.value[0], self.pv_gateway[_row].pv_name)
-
+
ts_now = pvdata.ts[0] + pvdata.ts[1] * 10**(-9)
- ts_previous = (self.pvd_previous_list[_row].ts[0] +
- self.pvd_previous_list[_row].ts[1] * 10**(-9))
- ts_delta = ts_now - ts_previous
-
+ ts_previous = (self.pvd_previous_list[_row].ts[0] +
+ self.pvd_previous_list[_row].ts[1] * 10**(-9))
+ ##ts_delta = ts_now - ts_previous
+
if (pvdata.ts[0] == self.pvd_previous_list[_row].ts[0]) and (
pvdata.ts[1] == self.pvd_previous_list[_row].ts[1]):
pvdata.show()
self.pvd_previous_list[_row].show()
return
-
- value = pvdata.value[0]
+
+ value = pvdata.value[0]
#discard first callbacks
#if ts_delta > 2.0:
- # self.pvd_previous_list[_row] = _pvd
+ # self.pvd_previous_list[_row] = _pvd
# return;
self.pvd_previous_list[_row] = pvdata
self.val_previous[_row] = value
- #self.pvd_previous_list[_row].ts[0] = _pvd.ts[0]
+ #self.pvd_previous_list[_row].ts[0] = _pvd.ts[0]
#self.pvd_previous_list[_row].ts[1] = _pvd.ts[1]
-
+
self.databuffer[_row].append(value)
self.timebuffer[_row].append(self.time_delta[_row])
highest_ts = self.timebuffer[0][0] \
if self.timebuffer[0][0] is not None else 0
- for i in range(1, len(self.timebuffer)):
+ for i in range(1, len(self.timebuffer)):
if self.timebuffer[i][0] is None:
continue
elif self.timebuffer[i][0] > highest_ts:
highest_ts = self.timebuffer[i][0]
-
+
if self.timebuffer[_row][0] is not None:
for i, val in enumerate(self.timebuffer[_row]):
if val > highest_ts:
self.idx[_row] = i - 1
- break
+ break
self.y[_row][:] = self.databuffer[_row]
self.x[_row][:] = self.timebuffer[_row]
-
+
idx = self.idx[_row]
- self.x_shifted[_row] = list(map(lambda m : (m - self.time_delta[_row]), self.x[_row][idx:]))
+ self.x_shifted[_row] = list(
+ map(lambda m: (m - self.time_delta[_row]), self.x[_row][idx:]))
- #print("idx", self.idx)
-
self.curve[_row].setData(self.x_shifted[_row], self.y[_row][idx:])
-
- self.time_delta[_row] = (
- pvdata.ts[0] + pvdata.ts[1]*10**(-9)) - self.time_zero[0]
-
- #QApplication.processEvents()
-
+ self.time_delta[_row] = (
+ pvdata.ts[0] + pvdata.ts[1]*10**(-9)) - self.time_zero[0]
+
+
@Slot(str, int, int)
@Slot(int, int, int)
@Slot(float, int, int)
def receive_monitor_update(self, value, status, alarm_severity):
-
+
#self.pv_gateway.receive_monitor_update(value, status, alarm_severity)
_row = self.pv2item_dict[self.sender()]
- #if _row == 1:
- # return
- print("row, value===>", _row, value, self.pv_gateway[_row].pv_name)
+
+ #print("row, value===>", _row, value, self.pv_gateway[_row].pv_name)
_pvd = self.pv_gateway[_row].cafe.getPVCache(
self.pv_gateway[_row].handle)
@@ -2946,106 +2644,109 @@ class CAQStripChart(PlotWidget):
_pvd2 = self.pv_gateway[_row].pvd
- print ("val", value, _pvd2.value[0], _pvd.value[0], self.pvd_previous_list[_row].value[0])
+ print("val", value, _pvd2.value[0], _pvd.value[0],
+ self.pvd_previous_list[_row].value[0])
ts_now = _pvd.ts[0] + _pvd.ts[1] * 10**(-9)
- ts_previous = (self.pvd_previous_list[_row].ts[0] +
- self.pvd_previous_list[_row].ts[1] * 10**(-9))
- ts_delta = ts_now - ts_previous
-
+ ts_previous = (self.pvd_previous_list[_row].ts[0] +
+ self.pvd_previous_list[_row].ts[1] * 10**(-9))
+ ts_delta = ts_now - ts_previous
+
if value == self.val_previous[_row]:
- #if (_pvd.ts[0] == self.pvd_previous_list[_row].ts[0]) and (
- # _pvd.ts[1] == self.pvd_previous_list[_row].ts[1]):
_pvd.show()
- #self.pvd_previous_list[_row].show()
+
return
-
-
+
+
#discard first callbacks
#if ts_delta > 2.0:
- # self.pvd_previous_list[_row] = _pvd
+ # self.pvd_previous_list[_row] = _pvd
# return;
self.pvd_previous_list[_row] = _pvd2
self.val_previous[_row] = value
- #self.pvd_previous_list[_row].ts[0] = _pvd.ts[0]
+ #self.pvd_previous_list[_row].ts[0] = _pvd.ts[0]
#self.pvd_previous_list[_row].ts[1] = _pvd.ts[1]
-
+
self.databuffer[_row].append(value)
self.timebuffer[_row].append(self.time_delta[_row])
highest_ts = self.timebuffer[0][0] \
if self.timebuffer[0][0] is not None else 0
- for i in range(1, len(self.timebuffer)):
+ for i in range(1, len(self.timebuffer)):
if self.timebuffer[i][0] is None:
continue
elif self.timebuffer[i][0] > highest_ts:
highest_ts = self.timebuffer[i][0]
-
-
+
+
if self.timebuffer[_row][0] is not None:
for i, val in enumerate(self.timebuffer[_row]):
if val > highest_ts:
self.idx[_row] = i - 1
- break
+ break
+
+
+ #for i in range(1, self.timebuffer):
+ # if self.timebuffer[i][0] is not None:
+ # a = self.timebuffer[0][0]
+ # for i, val in enumerate(self.timebuffer[_row]):
+ # if val > a:
+ # idx = i - 1
+ # break
- '''
- for i in range(1, self.timebuffer):
- if self.timebuffer[i][0] is not None:
- a = self.timebuffer[0][0]
- for i, val in enumerate(self.timebuffer[_row]):
- if val > a:
- idx = i - 1
- break
- '''
-
self.y[_row][:] = self.databuffer[_row]
self.x[_row][:] = self.timebuffer[_row]
-
+
#self.y[_row][:] = self.databuffer[_row]
#self.x[_row][:] = self.timebuffer[_row]
'''
#print(ts_delta, value, self.pvd_previous.value[0])
- #if (ts_delta < self.ts_delta_max) and (value < self.pvd_previous.value[0]) :
+ #if (ts_delta < self.ts_delta_max) and (value <
+ #self.pvd_previous.value[0]) :
if (value < self.pvd_previous.value[0]) :
self.data_series_buffer.append(value)
- self.time_series_buffer.append(ts_now - self.time_zero ) #self.time_delta)
+ self.time_series_buffer.append(ts_now - self.time_zero )
self.y_series[:] = self.data_series_buffer
self.x_series[:] = self.time_series_buffer
#print(self.x_series, self.y_series)
#elif ts_delta < 1.0:
- if len(self.data_series_buffer) > 15:
+ if len(self.data_series_buffer) > 15:
#x_series = np.array(self.time_series, dtype=np.float)
#y_series = np.array(self.data_series, dtype=np.float)
_x=self.x_series.reshape((-1, 1))
-
+
model = LinearRegression()
model.fit(_x, self.y_series)
r_sq = model.score(_x, self.y_series)
- ###JCprint('coefficient of determination:', r_sq, "slope", model.coef_ , "lifetime:", self.y_series[0]/model.coef_ / 3600)
+ ###JCprint('coefficient of determination:',
+ ##r_sq, "slope", model.coef_ , "lifetime:",
+ ###self.y_series[0]/model.coef_ / 3600)
#print('intercept:', model.intercept_)
#print('slope:', model.coef_)
#print('max value', y_series[0], y_series[1])
if r_sq > 0.995:
_I = self.y_series[0]
###JCprint("lifetime:", _I/model.coef_ / 3600)
-
-
+
+
y_pred = model.predict(_x)
- #print("len, y_pred, _x", len(y_pred), len(self.y_series), len(_x))
+ #print("len, y_pred, _x", len(y_pred),
+ #len(self.y_series), len(_x))
#print('predicted response:', y_pred, sep='\n')
m_sq_error = mean_squared_error(self.y_series, y_pred)
#print('Mean squared error: {0:.9f}'.format(
# mean_squared_error(y_series, y_pred)))
#print('Coefficient of determination: {0:.9f}'.format(
- # r2_score(y_series, y_pred)))
+ # r2_score(y_series, y_pred)))
-
- self.trigger_series_sequence.emit(self.x_series, self.y_series)
+
+ self.trigger_series_sequence.emit(self.x_series,
+ self.y_series)
#print("emit")
self.data_series = []
self.time_series = []
@@ -3053,73 +2754,75 @@ class CAQStripChart(PlotWidget):
else:
self.data_series = []
self.time_series = []
-
- '''
-
- #dt = (self.x[-1] - self.x[-2])
+ '''
+
+
+ #dt = (self.x[-1] - self.x[-2])
#print("dt", dt)
#Lowet IPCT before trigger is set to t=0
idx = self.idx[_row]
- self.x_shifted[_row] = list(map(lambda m : (m - self.time_delta[_row]), self.x[_row][idx:]))
+ self.x_shifted[_row] = list(
+ map(lambda m: (m - self.time_delta[_row]), self.x[_row][idx:]))
##self.y = np.where(self.y != self.y, 0, self.y) #test for nan
-
+
#print("row len len ", _row, self.time_delta[0], self.time_delta[1])
-
-
+
+
self.curve[_row].setData(self.x_shifted[_row], self.y[_row][idx:])
-
+
self.time_delta[_row] = (
- _pvd.ts[0] + _pvd.ts[1]*10**(-9)) - self.time_zero[0]
-
-
+ _pvd.ts[0] + _pvd.ts[1]*10**(-9)) - self.time_zero[0]
+
+
'''
- LOOK_BACK = -800
+ LOOK_BACK = -800
if 'ARIDI-PCT2:CURRENT' in self.pv_gateway[_row].pv_name:
LOOK_BACK = -250
- if value > self.y[-2]:
+ if value > self.y[-2]:
if not self.found:
#print(x_shifted[-240:], self.y[-240:])
#self.y = np.where(self.y != self.y, 0, self.y) #test for nan
max_index = self.y[LOOK_BACK:].argmax()
-
+
if max_index == 0:
return
print("max index=", max_index)
-
- #print(x_shifted[-600+max_index:], self.x[-600+max_index:])
- #print(self.y[-600+max_index:-2])
+
+ #print(x_shifted[-600+max_index:], self.x[-600+max_index:])
+ #print(self.y[-600+max_index:-2])
self.found = True
#print("Are Signals blocked??", self.signalsBlocked())
self.trigger_decay_sequence.emit(np.array(
- x_shifted[LOOK_BACK+max_index+9:-2]), self.y[LOOK_BACK+max_index+9:-2])
+ x_shifted[LOOK_BACK+max_index+9:-2]),
+ self.y[LOOK_BACK+max_index+9:-2])
else:
self.found = False
- '''
-
+ '''
+
@Slot(int, str, int)
def receive_connect_update(self, handle: int, pv_name: str, status: int):
'''Triggered by connect signal'''
print("pv_name==>", pv_name)
-
+
_row = self.pv2item_dict[self.sender()]
- self.pv_gateway[_row].receive_connect_update(handle, pv_name, status,
+ self.pv_gateway[_row].receive_connect_update(handle, pv_name, status,
post_display=False)
#self.pv_gateway.receive_connect_update(handle, pv_name, status)
- _pvd = self.pv_gateway[_row].cafe.getPVCache(self.pv_gateway[_row].handle)
+ _pvd = self.pv_gateway[_row].cafe.getPVCache(
+ self.pv_gateway[_row].handle)
if self.time_zero[_row] == 0:
self.time_zero[_row] = _pvd.ts[0] + _pvd.ts[1]*10**(-9)
-
+
self.pvd_previous = _pvd
#renove highlighting which persists after mouse leaves
def mouseMoveEvent(self, event):
- #event.ignore()
pass
def leaveEvent(self, event):
@@ -3129,69 +2832,67 @@ class CAQStripChart(PlotWidget):
class CAQPCTChart(PlotWidget):
'''Channel access enabled pyqtgraph.PlotWidget'''
- #trigger_monitor_float = Signal(float, int, int)
+ #trigger_monitor_float = Signal(float, int, int)
#trigger_monitor_int = Signal(int, int, int)
#trigger_monitor_str = Signal(str, int, int)
- #trigger_connect = Signal(int, str, int)
-
+ #trigger_connect = Signal(int, str, int)
+
trigger_decay_sequence = Signal(np.ndarray, np.ndarray)
trigger_series_sequence = Signal(np.ndarray, np.ndarray)
#def py_connect_callback(self, handle, pvname, status):
- # self.trigger_connect.emit(int(handle), str(pvname), int(status))
+ # self.trigger_connect.emit(int(handle), str(pvname), int(status))
# print("py connect callback", handle, pvname, status)
-
+
def daq_start(self):
self.blockSignals(False)
-
-
- def daq_pause(self):
- self.blockSignals(True)
-
- def daq_stop(self):
- self.blockSignals(True)
-
- def __init__(self, parent=None, pv_list: list = ['PV_NAME_NOT_GIVEN'],
- monitor_callback=None, pv_within_daq_group: bool = False,
- color_mode = None, show_units: bool = False, prefix: str = "",
+ def daq_pause(self):
+ self.blockSignals(True)
+
+ def daq_stop(self):
+ self.blockSignals(True)
+
+ def __init__(self, parent=None, pv_list: list = ['PV_NAME_NOT_GIVEN'],
+ monitor_callback=None, pv_within_daq_group: bool = False,
+ color_mode=None, show_units: bool = False, prefix: str = "",
suffix: str = "", notify_freq_hz: int = 0):
super().__init__()
self.found = False
self.time_zero = 0
self.time_delta = 0
- self.pv_list = pv_list
+ self.pv_list = pv_list
self.pv2item_dict = {}
self.pv_gateway = [None] * len(self.pv_list)
self.pvd_previous = None
- for i in range (0, len(self.pv_list)):
- self.pv_gateway[i] = PVGateway().__init__(
- parent, pv_list[i], monitor_callback, pv_within_daq_group,
- color_mode, show_units, prefix, suffix,
+ for i in range(0, len(self.pv_list)):
+ self.pv_gateway[i] = PVGateway(
+ parent, pv_list[i], monitor_callback, pv_within_daq_group,
+ color_mode, show_units, prefix, suffix,
#connect_callback=self.py_connect_callback,
- connect_triggers=False, notify_freq_hz=notify_freq_hz )
+ connect_triggers=False, notify_freq_hz=notify_freq_hz)
self.pv_gateway[i].is_initialize_complete()
-
+
self.pv_gateway[i].trigger_connect.connect(
self.receive_connect_update)
-
+
self.pv_gateway[i].trigger_monitor_str.connect(
- self.receive_monitor_update)
+ self.receive_monitor_update)
self.pv_gateway[i].trigger_monitor_int.connect(
self.receive_monitor_update)
self.pv_gateway[i].trigger_monitor_float.connect(
self.receive_monitor_update)
-
- self.pv_gateway[i].widget_class = "PlotWidget"
+
+ self.pv_gateway[i].widget_class = "PlotWidget"
self.pv2item_dict[self.pv_gateway[i]] = i
self.cafe = self.pv_gateway[0].cafe
self.cyca = self.pv_gateway[0].cyca
- for i in range(0, len(self.pv_gateway)):
+ for i in range(0, len(self.pv_gateway)):
if self.cafe.isConnected(self.pv_gateway[i].pv_name):
self.pv_gateway[i].trigger_connect.emit(
self.pv_gateway[i].handle, str(self.pv_gateway[i].pv_name),
@@ -3200,21 +2901,21 @@ class CAQPCTChart(PlotWidget):
for i in range(0, len(self.pv_gateway)):
if not self.pv_gateway[i].pv_within_daq_group:
self.pv_gateway[i].monitor_start()
-
- sampleinterval=0.333
- timewindow=1800.0
+
+ sampleinterval = 0.333
+ timewindow = 1800.0
self.ts_delta_max = 0.6
-
+
# Data stuff
self._interval = int(sampleinterval*1000)
self._bufsize = int(timewindow/sampleinterval)
self.databuffer = collections.deque([None]*self._bufsize, self._bufsize)
self.timebuffer = collections.deque([0]*self._bufsize, self._bufsize)
-
- _long_size=20
+
+ _long_size = 20
self.data_series_buffer = collections.deque([0]*_long_size, _long_size)
- self.time_series_buffer = collections.deque([0]*_long_size, _long_size)
+ self.time_series_buffer = collections.deque([0]*_long_size, _long_size)
self.data_series = []
self.time_series = []
@@ -3224,45 +2925,45 @@ class CAQPCTChart(PlotWidget):
#self.x = np.linspace(-timewindow, 0.0, self._bufsize)
self.x = np.zeros(self._bufsize, dtype=np.float)
self.y = np.zeros(self._bufsize, dtype=np.float)
-
- self.x_series = np.zeros(_long_size, dtype=np.float)
- self.y_series = np.zeros(_long_size, dtype=np.float)
-
- self.setTitle("PCT(t)") #self.pv_gateway[0].pv_name)
+
+ self.x_series = np.zeros(_long_size, dtype=np.float)
+ self.y_series = np.zeros(_long_size, dtype=np.float)
+
+ self.setTitle("PCT(t)") #self.pv_gateway[0].pv_name)
self.showGrid(x=True, y=True)
self.setLabel('left', 'I', 'mA')
- self.setLabel('bottom', 'time', 's')
+ self.setLabel('bottom', 'time', 's')
self.setBackground((60, 60, 60)) #247, 236, 249))
self.setLimits(yMin=-0.11)
self.plotItem.setMouseEnabled(y=False) # Only allow zoom in X-axis
self.plotItem.setMouseEnabled(x=True) # Only allow zoom in Y-axis
- self.curve = self.plot(self.x, self.y, pen=(125, 249, 255)) # (0, 253, 235))
+ self.curve = self.plot(self.x, self.y, pen=(125, 249, 255))
#self.curve2 = self.plot(self.x, self.y, pen=(255,255,0))
-
- l=pg.LegendItem(offset=(0., 0.5))
+
+ l = pg.LegendItem(offset=(0., 0.5))
l.setParentItem(self.graphicsItem())
l.setLabelTextColor((125, 249, 255))
- #l.setOffset(-60)
- l.addItem(self.curve, str(self.pv_gateway[0].pv_name))
+
+ l.addItem(self.curve, str(self.pv_gateway[0].pv_name))
'''
- l2=self.addLegend()
+ l2=self.addLegend()
l2.setLabelTextColor('g')
l2.setOffset(10)
l2.addItem(self.curve2, str(self.pv_gateway[0].pv_name))
- '''
- self.daq_stop()
+ '''
+ self.daq_stop()
print(self._bufsize)
print(len(self.x), len(self.y))
-
-
-
+
+
+
@Slot(str, int, int)
@Slot(int, int, int)
@Slot(float, int, int)
def receive_monitor_update(self, value, status, alarm_severity):
-
+
#self.pv_gateway.receive_monitor_update(value, status, alarm_severity)
_row = self.pv2item_dict[self.sender()]
#print("value===>", value, self.pv_gateway[_row].pv_name)
@@ -3270,19 +2971,18 @@ class CAQPCTChart(PlotWidget):
self.pv_gateway[_row].handle)
ts_now = _pvd.ts[0] + _pvd.ts[1] * 10**(-9)
- ts_previous = self.pvd_previous.ts[0] + self.pvd_previous.ts[1] * 10**(-9)
- ts_delta = ts_now - ts_previous
-
+ ts_previous = self.pvd_previous.ts[0] + self.pvd_previous.ts[1]*10**(-9)
+ ts_delta = ts_now - ts_previous
+
if (_pvd.ts[0] == self.pvd_previous.ts[0]) and (
_pvd.ts[1] == self.pvd_previous.ts[1]):
#_pvd.show()
return
-
-
+
#discard first callbacks
if ts_delta > 2.0:
- self.pvd_previous = _pvd
- return;
+ self.pvd_previous = _pvd
+ return
self.databuffer.append(value)
self.y[:] = self.databuffer
@@ -3290,43 +2990,48 @@ class CAQPCTChart(PlotWidget):
self.x[:] = self.timebuffer
#print(ts_delta, value, self.pvd_previous.value[0])
- #if (ts_delta < self.ts_delta_max) and (value < self.pvd_previous.value[0]) :
- if (value < self.pvd_previous.value[0]) :
+ #if (ts_delta < self.ts_delta_max) and (value <
+ # self.pvd_previous.value[0]):
+ if value < self.pvd_previous.value[0]:
self.data_series_buffer.append(value)
- self.time_series_buffer.append(ts_now - self.time_zero ) #self.time_delta)
+ self.time_series_buffer.append(ts_now - self.time_zero)
self.y_series[:] = self.data_series_buffer
self.x_series[:] = self.time_series_buffer
#print(self.x_series, self.y_series)
#elif ts_delta < 1.0:
- if len(self.data_series_buffer) > 15:
+ if len(self.data_series_buffer) > 15:
#x_series = np.array(self.time_series, dtype=np.float)
#y_series = np.array(self.data_series, dtype=np.float)
- _x=self.x_series.reshape((-1, 1))
-
+ _x = self.x_series.reshape((-1, 1))
+
model = LinearRegression()
model.fit(_x, self.y_series)
r_sq = model.score(_x, self.y_series)
- ###JCprint('coefficient of determination:', r_sq, "slope", model.coef_ , "lifetime:", self.y_series[0]/model.coef_ / 3600)
+ ###JCprint('coefficient of determination:',
+ ###r_sq, "slope", model.coef_ , "lifetime:",
+ ###self.y_series[0]/model.coef_ / 3600)
#print('intercept:', model.intercept_)
#print('slope:', model.coef_)
#print('max value', y_series[0], y_series[1])
if r_sq > 0.995:
- _I = self.y_series[0]
+ #_I = self.y_series[0]
+
+
###JCprint("lifetime:", _I/model.coef_ / 3600)
-
-
- y_pred = model.predict(_x)
- #print("len, y_pred, _x", len(y_pred), len(self.y_series), len(_x))
+
+ ####y_pred = model.predict(_x)
+ #print("len, y_pred, _x", len(y_pred), len(self.y_series),
+ # len(_x))
#print('predicted response:', y_pred, sep='\n')
- m_sq_error = mean_squared_error(self.y_series, y_pred)
+ ##m_sq_error = mean_squared_error(self.y_series, y_pred)
#print('Mean squared error: {0:.9f}'.format(
# mean_squared_error(y_series, y_pred)))
#print('Coefficient of determination: {0:.9f}'.format(
- # r2_score(y_series, y_pred)))
+ # r2_score(y_series, y_pred)))
-
- self.trigger_series_sequence.emit(self.x_series, self.y_series)
+ self.trigger_series_sequence.emit(self.x_series,
+ self.y_series)
#print("emit")
self.data_series = []
self.time_series = []
@@ -3334,62 +3039,64 @@ class CAQPCTChart(PlotWidget):
else:
self.data_series = []
self.time_series = []
-
-
+
+
self.pvd_previous = _pvd
- #dt = (self.x[-1] - self.x[-2])
+ #dt = (self.x[-1] - self.x[-2])
#print("dt", dt)
#Lowet IPCT before trigger is set to t=0
- x_shifted= list(map(lambda m : (m - self.time_delta), self.x))
+ x_shifted = list(map(lambda m: (m - self.time_delta), self.x))
##self.y = np.where(self.y != self.y, 0, self.y) #test for nan
self.curve.setData(x_shifted, self.y)
self.time_delta = (
- _pvd.ts[0] + _pvd.ts[1]*10**(-9)) - self.time_zero
+ _pvd.ts[0] + _pvd.ts[1]*10**(-9)) - self.time_zero
#x_shifted2= list(map(lambda m : m -self.time_delta-1 , self.x))
#self.curve2.setData(x_shifted2, self.y)
#QApplication.processEvents()
#print(type(x_shifted), type(self.y), type([1.1]), type(1.1))
-
- LOOK_BACK = -800
+
+ LOOK_BACK = -800
if 'ARIDI-PCT2:CURRENT' in self.pv_gateway[_row].pv_name:
LOOK_BACK = -250
- if value > self.y[-2]:
+ if value > self.y[-2]:
if not self.found:
#print(x_shifted[-240:], self.y[-240:])
#self.y = np.where(self.y != self.y, 0, self.y) #test for nan
max_index = self.y[LOOK_BACK:].argmax()
-
+
if max_index == 0:
return
print("max index=", max_index)
-
- #print(x_shifted[-600+max_index:], self.x[-600+max_index:])
- #print(self.y[-600+max_index:-2])
+
+ #print(x_shifted[-600+max_index:], self.x[-600+max_index:])
+ #print(self.y[-600+max_index:-2])
self.found = True
#print("Are Signals blocked??", self.signalsBlocked())
- self.trigger_decay_sequence.emit(np.array(
- x_shifted[LOOK_BACK+max_index+9:-2]), self.y[LOOK_BACK+max_index+9:-2])
+ self.trigger_decay_sequence.emit(
+ np.array(x_shifted[LOOK_BACK+max_index+9:-2]),
+ self.y[LOOK_BACK+max_index+9:-2])
else:
self.found = False
-
-
+
+
@Slot(int, str, int)
def receive_connect_update(self, handle: int, pv_name: str, status: int):
'''Triggered by connect signal'''
print("pv_name==>", pv_name)
-
+
_row = self.pv2item_dict[self.sender()]
- self.pv_gateway[_row].receive_connect_update(handle, pv_name, status,
+ self.pv_gateway[_row].receive_connect_update(handle, pv_name, status,
post_display=False)
#self.pv_gateway.receive_connect_update(handle, pv_name, status)
- _pvd = self.pv_gateway[_row].cafe.getPVCache(self.pv_gateway[_row].handle)
+ _pvd = self.pv_gateway[_row].cafe.getPVCache(
+ self.pv_gateway[_row].handle)
if self.time_zero == 0:
self.time_zero = _pvd.ts[0] + _pvd.ts[1]*10**(-9)
#print(self.time_zero)
@@ -3398,7 +3105,6 @@ class CAQPCTChart(PlotWidget):
#renove highlighting which persists after mouse leaves
def mouseMoveEvent(self, event):
- #event.ignore()
pass
def leaveEvent(self, event):
diff --git a/pvwidgets.py- b/pvwidgets.py-
new file mode 100644
index 0000000..f44f251
--- /dev/null
+++ b/pvwidgets.py-
@@ -0,0 +1,3508 @@
+''' Module with channel access enabled QtWidgets.'''
+__author__ = 'Jan T. M. Chrin'
+
+import re
+import sys
+import time
+
+import collections
+import numpy as np
+from sklearn.linear_model import LinearRegression
+from sklearn.metrics import mean_squared_error, r2_score
+from distutils.version import LooseVersion
+from functools import reduce as func_reduce
+
+from qtpy.QtCore import (qVersion, QEvent, QEventLoop, QObject, QPoint, QSize,
+ Qt, QThread, QTimer, Signal, Slot)
+from qtpy.QtGui import (QBrush, QCloseEvent, QColor, QCursor, QFont, QFontMetricsF,
+ QIcon, QKeySequence)
+from qtpy.QtCore import __version__ as QT_VERSION_STR
+from qtpy.QtWidgets import (QAbstractItemView, QAbstractSpinBox, QAction,
+ QApplication, QBoxLayout, QCheckBox, QComboBox, QDialog,
+ QDockWidget, QDoubleSpinBox, QFrame, QGroupBox,
+ QHeaderView, QHBoxLayout, QLabel, QLineEdit,
+ QListWidget, QMenu, QMessageBox, QPushButton,
+ QSpinBox, QStyle, QStyleOptionSpinBox, QTableWidget,
+ QTableWidgetItem, QVBoxLayout, QWidget)
+
+import pyqtgraph as pg
+from pyqtgraph import PlotWidget
+from caqtwidgets.pvgateway import PVGateway
+
+
+class QTaggedLineEdit(QWidget):
+ def __init__(self, label_text=str(""), value="",
+ position="LEFT", parent=None):
+ super(QTaggedLineEdit, self).__init__(parent)
+ self.parameter = str(value)
+ self.label = QLabel(label_text)
+ self.label.setObjectName("Tagged")
+ self.label.setFixedHeight(24)
+ self.label.setContentsMargins(10, 0, 0, 0)
+ #self.label.setFixedWidth(80)
+ self.line_edit = QLineEdit(self.parameter)
+ self.line_edit.setObjectName("Write")
+ self.line_edit.setFixedHeight(24)
+ font = QFont("sans serif", 16)
+ fm = QFontMetricsF(font)
+ self.line_edit.setMaximumWidth(fm.width(self.parameter)+20)
+ self.label.setBuddy(self.line_edit)
+ layout = QBoxLayout(QBoxLayout.LeftToRight if position == "LEFT" \
+ else QBoxLayout.TopToBottom)
+ layout.addWidget(self.label)
+ layout.addWidget(self.line_edit)
+ layout.addStretch()
+ layout.setSpacing(2)
+ layout.setContentsMargins(0, 0, 0, 0)
+ self.setLayout(layout)
+
+class QHLine(QFrame):
+ def __init__(self):
+ super(QHLine, self).__init__()
+ self.setFrameShape(QFrame.HLine)
+ self.setFrameShadow(QFrame.Sunken)
+
+class QVLine(QFrame):
+ def __init__(self):
+ super(QVLine, self).__init__()
+ self.setFrameShape(QFrame.VLine)
+ self.setFrameShadow(QFrame.Sunken)
+
+class AppQLineEdit(QLineEdit):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ def leaveEvent(self, event):
+ self.clearFocus()
+ del event
+
+class CAQLineEdit(QLineEdit, PVGateway):
+ '''Channel access enabled QLineEdit widget'''
+ trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_int = Signal(int, int, int)
+ trigger_monitor_str = Signal(str, int, int)
+ trigger_monitor = Signal(object, int)
+ trigger_connect = Signal(int, str, int)
+
+ trigger_daq = Signal(object, str, int)
+ trigger_daq_int = Signal(object, str, int)
+ trigger_daq_str = Signal(object, str, int)
+
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode = None,
+ show_units: bool = False, prefix: str = "", suffix: str = "",
+ notify_freq_hz: int = 0, precision: int = 0):
+ #super(CAQLineEdit, self).__init__(parent)
+
+ super().__init__(parent, pv_name, monitor_callback,
+ pv_within_daq_group, color_mode, show_units, prefix,
+ suffix, connect_callback=self.py_connect_callback,
+ notify_freq_hz=notify_freq_hz, precision=precision)
+
+ self.is_initialize_complete()
+ self.configure_widget()
+
+ if not self.pv_within_daq_group:
+ self.monitor_start()
+
+ '''
+ print("fixed width==========>", self.width())
+ print ("size", self.size()) # (100, 30)
+ print ("sizeHint", self.sizeHint()) # (190, 37)
+ print ("min Size", self.minimumSize()) #(0, 0)
+ print ("min SizeHint", self.minimumSizeHint()) # (26, 37)
+ print ("sizePolicy", self.sizePolicy()) #Object
+ '''
+
+ def py_connect_callback(self, handle, pvname, status):
+ '''Callback function to be invoked on change of pv connection status.
+ '''
+ self.trigger_connect.emit(int(handle), str(pvname), int(status))
+
+ @Slot(object, str, int)
+ def receive_daq_update(self, daq_pvd, daq_mode, daq_state):
+ PVGateway.receive_daq_update(self, daq_pvd, daq_mode, daq_state)
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+ PVGateway.receive_monitor_update(self, value, status, alarm_severity)
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ PVGateway.receive_connect_update(self, handle, pv_name, status)
+
+ def configure_widget(self):
+ self.setFocusPolicy(Qt.NoFocus)
+
+ fm = QFontMetricsF(QFont("Sans Serif", 10))
+ qrect = fm.boundingRect(self.suggested_text)
+ _width_scaling_factor = 1.15
+ self.setFixedHeight((fm.lineSpacing()*1.8))
+ self.setFixedWidth(((qrect.width()) * _width_scaling_factor))
+
+ if self.pv_within_daq_group:
+ self.qt_property_initial_values(qt_object_name = self.PV_DAQ_CA)
+ else:
+ self.qt_property_initial_values(qt_object_name = self.PV_READBACK)
+
+ #renove highlighting which persists after mouse leaves
+ def mouseMoveEvent(self, event):
+ #event.ignore()
+ pass
+
+ def leaveEvent(self, event):
+ self.clearFocus()
+ del event
+
+class CAQLabel(QLabel, PVGateway):
+ '''Channel access enabled QLabel widget'''
+ trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_int = Signal(int, int, int)
+ trigger_monitor_str = Signal(str, int, int)
+ trigger_monitor = Signal(object, int)
+
+ trigger_connect = Signal(int, str, int)
+
+ trigger_daq = Signal(object, str, int)
+ trigger_daq_int = Signal(object, str, int)
+ trigger_daq_str = Signal(object, str, int)
+
+
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode = None,
+ show_units: bool = False, prefix: str = "", suffix: str = "",
+ notify_freq_hz: int = 0, precision: int = 0, ):
+ #super(CAQLabel, self).__init__(parent)
+
+ super().__init__(parent, pv_name, monitor_callback,
+ pv_within_daq_group, color_mode, show_units, prefix,
+ suffix, connect_callback=self.py_connect_callback,
+ notify_freq_hz= notify_freq_hz, precision=precision)
+
+ self.is_initialize_complete()
+
+ self.configure_widget()
+
+ if self.pv_within_daq_group is False:
+ self.monitor_start()
+
+ def py_connect_callback(self, handle, pvname, status):
+ '''Callback function to be invoked on change of pv connection status.
+ '''
+ self.trigger_connect.emit(int(handle), str(pvname), int(status))
+
+ @Slot(object, str, int)
+ def receive_daq_update(self, daq_pvd, daq_mode, daq_state):
+ PVGateway.receive_daq_update(self, daq_pvd, daq_mode, daq_state)
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+
+ #print(self, self.pv_name, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
+ # if (time.monotonic() - self.time_monotonic) < 0.9945:
+ # return
+ #self.time_monotonic = time.monotonic()
+ #self.lock.acquire()
+ PVGateway.receive_monitor_update(self, value, status, alarm_severity)
+ #self.lock.release()
+ #QApplication.processEvents()
+
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ PVGateway.receive_connect_update(self, handle, pv_name, status)
+
+
+ def configure_widget(self):
+ self.setFocusPolicy(Qt.NoFocus)
+
+ fm = QFontMetricsF(QFont("Sans Serif", 10))
+ qrect = fm.boundingRect(self.suggested_text)
+ _width_scaling_factor = 1.15
+
+ self.setFixedHeight((fm.lineSpacing()*1.8))
+ self.setFixedWidth((qrect.width() * _width_scaling_factor))
+ #self.setFixedWidth(140)
+
+ if self.pv_within_daq_group:
+ self.qt_property_initial_values(qt_object_name = self.PV_DAQ_CA)
+ else:
+ self.qt_property_initial_values(qt_object_name = self.PV_READBACK)
+
+#For use with CAQMenu
+class QLineEditExtended(QLineEdit):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.parent = parent
+
+ def mousePressEvent(self, event):
+ button = event.button()
+ if button == Qt.RightButton:
+ self.parent.showContextMenu()
+ elif button == Qt.LeftButton:
+ self.parent.mousePressEvent(event)
+
+class CAQMenu(QComboBox, PVGateway):
+ '''Channel access enabled QMenu widget'''
+ trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_int = Signal(int, int, int)
+ trigger_monitor_str = Signal(str, int, int)
+ trigger_monitor = Signal(object, int)
+ trigger_connect = Signal(int, str, int)
+
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode = None,
+ show_units = False, prefix: str = "", suffix: str = ""):
+ #super(CAQMenu, self)
+ super().__init__(parent, pv_name, monitor_callback,
+ pv_within_daq_group, color_mode, show_units, prefix,
+ suffix, connect_callback=self.py_connect_callback)
+
+ self.is_initialize_complete()
+
+ self.configure_widget()
+
+ #After configure:widget
+ self.currentIndexChanged.connect(self.value_change)
+
+ if self.pv_within_daq_group is False:
+ self.monitor_start()
+
+ def py_connect_callback(self, handle, pvname, status):
+ '''Callback function to be invoked on change of pv connection status.
+ '''
+ self.trigger_connect.emit(int(handle), str(pvname), int(status))
+
+
+ def configure_widget(self):
+
+ self.previousIndex = None
+
+ self.setFocusPolicy(Qt.NoFocus)
+ self.setEditable(True)
+ self.setLineEdit(QLineEditExtended(self))
+ self.lineEdit().setReadOnly(True)
+ self.lineEdit().setAlignment(Qt.AlignCenter)
+
+ #self.lineEdit().setMouseTracking(True)
+ #self.setAttribute(Qt.WA_MouseNoMask)
+ #self.lineEdit().setAttribute(Qt.WA_NoMousePropagation)
+ #self.lineEdit().setAttribute(Qt.WA_WindowPropagation)
+
+ enumStringList = self.cafe.getEnumStrings(self.handle)
+
+ self.addItems(enumStringList)
+ for i in range(0, self.count()):
+ self.setItemData(i, Qt.AlignCenter, Qt.TextAlignmentRole);
+
+ '''
+ self.ensurePolished()
+ print(dir(self.style().property("font")))
+ f=self.style().property("font")
+ self.style().unpolish(self);
+ self.style().polish(self);
+ self.update()
+ '''
+
+ fm = QFontMetricsF(QFont("Sans Serif", 12))
+ qrect = fm.boundingRect(self.suggested_text)
+
+ _width_scaling_factor = 1.1
+
+ self.setFixedHeight(fm.lineSpacing()*1.8)
+ self.setFixedWidth((qrect.width()+40) * _width_scaling_factor)
+
+ self.qt_property_initial_values(qt_object_name=self.PV_CONTROLLER)
+
+ def post_display_value(self, value):
+ '''Convert value to index'''
+ if "setCurrentIndex" in dir(self):
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+
+ if isinstance(value, str):
+ self.setCurrentIndex(self.cafe.getEnumFromString(self.handle,
+ value))
+
+ elif isinstance(value, int):
+ self.setCurrentIndex(value)
+ #Should not happen
+ elif isinstance(value, float):
+ self.setCurrentIndex(int(value))
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(False)
+
+
+ #self.previousIndex = self.currentIndex()
+ return
+ else:
+ print(("ERROR: overloaded post_display_value: 'setCurrentIndex' "
+ "method does not exist!"))
+
+
+ def value_change(self, indx):
+
+ status = self.cafe.set(self.handle, indx)
+
+ if status != self.cyca.ICAFE_NORMAL:
+ #self.showSetErrorMsg(status)
+
+ value = self.cafe.getCache(self.handle, 'int')
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+
+ if value is not None:
+ self.setCurrentIndex(value)
+ else:
+ if self.previousIndex is not None:
+ self.setCurrentIndex(self.previousIndex)
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(False)
+
+ self.pv_message_in_a_box.setText(
+ ("CAQMenu set operation reports error:\n{0}"
+ .format(self.cafe.getStatusCodeAsString(status))))
+ self.pv_message_in_a_box.exec()
+
+
+ def mousePressEvent(self, event):
+
+ button = event.button()
+ if button == Qt.RightButton:
+ PVGateway.mousePressEvent(self, event)
+
+ elif self.pv_info is not None:
+ if self.pv_info.accessWrite == 0:
+ event.ignore()
+ return
+ else:
+ QComboBox.mousePressEvent(self, event)
+
+ self.previousIndex = self.currentIndex()
+
+ def enterEvent(self, event):
+ if self.pv_info is not None:
+ if self.pv_info.accessWrite == 0:
+ for i in range(0, self.count()):
+ self.setItemIcon(i, QIcon(":/forbidden.png"))
+ self.setStyleSheet(("QComboBox {background: transparent}" +
+ "QComboBox::drop-down {image: url(:/forbidden.png)}"))
+
+ def leaveEvent(self, event):
+ if self.pv_info is not None:
+ if self.pv_info.accessWrite == 0:
+ for i in range(0, self.count()):
+ self.setItemIcon(i, QIcon())
+ self.setStyleSheet("QComboBox::drop-down {background: transparent}")
+
+
+ #The widget should not gain focus by using the mouse wheel.
+ #This is accomplished by setting the focus policy to Qt.StrongFocus.
+ #The widget should only accept wheel events if it already has the focus.
+ #This is accomplished by reimplementing QWidget.wheelEvent within a QSpinBox subclass:
+ def wheelEvent(self, event):
+ if self.hasFocus() is False:
+ event.ignore()
+ else:
+ QComboBox.wheelEvent(self, event)
+
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+ '''Triggered by monitor signal'''
+
+ PVGateway.receive_monitor_update(self, value, status, alarm_severity)
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ PVGateway.receive_connect_update(self, handle, pv_name, status)
+
+
+class CAQMessageButton(QPushButton, PVGateway):
+ trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_int = Signal(int, int, int)
+ trigger_monitor_str = Signal(str, int, int)
+ trigger_monitor = Signal(object, int)
+ trigger_connect = Signal(int, str, int)
+
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ notify_freq_hz: int = 0,
+ pv_within_daq_group: bool = False, color_mode = None,
+ show_units = False, msg_label: str = "",
+ msg_press_value = None, msg_release_value =None,
+ start_monitor=False):
+ super().__init__(parent=parent, pv_name=pv_name,
+ monitor_callback=monitor_callback,
+ notify_freq_hz=\
+ notify_freq_hz,
+ pv_within_daq_group=pv_within_daq_group,
+ color_mode=color_mode, show_units=show_units,
+ msg_label=msg_label,
+ connect_callback=self.py_connect_callback)
+
+
+ self.msg_press_value = msg_press_value
+ self.msg_release_value = msg_release_value
+
+ if self.msg_press_value is not None:
+ self.pressed.connect(self.act_on_pressed)
+ if self.msg_release_value is not None:
+ self.released.connect(self.act_on_released)
+
+ self.msg_label = msg_label
+ self.suggested_text = self.msg_label
+ _suggested_text_length = len(self.suggested_text)+3
+ self.suggested_text = self.suggested_text.rjust(
+ _suggested_text_length,"^")
+
+ self.configure_widget()
+
+ self.msg_press_status = self.cyca.ICAFE_NORMAL
+ self.msg_release_status = self.cyca.ICAFE_NORMAL
+ self.msg_report_status = "PV={0}\n".format(self.pv_name)
+ self.msg_has_error = False
+
+ if not self.pv_within_daq_group and start_monitor:
+ self.monitor_start()
+
+ def py_connect_callback(self, handle, pvname, status):
+ '''Callback function to be invoked on change of pv connection status.
+ '''
+ self.trigger_connect.emit(int(handle), str(pvname), int(status))
+
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+ #print(self, self.pv_name, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
+ PVGateway.receive_monitor_update(self, value, status, alarm_severity)
+
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ PVGateway.receive_connect_update(self, handle, pv_name, status)
+
+ def configure_widget(self):
+ self.setFocusPolicy(Qt.StrongFocus)
+ #self.setAutoDefault(True)
+ self.setCheckable(True) #Recognizes press and release states
+ #self.setChecked(True)
+ #self.setFlat(True)
+
+ fm = QFontMetricsF(QFont("Sans Serif", 12))
+ qrect = fm.boundingRect(self.suggested_text)
+
+ _width_scaling_factor = 1.0
+
+ self.setText(self.msg_label)
+ self.setFixedHeight((fm.lineSpacing()*2.0))
+ self.setFixedWidth((qrect.width() * _width_scaling_factor))
+
+ self.qt_property_initial_values(qt_object_name = self.PV_CONTROLLER)
+
+
+
+ def enterEvent(self, event):
+ if self.pv_info is not None:
+ if self.pv_info.accessWrite == 0:
+ self.setProperty("readOnly", True)
+ self.qt_style_polish()
+
+ def leaveEvent(self, event):
+ #if self.pv_info.accessWrite == 0:
+ if self.property("readOnly"):
+ self.setProperty(self.qt_dynamic_property_get(), True)
+ self.qt_style_polish()
+ '''
+ def enterEvent(self, event):
+ if self.pv_info.accessWrite == 0:
+ self.setEnabled(False)
+
+ def leaveEvent(self, event):
+ if not self.isEnabled():
+ self.setEnabled(True)
+ '''
+
+ def mouseReleaseEvent(self, event):
+ #print("LOCAL mouseRelease = = > This Event is required so that clicked is activated!!!!!!")
+ if self.msg_release_value is not None:
+ time.sleep(0.1)
+ QPushButton.mouseReleaseEvent(self, event)
+
+ def mousePressEvent(self, event):
+ if self.pv_info is not None:
+ if self.pv_info.accessWrite == 1:
+ QPushButton.mousePressEvent(self, event)
+ if event.button() == Qt.RightButton:
+ PVGateway.mousePressEvent(self, event)
+
+ def act_on_pressed(self):
+ #print("caQPushButton press ValueChanged--> ")
+ if self.msg_press_value is not None:
+ self.msg_press_status = self.cafe.set(self.handle, self.msg_press_value)
+ if self.msg_press_status != self.cyca.ICAFE_NORMAL:
+ self.msg_report_status += ("Error in set operation (at press button):\n{0}\n"
+ .format(self.cafe.getStatusCodeAsString(self.msg_press_status)))
+ self.msg_has_error = True
+ qm = QMessageBox()
+ qm.setText(self.msg_report_status)
+ qm.exec()
+ QApplication.processEvents()
+
+ def act_on_released(self):
+ #print("caQPushButton release ValueChanged--> ")
+ if self.msg_release_value is not None:
+ self.msg_release_status = self.cafe.set(self.handle, self.msg_release_value)
+ if self.msg_release_status != self.cyca.ICAFE_NORMAL:
+ self.msg_report_status += ("Error in set operation (at release button):\n{0}\n"
+ .format(self.cafe.getStatusCodeAsString(self.msg_release_status)))
+ self.msg_has_error = True
+
+ if self.msg_has_error:
+ self.msg_has_error = False
+ self.pv_message_in_a_box.setText(self.msg_report_status)
+ self.pv_message_in_a_box.exec()
+ self.msg_report_status = "PV={0}\n".format(self.pv_name)
+ qm = QMessageBox()
+ qm.setText(self.msg_report_status)
+ qm.exec()
+ QApplication.processEvents()
+
+class CAQTextEntry(QLineEdit, PVGateway):
+ '''Channel access enabled QTextEntry widget'''
+ trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_int = Signal(int, int, int)
+ trigger_monitor_str = Signal(str, int, int)
+ trigger_monitor = Signal(object, int)
+ trigger_connect = Signal(int, str, int)
+
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode = None,
+ show_units = False, prefix: str = "", suffix: str = ""):
+ super().__init__(parent, pv_name, monitor_callback,
+ pv_within_daq_group, color_mode, show_units, prefix,
+ suffix, connect_callback=self.py_connect_callback)
+
+ self.is_initialize_complete() #waits a fraction of a second
+
+ self.currentText =""
+ self.returnPressed.connect(self.valuechange)
+ self.configure_widget()
+ if self.pv_within_daq_group is False:
+ self.monitor_start()
+
+ def py_connect_callback(self, handle, pvname, status):
+ '''Callback function to be invoked on change of pv connection status.
+ '''
+ self.trigger_connect.emit(int(handle), str(pvname), int(status))
+
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+ #print(self, self.pv_name, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
+ PVGateway.receive_monitor_update(self, value, status, alarm_severity)
+
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ PVGateway.receive_connect_update(self, handle, pv_name, status)
+
+ def configure_widget(self):
+ self.setFocusPolicy(Qt.StrongFocus)
+
+ fm = QFontMetricsF(QFont("Sans Serif", 12))
+ qrect = fm.boundingRect(self.suggested_text)
+
+ _width_scaling_factor = 1.15
+
+ self.setFixedHeight((fm.lineSpacing()*1.8))
+ self.setFixedWidth(((qrect.width()+10) * _width_scaling_factor))
+
+ self.qt_property_initial_values(qt_object_name = self.PV_CONTROLLER)
+
+ def valuechange(self):
+ print(self.handle, self.text())
+ status = self.cafe.set(self.handle, self.text())
+ if status != self.cyca.ICAFE_NORMAL:
+ #elf.showSetErrorMsg(status)
+ if self.cafe.getNoMonitors(self.handle) == 0:
+ val = self.cafe.get(self.handle, 'native')
+ else:
+ val = self.cafe.getCache(self.handle, 'native')
+ #print("val", val)
+ if val is not None:
+ if isinstance(val, str):
+ strText = val
+ else:
+ valStr = ("{: .%sf}" % self.precision)
+ strText = valStr.format(round(val, self.precision)) ### + " " + self.units
+ print(strText, " precision ", self.precision)
+ self.setText(strText)
+ else:
+ #Do this for TextInfo cache
+ if self.cafe.getNoMonitors(self.handle) == 0:
+ val = self.cafe.get(self.handle, 'native')
+
+ def setText(self, value):
+
+ QLineEdit.setText(self, value)
+
+ #QLineEdit.setFixedWidth(self, QLineEdit.width(self)+10)
+ self.currentText = self.text()
+
+ #status = self.cafe.set(self.handle, value)
+ #if status ! = self.cyca.ICAFE_NORMAL:
+ #self.showSetErrorMsg(status)
+
+ def enterEvent(self, event):
+ if self.pv_info is not None:
+ if self.pv_info.accessWrite == 0:
+ self.setProperty("readOnly", True)
+ self.qt_style_polish()
+ self.setReadOnly(True)
+ self.setFocusPolicy(Qt.StrongFocus)
+
+ def leaveEvent(self, event):
+
+ if self.isReadOnly():
+ self.setReadOnly(False)
+ self.setProperty(self.qt_dynamic_property_get(), True)
+ self.qt_style_polish()
+
+ if self.text() != self.currentText:
+ QLineEdit.setText(self, self.currentText)
+
+ self.setCursorPosition(100)
+ self.clearFocus()
+ self.setFocusPolicy(Qt.NoFocus)
+ del event
+
+ #def mouseMoveEvent(self, event):
+ # print("mouseMoveEvent", event.type())
+
+ def mousePressEvent(self, event):
+ if event.button() == Qt.RightButton:
+ PVGateway.mousePressEvent(self, event)
+ self.clearFocus()
+ return
+ local_event_position = QPoint(event.x(), event.y())
+ local_cursor_position = self.cursorPositionAt(local_event_position)
+ self.setCursorPosition(local_cursor_position)
+
+
+
+
+
+class CAQSpinBox(QSpinBox, PVGateway):
+ '''Channel access enabled QTextEntry widget'''
+ trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_int = Signal(int, int, int)
+ trigger_monitor_str = Signal(str, int, int)
+ trigger_monitor = Signal(object, int)
+ trigger_connect = Signal(int, str, int)
+
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode = None,
+ show_units = False, prefix: str = "", suffix: str = ""):
+ super().__init__(parent, pv_name, monitor_callback,
+ pv_within_daq_group, color_mode, show_units, prefix,
+ suffix, connect_callback=self.py_connect_callback)
+ #super().__init__(parent=parent, pv_name=pv_name, monitor_callback=monitor_callback,
+ # pv_within_daq_group= pv_within_daq_group, color_mode=color_mode, show_units=show_units, prefix=prefix,
+ # suffix=suffix, connect_callback=self.py_connect_callback)
+
+
+ self.is_initialize_complete()
+
+ self.valueChanged.connect(self.value_change)
+ self.configure_widget()
+ if not self.pv_within_daq_group:
+ self.monitor_start()
+
+
+ def py_connect_callback(self, handle, pvname, status):
+ '''Callback function to be invoked on change of pv connection status.
+ '''
+ #print(" py_connect_callback::::::::::::::::::::::::::::::::::::::::::::::::::::::::::..: START ")
+ self.trigger_connect.emit(int(handle), str(pvname), int(status))
+ #print(" py_connect_callback:: END ")
+
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+ PVGateway.receive_monitor_update(self, value, status, alarm_severity)
+
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ PVGateway.receive_connect_update(self, handle, pv_name, status)
+
+ def configure_widget(self):
+ self.previousValue = None
+ self.currentValue = None
+ self.setFocusPolicy(Qt.StrongFocus)
+ self.setButtonSymbols(QAbstractSpinBox.UpDownArrows)
+ self.setAccelerated(False)
+ self.setLineEdit(QLineEditExtended(self))
+ self.lineEdit().setEnabled(True)
+ self.lineEdit().setReadOnly(False)
+ self.lineEdit().setAlignment(Qt.AlignLeft)
+ self.lineEdit().setFont(QFont("Sans Serif", 16))
+
+ fm = QFontMetricsF(QFont("Sans Serif", 12))
+
+ _suggested_text = self.max_control_abs_str
+ _added_text = ""
+
+ if self.show_units:
+ _added_text += " " + self.units
+ _suggested_text += self.units
+ if len(self.suffix) > 0:
+ _added_text += " " + self.suffix
+ _suggested_text += self.suffix
+
+ self.setSuffix(_added_text)
+
+ qrect = fm.boundingRect(_suggested_text)
+ _width_scaling_factor = 1.0
+
+ self.setFixedHeight((fm.lineSpacing()*1.8))
+ self.setFixedWidth(((qrect.width()) * _width_scaling_factor))
+
+ self.qt_property_initial_values(qt_object_name = self.PV_CONTROLLER)
+
+ if self.pv_ctrl is not None:
+ self.setRange(int(self.pv_ctrl.lowerControlLimit),
+ int(self.pv_ctrl.upperControlLimit))
+
+
+ def post_display_value(self, value):
+ '''Convert value to index'''
+ #print ("MON VALUE IN QSPINBOX ", value, " //////////// ", int(value))
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+ self.setValue(int(round(value)))
+ self.blockSignals(False)
+ else:
+ self.setValue(int(round(value)))
+
+
+ def mousePressEvent(self, event):
+ _opt = QStyleOptionSpinBox()
+ self.initStyleOption(_opt)
+ _rect_up = self.style().subControlRect(QStyle.CC_SpinBox, _opt,
+ QStyle.SC_SpinBoxUp, self)
+ _rect_down = self.style().subControlRect(QStyle.CC_SpinBox, _opt,
+ QStyle.SC_SpinBoxDown, self)
+
+ #print(_rect_up.x(), _rect_down.x(), event.x(), event.y(), event.localPos(), event.pos())
+
+ self.previousValue = self.value()
+
+ if event.button() == Qt.LeftButton:
+ if _rect_up.contains(event.pos(), proper=True) or \
+ _rect_down.contains(event.pos(), proper=True):
+
+ if not self.cafe.isConnected(self.handle):
+ self.pv_message_in_a_box.setText(("Spinbox change value "
+ "events currently suspended\n"
+ "as channel {0} is disconnected.".format(self.pv_name)))
+ self.pv_message_in_a_box.exec()
+
+ return
+
+ QSpinBox.mousePressEvent(self, event)
+ #Clear Focus: only one step per mouse click.
+ self.clearFocus()
+
+ local_event_position = QPoint(event.x(), event.y())
+ local_cursor_position = self.lineEdit().cursorPositionAt(local_event_position)
+
+ #print(local_event_position, " QSPINBOX VALUE POS ", local_cursor_position)
+ self.lineEdit().setCursorPosition(local_cursor_position)
+
+
+ PVGateway.mousePressEvent(self, event)
+
+ #Clear Focus: only one step per mouse click.
+ #self.clearFocus()
+
+ def setValue(self, intVal):
+ #print( QSpinBox.value(self), intVal)
+ #print( "setValue called//1//", intVal)
+ QSpinBox.setValue(self, intVal)
+ self.currentValue = self.value()
+ #print( "setValue called//2//", intVal)
+
+
+ def value_change(self, intVal):
+ #print("valuechange called", intVal, QSpinBox.value(self))
+ status = self.cafe.set(self.handle, intVal)
+
+ if status != self.cyca.ICAFE_NORMAL:
+
+ #print("previous current", self.previousValue, self.currentValue)
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+
+ if self.previousValue is not None:
+ self.setValue(self.previousValue)
+ else:
+ _value = self.cafe.getCache(self.handle, 'int')
+
+ if _value is not None:
+ self.setValue(_value)
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(False)
+
+ self.pv_message_in_a_box.setText(
+ ("Spinbox set operation reports error:\n{0}"
+ .format(self.cafe.getStatusCodeAsString(status))))
+ self.pv_message_in_a_box.exec()
+
+ else:
+ if self.previousValue is not None:
+ self.setValue(self.previousValue)
+ else:
+ _value = self.cafe.getCache(self.handle, 'int')
+
+ if _value is not None:
+ self.setValue(_value)
+
+ self.parent.statusbar.showMessage(
+ (self.widget_class + " " +
+ self.cafe.getStatusCodeAsString(status)))
+
+
+
+ def enterEvent(self, event):
+ if self.pv_info is not None:
+ if self.pv_info.accessWrite == 0:
+ self.setProperty("readOnly", True)
+ self.qt_style_polish()
+ self.setReadOnly(True)
+ self.setFocusPolicy(Qt.StrongFocus)
+
+ def leaveEvent(self, event):
+ #if self.value() != self.currentValue:
+ # self.setValue(self.currentValue)
+ if self.isReadOnly():
+ self.setReadOnly(False)
+ self.setProperty(self.qt_dynamic_property_get(), True)
+ self.qt_style_polish()
+
+ self.clearFocus()
+ self.setFocusPolicy(Qt.NoFocus)
+ del event
+
+
+ def keyPressEvent(self, event):
+ #print("key press event", event.type(), hex(event.key()))
+ if event.key() in (Qt.Key_Return, Qt.Key_Enter):
+ QSpinBox.keyPressEvent(self, event)
+ self.clearFocus()
+ elif event.key() in (Qt.Key_Up, Qt.Key_Down):
+ QSpinBox.keyPressEvent(self, event)
+ else:
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+ QSpinBox.keyPressEvent(self, event)
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(False)
+
+
+ # The spin box should not gain focus by using the mouse wheel.
+ # This is accomplished by setting the focus policy to Qt.StrongFocus.
+ # The spin box should only accept wheel events if it already has the focus.
+ # This is accomplished by reimplementing QWidget.wheelEvent within a QSpinBox subclass:
+ def wheelEvent(self, event):
+ #print("wheelEvent", self.hasFocus())
+ if self.hasFocus() is False:
+ event.ignore()
+ else:
+ QSpinBox.wheelEvent(self, event)
+
+ # def enterEvent(self,event):
+ # print("EnterEvent set focusPolicy to Strong")
+ # self.setFocusPolicy(Qt.StrongFocus)
+
+
+
+class CAQDoubleSpinBox(QDoubleSpinBox, PVGateway):
+ '''Channel access enabled QDoubleSpinBox widget'''
+ trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_int = Signal(int, int, int)
+ trigger_monitor_str = Signal(str, int, int)
+ trigger_monitor = Signal(object, int)
+ trigger_connect = Signal(int, str, int)
+
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode = None,
+ show_units: bool = False, prefix: str = "", suffix: str = ""):
+ super().__init__(parent=parent, pv_name=pv_name,
+ monitor_callback=monitor_callback,
+ pv_within_daq_group= pv_within_daq_group,
+ color_mode=color_mode, show_units=show_units,
+ prefix=prefix, suffix=suffix,
+ connect_callback=self.py_connect_callback)
+
+ self.is_initialize_complete()
+ self.valueChanged.connect(self.valuechange)
+ self.configure_widget()
+
+ if self.pv_within_daq_group is False:
+ self.monitor_start()
+
+
+ def py_connect_callback(self, handle, pvname, status):
+ '''Callback function to be invoked on change of pv connection status.
+ '''
+ #print(" py_connect_callback::::::::::::::::::::::::::::::::::::::::::::::::::::::::::..: START ")
+ self.trigger_connect.emit(int(handle), str(pvname), int(status))
+ #print(" py_connect_callback:: END ")
+
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+ PVGateway.receive_monitor_update(self, value, status, alarm_severity)
+
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ PVGateway.receive_connect_update(self, handle, pv_name, status)
+ #self.configure_widget()
+ #self.setSingleStep(0.1)
+
+
+ def configure_widget(self):
+ self.previousValue = None
+ self.currentValue = None
+ self.setFocusPolicy(Qt.StrongFocus)
+ self.setButtonSymbols(QAbstractSpinBox.UpDownArrows)
+ self.setAccelerated(False)
+ self.setLineEdit(QLineEditExtended(self))
+ #self.lineEdit().setObjectName("Controller")
+ #self.lineEdit().setProperty("notActOnBeam", True)
+ #self.lineEdit().setEnabled(False)
+ self.lineEdit().setReadOnly(False)
+ self.lineEdit().setAlignment(Qt.AlignRight)
+ self.lineEdit().setFont(QFont("Sans Serif", 12))
+
+ _stepsize = 10**(self.precision * -1)
+ self.setSingleStep(_stepsize)
+ self.setDecimals(self.precision)
+
+ fm = QFontMetricsF(QFont("Sans Serif", 12))
+
+ _suggested_text = self.suggested_text
+ _added_text = ""
+
+ if self.show_units:
+ _added_text += " " + self.units
+ _suggested_text += self.units
+ if len(self.suffix) > 0:
+ _added_text += " " + self.suffix
+ _suggested_text += self.suffix
+
+ self.setSuffix(_added_text)
+
+ qrect = fm.boundingRect(_suggested_text)
+
+ _width_scaling_factor = 1.15
+
+ self.setFixedHeight((fm.lineSpacing()*1.8))
+ self.setFixedWidth(((qrect.width()) * _width_scaling_factor))
+
+ self.qt_property_initial_values(qt_object_name = self.PV_CONTROLLER)
+
+ if self.pv_ctrl is not None:
+ self.setRange(int(self.pv_ctrl.lowerControlLimit),
+ int(self.pv_ctrl.upperControlLimit))
+
+
+ #if self.cafe.isConnected(self.handle):
+ # self.setButtonSymbols(QAbstractSpinBox.UpDownArrows)
+ #else:
+ # self.setButtonSymbols(QAbstractSpinBox.NoButtons)
+
+ def post_display_value(self, value):
+ '''set value from monitor'''
+ #print("value: : ", value)
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+
+ self.setValue(value)
+ self.blockSignals(False)
+ else:
+ self.setValue(value)
+
+ def mousePressEvent(self, event):
+
+ _opt = QStyleOptionSpinBox()
+ self.initStyleOption(_opt)
+ _rect_up = self.style().subControlRect(QStyle.CC_SpinBox, _opt,
+ QStyle.SC_SpinBoxUp, self)
+ _rect_down = self.style().subControlRect(QStyle.CC_SpinBox, _opt,
+ QStyle.SC_SpinBoxDown, self)
+ self.previousValue = self.value()
+
+ if event.button() == Qt.LeftButton:
+ if _rect_up.contains(event.pos(), proper=False) or \
+ _rect_down.contains(event.pos(), proper=False):
+
+ if not self.cafe.isConnected(self.handle):
+ self.pv_message_in_a_box.setText(("Spinbox change value "
+ "events currently suspended\n"
+ "as channel {0} is disconnected.".format(self.pv_name)))
+ self.pv_message_in_a_box.exec()
+ return
+
+ QDoubleSpinBox.mousePressEvent(self, event)
+
+ local_event_position = QPoint(event.x(), event.y())
+ local_cursor_position = self.lineEdit().cursorPositionAt(
+ local_event_position)
+ #print(local_event_position, " POS ", local_cursor_position)
+ self.lineEdit().setCursorPosition(local_cursor_position)
+
+
+ PVGateway.mousePressEvent(self, event)
+
+ #Clear Focus: only one step per mouse click.
+ #Not wanted for floats
+ #self.clearFocus()
+
+ def mouseReleaseEvent(self, event):
+ self.clearFocus()
+
+ def setValue(self, value):
+ #print("setValue called (B)", value, self.value())
+ self.currentValue = self.value()
+ QDoubleSpinBox.setValue(self, value)
+ #time.sleep(0.01)
+ #print("setValue called (A)", value, self.value())
+
+
+ def valuechange(self, fval):
+ #print("valuechange called, fval, value(), previous", fval, self.value(), self.previousValue)
+ status = self.cafe.set(self.handle, fval)
+
+
+ if status != self.cyca.ICAFE_NORMAL:
+
+ #print("previous current", self.previousValue, self.currentValue)
+
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+ if self.previousValue is not None:
+ self.setValue(self.previousValue)
+ else:
+ _value = self.cafe.getCache(self.handle, 'float')
+
+ if _value is not None:
+ self.setValue(_value)
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(False)
+
+ self.pv_message_in_a_box.setText(
+ ("Spinbox set operation reports error:\n{0}"
+ .format(self.cafe.getStatusCodeAsString(status))))
+ self.pv_message_in_a_box.exec()
+
+ else:
+ if self.previousValue is not None:
+ self.setValue(self.previousValue)
+ else:
+ _value = self.cafe.getCache(self.handle, 'float')
+
+ if _value is not None:
+ self.setValue(_value)
+
+ self.parent.statusbar.showMessage(
+ (self.widget_class + " " +
+ self.cafe.getStatusCodeAsString(status)))
+
+
+ def enterEvent(self, event):
+ self.setFocusPolicy(Qt.StrongFocus)
+ #print("eventtype", event.type())
+ if self.pv_info is not None:
+ if self.pv_info.accessWrite == 0:
+ self.setProperty("readOnly", True)
+ self.qt_style_polish()
+ self.setReadOnly(True)
+
+ def leaveEvent(self, event):
+ #if self.value() != self.currentValue:
+ # self.setValue(self.currentValue)
+ if self.isReadOnly():
+ self.setReadOnly(False)
+ self.setProperty(self.qt_dynamic_property_get(), True)
+ self.qt_style_polish()
+
+ self.clearFocus()
+ self.setFocusPolicy(Qt.NoFocus)
+ del event
+
+ def keyPressEvent(self, event):
+ #print("key press event", event.type(), hex(event.key()))
+ if event.key() in (Qt.Key_Return, Qt.Key_Enter):
+ QDoubleSpinBox.keyPressEvent(self, event)
+ self.clearFocus()
+ elif event.key() in (Qt.Key_Up, Qt.Key_Down):
+ QDoubleSpinBox.keyPressEvent(self, event)
+ else:
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+ QDoubleSpinBox.keyPressEvent(self, event)
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(False)
+
+
+
+ #def keyReleaseEvent(self, event):
+ # print("key release")
+ # QDoubleSpinBox.keyReleaseEvent(self, event)
+
+
+ # The spin box should not gain focus by using the mouse wheel.
+ # This is accomplished by setting the focus policy to Qt.StrongFocus.
+ # The spin box should only accept wheel events if it already has the focus.
+ # This is accomplished by reimplementing QWidget.wheelEvent within a QSpinBox subclass:
+ def wheelEvent(self, event):
+ #print("wheelEvent", self.hasFocus())
+ if self.hasFocus() is False:
+ event.ignore()
+ else:
+ QDoubleSpinBox.wheelEvent(self, event)
+
+
+class reconnectQPushButton(QPushButton, QThread):
+ def __init__(self, parent=None):
+ super().__init__()
+ self.parent = parent
+ self.clicked.connect(self.onClicked)
+ self.isdirty = False
+ self._handles_to_reconnect = []
+ self.reconnectThread = None
+
+ #def __del__(self):
+ # print("D Called")
+ # self.reconnectThread.wait()
+
+ def onClicked(self, event):
+
+ self._handles_to_reconnect = []
+
+ for i in range(0, len(self.parent.pv_gateway)):
+ if self.parent.item(i, self.parent.no_columns-1).checkState() == Qt.Checked:
+ #print("handle", self.parent.pv_gateway[i].handle)
+ #self.parent.cafe.monitorStop(self.parent.pv_gateway[1].handle)
+ #self.parent.cafe.reconnect([self.parent.pv_gateway[1].handle])
+ self._handles_to_reconnect.append(self.parent.pv_gateway[i].handle)
+
+ #print("isConnected ", self.parent.cafe.isConnected(self.parent.pv_gateway[i].handle))
+
+ #self.reconnectThread = ReconnectThread(self.parent, self._handles_to_reconnect ) # QThread(self).create(self.reconnect)
+ #self.reconnectThread.start()
+ self.reconnect() #,self._handles_to_reconnect)
+ QApplication.processEvents()
+ #self.reconnectThread.wait()
+
+ def reconnect(self):
+ QApplication.processEvents()
+ #self.parent.cafe.printHandles()
+ #print(self._handles_to_reconnect)
+ self.isdirty = True
+ if len(self._handles_to_reconnect) > 0:
+ status=self.parent.cafe.reconnect(self._handles_to_reconnect)
+ self.isdirty = False
+ #Uncheck reconnected channels
+ for i in range(0, len(self.parent.pv_gateway)):
+ if self.parent.item(i, self.parent.no_columns-1).checkState() == Qt.Checked:
+ if self.parent.cafe.isConnected(self.parent.pv_gateway[i].handle):
+ self.parent.item(i, self.parent.no_columns-1).setCheckState(False)
+
+ #Uncheck global reconnect check box
+ self.parent.cb_item_all.setCheckState(Qt.Unchecked)
+
+
+'''
+class ReconnectThread(QThread):
+
+ def __init__(self, parent, handles):
+ QThread.__init__(self)
+ self.parent=parent
+ self._handles = handles
+ print("Initialized")
+
+ def __del__(self):
+ self.wait()
+
+ def run(self):
+ print("reconnect")
+ self.isdirty = True
+ if len(self._handles) > 0:
+ #status=self.parent.cafe.reconnect(self._handles)
+ print("status")
+ self.isdirty = False
+ print("still running")
+'''
+
+class CAQTableWidget(QTableWidget):
+ '''Channel access enabled QTableWidget widget'''
+ #trigger_monitor_float = Signal(float, int, int)
+ #trigger_monitor_int = Signal(int, int, int)
+ #trigger_monitor_str = Signal(str, int, int)
+ #trigger_connect = Signal(int, str, int)
+
+ def hasNewData(self, _row, pv_data):
+
+ if self.pv_gateway[_row].pvd_previous is None:
+ return True
+
+ newDataFlag = False
+
+ if self.pv_gateway[_row].pvd_previous.ts[1] != pv_data.ts[1]:
+ newDataFlag = True
+ elif self.pv_gateway[_row].pvd_previous.ts[0] != pv_data.ts[0]:
+ newDataFlag = True
+ # Catch disconnect events(!!) and set newDataFlag only
+ elif self.pv_gateway[_row].pvd_previous.status != pv_data.status:
+ newDataFlag = True
+ return newDataFlag
+
+
+
+ def paint_rows(self, row_range: list = [], reset=False, last_row=[" ", " "],
+ columns=[0]):
+
+ _qcolor_last_line = QColor("#d1e8e9")
+ self.font_pts11 = QTableWidgetItem().font()
+ self.font_pts11.setPixelSize(11)
+ if reset:
+ _qcolor = self.item(0, self.columnCount()-1).background()
+ _start = 0
+ _end = self.rowCount()-1
+ else:
+ _qcolor = _qcolor_last_line
+ _start = row_range[0]
+ _end = row_range[1]
+
+ for _row in range(_start, _end):
+ _cell = QTableWidgetItem("{0}".format(_row+1))
+ if not reset:
+ _cell.setFont(self.font_pts11)
+ _cell.setBackground(_qcolor)
+
+ if 1 in columns:
+ self.item(_row, 0).setBackground(_qcolor)
+ self.item(_row, 0).setFont(self.font_pts11)
+ if 0 in columns:
+ self.setVerticalHeaderItem(_row, _cell)
+
+ #last row
+
+ if reset and 0 in columns:
+ _cell = QTableWidgetItem("{0}".format(last_row[0]))
+ _cell.setFont(self.font_pts11)
+ self.setVerticalHeaderItem(self.rowCount()-1, _cell)
+
+ self.item(self.rowCount()-1, 0).setTextAlignment(Qt.AlignCenter)
+ self.item(self.rowCount()-1, 0).setText(str(last_row[1]))
+ self.item(self.rowCount()-1, 0).setBackground(_qcolor)
+ self.item(self.rowCount()-1, 0).setFont(self.font_pts11)
+ elif last_row[0] != " ":
+ _cell = QTableWidgetItem("{0}".format(last_row[0]))
+ _cell.setBackground(_qcolor_last_line)
+ _cell.setFont(self.font_pts11)
+ self.setVerticalHeaderItem(self.rowCount()-1, _cell)
+
+ if columns:
+ self.item(self.rowCount()-1, 0).setTextAlignment(Qt.AlignCenter)
+ self.item(self.rowCount()-1, 0).setText(str(last_row[1]))
+ self.item(self.rowCount()-1, 0).setBackground(_qcolor_last_line)
+ self.item(self.rowCount()-1, 0).setFont(self.font_pts11)
+
+
+
+
+
+ def widget_update(self):
+
+ for _row, pvgate in enumerate(self.pv_gateway):
+ #for _row in range(0, len(self.pv_gateway)):
+ if not pvgate.notify_unison:
+ continue
+ _handle = pvgate.handle
+ _pvd = pvgate.cafe.getPVCache(_handle)
+
+ #Only if unison flag is set else return
+
+ #Does not cater for reconnections
+ #print(_row, self.pv_gateway[_row].pv_name, _pvd.status)
+ #if not self.hasNewData(_row, _pvd) and _pvd.status == \
+ # self.cyca.ICAFE_NORMAL:
+ #print(_row, " has no new data")
+ #continue
+
+ if _pvd.status in (self.cyca.ICAFE_CS_NEVER_CONN,
+ self.cyca.ICAFE_CA_OP_CONN_DOWN):
+ pvgate.pvd_previous = _pvd
+ continue
+
+ pvgate.pvd_previous = _pvd
+
+ #if timestamps the same - then skip
+ _value = _pvd.value[0]
+ _value = pvgate.format_display_value(_value)
+
+ qtwi = QTableWidgetItem(str(_value)+ " ")
+ f = qtwi.font()
+ f.setPointSize(8)
+ qtwi.setFont(f)
+
+
+ self.setItem(_row, self.no_columns-3,
+ QTableWidgetItem(qtwi))
+ self.item(_row, self.no_columns-3).setTextAlignment(Qt.AlignRight |
+ Qt.AlignVCenter)
+
+ _ts_date = _pvd.tsDateAsString
+ _ts_str_len = len(_ts_date)
+ _ilength_target = self.format_ts_nano
+
+ while _ts_str_len < _ilength_target:
+ _ts_date += "0"
+ _ilength_target = _ilength_target - 1
+ _ts_str_len = len(_ts_date)
+ _ts_str = _ts_date[0:_ts_str_len - (
+ self.format_ts_nano-self.format_ts_decimal_part)]
+ _ts_str_len = len(_ts_str)
+
+ _ilength_target = self.format_ts_decimal_part
+ if self.format_ts_decimal_part == self.format_ts_deci:
+ if _ts_str_len == self.format_ts_sec :
+ _ts_str += "."
+ while _ts_str_len < _ilength_target :
+ _ts_str += "0"
+ _ilength_target = _ilength_target -1
+
+ qtwi = QTableWidgetItem( _ts_str)
+ f = qtwi.font()
+ f.setPointSize(8)
+ qtwi.setFont(f)
+
+ self.setItem(_row, self.no_columns-2, QTableWidgetItem(qtwi))
+ self.item(_row, self.no_columns-2).setTextAlignment(Qt.AlignCenter)
+
+ _prop = pvgate.qt_dynamic_property_get()
+
+ alarm_severity = _pvd.alarmSeverity
+
+ if _prop == pvgate.READBACK_ALARM:
+
+ if alarm_severity == pvgate.cyca.SEV_MAJOR:
+ _bgcolor = pvgate.fg_alarm_major
+ _fgcolor = "black"
+ elif alarm_severity == pvgate.cyca.SEV_MINOR:
+ _bgcolor = pvgate.fg_alarm_minor
+ _fgcolor = "black"
+ elif alarm_severity == pvgate.cyca.SEV_INVALID:
+ _bgcolor = pvgate.fg_alarm_invalid
+ _fgcolor = "#777777"
+ else:
+ _bgcolor = pvgate.fg_alarm_noalarm
+ _fgcolor = "black"
+
+ #Colors for bg/fg reversed as is the old norm
+ self.item(_row, self.no_columns-3).setBackground(
+ QColor(_bgcolor))
+ self.item(_row, self.no_columns-2).setBackground(
+ QColor(_bgcolor))
+ self.item(_row, self.no_columns-3).setForeground(
+ QColor(_fgcolor))
+ self.item(_row, self.no_columns-2).setForeground(
+ QColor(_fgcolor))
+
+ elif _prop == pvgate.READBACK_STATIC:
+
+ self.item(_row, self.no_columns-3).setBackground(
+ QColor(pvgate.bg_readback))
+ self.item(_row, self.no_columns-2).setBackground(
+ QColor(pvgate.bg_readback))
+
+ elif _prop == pvgate.DISCONNECTED:
+ self.item(_row, self.no_columns-3).setBackground(
+ QColor("#ffffff"))
+ self.item(_row, self.no_columns-2).setBackground(QColor(
+ "#ffffff"))
+ self.item(_row, self.no_columns-3).setForeground(QColor(
+ "#777777"))
+ self.item(_row, self.no_columns-2).setForeground(QColor(
+ "#777777"))
+
+ else:
+ print (_prop, "widget_update unknown in element/row", _row,
+ _row+1)
+
+ QApplication.processEvents()
+
+ def __init__(self, parent=None, pv_list: list = ["PV_NAME_NOT_GIVEN"],
+ monitor_callback=None, pv_within_daq_group: bool = False,
+ color_mode = None, show_units: bool = True, prefix: str = "",
+ suffix: str = "", ts_res: str = "milli",
+ init_column: bool = False, init_list: list = [],
+ notify_freq_hz: int = 0, notify_unison: bool = True,
+ precision: int = 0, scale_factor: float = 1,
+ show_timestamp: bool = True, pv_list_show: list = None):
+
+ super().__init__()
+ self.columns_dict = {}
+ _column_dict_value = 0
+ self.columns_dict['PV'] = _column_dict_value
+ if init_column:
+ _column_dict_value += 1
+ self.columns_dict['Init'] = _column_dict_value
+ _column_dict_value += 1
+ self.columns_dict['Value'] = _column_dict_value
+ if show_timestamp:
+ _column_dict_value += 1
+ self.columns_dict['Timestamp'] = _column_dict_value
+ _column_dict_value += 1
+ self.columns_dict['Reconnect'] = _column_dict_value
+
+ self.setWindowModality(Qt.ApplicationModal)
+ self.no_columns = _column_dict_value + 1
+
+ self.init_column = init_column
+
+ self.init_list = init_list
+ if self.init_column and not self.init_list:
+ self.init_list = pv_list
+
+ self.icount = 0
+ self.notify_freq_hz = abs(notify_freq_hz)
+ self.notify_freq_hz_default = self.notify_freq_hz
+ self.notify_milliseconds = 0 if self.notify_freq_hz == 0 else \
+ 1000 / self.notify_freq_hz
+
+ self.notify_unison = True if notify_unison and \
+ self.notify_freq_hz > 0 else False
+
+ self.precision = precision
+ self.scale_factor = scale_factor
+ self.show_timestamp = show_timestamp
+
+ self.format_ts_nano = 31 #max length of date
+ self.format_ts_micro = 28
+ self.format_ts_milli = 25
+ self.format_ts_deci = 23 #-8
+ self.format_ts_sec = 21
+ if "nano" in ts_res.lower():
+ self.format_ts_decimal_part = self.format_ts_nano
+ elif "micro" in ts_res.lower():
+ self.format_ts_decimal_part = self.format_ts_micro
+ elif "milli" in ts_res.lower():
+ self.format_ts_decimal_part = self.format_ts_milli
+ elif "deci" in ts_res.lower():
+ self.format_ts_decimal_part = self.format_ts_deci
+ elif "sec" in ts_res.lower():
+ self.format_ts_decimal_part = self.format_ts_sec
+ else:
+ self.format_ts_decimal_part = self.format_ts_milli
+
+ self.pv2item_dict = {}
+
+ self.pv_list = pv_list
+ self.pv_gateway = [None] * len(self.pv_list)
+
+ self.pv_list_show = pv_list_show
+ if self.pv_list_show is None:
+ self.pv_list_show = self.pv_list
+
+ _color_mode = [None] * len(self.pv_list)
+
+ if isinstance(color_mode, list):
+ for i in range (0, len(color_mode)):
+ _color_mode[i] = color_mode[i]
+
+ for i in range (0, len(self.pv_list)):
+
+ self.pv_gateway[i] = PVGateway().__init__(
+ parent, self.pv_list[i], monitor_callback,
+ pv_within_daq_group, _color_mode[i], show_units, prefix, suffix,
+ connect_triggers=False, notify_freq_hz=self.notify_freq_hz,
+ notify_unison=self.notify_unison, precision=self.precision)
+
+ self.pv_gateway[i].is_initialize_complete()
+ self.pv_gateway[i].trigger_connect.connect(
+ self.receive_connect_update)
+ self.pv_gateway[i].trigger_monitor_str.connect(
+ self.receive_monitor_update)
+ self.pv_gateway[i].trigger_monitor_int.connect(
+ self.receive_monitor_update)
+ self.pv_gateway[i].trigger_monitor_float.connect(
+ self.receive_monitor_update)
+
+ self.pv_gateway[i].widget_class = "QTableWidgetItem"
+
+ self.pv_gateway[i].qt_property_initial_values(
+ qt_object_name = self.pv_gateway[i].PV_READBACK,
+ tool_tip=False)
+ #cant do this - sender will be a qspinbox
+ #self.pv_gateway[i].post_display_value = self.post_display_value
+
+
+ #required for reconnect
+ self.cafe = self.pv_gateway[0].cafe
+ self.cyca = self.pv_gateway[0].cyca
+
+ self.timer = None
+ if self.notify_unison:
+ self.timer = QTimer()
+ self.timer.timeout.connect(self.widget_update)
+ self.timer.singleShot(0, self.widget_update)
+ self.timer.start(self.notify_milliseconds)
+
+ self.configure_widget()
+
+ #Connect only deals with colours - only helps on reconnect
+ # In any case monitors take over
+ #Got to do this earlier or emit immediately after!!
+ for i in range(0, len(self.pv_gateway)):
+ if self.cafe.isConnected(self.pv_gateway[i].pv_name):
+ self.pv_gateway[i].trigger_connect.emit(
+ self.pv_gateway[i].handle, str(self.pv_gateway[i].pv_name),
+ self.pv_gateway[i].cyca.ICAFE_CS_CONN)
+
+
+ for i in range(0, len(self.pv_gateway)):
+ if not self.pv_gateway[i].pv_within_daq_group:
+ self.pv_gateway[i].monitor_start()
+
+ self.update_init_values()
+
+ self.configure_context_menu()
+
+
+ def configure_context_menu(self):
+ self.table_context_menu = QMenu()
+ self.table_context_menu.setObjectName("contextMenu")
+ self.table_context_menu.setWindowModality(Qt.NonModal)
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.table_context_menu.addSection("---")
+
+ action1 = QAction("Configure Table PVs", self)
+ action1.triggered.connect(self.display_table_parameters)
+ self.table_context_menu.addAction(action1)
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.table_context_menu.addSection("---")
+
+ QApplication.processEvents()
+
+
+ def restore_init_values(self, pv_list:list = []):
+ _set_values_dict = self.get_init_values()
+ #print("pvwidgets.py set val dict", _set_values_dict)
+ #print("same as init vals", self.is_same_as_init_values())
+
+
+ if not pv_list:
+ _pvs_to_set, _values_to_set = zip(*_set_values_dict.items())
+ #zip returns tuples
+ #print(_pvs_to_set)
+ #print(_values_to_set)
+ _pvs_to_set = list(_pvs_to_set)
+ _values_to_set = list(_values_to_set)
+ else:
+ _pvs_to_set = pv_list
+ _values_to_set = []
+ for pv in pv_list:
+ _values_to_set.append(_set_values_dict[pv])
+
+ #print(_pvs_to_set, _values_to_set)
+ status, status_list = self.cafe.setScalarList(_pvs_to_set,
+ _values_to_set)
+
+ if status != self.cyca.ICAFE_NORMAL:
+ _mess = ("The following device(s) reported an error " +
+ "in 'set' operation:")
+ for i, status_value in enumerate(status_list):
+ if status_value != self.cyca.ICAFE_NORMAL:
+ _mess += ("\n" + _pvs_to_set[i] + " has status = " +
+ str(status_value) + " " +
+ self.cafe.getStatusCodeAsString(status_value) +
+ " " + self.cafe.getStatusInfo(status_value) )
+ qm = QMessageBox()
+ qm.setText(_mess)
+
+ qm.exec()
+ QApplication.processEvents()
+
+ self.init_value_button.setEnabled(True)
+
+
+ def is_same_as_init_values(self):
+ _init_values_dict = self.get_column_values(self.columns_dict['Init'])
+ _pvs, _init_values = zip(*_init_values_dict.items())
+ _current_values_dict = self.get_column_values(self.columns_dict['Value'])
+ _pvs, _current_values = zip(*_current_values_dict.items())
+ #zip returns tuples
+
+ if func_reduce(lambda i, j : i and j, map(
+ lambda m, k: m == k, _init_values, _current_values), True):
+ return True
+ else:
+ return False
+
+
+ def get_column_values(self, column_no):
+ _values_dict = {}
+ _start = 0
+ _end = len(self.pv_gateway)
+ _pvs = [None] * _end
+ _values_str = [None] * _end
+ _values = [None] * _end
+
+ for _row in range(_start, _end):
+ _values_str[_row] = self.item(_row, column_no).text()
+ _pvs[_row] = self.item(_row, 0).text()
+
+ _value_list = [float(_value_list) for _value_list in re.findall(
+ r'-?\d+\.?\d*', _values_str[_row])]
+
+ if not _value_list:
+ print("row", _row, "values", _values_str[_row], _pvs[_row])
+ _values[_row] = _values_str[_row] #Can be enum string
+ else:
+ _values[_row] = _value_list[0]
+
+
+ if _pvs[_row] in self.pv_list_show:
+ #_values_dict[_pvs[_row]] = _values[_row]
+ _values_dict[self.pv_gateway[_row].pv_name] = _values[_row]
+
+ #print("_pvs", _pvs)
+ #print("show", self.pv_list_show)
+ return _values_dict #_pvs_to_set, _values_to_set
+
+
+ def get_init_values(self):
+ return self.get_column_values(self.columns_dict['Init'])
+
+ def get_init_values_previous(self):
+ _set_values_dict = {}
+ _start = 0
+ _end = len(self.pv_gateway)
+ _pvs_to_set = [None] * _end
+ _values_to_set_str = [None] * _end
+ _values_to_set = [None] * _end
+ for _row in range(_start, _end):
+ _values_to_set_str[_row] = self.item(_row, self.columns_dict['Init']).text()
+ _pvs_to_set[_row] = self.item(_row, self.columns_dict['PV']).text()
+
+ _value_list = [float(_value_list) for _value_list in re.findall(
+ r'-?\d+\.?\d*', _values_to_set_str[_row])]
+
+ if not _value_list:
+ print("//row", _row, "values", _values_to_set_str[_row],
+ _pvs_to_set[_row])
+ _values_to_set[_row] = _values_to_set_str[_row] #Can be enum string
+ else:
+ _values_to_set[_row] = _value_list[0]
+
+
+ if _pvs_to_set[_row] in self.init_list:
+ #_set_values_dict[_pvs_to_set[_row]] = _values_to_set[_row]
+ _set_values_dict[self.pv_gateway[_row].pv_name] = _values_to_set[_row]
+ #print(_set_values_dict)
+ return _set_values_dict #_pvs_to_set, _values_to_set
+
+
+ def update_init_values(self):
+ _start = 0
+ _end=len(self.pv_gateway)
+ for _row in range(_start, _end):
+ _handle = self.pv_gateway[_row].handle
+ _value = self.pv_gateway[_row].cafe.getCache(_handle)
+
+ if _value is not None:
+ if self.scale_factor != 1:
+ _value = _value * self.scale_factor
+ _value = self.pv_gateway[_row].format_display_value(_value)
+ #print("value from update", _value)
+ qtwi = QTableWidgetItem(str(_value)+ " ")
+ _f = qtwi.font()
+ _f.setPointSize(8)
+ qtwi.setFont(_f)
+ self.setItem(_row, 1, qtwi)
+ self.item(_row, 1).setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
+
+
+ def configure_widget(self):
+
+ _column_width_pvname = 180
+ _column_width_value = 90
+ _column_width_timestamp = 210
+ _column_width_checkbox = 22
+
+ self.setRowCount(len(self.pv_gateway)+1)
+ self.setColumnCount(self.no_columns)
+ self.setEditTriggers(QAbstractItemView.NoEditTriggers)
+ self.resizeColumnsToContents()
+ self.resizeRowsToContents()
+ #self.horizontalHeader().setStretchLastSection(True);
+ self.setColumnWidth(self.columns_dict['PV'], _column_width_pvname)
+
+ self.setColumnWidth(self.columns_dict['Value'], _column_width_value)
+ if 'Init' in self.columns_dict.keys():
+ self.setColumnWidth(self.columns_dict['Init'], _column_width_value)
+ if 'Timestamp' in self.columns_dict.keys():
+ self.setColumnWidth(self.columns_dict['Timestamp'], _column_width_timestamp)
+ self.setColumnWidth(self.columns_dict['Reconnect'], _column_width_checkbox)
+
+ _pv_column = self.columns_dict['PV']
+ for i in range(0, len(self.pv_gateway)):
+ #self.setItem(i, _pv_column, QTableWidgetItem(self.pv_gateway[i].pv_name))
+ qtwt = QTableWidgetItem(self.pv_list_show[i])
+ f = qtwt.font()
+ f.setPointSize(8)
+ qtwt.setFont(f)
+ #qtwt.setTextAlignment(Qt.AlignLeft)
+ self.setItem(i, _pv_column, qtwt)
+ self.item(i, _pv_column).setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
+ for i_column in range(1, self.no_columns-1):
+ self.setItem(i, i_column, QTableWidgetItem(str("")))
+ self.item(i, i_column).setTextAlignment(Qt.AlignHCenter |
+ Qt.AlignVCenter)
+ self.pv2item_dict[self.pv_gateway[i]] = i
+
+ cb_item = QTableWidgetItem()
+ cb_item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
+ cb_item.setCheckState(Qt.Unchecked)
+ cb_item.setTextAlignment(Qt.AlignCenter)
+ cb_item.setToolTip(self.pv_gateway[i].pv_name)
+
+ self.setItem(i, self.no_columns-1, cb_item)
+ self.item(i, self.no_columns-1).setTextAlignment(Qt.AlignCenter)
+
+ if self.init_column:
+ self.init_widget = QWidget()
+ _init_layout = QHBoxLayout(self.init_widget)
+ self.init_value_button = QPushButton()
+ self.init_value_button.setText("Update")
+ _f = self.init_value_button.font()
+ _f.setPointSize(8)
+ self.init_value_button.setFont(_f)
+ self.init_value_button.setFixedWidth(80)
+ self.init_value_button.clicked.connect(self.update_init_values)
+ self.init_value_button.setToolTip(
+ ("Stores initial, pre-measurement value. Update is also " +
+ "typically executed automatically before new optics are set."))
+ _init_layout.addWidget(self.init_value_button)
+ _init_layout.setAlignment(Qt.AlignRight)
+ _init_layout.setContentsMargins(1,1,0,0) #Required
+ self.init_widget.setLayout(_init_layout)
+ self.setCellWidget(len(self.pv_gateway), 1, self.init_widget)
+
+ _restore_widget = QWidget()
+ _restore_layout = QHBoxLayout(_restore_widget)
+ self.restore_value_button = QPushButton()
+ #self.restore_value_button.setObjectName("Controller")
+ #self.restore_value_button.setProperty('actOnBeam', True)
+ self.restore_value_button.setStyleSheet(
+ "QPushButton{background-color: rgb(212, 219, 157);}")
+ self.restore_value_button.setText("Restore")
+ _f = self.restore_value_button.font()
+ _f.setPointSize(8)
+ self.restore_value_button.setFont(_f)
+ self.restore_value_button.setFixedWidth(80)
+ self.restore_value_button.clicked.connect(self.restore_init_values)
+ self.restore_value_button.setToolTip(
+ ("Restore devices to their pre-measurement values"))
+ _restore_layout.addWidget(self.restore_value_button)
+ _restore_layout.setAlignment(Qt.AlignRight)
+ _restore_layout.setContentsMargins(1,1,0,0) #Required
+ _restore_widget.setLayout(_restore_layout)
+ self.setCellWidget(len(self.pv_gateway), 2, _restore_widget)
+
+ #Do not display no for last row (Reconnect button)
+ _row_digit_last_cell = QTableWidgetItem(str(""))
+ self.setVerticalHeaderItem(len(self.pv_gateway), _row_digit_last_cell)
+ self.setItem(len(self.pv_gateway), 0, QTableWidgetItem(str("")))
+
+ _qwb = QWidget()
+
+ self.reconnect_button = reconnectQPushButton(self) #self required
+ #self.reconnect_button.setFont(self.font12);QFont("Sans Serif", 12)
+
+ f = self.reconnect_button.font()
+
+ if 'Timestamp' in self.columns_dict.keys():
+ f.setPointSize(8)
+ self.reconnect_button.setFixedWidth(100)
+ else:
+ f.setPointSize(6)
+ self.reconnect_button.setFixedWidth(58)
+
+ self.reconnect_button.setFont(f)
+
+
+ self.reconnect_button.setText("Reconnect")
+
+
+
+
+ _layout = QHBoxLayout(_qwb)
+ _layout.addWidget(self.reconnect_button)
+ _layout.setAlignment(Qt.AlignCenter)
+ _layout.setContentsMargins(0,0,0,0) #Required
+ #_qwb.setLayout(_layout)
+
+ #_reconnect_button
+ self.setCellWidget(len(self.pv_gateway), self.no_columns-2, _qwb)
+
+
+ self.cb_item_all = QCheckBox()
+ self.cb_item_all.setCheckState(Qt.Unchecked)
+ self.cb_item_all.stateChanged.connect(self.reconnectStateChanged)
+ self.cb_item_all.setObjectName("Reconnect")
+
+
+ '''
+ _qwc = QWidget()
+
+ _layout_cb = QHBoxLayout(_qwc)
+ _layout_cb.addWidget(self.cb_item_all)
+ _layout_cb.setAlignment(Qt.AlignLeft)
+ _layout_cb.setContentsMargins(3,2,0,1) #Required LTRB
+ _qwc.setLayout(_layout_cb)
+ '''
+
+ self.setCellWidget(len(self.pv_gateway), self.no_columns-1, self.cb_item_all)
+
+
+ header_item = QTableWidgetItem("Process Variable")
+
+ '''
+ header_item.setText("Process Variable")
+ f= header_item.font()
+ f.setPixelSize(12)
+ header_item.setFont(f)
+ '''
+
+ self.setHorizontalHeaderItem(self.columns_dict['PV'], header_item)
+
+ if 'Init' in self.columns_dict.keys():
+ self.setHorizontalHeaderItem(self.columns_dict['Init'],
+ QTableWidgetItem("Initial Value"))
+
+ self.setHorizontalHeaderItem(self.columns_dict['Value'],
+ QTableWidgetItem("Value"))
+
+ if 'Timestamp' in self.columns_dict.keys():
+ self.setHorizontalHeaderItem(self.columns_dict['Timestamp'],
+ QTableWidgetItem("Timestamp"))
+ self.setHorizontalHeaderItem(self.columns_dict['Reconnect'],
+ QTableWidgetItem("R"))
+ self.setFocusPolicy(Qt.NoFocus)
+ self.setEditTriggers(QAbstractItemView.NoEditTriggers)
+ self.setSelectionMode(QAbstractItemView.NoSelection)
+
+ self.verticalHeader().setDefaultAlignment(Qt.AlignRight)
+ self.verticalHeader().setFixedWidth(22)
+
+
+ #self.verticalHeader().setVisible(False)
+ #self.horizontalHeader().setSectionResizeMode( 0, QHeaderView.Stretch )
+
+ _fm_font = QFont("Sans Serif")
+ _fm_font.setPointSize(12)
+ fm = QFontMetricsF(_fm_font) #QFont("Sans Serif", 12))
+
+ _factor = 1
+ if LooseVersion(QT_VERSION_STR) < LooseVersion("5.0"):
+ _factor = 1.18
+
+ self.setFixedHeight(int(fm.lineSpacing() * _factor *
+ (len(self.pv_gateway)+3)))
+ _min_table_width = 620 if not self.init_column else 650
+ self.setMinimumWidth(_min_table_width)
+
+
+ for _row in range(0, len(self.pv_gateway)):
+ #print("name/row/columns", self.pv_gateway[_row].pv_name, _row, self.no_columns)
+ self.item(_row, _pv_column).setForeground(QColor("#000000"))
+ ##self.item(_row, _pv_column).setTextAlignment(Qt.AlignCenter)
+
+ for i_column in range(1, self.no_columns-2):
+ self.item(_row, i_column).setForeground(QColor("#000000"))
+ self.item(_row, i_column).setTextAlignment(Qt.AlignRight |
+ Qt.AlignVCenter)
+
+ self.item(_row, self.columns_dict['Value']).setBackground(QColor("#ffffff"))
+ if 'Timestamp' in self.columns_dict.keys():
+ self.item(_row, self.columns_dict['Timestamp']).setTextAlignment(Qt.AlignCenter)
+ self.item(_row, self.columns_dict['Timestamp']).setBackground(QColor("#ffffff"))
+
+ @Slot(int)
+ def reconnectStateChanged(self, state):
+ if state == Qt.Unchecked:
+ for i in range(0, len(self.pv_gateway)):
+ self.item(i, self.columns_dict['Reconnect']).setCheckState(Qt.Unchecked)
+ else:
+ for i in range(0, len(self.pv_gateway)):
+ self.item(i, self.columns_dict['Reconnect']).setCheckState(Qt.Checked)
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+
+ _row = self.pv2item_dict[self.sender()]
+ '''
+ if _row in (3,):
+ print("receive mon update before post_display, value/row==", value, _row, self.pv_gateway[_row].pv_name)
+ print(self, _row, self.pv_gateway[_row].pv_name, ">>>>>>>from gateway>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
+ print("sender", self.sender())
+ '''
+
+ #print(self.pv2item_dict)
+ #print( value, status, alarm_severity)
+ #Timing on CAQTableWidget basis! Miss last events if many events
+ #First trigger should always happen
+ #if self.update_hz is not None:
+ # print(time.monotonic(), self.pv_gateway[_row].time_monotonic, (1/self.update_hz))
+ # if (time.monotonic() - self.pv_gateway[_row].time_monotonic) < (1/self.update_hz):
+ # return
+ # self.pv_gateway[_row].receive_monitor_update(value, status, alarm_severity)
+ self.pv_gateway[_row].time_monotonic = time.monotonic()
+ if self.scale_factor != 1:
+ value = value * self.scale_factor
+ _value = self.pv_gateway[_row].format_display_value(value)
+
+ #print("row no//", _row)
+ qtwi = QTableWidgetItem(str(_value) + " ")
+ f = qtwi.font()
+ f.setPointSize(8)
+ qtwi.setFont(f)
+ self.setItem(_row, self.columns_dict['Value'], qtwi)
+ self.item(_row, self.columns_dict['Value']).setTextAlignment(
+ Qt.AlignRight | Qt.AlignVCenter)
+
+ if 'Timestamp' in self.columns_dict.keys():
+
+ _handle = self.pv_gateway[_row].handle
+ #print("post_display HANDLE/val/row", _handle, _value, _row)
+ #_status = self.pv_gateway[_row].cafe.getStatus(_handle)
+ _pvd = self.pv_gateway[_row].cafe.getPVCache(_handle)
+
+
+ _ts_date = _pvd.tsDateAsString
+ _ts_str_len = len(_ts_date)
+ _ilength_target = self.format_ts_nano
+
+ while _ts_str_len < _ilength_target:
+ _ts_date += "0"
+ _ilength_target = _ilength_target -1
+
+ ts_str_len = len(_ts_date)
+ _ts_str = _ts_date[0:_ts_str_len-(
+ self.format_ts_nano-self.format_ts_decimal_part)]
+ _ts_str_len = len(_ts_str)
+ #print(_ts_date)
+ #print(_ts_str)
+ #print("length of timestamp string ", _ts_str_len)
+ _ilength_target = self.format_ts_decimal_part
+ if self.format_ts_decimal_part == self.format_ts_deci:
+ if _ts_str_len == self.format_ts_sec:
+ _ts_str += "."
+ while _ts_str_len < _ilength_target :
+ _ts_str += "0"
+ _ilength_target = _ilength_target -1
+
+ qtwi = QTableWidgetItem( _ts_str)
+ f = qtwi.font()
+ f.setPointSize(8)
+ qtwi.setFont(f)
+
+ self.setItem(_row, self.columns_dict['Timestamp'], qtwi)
+ self.item(_row, self.columns_dict['Timestamp']).setTextAlignment(Qt.AlignCenter)
+
+ _prop = self.pv_gateway[_row].qt_dynamic_property_get()
+
+ if _prop == self.pv_gateway[_row].READBACK_ALARM:
+
+ if alarm_severity == self.pv_gateway[_row].cyca.SEV_MAJOR:
+ _bgcolor = self.pv_gateway[_row].settings.fgAlarmMajor
+ _fgcolor = "black"
+ elif alarm_severity == self.pv_gateway[_row].cyca.SEV_MINOR:
+ _bgcolor = self.pv_gateway[_row].settings.fgAlarmMinor
+ _fgcolor = "black"
+ elif alarm_severity == self.pv_gateway[_row].cyca.SEV_INVALID:
+ _bgcolor = self.pv_gateway[_row].settings.fgAlarmInvalid
+ _fgcolor = "#777777"
+ else:
+ _bgcolor = self.pv_gateway[_row].settings.fgAlarmNoAlarm
+ #_bgcolor = self.pv_gateway[_row].settings.bgReadbackAlarm
+ _fgcolor = "black"
+
+ #Colors for bg/fg reversed as is the old norm
+ self.item(_row, self.columns_dict['Value']).setBackground(QColor(_bgcolor))
+ self.item(_row, self.columns_dict['Value']).setForeground(QColor(_fgcolor))
+ if 'Timestamp' in self.columns_dict.keys():
+ self.item(_row, self.columns_dict['Timestamp']).setBackground(QColor(_bgcolor))
+ self.item(_row, self.columns_dict['Timestamp']).setForeground(QColor(_fgcolor))
+
+
+ elif _prop == self.pv_gateway[_row].DISCONNECTED or \
+ alarm_severity == self.pv_gateway[_row].cyca.SEV_INVALID:
+ self.item(_row, self.columns_dict['Value']).setBackground(
+ QColor("#ffffff"))
+ self.item(_row, self.columns_dict['Value']).setForeground(
+ QColor("#777777"))
+
+ if 'Timestamp' in self.columns_dict.keys():
+ self.item(_row, self.columns_dict['Timestamp']).setBackground(
+ QColor("#ffffff"))
+ self.item(_row, self.columns_dict['Timestamp']).setForeground(
+ QColor("#777777"))
+
+
+ elif _prop == self.pv_gateway[_row].READBACK_STATIC:
+ self.item(_row, self.columns_dict['Value']).setBackground(
+ QColor(self.pv_gateway[_row].bg_readback))
+ if 'Timestamp' in self.columns_dict.keys():
+ self.item(_row, self.columns_dict['Timestamp']).setBackground(
+ QColor(self.pv_gateway[_row].bg_readback))
+ else:
+
+ print (_prop, self.pv_gateway[_row].DISCONNECTED, "(in monitor) unknown in element/row no.", _row, _row+1)
+ #TRZ SET PROPERTY
+
+ QApplication.processEvents(QEventLoop.AllEvents, 10)
+ #self.post_display_value(value)
+
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ _row = self.pv2item_dict[self.sender()]
+
+ #print(self, self.pv_gateway[_row].pv_name, ">>>>>>>receive connect >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
+ #print("sender", self.sender())
+ #print("MY RECEIVE receive_connect_update for row = ", _row, " status=", status)
+ #print(handle, pv_name)
+ self.pv_gateway[_row].receive_connect_update(handle, pv_name, status,
+ post_display=False)
+ #print("after gateway connect update")
+ _prop = self.pv_gateway[_row].qt_dynamic_property_get()
+
+ #self.post_display_value(status)
+ if _prop == self.pv_gateway[_row].DISCONNECTED:
+ #self.item(_row,0).setBackground(QColor("#ffffff"))
+ self.item(_row, self.columns_dict['Value']).setBackground(QColor("#ffffff"))
+ #self.item(_row,0).setForeground(QColor("#777777"))
+ self.item(_row, self.columns_dict['Value']).setForeground(QColor("#777777"))
+ if 'Timestamp' in self.columns_dict.keys():
+ self.item(_row, self.columns_dict['Timestamp']).setBackground(QColor("#ffffff"))
+ self.item(_row, self.columns_dict['Timestamp']).setForeground(QColor("#777777"))
+
+ QApplication.processEvents()
+
+ '''
+ def post_display_value(self, value):
+
+#IS CLEAN BEFORE EXIT
+ self.icount += 1;
+ print("recursion limit", sys.getrecursionlimit(), self.icount)
+ _row = self.pv2item_dict[self.sender()]
+ print("row no", _row)
+ _value = self.pv_gateway[_row].format_display_value(value)
+ print("row no//", _row)
+ self.setItem(_row, self.no_columns-3,
+ QTableWidgetItem(str(_value)+ " "))
+ self.item(_row, self.no_columns-3).setTextAlignment(Qt.AlignRight)
+
+ _handle = self.pv_gateway[_row].handle
+ #print("post_display HANDLE/val/row", _handle, _value, _row)
+ #_status = self.pv_gateway[_row].cafe.getStatus(_handle)
+ _pvd = self.pv_gateway[_row].cafe.getPVCache(_handle)
+
+ _ts_date = _pvd.tsDateAsString
+ _ts_str_len = len(_ts_date)
+ _ilength_target = self.format_ts_nano
+
+ while _ts_str_len < _ilength_target:
+ _ts_date += "0"
+ _ilength_target = _ilength_target -1
+ _ts_str_len = len(_ts_date)
+ _ts_str = _ts_date[0:_ts_str_len - (self.format_ts_nano -
+ self.format_ts_decimal_part)]
+ _ts_str_len = len(_ts_str)
+ #print(_ts_date)
+ #print(_ts_str)
+ #print("length of timestamp string ", _ts_str_len)
+ _ilength_target = self.format_ts_decimal_part
+ if self.format_ts_decimal_part == self.format_ts_deci:
+ if _ts_str_len == self.format_ts_sec :
+ _ts_str += "."
+ while _ts_str_len < _ilength_target :
+ _ts_str += "0"
+ _ilength_target = _ilength_target -1
+
+ self.setItem(_row, self.no_columns-2, QTableWidgetItem( _ts_str))
+ self.item(_row, self.no_columns-2).setTextAlignment(Qt.AlignCenter)
+
+ _prop = self.pv_gateway[_row].qt_dynamic_property_get()
+
+ if _prop == self.pv_gateway[_row].READBACK_ALARM:
+ #self.item(_row,0).setBackground(QColor("#c8c8c8"))
+ self.item(_row, self.no_columns-3).setBackground(QColor("#c8c8c8"))
+ self.item(_row, self.no_columns-2).setBackground(QColor("#c8c8c8"))
+
+ elif _prop == self.pv_gateway[_row].READBACK_STATIC:
+ #self.item(_row,0).setBackground(QColor("#ffffe0"))
+ self.item(_row, self.no_columns-3).setBackground(QColor("#ffffe0"))
+ self.item(_row, self.no_columns-2).setBackground(QColor("#ffffe0"))
+
+ elif _prop == self.pv_gateway[_row].DISCONNECTED:
+ self.item(_row, self.no_columns-3).setBackground(QColor("#ffffff"))
+ self.item(_row, self.no_columns-2).setBackground(QColor("#ffffff"))
+ self.item(_row, self.no_columns-3).setForeground(QColor("#777777"))
+ self.item(_row, self.no_columns-2).setForeground(QColor("#777777"))
+
+ else:
+ print (_prop, "unknown in element/row ==>", _row, _row+1)
+ #TRZ SET PROPERTY
+
+ QApplication.processEvents()
+ #self.setStyleSheet("QTableWidget::item {margin-right: 5 }");
+
+ #self.setStyleSheet("QHeaderView::section:horizontal {margin-right: 2; border: 1px solid}");
+ '''
+ def table_precision_user_changed(self, new_value):
+ self.pvgateway_precision = new_value
+
+ for pvgate in self.pv_gateway:
+ if pvgate.pv_ctrl is not None:
+ self.pvgateway_precision = min(pvgate.pv_ctrl.precision,
+ new_value)
+
+ pvgate.precision_user = self.pvgateway_precision
+ pvgate.precision = self.pvgateway_precision
+
+ _pvd = self.cafe.getPVCache(pvgate.handle)
+
+ if _pvd.value[0] is not None:
+ if isinstance(_pvd.value[0], float):
+ pvgate.trigger_monitor_float.emit(
+ _pvd.value[0], _pvd.status, _pvd.alarmSeverity)
+
+
+ def table_precision_ioc_reset(self):
+ '''
+ _max_current_precision_value = -1
+ for i, pvgate in enumerate(self.pv_gateway):
+ if pvgate.pv_ctrl is not None:
+
+ _max_current_precision_value = max(
+ pvgate.precision_user, _max_current_precision_value)
+
+ if _max_current_precision_value == -1:
+ _max_current_precision_value = self.max_precision_value
+ '''
+ if self.max_precision_value == self.table_precision_user_wgt.value():
+ self.table_precision_user_changed(self.max_precision_value)
+ else:
+ self.table_precision_user_wgt.setValue(self.max_precision_value)
+
+ def table_refresh_rate_changed(self, new_idx):
+
+ _notify_freq_hz = self.refresh_freq_combox_idx_dict[new_idx]
+ _notify_milliseconds = 0 if _notify_freq_hz == 0 else \
+ 1000 / _notify_freq_hz
+
+ self.notify_freq_hz = _notify_freq_hz
+
+ if _notify_milliseconds == 0:
+ for pvgate in self.pv_gateway:
+ pvgate.notify_unison = False
+ pvgate.notify_milliseconds = _notify_milliseconds
+ pvgate.notify_freq_hz = self.notify_freq_hz
+ pvgate.monitor_stop()
+ time.sleep(0.01)
+ for pvgate in self.pv_gateway:
+ pvgate.monitor_start()
+
+ else:
+ for pvgate in self.pv_gateway:
+ if not pvgate.notify_unison:
+ pvgate.monitor_stop()
+
+ for pvgate in self.pv_gateway:
+ pvgate.notify_milliseconds = _notify_milliseconds
+ pvgate.notify_freq_hz = self.notify_freq_hz
+
+ if not pvgate.notify_unison:
+ pvgate.notify_unison = True
+ pvgate.monitor_start()
+ else:
+
+ self.cafe.updateMonitorPolicyDeltaMS(
+ pvgate.handle, pvgate.monitor_id,
+ pvgate.notify_milliseconds)
+
+ #for pvgate in self.pv_gateway:
+ # pvgate.monitor_start()
+ # print("pvgate / mon started", pvgate)
+
+ if self.timer is not None:
+ self.timer.stop()
+ else:
+ self.timer = QTimer()
+ self.timer.timeout.connect(self.widget_update)
+ self.timer.singleShot(0, self.widget_update)
+
+ if _notify_milliseconds > 0:
+ self.timer.start(_notify_milliseconds)
+
+ def table_ts_resolution_changed(self, new_idx):
+
+ for i, (key, ts_res) in enumerate(self.ts_combox_idx_dict.items()):
+ if i == new_idx:
+ self.format_ts_decimal_part = ts_res
+ break;
+
+ for pvgate in self.pv_gateway:
+ _pvd = self.cafe.getPVCache(pvgate.handle)
+ if _pvd.value[0] is not None:
+ if isinstance(_pvd.value[0], float):
+ pvgate.trigger_monitor_float.emit(
+ _pvd.value[0], _pvd.status, _pvd.alarmSeverity)
+ elif isinstance(_pvd.value[0], int):
+ pvgate.trigger_monitor_int.emit(
+ _pvd.value[0], _pvd.status, _pvd.alarmSeverity)
+ else:
+ pvgate.trigger_monitor_str.emit(
+ str(_pvd.value[0]), _pvd.status,
+ _pvd.alarmSeverity)
+
+
+ def display_table_parameters(self):
+ display_wgt = QDialog(self)
+ display_wgt.setWindowTitle("PV Parameters")
+ layout = QVBoxLayout()
+ common_label_width = 120
+ common_wgt_width = 160
+ common_hbox_width = common_label_width + common_wgt_width + 20
+
+ self.initial_value = 0
+ self.max_precision_value = 0
+ for i, pvgate in enumerate(self.pv_gateway):
+ if pvgate.pv_ctrl is not None:
+ if pvgate.pv_ctrl.precision > 0:
+ self.max_precision_value = max(self.max_precision_value,
+ pvgate.pv_ctrl.precision)
+ self.initial_value = max(self.initial_value,
+ pvgate.precision)
+
+ if self.max_precision_value > 0:
+ #precision user
+ _hbox_wgt = QWidget()
+ _hbox = QHBoxLayout()
+ precision_user_label = QLabel("Precision (user):")
+ self.table_precision_user_wgt = QSpinBox(self)
+ self.table_precision_user_wgt.setFocusPolicy(Qt.NoFocus)
+ self.table_precision_user_wgt.setValue(self.initial_value)
+ self.table_precision_user_wgt.setMaximum(self.max_precision_value)
+ self.table_precision_user_wgt.valueChanged.connect(
+ self.table_precision_user_changed)
+ precision_user_label.setAlignment(Qt.AlignLeft)
+ self.table_precision_user_wgt.setAlignment(Qt.AlignLeft)
+ _hbox.addWidget(precision_user_label)
+ _hbox.addWidget(self.table_precision_user_wgt)
+ _hbox.setAlignment(Qt.AlignLeft)
+ _hbox_wgt.setLayout(_hbox)
+
+ precision_user_label.setFixedWidth(common_label_width)
+ self.table_precision_user_wgt.setFixedWidth(40)
+ _hbox_wgt.setFixedWidth(common_hbox_width)
+
+ #precision ioc
+ _hbox2_wgt = QWidget()
+ _hbox2 = QHBoxLayout()
+ precision_ioc_label = QLabel("Precision (ioc): ")
+ precision_ioc = QPushButton(self)
+ precision_ioc.setText("Reset")
+ precision_ioc.clicked.connect(self.table_precision_ioc_reset)
+ precision_ioc_label.setAlignment(Qt.AlignLeft)
+
+
+ _hbox2.addWidget(precision_ioc_label)
+ _hbox2.addWidget(precision_ioc)
+ _hbox2.setAlignment(Qt.AlignLeft)
+
+ _hbox2_wgt.setLayout(_hbox2)
+
+ precision_ioc_label.setFixedWidth(common_label_width)
+ precision_ioc.setFixedWidth(50)
+
+ _hbox2_wgt.setFixedWidth(common_hbox_width)
+
+
+ layout.addWidget(_hbox_wgt)
+ layout.addWidget(_hbox2_wgt)
+
+
+ if 'Timestamp' in self.columns_dict.keys():
+ #time-stamp
+ _hbox4_wgt = QWidget()
+ _hbox4 = QHBoxLayout()
+ ts_label = QLabel("Timestamp: ")
+
+ self.ts_combox_idx_dict = {'second (s)':self.format_ts_sec,
+ 'decisecond (ds)':self.format_ts_deci,
+ 'millisecond (ms)':self.format_ts_milli,
+ 'microsecond (\u03bcs)':self.format_ts_micro,
+ 'nanosecond (ns)':self.format_ts_nano}
+
+ ts_resolution = QComboBox(self)
+ for (key, ts_res) in (self.ts_combox_idx_dict.items()):
+ ts_resolution.addItem(key)
+
+
+ _current_idx = 0
+
+ for i, (key, ts_res) in enumerate(self.ts_combox_idx_dict.items()):
+ if ts_res == self.format_ts_decimal_part:
+ _current_idx = i
+ break
+
+ ts_resolution.setCurrentIndex(_current_idx)
+ ts_resolution.currentIndexChanged.connect(
+ self.table_ts_resolution_changed)
+
+ _hbox4.addWidget(ts_label)
+ _hbox4.addWidget(ts_resolution)
+ _hbox4_wgt.setLayout(_hbox4)
+
+ ts_label.setFixedWidth(common_label_width)
+ ts_resolution.setFixedWidth(common_wgt_width)
+ _hbox4_wgt.setFixedWidth(common_hbox_width)
+
+ layout.addWidget(_hbox4_wgt)
+
+ #precision refresh rate
+ _hbox3_wgt = QWidget()
+ _hbox3 = QHBoxLayout()
+ refresh_freq_label = QLabel("Refresh rate: ")
+ #_default_refresh_val = 0 if self.notify_freq_hz <= 0 else \
+ # self.notify_freq_hz
+ _default_refresh_val = 0 if self.notify_freq_hz_default <= 0 else \
+ self.notify_freq_hz_default
+
+ self.refresh_freq_combox_idx_dict = {0:0, 1:10, 2:5, 3:2, 4:1, 5:0.5,
+ 6:_default_refresh_val}
+ refresh_freq = QComboBox(self)
+ refresh_freq.addItem('direct')
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[1]))
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[2]))
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[3]))
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[4]))
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[5]))
+
+ _default_text = 'default (direct)' if _default_refresh_val == 0 else \
+ 'default ({0} Hz)'.format(self.refresh_freq_combox_idx_dict[6])
+
+ refresh_freq.addItem(_default_text)
+
+ for key, value in self.refresh_freq_combox_idx_dict.items():
+ if value == self.notify_freq_hz:
+ refresh_freq.setCurrentIndex(key)
+ break
+
+
+ refresh_freq.currentIndexChanged.connect(
+ self.table_refresh_rate_changed)
+
+
+ _hbox3.addWidget(refresh_freq_label)
+ _hbox3.addWidget(refresh_freq)
+ _hbox3_wgt.setLayout(_hbox3)
+
+ refresh_freq_label.setFixedWidth(common_label_width)
+ refresh_freq.setFixedWidth(common_wgt_width)
+ _hbox3_wgt.setFixedWidth(common_hbox_width)
+
+ layout.addWidget(_hbox3_wgt)
+
+ layout.setAlignment(Qt.AlignLeft)
+ layout.setContentsMargins(10, 0, 0, 0)
+ layout.setSpacing(0)
+
+ display_wgt.setMinimumWidth(340)
+ display_wgt.setLayout(layout)
+
+ display_wgt.exec()
+
+
+ def mousePressEvent(self, event):
+ #print(event.pos())
+ row = self.indexAt(event.pos()).row()
+ #print("current item", row)
+ if row < len(self.pv_list) and row > -1:
+ self.pv_gateway[row].mousePressEvent(event)
+ #elif row == -1:
+ # self.display_table_parameters()
+ else:
+ button = event.button()
+ #print("button", button, Qt.RightButton)
+ if button == Qt.RightButton:
+ self.table_context_menu.exec(QCursor.pos())
+ self.clearFocus()
+
+
+
+ #remove highlighting which persists after mouse leaves
+ def mouseMoveEvent(self, event):
+ #event.ignore()
+ pass
+
+ def leaveEvent(self, event):
+ self.clearSelection()
+ self.clearFocus()
+ del event
+
+
+
+class QMessageWidget(QListWidget):
+ """Log message window."""
+ def __init__(self, parent=None):
+ super(QMessageWidget, self).__init__(parent)
+ self.myItem = None
+ self.setSelectionMode(QAbstractItemView.ExtendedSelection)
+ self.setFocusPolicy(Qt.StrongFocus)
+
+ def leaveEvent(self, event):
+ if self.myItem:
+ self.clearSelection()
+ self.clearFocus()
+ del event
+
+ def mousePressEvent(self, event):
+ item = self.itemAt(event.x(), event.y())
+ if item:
+ self.myItem = item
+ self.setCurrentItem(self.myItem)
+
+ def keyPressEvent(self, event):
+ if event.matches(QKeySequence.Copy):
+ nitem = event.count()
+ if nitem:
+ if self.myItem is not None:
+ _str = self.myItem.text()
+ #beg = mystr.find("file = ")
+ #end = mystr.find("'", beg+6)
+ #newstr = "\""+mystr[beg+6:end]+"\""
+ QApplication.clipboard().setText(_str)
+
+
+
+
+
+class QResultsWidget:
+ """Results table"""
+ def __init__(self, summary_dict=None, table_dict=None):
+
+ self.summary_dict = summary_dict
+ self.table_dict = table_dict
+ self._group_box = None
+
+ def group_box(self, title=""):
+ self._group_box = QGroupBox(title)
+ self._group_box.setObjectName("OUTERLEFT")
+ _vbox = QVBoxLayout()
+ _qspace = QFrame()
+ _qspace.setFixedHeight(10)
+ _vbox.addWidget(_qspace)
+
+ _font = QFont("Sans Serif", 10)
+
+ longest_str_item1 = ""
+ longest_str_item2 = ""
+
+ for i, (label, text) in enumerate(self.summary_dict.items()):
+ if len(str(label)) > len(longest_str_item1):
+ longest_str_item1 = str(label)
+ if len(str(text)) > len(longest_str_item2):
+ longest_str_item2 = str(text)
+
+ fm = QFontMetricsF(_font)
+
+ _factor = 1.15
+
+ if LooseVersion(QT_VERSION_STR) < LooseVersion("5.0"):
+ _factor = 1.18
+
+ qrect1 = fm.boundingRect(longest_str_item1)
+ qrect2 = fm.boundingRect(longest_str_item2)
+ _width_scaling_factor = 1.5
+ _width_scaling_factor_le = 1.15
+ _widget_height = 25
+ for i, (label, text) in enumerate(self.summary_dict.items()):
+ #print(label, text)
+ qlabel = QLabel(label)
+ qle = QLineEdit(text)
+ qlabel.setFont(_font)
+ qlabel.setStyleSheet(("QLabel{color:black;" +
+ "margin:0px; padding:2px;}"))
+ qlabel.setFixedWidth(qrect1.width() * _width_scaling_factor)
+ qlabel.setFixedHeight(_widget_height)
+
+ qle.setFocusPolicy(Qt.NoFocus)
+ qle.setFont(_font)
+ qle.setStyleSheet(("QLineEdit{color:blue;" +
+ "background-color: lightgray;" +
+ "qproperty-readOnly: true;" +
+ "margin:0px; padding:2px;}"))
+ qle.setFixedWidth(qrect2.width() * _width_scaling_factor_le)
+ qle.setFixedHeight(_widget_height)
+ qle.setAlignment(Qt.AlignRight)
+
+ _hbox_widget = QWidget()
+ _hbox = QHBoxLayout()
+ _hbox.addWidget(qlabel)
+ _hbox.addWidget(qle)
+ _hbox_widget.setLayout(_hbox)
+ _hbox.setAlignment(Qt.AlignCenter)
+ _hbox.setContentsMargins(0, 2, 0, 0)
+ _vbox.addWidget(_hbox_widget)
+
+ _vbox.setContentsMargins(0, 0, 0, 0)
+ _vbox.setAlignment(Qt.AlignCenter|Qt.AlignTop)
+
+ _vbox2_widget = QWidget()
+ _vbox2 = QVBoxLayout()
+ _vbox2.setContentsMargins(0, 20, 0, 40)
+ table = QTableWidget(len(self.table_dict)-1, 2)
+ table.verticalHeader().setVisible(False)
+ table.setFocusPolicy(Qt.NoFocus)
+ #table.setFont(_font)
+
+ longest_str_item1 = ""
+ longest_str_item2 = ""
+
+ for i, (label, text) in enumerate(self.table_dict.items()):
+ item1 = QTableWidgetItem(str(label))
+ item2 = QTableWidgetItem(str(text))
+ item1.setTextAlignment(Qt.AlignCenter)
+ item2.setTextAlignment(Qt.AlignCenter)
+ item1.setForeground(QColor("black"))
+ item2.setForeground(QColor("black"))
+ if i%2 == 0:
+ item1.setBackground(QColor("lightgray"))
+ item2.setBackground(QColor("lightgray"))
+
+ if len(str(label)) > len(longest_str_item1):
+ longest_str_item1 = str(label)
+ if len(str(text)) > len(longest_str_item2):
+ longest_str_item2 = str(text)
+
+ if i == 0:
+ #item1.setFont(_font)
+ #item2.setFont(_font)
+ table.setHorizontalHeaderItem(0, item1)
+ table.setHorizontalHeaderItem(1, item2)
+ else:
+ table.setItem(i-1, 0, item1)
+ table.setItem(i-1, 1, item2)
+
+
+ fm = QFontMetricsF(_font)
+
+ _factor = 1.2
+
+ if LooseVersion(QT_VERSION_STR) < LooseVersion("5.0"):
+ _factor = 1.18
+
+ qrect = fm.boundingRect(longest_str_item1 + longest_str_item2 )
+
+ _width_scaling_factor = 1.04
+ table.resizeColumnsToContents()
+ table.resizeRowsToContents()
+ #print(fm.lineSpacing())
+ table.setFixedHeight((fm.lineSpacing() * _factor * len(self.table_dict))
+ + fm.lineSpacing()*2)
+
+ #table.setColumnWidth(0, fm.boundingRect(longest_str_item1).width() * _width_scaling_factor)
+ #table.setColumnWidth(1, fm.boundingRect(longest_str_item2).width() * _width_scaling_factor)
+ table.setFixedWidth(((qrect.width()) * _width_scaling_factor))
+ #table.setFixedWidth(220)
+ _vbox2.addWidget(table)
+ _vbox2.setAlignment(Qt.AlignCenter|Qt.AlignTop)
+ _vbox2_widget.setLayout(_vbox2)
+
+ _vbox.addWidget(_vbox2_widget)
+
+ self._group_box.setLayout(_vbox)
+ self._group_box.setContentsMargins(20, 20, 20, 20)
+ self._group_box.setAlignment(Qt.AlignTop)
+ self._group_box.setFixedHeight(table.height() + (
+ _widget_height*len(self.summary_dict))) #_vbox2_widget.height() -50)
+ self._group_box.setFixedWidth(table.width() + 20)
+ return self._group_box
+
+
+class QResultsTableWidget():
+ """Results table"""
+ def __init__(self, column_headings=None):
+
+ self.column_headings = column_headings
+ self._group_box = None
+
+ def group_box(self, title="Table of Results"):
+ self._group_box = QGroupBox(title)
+ self._group_box.setObjectName("OUTER")
+
+ _font = QFont("Sans Serif", 10)
+
+ _vbox2_widget = QWidget()
+ _vbox2 = QVBoxLayout()
+ _vbox2.setContentsMargins(0, 20, 0, 40)
+ table = QTableWidget(1, len(self.column_headings))
+ table.verticalHeader().setVisible(True)
+ table.setFocusPolicy(Qt.NoFocus)
+ table.setFont(_font)
+
+ longest_str_item1 = ""
+ longest_str_item2 = ""
+
+ for i, heading in enumerate(self.column_headings):
+ _item = QTableWidgetItem(str(heading))
+ table.setHorizontalHeaderItem(i, _item)
+
+
+ table.resizeColumnsToContents()
+ table.resizeRowsToContents()
+ #print(fm.lineSpacing())
+ table.setFixedHeight(400)
+
+ _vbox2.addWidget(table)
+ _vbox2.setAlignment(Qt.AlignCenter|Qt.AlignTop)
+ _vbox2_widget.setLayout(_vbox2)
+
+
+ self._group_box.setLayout(_vbox2)
+ self._group_box.setContentsMargins(20, 20, 20, 20)
+ self._group_box.setAlignment(Qt.AlignTop)
+
+ self._group_box.setFixedWidth(table.width() + 20)
+ return self._group_box
+
+
+class QHDFDockWidget(QDockWidget):
+
+ def __init__(self, title=None, parent=None):
+ super().__init__(title, parent)
+ self.parent = parent
+ self.is_docked = True
+ self.geometry_from_qsettings = self.parent.application_geometry
+ self.topLevelChanged.connect(self._top_level_changed)
+ self.setVisible(False)
+ self.setFloating(False)
+ self.geometry_from_qsettings = self.parent.geometry()
+ print( "START GEOEMTRY ",self.geometry_from_qsettings)
+
+
+ def closeEvent(self, event: QCloseEvent):
+ ######################print("Super ClosingEvent....")
+ super().closeEvent(event)
+ print("Super ClosedEvent....")
+ print("float/1", self.isFloating())
+ print("visible/1", self.isVisible())
+ #print("isDocked/1", self.is_docked)
+ print("before", self.parent.geometry(), self.geometry_from_qsettings)
+ self.parent.setGeometry(self.geometry_from_qsettings)
+ self.setGeometry(self.geometry_from_qsettings)
+ QApplication.processEvents()
+ print("after", self.parent.geometry())
+ self.parent.setGeometry(self.geometry_from_qsettings)
+ print("after", self.parent.geometry())
+
+ def changeEvent(self, event):
+ print("event Type", event.type())
+ print("Sender", self.sender())
+ #This implies that one of restore/quit button of the widget was clicked
+ #if self.senderSignalIndex() == 34:
+ # self.close()
+ print("before//", self.parent.geometry(), self.geometry_from_qsettings)
+ #self.parent.setGeometry(self.geometry_from_qsettings)
+ #Generic
+ #if "QAbstractButton" in str(self.sender()):
+ # self.geometry_from_qsettings = self.parent.geometry()
+ print("after", self.parent.geometry())
+
+ def _top_level_changed(self, is_floating):
+ #Need MUTEX
+ print("is_floating", is_floating)
+ #self.setVisible(False)
+ #self.setFloating(True)
+ #ResetGeometry
+ #self.parent.setGeometry(self.geometry_from_qsettings)
+ #QApplication.processEvents()
+
+class QNoDockWidget(QDockWidget):
+
+ def __init__(self, title=None, parent=None):
+ super().__init__(title, parent)
+ self.parent = parent
+ self.is_docked = True
+ self.geometry_from_qsettings = self.parent.application_geometry #self.parent.getGeometry()
+ self.topLevelChanged.connect(self._top_level_changed)
+ self.setVisible(False)
+ self.setFloating(True)
+
+ def changeEvent(self, event):
+ #print("event Type", event.type())
+ #print("Sender", self.sender())
+ #This implies that one of restore/quit button of the widget was clicked
+ #if self.senderSignalIndex() == 34:
+ # self.close()
+ #Generic
+ if "QAbstractButton" in str(self.sender()):
+ self.geometry_from_qsettings = self.parent.geometry()
+
+
+ #def closeEvent(self, event: QCloseEvent):
+ ######################print("Super ClosingEvent....")
+ #super().closeEvent(event)
+ #print("Super ClosedEvent....")
+ #print("float/1", self.isFloating())
+ #print("visible/1", self.isVisible())
+ #print("isDocked/1", self.is_docked)
+
+
+
+ #This is for the quit button
+ #if not self.isVisible():
+ # self.topLevelChanged.emit(False)
+ # print("emitting..")
+ # self.topLevelChanged.emit(False)
+
+
+ def _top_level_changed(self, is_floating):
+ #Need MUTEX
+
+ self.setVisible(False)
+ self.setFloating(True)
+ #ResetGeometry
+ self.parent.setGeometry(self.geometry_from_qsettings)
+ QApplication.processEvents()
+
+
+
+class CAQStripChart(PlotWidget):
+ '''Channel access enabled pyqtgraph.PlotWidget'''
+
+ def __init__(self, parent=None, pv_list: list = ['PV_NAME_NOT_GIVEN'],
+ monitor_callback=None, pv_within_daq_group: bool = False,
+ color_mode = None, show_units: bool = False, prefix: str = "",
+ suffix: str = "", notify_freq_hz: int = 0, title: str = "",
+ ylabel: str = "", force_ts_align = True):
+ super().__init__()
+
+ self.no_channels = len(pv_list)
+
+ self.found = False
+ self.time_zero = [0] * self.no_channels
+ self.time_delta = [0] * self.no_channels
+ self.pv_list = pv_list
+ self.pv2item_dict = {}
+ self.pv_gateway = [None] * self.no_channels
+
+ self.pvd_previous_list = [None] * self.no_channels
+ self.val_previous = [None] * self.no_channels
+
+ self.curve = [None] * self.no_channels
+
+ for i in range (0, len(self.pv_list)):
+ self.pv_gateway[i] = PVGateway().__init__(
+ parent, pv_list[i], monitor_callback, pv_within_daq_group,
+ color_mode, show_units, prefix, suffix,
+ #connect_callback=self.py_connect_callback,
+ connect_triggers=False, notify_freq_hz=notify_freq_hz,
+ monitor_dbr_time = True)
+
+ self.pv_gateway[i].is_initialize_complete()
+
+
+ self.pvd_previous_list[i] = self.pv_gateway[i].pvd
+
+ self.pv_gateway[i].trigger_connect.connect(
+ self.receive_connect_update)
+
+ self.pv_gateway[i].trigger_monitor_str.connect(
+ self.receive_monitor_update)
+ self.pv_gateway[i].trigger_monitor_int.connect(
+ self.receive_monitor_update)
+ self.pv_gateway[i].trigger_monitor_float.connect(
+ self.receive_monitor_update)
+ self.pv_gateway[i].trigger_monitor.connect(
+ self.receive_monitor_dbr_time)
+
+ self.pv_gateway[i].widget_class = "PlotWidget"
+
+ self.pv2item_dict[self.pv_gateway[i]] = i
+
+ self.cafe = self.pv_gateway[0].cafe
+ self.cyca = self.pv_gateway[0].cyca
+ for i in range(0, len(self.pv_gateway)):
+ if self.cafe.isConnected(self.pv_gateway[i].pv_name):
+ self.pv_gateway[i].trigger_connect.emit(
+ self.pv_gateway[i].handle, str(self.pv_gateway[i].pv_name),
+ self.pv_gateway[i].cyca.ICAFE_CS_CONN)
+
+
+ for i in range(0, len(self.pv_gateway)):
+ if not self.pv_gateway[i].pv_within_daq_group:
+ self.pv_gateway[i].monitor_start()
+
+ sampleinterval = 0.2
+ timewindow = 1800.0
+
+ self.ts_delta_max = 0.6
+
+ # Data stuff
+ self._interval = int(sampleinterval*1000)
+ self._bufsize = 9000 #int(timewindow/0.33)
+ self._bufsize2 = 9000 # int(timewindow/1.33)
+ self.databuffer = [None] * self.no_channels
+ self.timebuffer = [None] * self.no_channels
+ self.x = [None] * self.no_channels
+ self.y = [None] * self.no_channels
+ self.x_shifted = [None] * self.no_channels
+
+ self.idx = [0] * self.no_channels
+
+ for i in range(0, self.no_channels):
+ bsize = self._bufsize if i == 0 else self._bufsize2
+ self.databuffer[i] = collections.deque([None]*bsize, bsize)
+ self.timebuffer[i] = collections.deque([0]*bsize, bsize)
+ self.x[i] = np.zeros(bsize, dtype=np.float)
+ self.y[i] = np.zeros(bsize, dtype=np.float)
+
+ _long_size=20
+ #self.data_series_buffer = collections.deque([0]*_long_size, _long_size)
+ #self.time_series_buffer = collections.deque([0]*_long_size, _long_size)
+
+ #self.data_series = [] * self.no_channels
+ #self.time_series = [] * self.no_channels
+
+ self.iflag_series = 0
+
+ #self.x = np.linspace(-timewindow, 0.0, self._bufsize)
+ #self.x_series = np.zeros(_long_size, dtype=np.float)
+ #self.y_series = np.zeros(_long_size, dtype=np.float)
+ if title is not None:
+ self.setTitle(str(title)) #self.pv_gateway[0].pv_name)
+ self.showGrid(x=True, y=True)
+ self.setLabel('left', ylabel, self.pv_gateway[0].units)
+ self.setLabel('bottom', 'time', 's')
+ self.setBackground((60, 60, 60)) #247, 236, 249))
+ self.setLimits(yMin=-0.11)
+
+ self.plotItem.setMouseEnabled(y=False) # Only allow zoom in X-axis
+ self.plotItem.setMouseEnabled(x=True) # Only allow zoom in Y-axis
+
+ pen_list = [(125, 249, 255), (255,255,0) ]
+
+ for i in range(0, len(self.pv_gateway)):
+ self.curve[i] = self.plot(self.x[0], self.y[0], pen=pen_list[i]) # (0, 253, 235))
+ #self.curve[1] = self.plot(self.x[1], self.y[1], pen=(255,255,0))
+
+ l=pg.LegendItem(offset=(0., 0.5), colCount=1)
+ l.setParentItem(self.graphicsItem())
+
+ l.setLabelTextColor((255, 255, 255))
+ #l.setOffset(-60)
+ for curv, pv in zip(self.curve, self.pv_gateway):
+ l.addItem(curv, pv.pv_name)
+
+ #self.daq_stop()
+ #print(self._bufsize)
+ #print(len(self.x), len(self.y))
+ QApplication.processEvents()
+
+ @Slot(object, int)
+ def receive_monitor_dbr_time(self, pvdata, alarm_severity):
+ _row = self.pv2item_dict[self.sender()]
+ #print("row, value from pvdata==>", _row, pvdata.value[0], self.pv_gateway[_row].pv_name)
+
+ ts_now = pvdata.ts[0] + pvdata.ts[1] * 10**(-9)
+ ts_previous = (self.pvd_previous_list[_row].ts[0] +
+ self.pvd_previous_list[_row].ts[1] * 10**(-9))
+ ts_delta = ts_now - ts_previous
+
+ if (pvdata.ts[0] == self.pvd_previous_list[_row].ts[0]) and (
+ pvdata.ts[1] == self.pvd_previous_list[_row].ts[1]):
+ pvdata.show()
+ self.pvd_previous_list[_row].show()
+ return
+
+ value = pvdata.value[0]
+ #discard first callbacks
+ #if ts_delta > 2.0:
+ # self.pvd_previous_list[_row] = _pvd
+ # return;
+ self.pvd_previous_list[_row] = pvdata
+ self.val_previous[_row] = value
+ #self.pvd_previous_list[_row].ts[0] = _pvd.ts[0]
+ #self.pvd_previous_list[_row].ts[1] = _pvd.ts[1]
+
+ self.databuffer[_row].append(value)
+ self.timebuffer[_row].append(self.time_delta[_row])
+
+ highest_ts = self.timebuffer[0][0] \
+ if self.timebuffer[0][0] is not None else 0
+ for i in range(1, len(self.timebuffer)):
+ if self.timebuffer[i][0] is None:
+ continue
+ elif self.timebuffer[i][0] > highest_ts:
+ highest_ts = self.timebuffer[i][0]
+
+ if self.timebuffer[_row][0] is not None:
+ for i, val in enumerate(self.timebuffer[_row]):
+ if val > highest_ts:
+ self.idx[_row] = i - 1
+ break
+
+ self.y[_row][:] = self.databuffer[_row]
+ self.x[_row][:] = self.timebuffer[_row]
+
+ idx = self.idx[_row]
+ self.x_shifted[_row] = list(map(lambda m : (m - self.time_delta[_row]), self.x[_row][idx:]))
+
+ #print("idx", self.idx)
+
+ self.curve[_row].setData(self.x_shifted[_row], self.y[_row][idx:])
+
+ self.time_delta[_row] = (
+ pvdata.ts[0] + pvdata.ts[1]*10**(-9)) - self.time_zero[0]
+
+ #QApplication.processEvents()
+
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+
+ #self.pv_gateway.receive_monitor_update(value, status, alarm_severity)
+ _row = self.pv2item_dict[self.sender()]
+ #if _row == 1:
+ # return
+ print("row, value===>", _row, value, self.pv_gateway[_row].pv_name)
+ _pvd = self.pv_gateway[_row].cafe.getPVCache(
+ self.pv_gateway[_row].handle)
+
+ #print("value", _pvd.value[0], self.pvd_previous_list[_row].value[0])
+
+ _pvd2 = self.pv_gateway[_row].pvd
+
+ print ("val", value, _pvd2.value[0], _pvd.value[0], self.pvd_previous_list[_row].value[0])
+
+ ts_now = _pvd.ts[0] + _pvd.ts[1] * 10**(-9)
+
+ ts_previous = (self.pvd_previous_list[_row].ts[0] +
+ self.pvd_previous_list[_row].ts[1] * 10**(-9))
+ ts_delta = ts_now - ts_previous
+
+ if value == self.val_previous[_row]:
+ #if (_pvd.ts[0] == self.pvd_previous_list[_row].ts[0]) and (
+ # _pvd.ts[1] == self.pvd_previous_list[_row].ts[1]):
+ _pvd.show()
+ #self.pvd_previous_list[_row].show()
+ return
+
+
+ #discard first callbacks
+ #if ts_delta > 2.0:
+ # self.pvd_previous_list[_row] = _pvd
+ # return;
+ self.pvd_previous_list[_row] = _pvd2
+ self.val_previous[_row] = value
+ #self.pvd_previous_list[_row].ts[0] = _pvd.ts[0]
+ #self.pvd_previous_list[_row].ts[1] = _pvd.ts[1]
+
+ self.databuffer[_row].append(value)
+ self.timebuffer[_row].append(self.time_delta[_row])
+
+ highest_ts = self.timebuffer[0][0] \
+ if self.timebuffer[0][0] is not None else 0
+ for i in range(1, len(self.timebuffer)):
+ if self.timebuffer[i][0] is None:
+ continue
+ elif self.timebuffer[i][0] > highest_ts:
+ highest_ts = self.timebuffer[i][0]
+
+
+ if self.timebuffer[_row][0] is not None:
+ for i, val in enumerate(self.timebuffer[_row]):
+ if val > highest_ts:
+ self.idx[_row] = i - 1
+ break
+
+
+ '''
+ for i in range(1, self.timebuffer):
+ if self.timebuffer[i][0] is not None:
+ a = self.timebuffer[0][0]
+ for i, val in enumerate(self.timebuffer[_row]):
+ if val > a:
+ idx = i - 1
+ break
+ '''
+
+ self.y[_row][:] = self.databuffer[_row]
+ self.x[_row][:] = self.timebuffer[_row]
+
+
+ #self.y[_row][:] = self.databuffer[_row]
+ #self.x[_row][:] = self.timebuffer[_row]
+
+ '''
+ #print(ts_delta, value, self.pvd_previous.value[0])
+ #if (ts_delta < self.ts_delta_max) and (value < self.pvd_previous.value[0]) :
+ if (value < self.pvd_previous.value[0]) :
+ self.data_series_buffer.append(value)
+ self.time_series_buffer.append(ts_now - self.time_zero ) #self.time_delta)
+ self.y_series[:] = self.data_series_buffer
+ self.x_series[:] = self.time_series_buffer
+ #print(self.x_series, self.y_series)
+ #elif ts_delta < 1.0:
+ if len(self.data_series_buffer) > 15:
+ #x_series = np.array(self.time_series, dtype=np.float)
+ #y_series = np.array(self.data_series, dtype=np.float)
+ _x=self.x_series.reshape((-1, 1))
+
+ model = LinearRegression()
+ model.fit(_x, self.y_series)
+ r_sq = model.score(_x, self.y_series)
+ ###JCprint('coefficient of determination:', r_sq, "slope", model.coef_ , "lifetime:", self.y_series[0]/model.coef_ / 3600)
+ #print('intercept:', model.intercept_)
+ #print('slope:', model.coef_)
+ #print('max value', y_series[0], y_series[1])
+ if r_sq > 0.995:
+ _I = self.y_series[0]
+ ###JCprint("lifetime:", _I/model.coef_ / 3600)
+
+
+ y_pred = model.predict(_x)
+ #print("len, y_pred, _x", len(y_pred), len(self.y_series), len(_x))
+ #print('predicted response:', y_pred, sep='\n')
+ m_sq_error = mean_squared_error(self.y_series, y_pred)
+ #print('Mean squared error: {0:.9f}'.format(
+ # mean_squared_error(y_series, y_pred)))
+ #print('Coefficient of determination: {0:.9f}'.format(
+ # r2_score(y_series, y_pred)))
+
+
+
+ self.trigger_series_sequence.emit(self.x_series, self.y_series)
+ #print("emit")
+ self.data_series = []
+ self.time_series = []
+ #print(len(self.x_series), len(self.y_series))
+ else:
+ self.data_series = []
+ self.time_series = []
+
+ '''
+
+
+ #dt = (self.x[-1] - self.x[-2])
+ #print("dt", dt)
+ #Lowet IPCT before trigger is set to t=0
+ idx = self.idx[_row]
+ self.x_shifted[_row] = list(map(lambda m : (m - self.time_delta[_row]), self.x[_row][idx:]))
+
+ ##self.y = np.where(self.y != self.y, 0, self.y) #test for nan
+
+ #print("row len len ", _row, self.time_delta[0], self.time_delta[1])
+
+
+ self.curve[_row].setData(self.x_shifted[_row], self.y[_row][idx:])
+
+ self.time_delta[_row] = (
+ _pvd.ts[0] + _pvd.ts[1]*10**(-9)) - self.time_zero[0]
+
+
+ '''
+ LOOK_BACK = -800
+ if 'ARIDI-PCT2:CURRENT' in self.pv_gateway[_row].pv_name:
+ LOOK_BACK = -250
+
+ if value > self.y[-2]:
+ if not self.found:
+ #print(x_shifted[-240:], self.y[-240:])
+ #self.y = np.where(self.y != self.y, 0, self.y) #test for nan
+ max_index = self.y[LOOK_BACK:].argmax()
+
+ if max_index == 0:
+ return
+ print("max index=", max_index)
+
+ #print(x_shifted[-600+max_index:], self.x[-600+max_index:])
+ #print(self.y[-600+max_index:-2])
+ self.found = True
+ #print("Are Signals blocked??", self.signalsBlocked())
+ self.trigger_decay_sequence.emit(np.array(
+ x_shifted[LOOK_BACK+max_index+9:-2]), self.y[LOOK_BACK+max_index+9:-2])
+ else:
+ self.found = False
+ '''
+
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ print("pv_name==>", pv_name)
+
+ _row = self.pv2item_dict[self.sender()]
+ self.pv_gateway[_row].receive_connect_update(handle, pv_name, status,
+ post_display=False)
+
+ #self.pv_gateway.receive_connect_update(handle, pv_name, status)
+ _pvd = self.pv_gateway[_row].cafe.getPVCache(self.pv_gateway[_row].handle)
+ if self.time_zero[_row] == 0:
+ self.time_zero[_row] = _pvd.ts[0] + _pvd.ts[1]*10**(-9)
+
+ self.pvd_previous = _pvd
+
+
+ #renove highlighting which persists after mouse leaves
+ def mouseMoveEvent(self, event):
+ #event.ignore()
+ pass
+
+ def leaveEvent(self, event):
+ self.clearFocus()
+ del event
+
+
+class CAQPCTChart(PlotWidget):
+ '''Channel access enabled pyqtgraph.PlotWidget'''
+ #trigger_monitor_float = Signal(float, int, int)
+ #trigger_monitor_int = Signal(int, int, int)
+ #trigger_monitor_str = Signal(str, int, int)
+
+ #trigger_connect = Signal(int, str, int)
+
+ trigger_decay_sequence = Signal(np.ndarray, np.ndarray)
+ trigger_series_sequence = Signal(np.ndarray, np.ndarray)
+ #def py_connect_callback(self, handle, pvname, status):
+ # self.trigger_connect.emit(int(handle), str(pvname), int(status))
+ # print("py connect callback", handle, pvname, status)
+
+ def daq_start(self):
+ self.blockSignals(False)
+
+
+ def daq_pause(self):
+ self.blockSignals(True)
+
+ def daq_stop(self):
+ self.blockSignals(True)
+
+
+ def __init__(self, parent=None, pv_list: list = ['PV_NAME_NOT_GIVEN'],
+ monitor_callback=None, pv_within_daq_group: bool = False,
+ color_mode = None, show_units: bool = False, prefix: str = "",
+ suffix: str = "", notify_freq_hz: int = 0):
+ super().__init__()
+
+ self.found = False
+ self.time_zero = 0
+ self.time_delta = 0
+ self.pv_list = pv_list
+ self.pv2item_dict = {}
+ self.pv_gateway = [None] * len(self.pv_list)
+ self.pvd_previous = None
+
+ for i in range (0, len(self.pv_list)):
+ self.pv_gateway[i] = PVGateway().__init__(
+ parent, pv_list[i], monitor_callback, pv_within_daq_group,
+ color_mode, show_units, prefix, suffix,
+ #connect_callback=self.py_connect_callback,
+ connect_triggers=False, notify_freq_hz=notify_freq_hz )
+
+ self.pv_gateway[i].is_initialize_complete()
+
+ self.pv_gateway[i].trigger_connect.connect(
+ self.receive_connect_update)
+
+ self.pv_gateway[i].trigger_monitor_str.connect(
+ self.receive_monitor_update)
+ self.pv_gateway[i].trigger_monitor_int.connect(
+ self.receive_monitor_update)
+ self.pv_gateway[i].trigger_monitor_float.connect(
+ self.receive_monitor_update)
+
+ self.pv_gateway[i].widget_class = "PlotWidget"
+
+ self.pv2item_dict[self.pv_gateway[i]] = i
+
+ self.cafe = self.pv_gateway[0].cafe
+ self.cyca = self.pv_gateway[0].cyca
+ for i in range(0, len(self.pv_gateway)):
+ if self.cafe.isConnected(self.pv_gateway[i].pv_name):
+ self.pv_gateway[i].trigger_connect.emit(
+ self.pv_gateway[i].handle, str(self.pv_gateway[i].pv_name),
+ self.pv_gateway[i].cyca.ICAFE_CS_CONN)
+
+ for i in range(0, len(self.pv_gateway)):
+ if not self.pv_gateway[i].pv_within_daq_group:
+ self.pv_gateway[i].monitor_start()
+
+ sampleinterval=0.333
+ timewindow=1800.0
+
+ self.ts_delta_max = 0.6
+
+ # Data stuff
+ self._interval = int(sampleinterval*1000)
+ self._bufsize = int(timewindow/sampleinterval)
+ self.databuffer = collections.deque([None]*self._bufsize, self._bufsize)
+ self.timebuffer = collections.deque([0]*self._bufsize, self._bufsize)
+
+ _long_size=20
+ self.data_series_buffer = collections.deque([0]*_long_size, _long_size)
+ self.time_series_buffer = collections.deque([0]*_long_size, _long_size)
+
+ self.data_series = []
+ self.time_series = []
+
+ self.iflag_series = 0
+
+ #self.x = np.linspace(-timewindow, 0.0, self._bufsize)
+ self.x = np.zeros(self._bufsize, dtype=np.float)
+ self.y = np.zeros(self._bufsize, dtype=np.float)
+
+ self.x_series = np.zeros(_long_size, dtype=np.float)
+ self.y_series = np.zeros(_long_size, dtype=np.float)
+
+ self.setTitle("PCT(t)") #self.pv_gateway[0].pv_name)
+ self.showGrid(x=True, y=True)
+ self.setLabel('left', 'I', 'mA')
+ self.setLabel('bottom', 'time', 's')
+ self.setBackground((60, 60, 60)) #247, 236, 249))
+ self.setLimits(yMin=-0.11)
+
+ self.plotItem.setMouseEnabled(y=False) # Only allow zoom in X-axis
+ self.plotItem.setMouseEnabled(x=True) # Only allow zoom in Y-axis
+
+ self.curve = self.plot(self.x, self.y, pen=(125, 249, 255)) # (0, 253, 235))
+ #self.curve2 = self.plot(self.x, self.y, pen=(255,255,0))
+
+ l=pg.LegendItem(offset=(0., 0.5))
+ l.setParentItem(self.graphicsItem())
+ l.setLabelTextColor((125, 249, 255))
+ #l.setOffset(-60)
+ l.addItem(self.curve, str(self.pv_gateway[0].pv_name))
+ '''
+ l2=self.addLegend()
+ l2.setLabelTextColor('g')
+ l2.setOffset(10)
+ l2.addItem(self.curve2, str(self.pv_gateway[0].pv_name))
+ '''
+ self.daq_stop()
+ print(self._bufsize)
+ print(len(self.x), len(self.y))
+
+
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+
+ #self.pv_gateway.receive_monitor_update(value, status, alarm_severity)
+ _row = self.pv2item_dict[self.sender()]
+ #print("value===>", value, self.pv_gateway[_row].pv_name)
+ _pvd = self.pv_gateway[_row].cafe.getPVCache(
+ self.pv_gateway[_row].handle)
+
+ ts_now = _pvd.ts[0] + _pvd.ts[1] * 10**(-9)
+ ts_previous = self.pvd_previous.ts[0] + self.pvd_previous.ts[1] * 10**(-9)
+ ts_delta = ts_now - ts_previous
+
+ if (_pvd.ts[0] == self.pvd_previous.ts[0]) and (
+ _pvd.ts[1] == self.pvd_previous.ts[1]):
+ #_pvd.show()
+ return
+
+
+ #discard first callbacks
+ if ts_delta > 2.0:
+ self.pvd_previous = _pvd
+ return;
+
+ self.databuffer.append(value)
+ self.y[:] = self.databuffer
+ self.timebuffer.append(self.time_delta)
+ self.x[:] = self.timebuffer
+
+ #print(ts_delta, value, self.pvd_previous.value[0])
+ #if (ts_delta < self.ts_delta_max) and (value < self.pvd_previous.value[0]) :
+ if (value < self.pvd_previous.value[0]) :
+ self.data_series_buffer.append(value)
+ self.time_series_buffer.append(ts_now - self.time_zero ) #self.time_delta)
+ self.y_series[:] = self.data_series_buffer
+ self.x_series[:] = self.time_series_buffer
+ #print(self.x_series, self.y_series)
+ #elif ts_delta < 1.0:
+ if len(self.data_series_buffer) > 15:
+ #x_series = np.array(self.time_series, dtype=np.float)
+ #y_series = np.array(self.data_series, dtype=np.float)
+ _x=self.x_series.reshape((-1, 1))
+
+ model = LinearRegression()
+ model.fit(_x, self.y_series)
+ r_sq = model.score(_x, self.y_series)
+ ###JCprint('coefficient of determination:', r_sq, "slope", model.coef_ , "lifetime:", self.y_series[0]/model.coef_ / 3600)
+ #print('intercept:', model.intercept_)
+ #print('slope:', model.coef_)
+ #print('max value', y_series[0], y_series[1])
+ if r_sq > 0.995:
+ _I = self.y_series[0]
+ ###JCprint("lifetime:", _I/model.coef_ / 3600)
+
+
+ y_pred = model.predict(_x)
+ #print("len, y_pred, _x", len(y_pred), len(self.y_series), len(_x))
+ #print('predicted response:', y_pred, sep='\n')
+ m_sq_error = mean_squared_error(self.y_series, y_pred)
+ #print('Mean squared error: {0:.9f}'.format(
+ # mean_squared_error(y_series, y_pred)))
+ #print('Coefficient of determination: {0:.9f}'.format(
+ # r2_score(y_series, y_pred)))
+
+
+
+ self.trigger_series_sequence.emit(self.x_series, self.y_series)
+ #print("emit")
+ self.data_series = []
+ self.time_series = []
+ #print(len(self.x_series), len(self.y_series))
+ else:
+ self.data_series = []
+ self.time_series = []
+
+
+ self.pvd_previous = _pvd
+
+ #dt = (self.x[-1] - self.x[-2])
+ #print("dt", dt)
+ #Lowet IPCT before trigger is set to t=0
+ x_shifted= list(map(lambda m : (m - self.time_delta), self.x))
+
+ ##self.y = np.where(self.y != self.y, 0, self.y) #test for nan
+ self.curve.setData(x_shifted, self.y)
+
+ self.time_delta = (
+ _pvd.ts[0] + _pvd.ts[1]*10**(-9)) - self.time_zero
+ #x_shifted2= list(map(lambda m : m -self.time_delta-1 , self.x))
+ #self.curve2.setData(x_shifted2, self.y)
+ #QApplication.processEvents()
+ #print(type(x_shifted), type(self.y), type([1.1]), type(1.1))
+
+ LOOK_BACK = -800
+ if 'ARIDI-PCT2:CURRENT' in self.pv_gateway[_row].pv_name:
+ LOOK_BACK = -250
+
+ if value > self.y[-2]:
+ if not self.found:
+ #print(x_shifted[-240:], self.y[-240:])
+ #self.y = np.where(self.y != self.y, 0, self.y) #test for nan
+ max_index = self.y[LOOK_BACK:].argmax()
+
+ if max_index == 0:
+ return
+ print("max index=", max_index)
+
+ #print(x_shifted[-600+max_index:], self.x[-600+max_index:])
+ #print(self.y[-600+max_index:-2])
+ self.found = True
+ #print("Are Signals blocked??", self.signalsBlocked())
+ self.trigger_decay_sequence.emit(np.array(
+ x_shifted[LOOK_BACK+max_index+9:-2]), self.y[LOOK_BACK+max_index+9:-2])
+ else:
+ self.found = False
+
+
+
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ print("pv_name==>", pv_name)
+
+ _row = self.pv2item_dict[self.sender()]
+ self.pv_gateway[_row].receive_connect_update(handle, pv_name, status,
+ post_display=False)
+
+ #self.pv_gateway.receive_connect_update(handle, pv_name, status)
+ _pvd = self.pv_gateway[_row].cafe.getPVCache(self.pv_gateway[_row].handle)
+ if self.time_zero == 0:
+ self.time_zero = _pvd.ts[0] + _pvd.ts[1]*10**(-9)
+ #print(self.time_zero)
+ self.pvd_previous = _pvd
+
+
+ #renove highlighting which persists after mouse leaves
+ def mouseMoveEvent(self, event):
+ #event.ignore()
+ pass
+
+ def leaveEvent(self, event):
+ self.clearFocus()
+ del event
diff --git a/pvwidgets.py-- b/pvwidgets.py--
new file mode 100644
index 0000000..3e05f6d
--- /dev/null
+++ b/pvwidgets.py--
@@ -0,0 +1,3112 @@
+''' Module with channel access enabled QtWidgets.'''
+__author__ = 'Jan T. M. Chrin'
+
+import re
+import time
+
+import collections
+import numpy as np
+from sklearn.linear_model import LinearRegression
+from distutils.version import LooseVersion
+from functools import reduce as func_reduce
+
+from qtpy.QtCore import QEventLoop, QPoint, Qt, QThread, QTimer, Signal, Slot
+from qtpy.QtGui import (QCloseEvent, QColor, QCursor, QFont, QFontMetricsF,
+ QIcon, QKeySequence)
+from qtpy.QtCore import __version__ as QT_VERSION_STR
+from qtpy.QtWidgets import (QAbstractItemView, QAbstractSpinBox, QAction,
+ QApplication, QBoxLayout, QCheckBox, QComboBox,
+ QDialog, QDockWidget, QDoubleSpinBox, QFrame,
+ QGroupBox, QHBoxLayout, QLabel, QLineEdit,
+ QListWidget, QMenu, QMessageBox, QPushButton,
+ QSpinBox, QStyle, QStyleOptionSpinBox, QTableWidget,
+ QTableWidgetItem, QVBoxLayout, QWidget)
+
+import pyqtgraph as pg
+from pyqtgraph import PlotWidget
+from caqtwidgets.pvgateway import PVGateway
+
+class QTaggedLineEdit(QWidget):
+ def __init__(self, label_text=str(""), value="",
+ position="LEFT", parent=None):
+ super(QTaggedLineEdit, self).__init__(parent)
+ self.parameter = str(value)
+ self.label = QLabel(label_text)
+ self.label.setObjectName("Tagged")
+ self.label.setFixedHeight(24)
+ self.label.setContentsMargins(10, 0, 0, 0)
+ #self.label.setFixedWidth(80)
+ self.line_edit = QLineEdit(self.parameter)
+ self.line_edit.setObjectName("Write")
+ self.line_edit.setFixedHeight(24)
+ font = QFont("sans serif", 16)
+ fm = QFontMetricsF(font)
+ self.line_edit.setMaximumWidth(fm.width(self.parameter)+20)
+ self.label.setBuddy(self.line_edit)
+ layout = QBoxLayout(
+ QBoxLayout.LeftToRight if position == "LEFT" else \
+ QBoxLayout.TopToBottom)
+ layout.addWidget(self.label)
+ layout.addWidget(self.line_edit)
+ layout.addStretch()
+ layout.setSpacing(2)
+ layout.setContentsMargins(0, 0, 0, 0)
+ self.setLayout(layout)
+
+class QHLine(QFrame):
+ def __init__(self):
+ super(QHLine, self).__init__()
+ self.setFrameShape(QFrame.HLine)
+ self.setFrameShadow(QFrame.Sunken)
+
+class QVLine(QFrame):
+ def __init__(self):
+ super(QVLine, self).__init__()
+ self.setFrameShape(QFrame.VLine)
+ self.setFrameShadow(QFrame.Sunken)
+
+class AppQLineEdit(QLineEdit):
+ def __init__(self, parent=None):
+ #super().__init__(parent)
+ pass
+ def leaveEvent(self, event):
+ self.clearFocus()
+ del event
+
+class CAQLineEdit(QLineEdit, PVGateway):
+ '''Channel access enabled QLineEdit widget'''
+ trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_int = Signal(int, int, int)
+ trigger_monitor_str = Signal(str, int, int)
+ trigger_monitor = Signal(object, int)
+ trigger_connect = Signal(int, str, int)
+
+ trigger_daq = Signal(object, str, int)
+ trigger_daq_int = Signal(object, str, int)
+ trigger_daq_str = Signal(object, str, int)
+
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode=None,
+ show_units: bool = False, prefix: str = "", suffix: str = "",
+ notify_freq_hz: int = 0, precision: int = 0):
+
+ super().__init__(parent, pv_name, monitor_callback,
+ pv_within_daq_group, color_mode, show_units, prefix,
+ suffix, connect_callback=self.py_connect_callback,
+ notify_freq_hz=notify_freq_hz, precision=precision)
+
+ self.is_initialize_complete()
+ self.configure_widget()
+
+ if not self.pv_within_daq_group:
+ self.monitor_start()
+
+ def py_connect_callback(self, handle, pvname, status):
+ '''Callback function to be invoked on change of pv connection status.
+ '''
+ self.trigger_connect.emit(int(handle), str(pvname), int(status))
+
+ @Slot(object, str, int)
+ def receive_daq_update(self, daq_pvd, daq_mode, daq_state):
+ PVGateway.receive_daq_update(self, daq_pvd, daq_mode, daq_state)
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+ PVGateway.receive_monitor_update(self, value, status, alarm_severity)
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ PVGateway.receive_connect_update(self, handle, pv_name, status)
+
+ def configure_widget(self):
+ self.setFocusPolicy(Qt.NoFocus)
+
+ fm = QFontMetricsF(QFont("Sans Serif", 10))
+ qrect = fm.boundingRect(self.suggested_text)
+ _width_scaling_factor = 1.15
+ self.setFixedHeight((fm.lineSpacing()*1.8))
+ self.setFixedWidth(((qrect.width()) * _width_scaling_factor))
+
+ if self.pv_within_daq_group:
+ self.qt_property_initial_values(qt_object_name=self.PV_DAQ_CA)
+ else:
+ self.qt_property_initial_values(qt_object_name=self.PV_READBACK)
+
+ #renove highlighting which persists after mouse leaves
+ def mouseMoveEvent(self, event):
+ #event.ignore()
+ pass
+
+ def leaveEvent(self, event):
+ self.clearFocus()
+ del event
+
+class CAQLabel(QLabel, PVGateway):
+ '''Channel access enabled QLabel widget'''
+ trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_int = Signal(int, int, int)
+ trigger_monitor_str = Signal(str, int, int)
+ trigger_monitor = Signal(object, int)
+
+ trigger_connect = Signal(int, str, int)
+
+ trigger_daq = Signal(object, str, int)
+ trigger_daq_int = Signal(object, str, int)
+ trigger_daq_str = Signal(object, str, int)
+
+
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode=None,
+ show_units: bool = False, prefix: str = "", suffix: str = "",
+ notify_freq_hz: int = 0, precision: int = 0):
+
+ super().__init__(parent, pv_name, monitor_callback, pv_within_daq_group,
+ color_mode, show_units, prefix, suffix,
+ connect_callback=self.py_connect_callback,
+ notify_freq_hz=notify_freq_hz, precision=precision)
+
+ self.is_initialize_complete()
+
+ self.configure_widget()
+
+ if self.pv_within_daq_group is False:
+ self.monitor_start()
+
+ def py_connect_callback(self, handle, pvname, status):
+ '''Callback function to be invoked on change of
+ pv connection status.
+ '''
+ self.trigger_connect.emit(int(handle), str(pvname), int(status))
+
+ @Slot(object, str, int)
+ def receive_daq_update(self, daq_pvd, daq_mode, daq_state):
+ PVGateway.receive_daq_update(self, daq_pvd, daq_mode, daq_state)
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+ PVGateway.receive_monitor_update(self, value, status, alarm_severity)
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ PVGateway.receive_connect_update(self, handle, pv_name, status)
+
+ def configure_widget(self):
+ self.setFocusPolicy(Qt.NoFocus)
+
+ fm = QFontMetricsF(QFont("Sans Serif", 10))
+ qrect = fm.boundingRect(self.suggested_text)
+ _width_scaling_factor = 1.15
+
+ self.setFixedHeight((fm.lineSpacing()*1.8))
+ self.setFixedWidth((qrect.width() * _width_scaling_factor))
+
+ if self.pv_within_daq_group:
+ self.qt_property_initial_values(qt_object_name=self.PV_DAQ_CA)
+ else:
+ self.qt_property_initial_values(qt_object_name=self.PV_READBACK)
+
+#For use with CAQMenu
+class QLineEditExtended(QLineEdit):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.parent = parent
+
+ def mousePressEvent(self, event):
+ button = event.button()
+ if button == Qt.RightButton:
+ self.parent.showContextMenu()
+ elif button == Qt.LeftButton:
+ self.parent.mousePressEvent(event)
+
+class CAQMenu(QComboBox, PVGateway):
+ '''Channel access enabled QMenu widget'''
+ trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_int = Signal(int, int, int)
+ trigger_monitor_str = Signal(str, int, int)
+ trigger_monitor = Signal(object, int)
+ trigger_connect = Signal(int, str, int)
+
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode=None,
+ show_units=False, prefix: str = "", suffix: str = ""):
+
+ super().__init__(parent, pv_name, monitor_callback,
+ pv_within_daq_group, color_mode, show_units, prefix,
+ suffix, connect_callback=self.py_connect_callback)
+
+ self.is_initialize_complete()
+
+ self.configure_widget()
+
+ #After configure:widget
+ self.currentIndexChanged.connect(self.value_change)
+
+ if self.pv_within_daq_group is False:
+ self.monitor_start()
+
+ def py_connect_callback(self, handle, pvname, status):
+ '''Callback function to be invoked on change of
+ pv connection status.
+ '''
+ self.trigger_connect.emit(int(handle), str(pvname), int(status))
+
+ def configure_widget(self):
+
+ self.previousIndex = None
+
+ self.setFocusPolicy(Qt.NoFocus)
+ self.setEditable(True)
+ self.setLineEdit(QLineEditExtended(self))
+ self.lineEdit().setReadOnly(True)
+ self.lineEdit().setAlignment(Qt.AlignCenter)
+
+ enumStringList = self.cafe.getEnumStrings(self.handle)
+
+ self.addItems(enumStringList)
+ for i in range(0, self.count()):
+ self.setItemData(i, Qt.AlignCenter, Qt.TextAlignmentRole)
+
+ fm = QFontMetricsF(QFont("Sans Serif", 10))
+ qrect = fm.boundingRect(self.suggested_text)
+
+ _width_scaling_factor = 1.1
+
+ self.setFixedHeight(fm.lineSpacing()*1.8)
+ self.setFixedWidth((qrect.width()+40) * _width_scaling_factor)
+
+ self.qt_property_initial_values(qt_object_name=self.PV_CONTROLLER)
+
+ def post_display_value(self, value):
+ '''Convert value to index'''
+ if "setCurrentIndex" in dir(self):
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+
+ if isinstance(value, str):
+ self.setCurrentIndex(self.cafe.getEnumFromString(self.handle,
+ value))
+
+ elif isinstance(value, int):
+ self.setCurrentIndex(value)
+ #Should not happen
+ elif isinstance(value, float):
+ self.setCurrentIndex(int(value))
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(False)
+
+
+ #self.previousIndex = self.currentIndex()
+ return
+ else:
+ print(("ERROR: overloaded post_display_value: 'setCurrentIndex' "
+ "method does not exist!"))
+
+
+ def value_change(self, indx):
+
+ status = self.cafe.set(self.handle, indx)
+
+ if status != self.cyca.ICAFE_NORMAL:
+ #self.showSetErrorMsg(status)
+
+ value = self.cafe.getCache(self.handle, 'int')
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+
+ if value is not None:
+ self.setCurrentIndex(value)
+ else:
+ if self.previousIndex is not None:
+ self.setCurrentIndex(self.previousIndex)
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(False)
+
+ self.pv_message_in_a_box.setText(
+ "CAQMenu set operation reports error:\n{0}".format(
+ self.cafe.getStatusCodeAsString(status)))
+ self.pv_message_in_a_box.exec()
+
+ def mousePressEvent(self, event):
+
+ button = event.button()
+ if button == Qt.RightButton:
+ PVGateway.mousePressEvent(self, event)
+
+ elif self.pv_info is not None:
+ if self.pv_info.accessWrite == 0:
+ event.ignore()
+ return
+ else:
+ QComboBox.mousePressEvent(self, event)
+
+ self.previousIndex = self.currentIndex()
+
+ def enterEvent(self, event):
+ if self.pv_info is not None:
+ if self.pv_info.accessWrite == 0:
+ for i in range(0, self.count()):
+ self.setItemIcon(i, QIcon(":/forbidden.png"))
+ self.setStyleSheet(
+ ("QComboBox {background: transparent}" +
+ "QComboBox::drop-down {image: url(:/forbidden.png)}"))
+
+ def leaveEvent(self, event):
+ if self.pv_info is not None:
+ if self.pv_info.accessWrite == 0:
+ for i in range(0, self.count()):
+ self.setItemIcon(i, QIcon())
+ self.setStyleSheet(
+ "QComboBox::drop-down {background: transparent}")
+
+
+ #The widget should not gain focus by using the mouse wheel.
+ #This is accomplished by setting the focus policy to Qt.StrongFocus.
+ #The widget should only accept wheel events if it already has the
+ #focus. This is accomplished by reimplementing QWidget.wheelEvent
+ #within a QSpinBox subclass:
+ def wheelEvent(self, event):
+ if self.hasFocus() is False:
+ event.ignore()
+ else:
+ QComboBox.wheelEvent(self, event)
+
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+ '''Triggered by monitor signal'''
+
+ PVGateway.receive_monitor_update(self, value, status, alarm_severity)
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ PVGateway.receive_connect_update(self, handle, pv_name, status)
+
+
+class CAQMessageButton(QPushButton, PVGateway):
+ trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_int = Signal(int, int, int)
+ trigger_monitor_str = Signal(str, int, int)
+ trigger_monitor = Signal(object, int)
+ trigger_connect = Signal(int, str, int)
+
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ notify_freq_hz: int = 0,
+ pv_within_daq_group: bool = False, color_mode=None,
+ show_units=False, msg_label: str = "",
+ msg_press_value=None, msg_release_value=None,
+ start_monitor=False):
+ super().__init__(parent=parent, pv_name=pv_name,
+ monitor_callback=monitor_callback,
+ notify_freq_hz=notify_freq_hz,
+ pv_within_daq_group=pv_within_daq_group,
+ color_mode=color_mode, show_units=show_units,
+ msg_label=msg_label,
+ connect_callback=self.py_connect_callback)
+
+ self.msg_press_value = msg_press_value
+ self.msg_release_value = msg_release_value
+
+ if self.msg_press_value is not None:
+ self.pressed.connect(self.act_on_pressed)
+ if self.msg_release_value is not None:
+ self.released.connect(self.act_on_released)
+
+ self.msg_label = msg_label
+ self.suggested_text = self.msg_label
+ _suggested_text_length = len(self.suggested_text)+3
+ self.suggested_text = self.suggested_text.rjust(_suggested_text_length,
+ "^")
+
+ self.configure_widget()
+
+ self.msg_press_status = self.cyca.ICAFE_NORMAL
+ self.msg_release_status = self.cyca.ICAFE_NORMAL
+ self.msg_report_status = "PV={0}\n".format(self.pv_name)
+ self.msg_has_error = False
+
+ if not self.pv_within_daq_group and start_monitor:
+ self.monitor_start()
+
+ def py_connect_callback(self, handle, pvname, status):
+ '''Callback function to be invoked on change of
+ pv connection status.
+ '''
+ self.trigger_connect.emit(int(handle), str(pvname), int(status))
+
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+ PVGateway.receive_monitor_update(self, value, status, alarm_severity)
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ PVGateway.receive_connect_update(self, handle, pv_name, status)
+
+ def configure_widget(self):
+ self.setFocusPolicy(Qt.StrongFocus)
+ self.setCheckable(True) #Recognizes press and release states
+
+ fm = QFontMetricsF(QFont("Sans Serif", 12))
+ qrect = fm.boundingRect(self.suggested_text)
+
+ _width_scaling_factor = 1.0
+
+ self.setText(self.msg_label)
+ self.setFixedHeight((fm.lineSpacing()*2.0))
+ self.setFixedWidth((qrect.width() * _width_scaling_factor))
+
+ self.qt_property_initial_values(qt_object_name=self.PV_CONTROLLER)
+
+
+ def enterEvent(self, event):
+ if self.pv_info is not None:
+ if self.pv_info.accessWrite == 0:
+ self.setProperty("readOnly", True)
+ self.qt_style_polish()
+
+ def leaveEvent(self, event):
+ if self.property("readOnly"):
+ self.setProperty(self.qt_dynamic_property_get(), True)
+ self.qt_style_polish()
+
+ def mouseReleaseEvent(self, event):
+ if self.msg_release_value is not None:
+ time.sleep(0.1)
+ QPushButton.mouseReleaseEvent(self, event)
+
+ def mousePressEvent(self, event):
+ if self.pv_info is not None:
+ if self.pv_info.accessWrite == 1:
+ QPushButton.mousePressEvent(self, event)
+ if event.button() == Qt.RightButton:
+ PVGateway.mousePressEvent(self, event)
+
+ def act_on_pressed(self):
+ if self.msg_press_value is not None:
+ self.msg_press_status = self.cafe.set(self.handle,
+ self.msg_press_value)
+ if self.msg_press_status != self.cyca.ICAFE_NORMAL:
+ self.msg_report_status += (
+ "Error in set operation (at press button):\n{0}\n".format(
+ self.cafe.getStatusCodeAsString(self.msg_press_status)))
+ self.msg_has_error = True
+ qm = QMessageBox()
+ qm.setText(self.msg_report_status)
+ qm.exec()
+ QApplication.processEvents()
+
+ def act_on_released(self):
+ if self.msg_release_value is not None:
+ self.msg_release_status = self.cafe.set(self.handle,
+ self.msg_release_value)
+ if self.msg_release_status != self.cyca.ICAFE_NORMAL:
+ self.msg_report_status += (
+ "Error in set operation (at release button):\n{0}\n".format(
+ self.cafe.getStatusCodeAsString(self.msg_release_status)))
+ self.msg_has_error = True
+
+ if self.msg_has_error:
+ self.msg_has_error = False
+ self.pv_message_in_a_box.setText(self.msg_report_status)
+ self.pv_message_in_a_box.exec()
+ self.msg_report_status = "PV={0}\n".format(self.pv_name)
+ qm = QMessageBox()
+ qm.setText(self.msg_report_status)
+ qm.exec()
+ QApplication.processEvents()
+
+class CAQTextEntry(QLineEdit, PVGateway):
+ '''Channel access enabled QTextEntry widget'''
+ trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_int = Signal(int, int, int)
+ trigger_monitor_str = Signal(str, int, int)
+ trigger_monitor = Signal(object, int)
+ trigger_connect = Signal(int, str, int)
+
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode=None,
+ show_units=False, prefix: str = "", suffix: str = ""):
+ super().__init__(parent, pv_name, monitor_callback, pv_within_daq_group,
+ color_mode, show_units, prefix, suffix,
+ connect_callback=self.py_connect_callback)
+
+ self.is_initialize_complete() #waits a fraction of a second
+
+ self.currentText = ""
+ self.returnPressed.connect(self.valuechange)
+ self.configure_widget()
+ if self.pv_within_daq_group is False:
+ self.monitor_start()
+
+ def py_connect_callback(self, handle, pvname, status):
+ '''Callback function to be invoked on change of
+ pv connection status.
+ '''
+ self.trigger_connect.emit(int(handle), str(pvname), int(status))
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+ PVGateway.receive_monitor_update(self, value, status, alarm_severity)
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ PVGateway.receive_connect_update(self, handle, pv_name, status)
+
+ def configure_widget(self):
+ self.setFocusPolicy(Qt.StrongFocus)
+
+ fm = QFontMetricsF(QFont("Sans Serif", 12))
+ qrect = fm.boundingRect(self.suggested_text)
+
+ _width_scaling_factor = 1.15
+
+ self.setFixedHeight((fm.lineSpacing()*1.8))
+ self.setFixedWidth(((qrect.width()+10) * _width_scaling_factor))
+
+ self.qt_property_initial_values(qt_object_name=self.PV_CONTROLLER)
+
+ def valuechange(self):
+ status = self.cafe.set(self.handle, self.text())
+ if status != self.cyca.ICAFE_NORMAL:
+ if self.cafe.getNoMonitors(self.handle) == 0:
+ val = self.cafe.get(self.handle, 'native')
+ else:
+ val = self.cafe.getCache(self.handle, 'native')
+
+ if val is not None:
+ if isinstance(val, str):
+ strText = val
+ else:
+ valStr = ("{: .%sf}" % self.precision)
+ strText = valStr.format(round(val, self.precision))
+ print(strText, " precision ", self.precision)
+ self.setText(strText)
+ else:
+ #Do this for TextInfo cache
+ if self.cafe.getNoMonitors(self.handle) == 0:
+ val = self.cafe.get(self.handle, 'native')
+
+ def setText(self, value):
+ QLineEdit.setText(self, value)
+ self.currentText = self.text()
+
+ def enterEvent(self, event):
+ if self.pv_info is not None:
+ if self.pv_info.accessWrite == 0:
+ self.setProperty("readOnly", True)
+ self.qt_style_polish()
+ self.setReadOnly(True)
+ self.setFocusPolicy(Qt.StrongFocus)
+
+ def leaveEvent(self, event):
+
+ if self.isReadOnly():
+ self.setReadOnly(False)
+ self.setProperty(self.qt_dynamic_property_get(), True)
+ self.qt_style_polish()
+
+ if self.text() != self.currentText:
+ QLineEdit.setText(self, self.currentText)
+
+ self.setCursorPosition(100)
+ self.clearFocus()
+ self.setFocusPolicy(Qt.NoFocus)
+ del event
+
+ def mousePressEvent(self, event):
+ if event.button() == Qt.RightButton:
+ PVGateway.mousePressEvent(self, event)
+ self.clearFocus()
+ return
+ local_event_position = QPoint(event.x(), event.y())
+ local_cursor_position = self.cursorPositionAt(local_event_position)
+ self.setCursorPosition(local_cursor_position)
+
+
+class CAQSpinBox(QSpinBox, PVGateway):
+ '''Channel access enabled QTextEntry widget'''
+ trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_int = Signal(int, int, int)
+ trigger_monitor_str = Signal(str, int, int)
+ trigger_monitor = Signal(object, int)
+ trigger_connect = Signal(int, str, int)
+
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode=None,
+ show_units=False, prefix: str = "", suffix: str = ""):
+ super().__init__(parent, pv_name, monitor_callback, pv_within_daq_group,
+ color_mode, show_units, prefix, suffix,
+ connect_callback=self.py_connect_callback)
+
+ self.is_initialize_complete()
+
+ self.valueChanged.connect(self.value_change)
+ self.configure_widget()
+ if not self.pv_within_daq_group:
+ self.monitor_start()
+
+
+ def py_connect_callback(self, handle, pvname, status):
+ '''
+ Callback function to be invoked on change of pv connection
+ status.
+ '''
+ self.trigger_connect.emit(int(handle), str(pvname), int(status))
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+ PVGateway.receive_monitor_update(self, value, status, alarm_severity)
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ PVGateway.receive_connect_update(self, handle, pv_name, status)
+
+ def configure_widget(self):
+ self.previousValue = None
+ self.currentValue = None
+ self.setFocusPolicy(Qt.StrongFocus)
+ self.setButtonSymbols(QAbstractSpinBox.UpDownArrows)
+ self.setAccelerated(False)
+ self.setLineEdit(QLineEditExtended(self))
+ self.lineEdit().setEnabled(True)
+ self.lineEdit().setReadOnly(False)
+ self.lineEdit().setAlignment(Qt.AlignLeft)
+ self.lineEdit().setFont(QFont("Sans Serif", 16))
+
+ fm = QFontMetricsF(QFont("Sans Serif", 12))
+
+ _suggested_text = self.max_control_abs_str
+ _added_text = ""
+
+ if self.show_units:
+ _added_text += " " + self.units
+ _suggested_text += self.units
+ if self.suffix:
+ _added_text += " " + self.suffix
+ _suggested_text += self.suffix
+
+ self.setSuffix(_added_text)
+
+ qrect = fm.boundingRect(_suggested_text)
+ _width_scaling_factor = 1.0
+
+ self.setFixedHeight((fm.lineSpacing()*1.8))
+ self.setFixedWidth(((qrect.width()) * _width_scaling_factor))
+
+ self.qt_property_initial_values(qt_object_name=self.PV_CONTROLLER)
+
+ if self.pv_ctrl is not None:
+ self.setRange(int(self.pv_ctrl.lowerControlLimit),
+ int(self.pv_ctrl.upperControlLimit))
+
+
+ def post_display_value(self, value):
+ '''Convert value to index'''
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+ self.setValue(int(round(value)))
+ self.blockSignals(False)
+ else:
+ self.setValue(int(round(value)))
+
+
+ def mousePressEvent(self, event):
+ _opt = QStyleOptionSpinBox()
+ self.initStyleOption(_opt)
+ _rect_up = self.style().subControlRect(QStyle.CC_SpinBox, _opt,
+ QStyle.SC_SpinBoxUp, self)
+ _rect_down = self.style().subControlRect(QStyle.CC_SpinBox, _opt,
+ QStyle.SC_SpinBoxDown, self)
+
+ self.previousValue = self.value()
+
+ if event.button() == Qt.LeftButton:
+ if _rect_up.contains(event.pos(), proper=True) or \
+ _rect_down.contains(event.pos(), proper=True):
+
+ if not self.cafe.isConnected(self.handle):
+ self.pv_message_in_a_box.setText(
+ ("Spinbox change value events currently suspended\n" +
+ "as channel {0} is disconnected.").format(
+ self.pv_name))
+ self.pv_message_in_a_box.exec()
+ return
+
+ QSpinBox.mousePressEvent(self, event)
+ #Clear Focus: only one step per mouse click.
+ self.clearFocus()
+
+ local_event_position = QPoint(event.x(), event.y())
+ local_cursor_position = self.lineEdit().cursorPositionAt(
+ local_event_position)
+
+ self.lineEdit().setCursorPosition(local_cursor_position)
+
+ PVGateway.mousePressEvent(self, event)
+
+ def setValue(self, intVal):
+ QSpinBox.setValue(self, intVal)
+ self.currentValue = self.value()
+
+ def value_change(self, intVal):
+
+ status = self.cafe.set(self.handle, intVal)
+ if status != self.cyca.ICAFE_NORMAL:
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+
+ if self.previousValue is not None:
+ self.setValue(self.previousValue)
+ else:
+ _value = self.cafe.getCache(self.handle, 'int')
+
+ if _value is not None:
+ self.setValue(_value)
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(False)
+
+ self.pv_message_in_a_box.setText(
+ ("Spinbox set operation reports error:\n{0}"
+ .format(self.cafe.getStatusCodeAsString(status))))
+ self.pv_message_in_a_box.exec()
+
+ else:
+ if self.previousValue is not None:
+ self.setValue(self.previousValue)
+ else:
+ _value = self.cafe.getCache(self.handle, 'int')
+
+ if _value is not None:
+ self.setValue(_value)
+
+ self.parent.statusbar.showMessage(
+ (self.widget_class + " " +
+ self.cafe.getStatusCodeAsString(status)))
+
+
+ def enterEvent(self, event):
+ if self.pv_info is not None:
+ if self.pv_info.accessWrite == 0:
+ self.setProperty("readOnly", True)
+ self.qt_style_polish()
+ self.setReadOnly(True)
+ self.setFocusPolicy(Qt.StrongFocus)
+
+ def leaveEvent(self, event):
+ if self.isReadOnly():
+ self.setReadOnly(False)
+ self.setProperty(self.qt_dynamic_property_get(), True)
+ self.qt_style_polish()
+
+ self.clearFocus()
+ self.setFocusPolicy(Qt.NoFocus)
+ del event
+
+
+ def keyPressEvent(self, event):
+ if event.key() in (Qt.Key_Return, Qt.Key_Enter):
+ QSpinBox.keyPressEvent(self, event)
+ self.clearFocus()
+ elif event.key() in (Qt.Key_Up, Qt.Key_Down):
+ QSpinBox.keyPressEvent(self, event)
+ else:
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+ QSpinBox.keyPressEvent(self, event)
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(False)
+
+
+ # The spin box should not gain focus by using the mouse wheel.
+ # This is accomplished by setting the focus policy to Qt.StrongFocus.
+ # The spin box should only accept wheel events if it already has the focus.
+ # This is accomplished by reimplementing QWidget.wheelEvent within a
+ # QSpinBox subclass:
+ def wheelEvent(self, event):
+ #print("wheelEvent", self.hasFocus())
+ if self.hasFocus() is False:
+ event.ignore()
+ else:
+ QSpinBox.wheelEvent(self, event)
+
+
+class CAQDoubleSpinBox(QDoubleSpinBox, PVGateway):
+ '''Channel access enabled QDoubleSpinBox widget'''
+ trigger_monitor_float = Signal(float, int, int)
+ trigger_monitor_int = Signal(int, int, int)
+ trigger_monitor_str = Signal(str, int, int)
+ trigger_monitor = Signal(object, int)
+ trigger_connect = Signal(int, str, int)
+
+ def __init__(self, parent=None, pv_name: str = "", monitor_callback=None,
+ pv_within_daq_group: bool = False, color_mode=None,
+ show_units: bool = False, prefix: str = "", suffix: str = ""):
+ super().__init__(parent=parent, pv_name=pv_name,
+ monitor_callback=monitor_callback,
+ pv_within_daq_group=pv_within_daq_group,
+ color_mode=color_mode, show_units=show_units,
+ prefix=prefix, suffix=suffix,
+ connect_callback=self.py_connect_callback)
+
+ self.is_initialize_complete()
+ self.valueChanged.connect(self.valuechange)
+ self.configure_widget()
+
+ if self.pv_within_daq_group is False:
+ self.monitor_start()
+
+
+ def py_connect_callback(self, handle, pvname, status):
+ '''
+ Callback function to be invoked on change of pv connection
+ status.
+ '''
+ self.trigger_connect.emit(int(handle), str(pvname), int(status))
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+ PVGateway.receive_monitor_update(self, value, status, alarm_severity)
+
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ PVGateway.receive_connect_update(self, handle, pv_name, status)
+
+ def configure_widget(self):
+ self.previousValue = None
+ self.currentValue = None
+ self.setFocusPolicy(Qt.StrongFocus)
+ self.setButtonSymbols(QAbstractSpinBox.UpDownArrows)
+ self.setAccelerated(False)
+ self.setLineEdit(QLineEditExtended(self))
+ self.lineEdit().setReadOnly(False)
+ self.lineEdit().setAlignment(Qt.AlignRight)
+ self.lineEdit().setFont(QFont("Sans Serif", 12))
+
+ _stepsize = 10**(self.precision * -1)
+ self.setSingleStep(_stepsize)
+ self.setDecimals(self.precision)
+
+ fm = QFontMetricsF(QFont("Sans Serif", 12))
+
+ _suggested_text = self.suggested_text
+ _added_text = ""
+
+ if self.show_units:
+ _added_text += " " + self.units
+ _suggested_text += self.units
+ if self.suffix:
+ _added_text += " " + self.suffix
+ _suggested_text += self.suffix
+
+ self.setSuffix(_added_text)
+
+ qrect = fm.boundingRect(_suggested_text)
+
+ _width_scaling_factor = 1.15
+
+ self.setFixedHeight((fm.lineSpacing()*1.8))
+ self.setFixedWidth(((qrect.width()) * _width_scaling_factor))
+
+ self.qt_property_initial_values(qt_object_name=self.PV_CONTROLLER)
+
+ if self.pv_ctrl is not None:
+ self.setRange(int(self.pv_ctrl.lowerControlLimit),
+ int(self.pv_ctrl.upperControlLimit))
+
+
+ def post_display_value(self, value):
+ '''set value from monitor'''
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+ self.setValue(value)
+ self.blockSignals(False)
+ else:
+ self.setValue(value)
+
+ def mousePressEvent(self, event):
+
+ _opt = QStyleOptionSpinBox()
+ self.initStyleOption(_opt)
+ _rect_up = self.style().subControlRect(QStyle.CC_SpinBox, _opt,
+ QStyle.SC_SpinBoxUp, self)
+ _rect_down = self.style().subControlRect(QStyle.CC_SpinBox, _opt,
+ QStyle.SC_SpinBoxDown, self)
+ self.previousValue = self.value()
+
+ if event.button() == Qt.LeftButton:
+ if _rect_up.contains(event.pos(), proper=False) or \
+ _rect_down.contains(event.pos(), proper=False):
+
+ if not self.cafe.isConnected(self.handle):
+ self.pv_message_in_a_box.setText(
+ ("Spinbox change value events currently suspended\n" +
+ "as channel {0} is disconnected.").format(
+ self.pv_name))
+ self.pv_message_in_a_box.exec()
+ return
+
+ QDoubleSpinBox.mousePressEvent(self, event)
+
+ local_event_position = QPoint(event.x(), event.y())
+ local_cursor_position = self.lineEdit().cursorPositionAt(
+ local_event_position)
+
+ self.lineEdit().setCursorPosition(local_cursor_position)
+
+ PVGateway.mousePressEvent(self, event)
+
+ def mouseReleaseEvent(self, event):
+ self.clearFocus()
+
+ def setValue(self, value):
+ self.currentValue = self.value()
+ QDoubleSpinBox.setValue(self, value)
+
+ def valuechange(self, fval):
+ status = self.cafe.set(self.handle, fval)
+
+ if status != self.cyca.ICAFE_NORMAL:
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+ if self.previousValue is not None:
+ self.setValue(self.previousValue)
+ else:
+ _value = self.cafe.getCache(self.handle, 'float')
+
+ if _value is not None:
+ self.setValue(_value)
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(False)
+
+ self.pv_message_in_a_box.setText(
+ ("Spinbox set operation reports error:\n{0}"
+ .format(self.cafe.getStatusCodeAsString(status))))
+ self.pv_message_in_a_box.exec()
+
+ else:
+ if self.previousValue is not None:
+ self.setValue(self.previousValue)
+ else:
+ _value = self.cafe.getCache(self.handle, 'float')
+
+ if _value is not None:
+ self.setValue(_value)
+
+ self.parent.statusbar.showMessage(
+ (self.widget_class + " " +
+ self.cafe.getStatusCodeAsString(status)))
+
+
+ def enterEvent(self, event):
+ self.setFocusPolicy(Qt.StrongFocus)
+ if self.pv_info is not None:
+ if self.pv_info.accessWrite == 0:
+ self.setProperty("readOnly", True)
+ self.qt_style_polish()
+ self.setReadOnly(True)
+
+ def leaveEvent(self, event):
+ if self.isReadOnly():
+ self.setReadOnly(False)
+ self.setProperty(self.qt_dynamic_property_get(), True)
+ self.qt_style_polish()
+
+ self.clearFocus()
+ self.setFocusPolicy(Qt.NoFocus)
+ del event
+
+ def keyPressEvent(self, event):
+
+ if event.key() in (Qt.Key_Return, Qt.Key_Enter):
+ QDoubleSpinBox.keyPressEvent(self, event)
+ self.clearFocus()
+ elif event.key() in (Qt.Key_Up, Qt.Key_Down):
+ QDoubleSpinBox.keyPressEvent(self, event)
+ else:
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(True)
+ QDoubleSpinBox.keyPressEvent(self, event)
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.blockSignals(False)
+
+ # The spin box should not gain focus by using the mouse wheel.
+ # This is accomplished by setting the focus policy to Qt.StrongFocus.
+ # The spin box should only accept wheel events if it already has the focus.
+ # This is accomplished by reimplementing QWidget.wheelEvent within a
+ # QSpinBox subclass:
+ def wheelEvent(self, event):
+ if self.hasFocus() is False:
+ event.ignore()
+ else:
+ QDoubleSpinBox.wheelEvent(self, event)
+
+
+class reconnectQPushButton(QPushButton, QThread):
+ def __init__(self, parent=None):
+ super().__init__()
+ self.parent = parent
+ self.clicked.connect(self.onClicked)
+ self.isdirty = False
+ self._handles_to_reconnect = []
+ self.reconnectThread = None
+
+ def onClicked(self, event):
+
+ self._handles_to_reconnect = []
+
+ for i in range(0, len(self.parent.pv_gateway)):
+ if self.parent.item(
+ i, self.parent.no_columns-1).checkState() == Qt.Checked:
+ self._handles_to_reconnect.append(
+ self.parent.pv_gateway[i].handle)
+
+ self.reconnect()
+ QApplication.processEvents()
+
+ def reconnect(self):
+ QApplication.processEvents()
+
+ self.isdirty = True
+ if self._handles_to_reconnect:
+ self.parent.cafe.reconnect(self._handles_to_reconnect)
+ self.isdirty = False
+ #Uncheck reconnected channels
+ for i in range(0, len(self.parent.pv_gateway)):
+ if self.parent.item(
+ i, self.parent.no_columns-1).checkState() == Qt.Checked:
+ if self.parent.cafe.isConnected(
+ self.parent.pv_gateway[i].handle):
+ self.parent.item(
+ i, self.parent.no_columns-1).setCheckState(False)
+
+ #Uncheck global reconnect check box
+ self.parent.cb_item_all.setCheckState(Qt.Unchecked)
+
+
+class CAQTableWidget(QTableWidget):
+ '''Channel access enabled QTableWidget widget'''
+ #trigger_monitor_float = Signal(float, int, int)
+ #trigger_monitor_int = Signal(int, int, int)
+ #trigger_monitor_str = Signal(str, int, int)
+ #trigger_connect = Signal(int, str, int)
+
+ def hasNewData(self, _row, pv_data):
+
+ if self.pv_gateway[_row].pvd_previous is None:
+ return True
+
+ newDataFlag = False
+
+ if self.pv_gateway[_row].pvd_previous.ts[1] != pv_data.ts[1]:
+ newDataFlag = True
+ elif self.pv_gateway[_row].pvd_previous.ts[0] != pv_data.ts[0]:
+ newDataFlag = True
+ # Catch disconnect events(!!) and set newDataFlag only
+ elif self.pv_gateway[_row].pvd_previous.status != pv_data.status:
+ newDataFlag = True
+ return newDataFlag
+
+
+ def paint_rows(self, row_range: list = [], reset=False, last_row=[" ", " "],
+ columns=[0]):
+
+ _qcolor_last_line = QColor("#d1e8e9")
+ self.font_pts11 = QTableWidgetItem().font()
+ self.font_pts11.setPixelSize(11)
+ if reset:
+ _qcolor = self.item(0, self.columnCount()-1).background()
+ _start = 0
+ _end = self.rowCount()-1
+ else:
+ _qcolor = _qcolor_last_line
+ _start = row_range[0]
+ _end = row_range[1]
+
+ for _row in range(_start, _end):
+ _cell = QTableWidgetItem("{0}".format(_row+1))
+ if not reset:
+ _cell.setFont(self.font_pts11)
+ _cell.setBackground(_qcolor)
+
+ if 1 in columns:
+ self.item(_row, 0).setBackground(_qcolor)
+ self.item(_row, 0).setFont(self.font_pts11)
+ if 0 in columns:
+ self.setVerticalHeaderItem(_row, _cell)
+
+
+ #last row
+
+ if reset and 0 in columns:
+ _cell = QTableWidgetItem("{0}".format(last_row[0]))
+ _cell.setFont(self.font_pts11)
+ self.setVerticalHeaderItem(self.rowCount()-1, _cell)
+
+ self.item(self.rowCount()-1, 0).setTextAlignment(Qt.AlignCenter)
+ self.item(self.rowCount()-1, 0).setText(str(last_row[1]))
+ self.item(self.rowCount()-1, 0).setBackground(_qcolor)
+ self.item(self.rowCount()-1, 0).setFont(self.font_pts11)
+ elif last_row[0] != " ":
+ _cell = QTableWidgetItem("{0}".format(last_row[0]))
+ _cell.setBackground(_qcolor_last_line)
+ _cell.setFont(self.font_pts11)
+ self.setVerticalHeaderItem(self.rowCount()-1, _cell)
+
+ if columns:
+ self.item(self.rowCount()-1, 0).setTextAlignment(Qt.AlignCenter)
+ self.item(self.rowCount()-1, 0).setText(str(last_row[1]))
+ self.item(self.rowCount()-1, 0).setBackground(_qcolor_last_line)
+ self.item(self.rowCount()-1, 0).setFont(self.font_pts11)
+
+
+ def widget_update(self):
+
+ for _row, pvgate in enumerate(self.pv_gateway):
+ #for _row in range(0, len(self.pv_gateway)):
+ if not pvgate.notify_unison:
+ continue
+ _handle = pvgate.handle
+ _pvd = pvgate.cafe.getPVCache(_handle)
+
+ if _pvd.status in (self.cyca.ICAFE_CS_NEVER_CONN,
+ self.cyca.ICAFE_CA_OP_CONN_DOWN):
+ pvgate.pvd_previous = _pvd
+ continue
+
+ pvgate.pvd_previous = _pvd
+
+ #if timestamps the same - then skip
+ _value = _pvd.value[0]
+ _value = pvgate.format_display_value(_value)
+
+ qtwi = QTableWidgetItem(str(_value)+ " ")
+ f = qtwi.font()
+ f.setPointSize(8)
+ qtwi.setFont(f)
+
+ self.setItem(_row, self.no_columns-3,
+ QTableWidgetItem(qtwi))
+ self.item(_row, self.no_columns-3).setTextAlignment(Qt.AlignRight |
+ Qt.AlignVCenter)
+
+ _ts_date = _pvd.tsDateAsString
+ _ts_str_len = len(_ts_date)
+ _ilength_target = self.format_ts_nano
+
+ while _ts_str_len < _ilength_target:
+ _ts_date += "0"
+ _ilength_target = _ilength_target - 1
+ _ts_str_len = len(_ts_date)
+ _ts_str = _ts_date[0: _ts_str_len - (self.format_ts_nano -
+ self.format_ts_decimal_part)]
+ _ts_str_len = len(_ts_str)
+ _ilength_target = self.format_ts_decimal_part
+ if self.format_ts_decimal_part == self.format_ts_deci:
+ if _ts_str_len == self.format_ts_sec:
+ _ts_str += "."
+ while _ts_str_len < _ilength_target:
+ _ts_str += "0"
+ _ilength_target = _ilength_target -1
+
+ qtwi = QTableWidgetItem(_ts_str)
+ f = qtwi.font()
+ f.setPointSize(8)
+ qtwi.setFont(f)
+
+ self.setItem(_row, self.no_columns-2, QTableWidgetItem(qtwi))
+ self.item(_row, self.no_columns-2).setTextAlignment(Qt.AlignCenter)
+
+ _prop = pvgate.qt_dynamic_property_get()
+
+ alarm_severity = _pvd.alarmSeverity
+
+ if _prop == pvgate.READBACK_ALARM:
+
+ if alarm_severity == pvgate.cyca.SEV_MAJOR:
+ _bgcolor = pvgate.fg_alarm_major
+ _fgcolor = "black"
+ elif alarm_severity == pvgate.cyca.SEV_MINOR:
+ _bgcolor = pvgate.fg_alarm_minor
+ _fgcolor = "black"
+ elif alarm_severity == pvgate.cyca.SEV_INVALID:
+ _bgcolor = pvgate.fg_alarm_invalid
+ _fgcolor = "#777777"
+ else:
+ _bgcolor = pvgate.fg_alarm_noalarm
+ _fgcolor = "black"
+
+ #Colors for bg/fg reversed as is the old norm
+ self.item(_row, self.no_columns-3).setBackground(
+ QColor(_bgcolor))
+ self.item(_row, self.no_columns-2).setBackground(
+ QColor(_bgcolor))
+ self.item(_row, self.no_columns-3).setForeground(
+ QColor(_fgcolor))
+ self.item(_row, self.no_columns-2).setForeground(
+ QColor(_fgcolor))
+
+ elif _prop == pvgate.READBACK_STATIC:
+
+ self.item(_row, self.no_columns-3).setBackground(
+ QColor(pvgate.bg_readback))
+ self.item(_row, self.no_columns-2).setBackground(
+ QColor(pvgate.bg_readback))
+
+ elif _prop == pvgate.DISCONNECTED:
+ self.item(_row, self.no_columns-3).setBackground(
+ QColor("#ffffff"))
+ self.item(_row, self.no_columns-2).setBackground(
+ QColor("#ffffff"))
+ self.item(_row, self.no_columns-3).setForeground(
+ QColor("#777777"))
+ self.item(_row, self.no_columns-2).setForeground(
+ QColor("#777777"))
+
+ else:
+ print(_prop, "widget_update unknown in element/row", _row,
+ _row+1)
+
+ QApplication.processEvents()
+
+ def __init__(self, parent=None, pv_list: list = ["PV_NAME_NOT_GIVEN"],
+ monitor_callback=None, pv_within_daq_group: bool = False,
+ color_mode=None, show_units: bool = True, prefix: str = "",
+ suffix: str = "", ts_res: str = "milli",
+ init_column: bool = False, init_list: list = [],
+ notify_freq_hz: int = 0, notify_unison: bool = True,
+ precision: int = 0, scale_factor: float = 1,
+ show_timestamp: bool = True, pv_list_show: list = None):
+
+ super().__init__()
+ self.columns_dict = {}
+ _column_dict_value = 0
+ self.columns_dict['PV'] = _column_dict_value
+ if init_column:
+ _column_dict_value += 1
+ self.columns_dict['Init'] = _column_dict_value
+ _column_dict_value += 1
+ self.columns_dict['Value'] = _column_dict_value
+ if show_timestamp:
+ _column_dict_value += 1
+ self.columns_dict['Timestamp'] = _column_dict_value
+ _column_dict_value += 1
+ self.columns_dict['Reconnect'] = _column_dict_value
+
+ self.setWindowModality(Qt.ApplicationModal)
+ self.no_columns = _column_dict_value + 1
+
+ self.init_column = init_column
+
+ self.init_list = init_list
+ if self.init_column and not self.init_list:
+ self.init_list = pv_list
+
+ self.icount = 0
+ self.notify_freq_hz = abs(notify_freq_hz)
+ self.notify_freq_hz_default = self.notify_freq_hz
+ self.notify_milliseconds = 0 if self.notify_freq_hz == 0 else \
+ 1000 / self.notify_freq_hz
+
+ self.notify_unison = bool(notify_unison) and bool(self.notify_freq_hz)
+
+ self.precision = precision
+ self.scale_factor = scale_factor
+ self.show_timestamp = show_timestamp
+
+ self.format_ts_nano = 31 #max length of date
+ self.format_ts_micro = 28
+ self.format_ts_milli = 25
+ self.format_ts_deci = 23 #-8
+ self.format_ts_sec = 21
+ if "nano" in ts_res.lower():
+ self.format_ts_decimal_part = self.format_ts_nano
+ elif "micro" in ts_res.lower():
+ self.format_ts_decimal_part = self.format_ts_micro
+ elif "milli" in ts_res.lower():
+ self.format_ts_decimal_part = self.format_ts_milli
+ elif "deci" in ts_res.lower():
+ self.format_ts_decimal_part = self.format_ts_deci
+ elif "sec" in ts_res.lower():
+ self.format_ts_decimal_part = self.format_ts_sec
+ else:
+ self.format_ts_decimal_part = self.format_ts_milli
+
+ self.pv2item_dict = {}
+
+ self.pv_list = pv_list
+ self.pv_gateway = [None] * len(self.pv_list)
+
+ self.pv_list_show = pv_list_show
+ if self.pv_list_show is None:
+ self.pv_list_show = self.pv_list
+
+ _color_mode = [None] * len(self.pv_list)
+
+ if isinstance(color_mode, list):
+ for i in range(0, len(color_mode)):
+ _color_mode[i] = color_mode[i]
+
+ for i in range(0, len(self.pv_list)):
+
+ self.pv_gateway[i] = PVGateway(
+ parent, self.pv_list[i], monitor_callback,
+ pv_within_daq_group, _color_mode[i], show_units, prefix, suffix,
+ connect_triggers=False, notify_freq_hz=self.notify_freq_hz,
+ notify_unison=self.notify_unison, precision=self.precision)
+
+ self.pv_gateway[i].is_initialize_complete()
+ self.pv_gateway[i].trigger_connect.connect(
+ self.receive_connect_update)
+ self.pv_gateway[i].trigger_monitor_str.connect(
+ self.receive_monitor_update)
+ self.pv_gateway[i].trigger_monitor_int.connect(
+ self.receive_monitor_update)
+ self.pv_gateway[i].trigger_monitor_float.connect(
+ self.receive_monitor_update)
+
+ self.pv_gateway[i].widget_class = "QTableWidgetItem"
+
+ self.pv_gateway[i].qt_property_initial_values(
+ qt_object_name=self.pv_gateway[i].PV_READBACK, tool_tip=False)
+
+ #required for reconnect
+ self.cafe = self.pv_gateway[0].cafe
+ self.cyca = self.pv_gateway[0].cyca
+
+ self.timer = None
+ if self.notify_unison:
+ self.timer = QTimer()
+ self.timer.timeout.connect(self.widget_update)
+ self.timer.singleShot(0, self.widget_update)
+ self.timer.start(self.notify_milliseconds)
+
+ self.configure_widget()
+
+ #Connect only deals with colours - only helps on reconnect
+ # In any case monitors take over
+ #Got to do this earlier or emit immediately after!!
+ for i in range(0, len(self.pv_gateway)):
+ if self.cafe.isConnected(self.pv_gateway[i].pv_name):
+ self.pv_gateway[i].trigger_connect.emit(
+ self.pv_gateway[i].handle, str(self.pv_gateway[i].pv_name),
+ self.pv_gateway[i].cyca.ICAFE_CS_CONN)
+
+ for i in range(0, len(self.pv_gateway)):
+ if not self.pv_gateway[i].pv_within_daq_group:
+ self.pv_gateway[i].monitor_start()
+
+ self.update_init_values()
+
+ self.configure_context_menu()
+
+
+ def configure_context_menu(self):
+ self.table_context_menu = QMenu()
+ self.table_context_menu.setObjectName("contextMenu")
+ self.table_context_menu.setWindowModality(Qt.NonModal)
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.table_context_menu.addSection("---")
+
+ action1 = QAction("Configure Table PVs", self)
+ action1.triggered.connect(self.display_table_parameters)
+ self.table_context_menu.addAction(action1)
+
+ if LooseVersion(QT_VERSION_STR) >= LooseVersion("5.3"):
+ self.table_context_menu.addSection("---")
+
+ QApplication.processEvents()
+
+
+ def restore_init_values(self, pv_list: list = []):
+ _set_values_dict = self.get_init_values()
+
+ if not pv_list:
+ _pvs_to_set, _values_to_set = zip(*_set_values_dict.items())
+ #zip returns tuples
+ _pvs_to_set = list(_pvs_to_set)
+ _values_to_set = list(_values_to_set)
+ else:
+ _pvs_to_set = []
+ _values_to_set = []
+ for pv in pv_list:
+ if pv in _set_values_dict.keys():
+ _pvs_to_set.append(pv)
+ _values_to_set.append(_set_values_dict[pv])
+
+ status, status_list = self.cafe.setScalarList(_pvs_to_set,
+ _values_to_set)
+
+ if status != self.cyca.ICAFE_NORMAL:
+ _mess = ("The following device(s) reported an error " +
+ "in 'set' operation:")
+ for i, status_value in enumerate(status_list):
+ if status_value != self.cyca.ICAFE_NORMAL:
+ _mess += ("\n" + _pvs_to_set[i] + " has status = " +
+ str(status_value) + " " +
+ self.cafe.getStatusCodeAsString(status_value) +
+ " " + self.cafe.getStatusInfo(status_value))
+ qm = QMessageBox()
+ qm.setText(_mess)
+
+ qm.exec()
+ QApplication.processEvents()
+
+ self.init_value_button.setEnabled(True)
+
+
+ def is_same_as_init_values(self):
+ _init_values_dict = self.get_column_values(self.columns_dict['Init'])
+ _pvs, _init_values = zip(*_init_values_dict.items())
+ _current_values_dict = self.get_column_values(
+ self.columns_dict['Value'])
+ _pvs, _current_values = zip(*_current_values_dict.items())
+ #zip returns tuples
+
+ return bool(func_reduce(lambda i, j: i and j, map(
+ lambda m, k: m == k, _init_values, _current_values), True))
+
+ #if func_reduce(lambda i, j: i and j, map(
+ # lambda m, k: m == k, _init_values, _current_values), True):
+ # return True
+ #else:
+ # return False
+
+
+ def get_column_values(self, column_no):
+ _values_dict = {}
+ _start = 0
+ _end = len(self.pv_gateway)
+ _pvs = [None] * _end
+ _values_str = [None] * _end
+ _values = [None] * _end
+
+ for _row in range(_start, _end):
+ _values_str[_row] = self.item(_row, column_no).text()
+ _pvs[_row] = self.item(_row, 0).text()
+
+ _value_list = [float(_value_list) for _value_list in re.findall(
+ r'-?\d+\.?\d*', _values_str[_row])]
+
+ if not _value_list:
+ print("row", _row, "values", _values_str[_row], _pvs[_row])
+ _values[_row] = _values_str[_row] #Can be enum string
+ else:
+ _values[_row] = _value_list[0]
+
+ if _pvs[_row] in self.pv_list_show:
+ _values_dict[self.pv_gateway[_row].pv_name] = _values[_row]
+
+ return _values_dict #_pvs_to_set, _values_to_set
+
+
+ def get_init_values(self):
+ return self.get_column_values(self.columns_dict['Init'])
+
+ def get_init_values_previous(self):
+ _set_values_dict = {}
+ _start = 0
+ _end = len(self.pv_gateway)
+ _pvs_to_set = [None] * _end
+ _values_to_set_str = [None] * _end
+ _values_to_set = [None] * _end
+ for _row in range(_start, _end):
+ _values_to_set_str[_row] = self.item(
+ _row, self.columns_dict['Init']).text()
+ _pvs_to_set[_row] = self.item(_row, self.columns_dict['PV']).text()
+
+ _value_list = [float(_value_list) for _value_list in re.findall(
+ r'-?\d+\.?\d*', _values_to_set_str[_row])]
+
+ if not _value_list:
+ print("//row", _row, "values", _values_to_set_str[_row],
+ _pvs_to_set[_row])
+ _values_to_set[_row] = _values_to_set_str[_row] #Can be enum str
+ else:
+ _values_to_set[_row] = _value_list[0]
+
+
+ if _pvs_to_set[_row] in self.init_list:
+ _set_values_dict[
+ self.pv_gateway[_row].pv_name] = _values_to_set[_row]
+
+ return _set_values_dict
+
+
+ def update_init_values(self):
+ _start = 0
+ _end = len(self.pv_gateway)
+
+ for _row in range(_start, _end):
+ _handle = self.pv_gateway[_row].handle
+ _value = self.pv_gateway[_row].cafe.getCache(_handle)
+
+ if _value is not None:
+ if self.scale_factor != 1:
+ _value = _value * self.scale_factor
+ _value = self.pv_gateway[_row].format_display_value(_value)
+
+ qtwi = QTableWidgetItem(str(_value)+ " ")
+ _f = qtwi.font()
+ _f.setPointSize(8)
+ qtwi.setFont(_f)
+ self.setItem(_row, 1, qtwi)
+ self.item(_row, 1).setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
+
+
+ def configure_widget(self):
+
+ _column_width_pvname = 180
+ _column_width_value = 90
+ _column_width_timestamp = 210
+ _column_width_checkbox = 22
+
+ self.setRowCount(len(self.pv_gateway)+1)
+ self.setColumnCount(self.no_columns)
+ self.setEditTriggers(QAbstractItemView.NoEditTriggers)
+ self.resizeColumnsToContents()
+ self.resizeRowsToContents()
+ #self.horizontalHeader().setStretchLastSection(True);
+ self.setColumnWidth(self.columns_dict['PV'], _column_width_pvname)
+
+ self.setColumnWidth(self.columns_dict['Value'], _column_width_value)
+ if 'Init' in self.columns_dict.keys():
+ self.setColumnWidth(self.columns_dict['Init'], _column_width_value)
+ if 'Timestamp' in self.columns_dict.keys():
+ self.setColumnWidth(self.columns_dict['Timestamp'],
+ _column_width_timestamp)
+ self.setColumnWidth(self.columns_dict['Reconnect'],
+ _column_width_checkbox)
+
+ _pv_column = self.columns_dict['PV']
+
+ for i in range(0, len(self.pv_gateway)):
+ qtwt = QTableWidgetItem(self.pv_list_show[i])
+ f = qtwt.font()
+ f.setPointSize(8)
+ qtwt.setFont(f)
+
+ self.setItem(i, _pv_column, qtwt)
+ self.item(i, _pv_column).setTextAlignment(Qt.AlignHCenter |
+ Qt.AlignVCenter)
+ for i_column in range(1, self.no_columns-1):
+ self.setItem(i, i_column, QTableWidgetItem(str("")))
+ self.item(i, i_column).setTextAlignment(Qt.AlignHCenter |
+ Qt.AlignVCenter)
+ self.pv2item_dict[self.pv_gateway[i]] = i
+
+ cb_item = QTableWidgetItem()
+ cb_item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
+ cb_item.setCheckState(Qt.Unchecked)
+ cb_item.setTextAlignment(Qt.AlignCenter)
+ cb_item.setToolTip(self.pv_gateway[i].pv_name)
+
+ self.setItem(i, self.no_columns-1, cb_item)
+ self.item(i, self.no_columns-1).setTextAlignment(Qt.AlignCenter)
+
+ if self.init_column:
+ self.init_widget = QWidget()
+ _init_layout = QHBoxLayout(self.init_widget)
+ self.init_value_button = QPushButton()
+ self.init_value_button.setText("Update")
+ _f = self.init_value_button.font()
+ _f.setPointSize(8)
+ self.init_value_button.setFont(_f)
+ self.init_value_button.setFixedWidth(80)
+ self.init_value_button.clicked.connect(self.update_init_values)
+ self.init_value_button.setToolTip(
+ ("Stores initial, pre-measurement value. Update is also " +
+ "typically executed automatically before new optics are set."))
+ _init_layout.addWidget(self.init_value_button)
+ _init_layout.setAlignment(Qt.AlignRight)
+ _init_layout.setContentsMargins(1, 1, 0, 0) #Required
+ self.init_widget.setLayout(_init_layout)
+ self.setCellWidget(len(self.pv_gateway), 1, self.init_widget)
+
+ _restore_widget = QWidget()
+ _restore_layout = QHBoxLayout(_restore_widget)
+ self.restore_value_button = QPushButton()
+ self.restore_value_button.setStyleSheet(
+ "QPushButton{background-color: rgb(212, 219, 157);}")
+ self.restore_value_button.setText("Restore")
+ _f = self.restore_value_button.font()
+ _f.setPointSize(8)
+ self.restore_value_button.setFont(_f)
+ self.restore_value_button.setFixedWidth(80)
+ self.restore_value_button.clicked.connect(self.restore_init_values)
+ self.restore_value_button.setToolTip(
+ ("Restore devices to their pre-measurement values"))
+ _restore_layout.addWidget(self.restore_value_button)
+ _restore_layout.setAlignment(Qt.AlignRight)
+ _restore_layout.setContentsMargins(1, 1, 0, 0)
+ _restore_widget.setLayout(_restore_layout)
+ self.setCellWidget(len(self.pv_gateway), 2, _restore_widget)
+
+ #Do not display no for last row (Reconnect button)
+ _row_digit_last_cell = QTableWidgetItem(str(""))
+ self.setVerticalHeaderItem(len(self.pv_gateway), _row_digit_last_cell)
+ self.setItem(len(self.pv_gateway), 0, QTableWidgetItem(str("")))
+
+ _qwb = QWidget()
+
+ self.reconnect_button = reconnectQPushButton(self) #self required
+
+ f = self.reconnect_button.font()
+
+ if 'Timestamp' in self.columns_dict.keys():
+ f.setPointSize(8)
+ self.reconnect_button.setFixedWidth(100)
+ else:
+ f.setPointSize(6)
+ self.reconnect_button.setFixedWidth(58)
+
+ self.reconnect_button.setFont(f)
+
+ self.reconnect_button.setText("Reconnect")
+
+ _layout = QHBoxLayout(_qwb)
+ _layout.addWidget(self.reconnect_button)
+ _layout.setAlignment(Qt.AlignCenter)
+ _layout.setContentsMargins(0, 0, 0, 0) #Required
+
+ #_reconnect_button
+ self.setCellWidget(len(self.pv_gateway), self.no_columns-2, _qwb)
+
+ self.cb_item_all = QCheckBox()
+ self.cb_item_all.setCheckState(Qt.Unchecked)
+ self.cb_item_all.stateChanged.connect(self.reconnectStateChanged)
+ self.cb_item_all.setObjectName("Reconnect")
+
+ self.setCellWidget(len(self.pv_gateway), self.no_columns-1,
+ self.cb_item_all)
+
+ header_item = QTableWidgetItem("Process Variable")
+
+ self.setHorizontalHeaderItem(self.columns_dict['PV'], header_item)
+
+ if 'Init' in self.columns_dict.keys():
+ self.setHorizontalHeaderItem(self.columns_dict['Init'],
+ QTableWidgetItem("Initial Value"))
+
+ self.setHorizontalHeaderItem(self.columns_dict['Value'],
+ QTableWidgetItem("Value"))
+
+ if 'Timestamp' in self.columns_dict.keys():
+ self.setHorizontalHeaderItem(self.columns_dict['Timestamp'],
+ QTableWidgetItem("Timestamp"))
+ self.setHorizontalHeaderItem(self.columns_dict['Reconnect'],
+ QTableWidgetItem("R"))
+ self.setFocusPolicy(Qt.NoFocus)
+ self.setEditTriggers(QAbstractItemView.NoEditTriggers)
+ self.setSelectionMode(QAbstractItemView.NoSelection)
+
+ self.verticalHeader().setDefaultAlignment(Qt.AlignRight)
+ self.verticalHeader().setFixedWidth(22)
+
+ _fm_font = QFont("Sans Serif")
+ _fm_font.setPointSize(12)
+ fm = QFontMetricsF(_fm_font)
+
+ _factor = 1
+ if LooseVersion(QT_VERSION_STR) < LooseVersion("5.0"):
+ _factor = 1.18
+
+ self.setFixedHeight(
+ int(fm.lineSpacing() * _factor * (len(self.pv_gateway)+3)))
+ _min_table_width = 620 if not self.init_column else 650
+ self.setMinimumWidth(_min_table_width)
+
+ for _row in range(0, len(self.pv_gateway)):
+ self.item(_row, _pv_column).setForeground(QColor("#000000"))
+
+ for i_column in range(1, self.no_columns-2):
+ self.item(_row, i_column).setForeground(QColor("#000000"))
+ self.item(_row, i_column).setTextAlignment(Qt.AlignRight |
+ Qt.AlignVCenter)
+
+ self.item(_row, self.columns_dict['Value']).setBackground(
+ QColor("#ffffff"))
+ if 'Timestamp' in self.columns_dict.keys():
+ self.item(_row,
+ self.columns_dict['Timestamp']).setTextAlignment(
+ Qt.AlignCenter)
+ self.item(_row,
+ self.columns_dict['Timestamp']).setBackground(
+ QColor("#ffffff"))
+
+ @Slot(int)
+ def reconnectStateChanged(self, state):
+ if state == Qt.Unchecked:
+ for i in range(0, len(self.pv_gateway)):
+ self.item(i, self.columns_dict['Reconnect']).setCheckState(
+ Qt.Unchecked)
+ else:
+ for i in range(0, len(self.pv_gateway)):
+ self.item(i, self.columns_dict['Reconnect']).setCheckState(
+ Qt.Checked)
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+
+ _row = self.pv2item_dict[self.sender()]
+ self.pv_gateway[_row].time_monotonic = time.monotonic()
+ if self.scale_factor != 1:
+ value = value * self.scale_factor
+ _value = self.pv_gateway[_row].format_display_value(value)
+
+ qtwi = QTableWidgetItem(str(_value) + " ")
+ f = qtwi.font()
+ f.setPointSize(8)
+ qtwi.setFont(f)
+ self.setItem(_row, self.columns_dict['Value'], qtwi)
+ self.item(_row, self.columns_dict['Value']).setTextAlignment(
+ Qt.AlignRight | Qt.AlignVCenter)
+
+ if 'Timestamp' in self.columns_dict.keys():
+ _handle = self.pv_gateway[_row].handle
+ _pvd = self.pv_gateway[_row].cafe.getPVCache(_handle)
+ _ts_date = _pvd.tsDateAsString
+ _ts_str_len = len(_ts_date)
+ _ilength_target = self.format_ts_nano
+
+ while _ts_str_len < _ilength_target:
+ _ts_date += "0"
+ _ilength_target = _ilength_target -1
+
+ ##ts_str_len = len(_ts_date)
+ _ts_str = _ts_date[0: _ts_str_len-(
+ self.format_ts_nano-self.format_ts_decimal_part)]
+ _ts_str_len = len(_ts_str)
+
+ _ilength_target = self.format_ts_decimal_part
+ if self.format_ts_decimal_part == self.format_ts_deci:
+ if _ts_str_len == self.format_ts_sec:
+ _ts_str += "."
+ while _ts_str_len < _ilength_target:
+ _ts_str += "0"
+ _ilength_target = _ilength_target -1
+
+ qtwi = QTableWidgetItem(_ts_str)
+ f = qtwi.font()
+ f.setPointSize(8)
+ qtwi.setFont(f)
+
+ self.setItem(_row, self.columns_dict['Timestamp'], qtwi)
+ self.item(_row, self.columns_dict['Timestamp']).setTextAlignment(
+ Qt.AlignCenter)
+
+ _prop = self.pv_gateway[_row].qt_dynamic_property_get()
+
+ if _prop == self.pv_gateway[_row].READBACK_ALARM:
+
+ if alarm_severity == self.pv_gateway[_row].cyca.SEV_MAJOR:
+ _bgcolor = self.pv_gateway[_row].settings.fgAlarmMajor
+ _fgcolor = "black"
+ elif alarm_severity == self.pv_gateway[_row].cyca.SEV_MINOR:
+ _bgcolor = self.pv_gateway[_row].settings.fgAlarmMinor
+ _fgcolor = "black"
+ elif alarm_severity == self.pv_gateway[_row].cyca.SEV_INVALID:
+ _bgcolor = self.pv_gateway[_row].settings.fgAlarmInvalid
+ _fgcolor = "#777777"
+ else:
+ _bgcolor = self.pv_gateway[_row].settings.fgAlarmNoAlarm
+ _fgcolor = "black"
+
+ #Colors for bg/fg reversed as is the old norm
+ self.item(_row, self.columns_dict['Value']).setBackground(
+ QColor(_bgcolor))
+ self.item(_row, self.columns_dict['Value']).setForeground(
+ QColor(_fgcolor))
+ if 'Timestamp' in self.columns_dict.keys():
+ self.item(_row, self.columns_dict['Timestamp']).setBackground(
+ QColor(_bgcolor))
+ self.item(_row, self.columns_dict['Timestamp']).setForeground(
+ QColor(_fgcolor))
+
+
+ elif _prop == self.pv_gateway[_row].DISCONNECTED or \
+ alarm_severity == self.pv_gateway[_row].cyca.SEV_INVALID:
+ self.item(_row, self.columns_dict['Value']).setBackground(
+ QColor("#ffffff"))
+ self.item(_row, self.columns_dict['Value']).setForeground(
+ QColor("#777777"))
+
+ if 'Timestamp' in self.columns_dict.keys():
+ self.item(_row, self.columns_dict['Timestamp']).setBackground(
+ QColor("#ffffff"))
+ self.item(_row, self.columns_dict['Timestamp']).setForeground(
+ QColor("#777777"))
+
+
+ elif _prop == self.pv_gateway[_row].READBACK_STATIC:
+ self.item(_row, self.columns_dict['Value']).setBackground(
+ QColor(self.pv_gateway[_row].bg_readback))
+ if 'Timestamp' in self.columns_dict.keys():
+ self.item(_row, self.columns_dict['Timestamp']).setBackground(
+ QColor(self.pv_gateway[_row].bg_readback))
+ else:
+
+ print(_prop, self.pv_gateway[_row].DISCONNECTED,
+ "(in monitor) unknown in element/row no.", _row, _row+1)
+
+ QApplication.processEvents(QEventLoop.AllEvents, 10)
+
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ _row = self.pv2item_dict[self.sender()]
+
+ self.pv_gateway[_row].receive_connect_update(handle, pv_name, status,
+ post_display=False)
+
+ _prop = self.pv_gateway[_row].qt_dynamic_property_get()
+
+ #self.post_display_value(status)
+ if _prop == self.pv_gateway[_row].DISCONNECTED:
+ self.item(_row, self.columns_dict['Value']).setBackground(
+ QColor("#ffffff"))
+ self.item(_row, self.columns_dict['Value']).setForeground(
+ QColor("#777777"))
+ if 'Timestamp' in self.columns_dict.keys():
+ self.item(_row, self.columns_dict['Timestamp']).setBackground(
+ QColor("#ffffff"))
+ self.item(_row, self.columns_dict['Timestamp']).setForeground(
+ QColor("#777777"))
+
+ QApplication.processEvents()
+
+ def table_precision_user_changed(self, new_value):
+ self.pvgateway_precision = new_value
+
+ for pvgate in self.pv_gateway:
+ if pvgate.pv_ctrl is not None:
+ self.pvgateway_precision = min(pvgate.pv_ctrl.precision,
+ new_value)
+
+ pvgate.precision_user = self.pvgateway_precision
+ pvgate.precision = self.pvgateway_precision
+
+ _pvd = self.cafe.getPVCache(pvgate.handle)
+
+ if _pvd.value[0] is not None:
+ if isinstance(_pvd.value[0], float):
+ pvgate.trigger_monitor_float.emit(
+ _pvd.value[0], _pvd.status, _pvd.alarmSeverity)
+
+
+ def table_precision_ioc_reset(self):
+ if self.max_precision_value == self.table_precision_user_wgt.value():
+ self.table_precision_user_changed(self.max_precision_value)
+ else:
+ self.table_precision_user_wgt.setValue(self.max_precision_value)
+
+ def table_refresh_rate_changed(self, new_idx):
+
+ _notify_freq_hz = self.refresh_freq_combox_idx_dict[new_idx]
+ _notify_milliseconds = 0 if _notify_freq_hz == 0 else \
+ 1000 / _notify_freq_hz
+
+ self.notify_freq_hz = _notify_freq_hz
+
+ if _notify_milliseconds == 0:
+ for pvgate in self.pv_gateway:
+ pvgate.notify_unison = False
+ pvgate.notify_milliseconds = _notify_milliseconds
+ pvgate.notify_freq_hz = self.notify_freq_hz
+ pvgate.monitor_stop()
+ time.sleep(0.01)
+ for pvgate in self.pv_gateway:
+ pvgate.monitor_start()
+
+ else:
+ for pvgate in self.pv_gateway:
+ if not pvgate.notify_unison:
+ pvgate.monitor_stop()
+
+ for pvgate in self.pv_gateway:
+ pvgate.notify_milliseconds = _notify_milliseconds
+ pvgate.notify_freq_hz = self.notify_freq_hz
+
+ if not pvgate.notify_unison:
+ pvgate.notify_unison = True
+ pvgate.monitor_start()
+ else:
+
+ self.cafe.updateMonitorPolicyDeltaMS(
+ pvgate.handle, pvgate.monitor_id,
+ pvgate.notify_milliseconds)
+
+ if self.timer is not None:
+ self.timer.stop()
+ else:
+ self.timer = QTimer()
+ self.timer.timeout.connect(self.widget_update)
+ self.timer.singleShot(0, self.widget_update)
+
+ if _notify_milliseconds > 0:
+ self.timer.start(_notify_milliseconds)
+
+ def table_ts_resolution_changed(self, new_idx):
+
+ for i, ts_res in enumerate(self.ts_combox_idx_dict.values()):
+ if i == new_idx:
+ self.format_ts_decimal_part = ts_res
+ break
+
+ for pvgate in self.pv_gateway:
+ _pvd = self.cafe.getPVCache(pvgate.handle)
+ if _pvd.value[0] is not None:
+ if isinstance(_pvd.value[0], float):
+ pvgate.trigger_monitor_float.emit(
+ _pvd.value[0], _pvd.status, _pvd.alarmSeverity)
+ elif isinstance(_pvd.value[0], int):
+ pvgate.trigger_monitor_int.emit(
+ _pvd.value[0], _pvd.status, _pvd.alarmSeverity)
+ else:
+ pvgate.trigger_monitor_str.emit(
+ str(_pvd.value[0]), _pvd.status, _pvd.alarmSeverity)
+
+
+ def display_table_parameters(self):
+ display_wgt = QDialog(self)
+ display_wgt.setWindowTitle("PV Parameters")
+ layout = QVBoxLayout()
+ common_label_width = 120
+ common_wgt_width = 160
+ common_hbox_width = common_label_width + common_wgt_width + 20
+
+ self.initial_value = 0
+ self.max_precision_value = 0
+ for i, pvgate in enumerate(self.pv_gateway):
+ if pvgate.pv_ctrl is not None:
+ if pvgate.pv_ctrl.precision > 0:
+ self.max_precision_value = max(self.max_precision_value,
+ pvgate.pv_ctrl.precision)
+ self.initial_value = max(self.initial_value,
+ pvgate.precision)
+
+ if self.max_precision_value > 0:
+ #precision user
+ _hbox_wgt = QWidget()
+ _hbox = QHBoxLayout()
+ precision_user_label = QLabel("Precision (user):")
+ self.table_precision_user_wgt = QSpinBox(self)
+ self.table_precision_user_wgt.setFocusPolicy(Qt.NoFocus)
+ self.table_precision_user_wgt.setValue(self.initial_value)
+ self.table_precision_user_wgt.setMaximum(self.max_precision_value)
+ self.table_precision_user_wgt.valueChanged.connect(
+ self.table_precision_user_changed)
+ precision_user_label.setAlignment(Qt.AlignLeft)
+ self.table_precision_user_wgt.setAlignment(Qt.AlignLeft)
+ _hbox.addWidget(precision_user_label)
+ _hbox.addWidget(self.table_precision_user_wgt)
+ _hbox.setAlignment(Qt.AlignLeft)
+ _hbox_wgt.setLayout(_hbox)
+
+ precision_user_label.setFixedWidth(common_label_width)
+ self.table_precision_user_wgt.setFixedWidth(40)
+ _hbox_wgt.setFixedWidth(common_hbox_width)
+
+ #precision ioc
+ _hbox2_wgt = QWidget()
+ _hbox2 = QHBoxLayout()
+ precision_ioc_label = QLabel("Precision (ioc): ")
+ precision_ioc = QPushButton(self)
+ precision_ioc.setText("Reset")
+ precision_ioc.clicked.connect(self.table_precision_ioc_reset)
+ precision_ioc_label.setAlignment(Qt.AlignLeft)
+
+ _hbox2.addWidget(precision_ioc_label)
+ _hbox2.addWidget(precision_ioc)
+ _hbox2.setAlignment(Qt.AlignLeft)
+
+ _hbox2_wgt.setLayout(_hbox2)
+
+ precision_ioc_label.setFixedWidth(common_label_width)
+ precision_ioc.setFixedWidth(50)
+
+ _hbox2_wgt.setFixedWidth(common_hbox_width)
+
+ layout.addWidget(_hbox_wgt)
+ layout.addWidget(_hbox2_wgt)
+
+ if 'Timestamp' in self.columns_dict.keys():
+ #time-stamp
+ _hbox4_wgt = QWidget()
+ _hbox4 = QHBoxLayout()
+ ts_label = QLabel("Timestamp: ")
+
+ self.ts_combox_idx_dict = {
+ 'second (s)': self.format_ts_sec,
+ 'decisecond (ds)': self.format_ts_deci,
+ 'millisecond (ms)': self.format_ts_milli,
+ 'microsecond (\u03bcs)': self.format_ts_micro,
+ 'nanosecond (ns)': self.format_ts_nano}
+
+ ts_resolution = QComboBox(self)
+ for key, ts_res in self.ts_combox_idx_dict.items():
+ ts_resolution.addItem(key)
+
+ _current_idx = 0
+
+ for i, (key, ts_res) in enumerate(self.ts_combox_idx_dict.items()):
+ if ts_res == self.format_ts_decimal_part:
+ _current_idx = i
+ break
+
+ ts_resolution.setCurrentIndex(_current_idx)
+ ts_resolution.currentIndexChanged.connect(
+ self.table_ts_resolution_changed)
+
+ _hbox4.addWidget(ts_label)
+ _hbox4.addWidget(ts_resolution)
+ _hbox4_wgt.setLayout(_hbox4)
+
+ ts_label.setFixedWidth(common_label_width)
+ ts_resolution.setFixedWidth(common_wgt_width)
+ _hbox4_wgt.setFixedWidth(common_hbox_width)
+
+ layout.addWidget(_hbox4_wgt)
+
+ #precision refresh rate
+ _hbox3_wgt = QWidget()
+ _hbox3 = QHBoxLayout()
+ refresh_freq_label = QLabel("Refresh rate: ")
+ #_default_refresh_val = 0 if self.notify_freq_hz <= 0 else \
+ # self.notify_freq_hz
+ _default_refresh_val = 0 if self.notify_freq_hz_default <= 0 else \
+ self.notify_freq_hz_default
+
+ self.refresh_freq_combox_idx_dict = {0: 0, 1: 10, 2: 5, 3: 2, 4: 1,
+ 5: 0.5, 6: _default_refresh_val}
+ refresh_freq = QComboBox(self)
+ refresh_freq.addItem('direct')
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[1]))
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[2]))
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[3]))
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[4]))
+ refresh_freq.addItem('{0} Hz'.format(
+ self.refresh_freq_combox_idx_dict[5]))
+
+ _default_text = 'default (direct)' if _default_refresh_val == 0 else \
+ 'default ({0} Hz)'.format(self.refresh_freq_combox_idx_dict[6])
+
+ refresh_freq.addItem(_default_text)
+
+ for key, value in self.refresh_freq_combox_idx_dict.items():
+ if value == self.notify_freq_hz:
+ refresh_freq.setCurrentIndex(key)
+ break
+
+ refresh_freq.currentIndexChanged.connect(
+ self.table_refresh_rate_changed)
+
+ _hbox3.addWidget(refresh_freq_label)
+ _hbox3.addWidget(refresh_freq)
+ _hbox3_wgt.setLayout(_hbox3)
+
+ refresh_freq_label.setFixedWidth(common_label_width)
+ refresh_freq.setFixedWidth(common_wgt_width)
+ _hbox3_wgt.setFixedWidth(common_hbox_width)
+
+ layout.addWidget(_hbox3_wgt)
+
+ layout.setAlignment(Qt.AlignLeft)
+ layout.setContentsMargins(10, 0, 0, 0)
+ layout.setSpacing(0)
+
+ display_wgt.setMinimumWidth(340)
+ display_wgt.setLayout(layout)
+
+ display_wgt.exec()
+
+
+ def mousePressEvent(self, event):
+ row = self.indexAt(event.pos()).row()
+
+ if row > -1:
+ if row < len(self.pv_list):
+ self.pv_gateway[row].mousePressEvent(event)
+ else:
+ button = event.button()
+ if button == Qt.RightButton:
+ self.table_context_menu.exec(QCursor.pos())
+ self.clearFocus()
+
+ #remove highlighting which persists after mouse leaves
+ def mouseMoveEvent(self, event):
+ pass
+
+ def leaveEvent(self, event):
+ self.clearSelection()
+ self.clearFocus()
+ del event
+
+
+class QMessageWidget(QListWidget):
+ """Log message window."""
+ def __init__(self, parent=None):
+ super(QMessageWidget, self).__init__(parent)
+ self.myItem = None
+ self.setSelectionMode(QAbstractItemView.ExtendedSelection)
+ self.setFocusPolicy(Qt.StrongFocus)
+
+ def leaveEvent(self, event):
+ if self.myItem:
+ self.clearSelection()
+ self.clearFocus()
+ del event
+
+ def mousePressEvent(self, event):
+ item = self.itemAt(event.x(), event.y())
+ if item:
+ self.myItem = item
+ self.setCurrentItem(self.myItem)
+
+ def keyPressEvent(self, event):
+ if event.matches(QKeySequence.Copy):
+ nitem = event.count()
+ if nitem:
+ if self.myItem is not None:
+ _str = self.myItem.text()
+ QApplication.clipboard().setText(_str)
+
+
+
+class QResultsWidget:
+ """Results table"""
+ def __init__(self, summary_dict=None, table_dict=None):
+
+ self.summary_dict = summary_dict
+ self.table_dict = table_dict
+ self._group_box = None
+
+ def group_box(self, title=""):
+ self._group_box = QGroupBox(title)
+ self._group_box.setObjectName("OUTERLEFT")
+ _vbox = QVBoxLayout()
+ _qspace = QFrame()
+ _qspace.setFixedHeight(10)
+ _vbox.addWidget(_qspace)
+
+ _font = QFont("Sans Serif", 10)
+
+ longest_str_item1 = ""
+ longest_str_item2 = ""
+
+ for i, (label, text) in enumerate(self.summary_dict.items()):
+ if len(str(label)) > len(longest_str_item1):
+ longest_str_item1 = str(label)
+ if len(str(text)) > len(longest_str_item2):
+ longest_str_item2 = str(text)
+
+ fm = QFontMetricsF(_font)
+
+ _factor = 1.15
+
+ if LooseVersion(QT_VERSION_STR) < LooseVersion("5.0"):
+ _factor = 1.18
+
+ qrect1 = fm.boundingRect(longest_str_item1)
+ qrect2 = fm.boundingRect(longest_str_item2)
+ _width_scaling_factor = 1.5
+ _width_scaling_factor_le = 1.15
+ _widget_height = 25
+ for i, (label, text) in enumerate(self.summary_dict.items()):
+ #print(label, text)
+ qlabel = QLabel(label)
+ qle = QLineEdit(text)
+ qlabel.setFont(_font)
+ qlabel.setStyleSheet(("QLabel{color:black;" +
+ "margin:0px; padding:2px;}"))
+ qlabel.setFixedWidth(qrect1.width() * _width_scaling_factor)
+ qlabel.setFixedHeight(_widget_height)
+
+ qle.setFocusPolicy(Qt.NoFocus)
+ qle.setFont(_font)
+ qle.setStyleSheet(("QLineEdit{color:blue;" +
+ "background-color: lightgray;" +
+ "qproperty-readOnly: true;" +
+ "margin:0px; padding:2px;}"))
+ qle.setFixedWidth(qrect2.width() * _width_scaling_factor_le)
+ qle.setFixedHeight(_widget_height)
+ qle.setAlignment(Qt.AlignRight)
+
+ _hbox_widget = QWidget()
+ _hbox = QHBoxLayout()
+ _hbox.addWidget(qlabel)
+ _hbox.addWidget(qle)
+ _hbox_widget.setLayout(_hbox)
+ _hbox.setAlignment(Qt.AlignCenter)
+ _hbox.setContentsMargins(0, 2, 0, 0)
+ _vbox.addWidget(_hbox_widget)
+
+ _vbox.setContentsMargins(0, 0, 0, 0)
+ _vbox.setAlignment(Qt.AlignCenter|Qt.AlignTop)
+
+ _vbox2_widget = QWidget()
+ _vbox2 = QVBoxLayout()
+ _vbox2.setContentsMargins(0, 20, 0, 40)
+ table = QTableWidget(len(self.table_dict)-1, 2)
+ table.verticalHeader().setVisible(False)
+ table.setFocusPolicy(Qt.NoFocus)
+ #table.setFont(_font)
+
+ longest_str_item1 = ""
+ longest_str_item2 = ""
+
+ for i, (label, text) in enumerate(self.table_dict.items()):
+ item1 = QTableWidgetItem(str(label))
+ item2 = QTableWidgetItem(str(text))
+ item1.setTextAlignment(Qt.AlignCenter)
+ item2.setTextAlignment(Qt.AlignCenter)
+ item1.setForeground(QColor("black"))
+ item2.setForeground(QColor("black"))
+ if i%2 == 0:
+ item1.setBackground(QColor("lightgray"))
+ item2.setBackground(QColor("lightgray"))
+
+ if len(str(label)) > len(longest_str_item1):
+ longest_str_item1 = str(label)
+ if len(str(text)) > len(longest_str_item2):
+ longest_str_item2 = str(text)
+
+ if i == 0:
+ #item1.setFont(_font)
+ #item2.setFont(_font)
+ table.setHorizontalHeaderItem(0, item1)
+ table.setHorizontalHeaderItem(1, item2)
+ else:
+ table.setItem(i-1, 0, item1)
+ table.setItem(i-1, 1, item2)
+
+ fm = QFontMetricsF(_font)
+
+ _factor = 1.2
+
+ if LooseVersion(QT_VERSION_STR) < LooseVersion("5.0"):
+ _factor = 1.18
+
+ qrect = fm.boundingRect(longest_str_item1 + longest_str_item2)
+
+ _width_scaling_factor = 1.04
+ table.resizeColumnsToContents()
+ table.resizeRowsToContents()
+
+ table.setFixedHeight((fm.lineSpacing() * _factor * len(
+ self.table_dict)) + fm.lineSpacing()*2)
+
+ table.setFixedWidth(((qrect.width()) * _width_scaling_factor))
+
+ _vbox2.addWidget(table)
+ _vbox2.setAlignment(Qt.AlignCenter|Qt.AlignTop)
+ _vbox2_widget.setLayout(_vbox2)
+
+ _vbox.addWidget(_vbox2_widget)
+
+ self._group_box.setLayout(_vbox)
+ self._group_box.setContentsMargins(20, 20, 20, 20)
+ self._group_box.setAlignment(Qt.AlignTop)
+ self._group_box.setFixedHeight(
+ table.height() + (_widget_height*len(self.summary_dict)))
+ self._group_box.setFixedWidth(table.width() + 20)
+ return self._group_box
+
+
+class QResultsTableWidget():
+ """Results table"""
+ def __init__(self, column_headings=None):
+
+ self.column_headings = column_headings
+ self._group_box = None
+
+ def group_box(self, title="Table of Results"):
+ self._group_box = QGroupBox(title)
+ self._group_box.setObjectName("OUTER")
+
+ _font = QFont("Sans Serif", 10)
+
+ _vbox2_widget = QWidget()
+ _vbox2 = QVBoxLayout()
+ _vbox2.setContentsMargins(0, 20, 0, 40)
+ table = QTableWidget(1, len(self.column_headings))
+ table.verticalHeader().setVisible(True)
+ table.setFocusPolicy(Qt.NoFocus)
+ table.setFont(_font)
+
+ for i, heading in enumerate(self.column_headings):
+ _item = QTableWidgetItem(str(heading))
+ table.setHorizontalHeaderItem(i, _item)
+
+ table.resizeColumnsToContents()
+ table.resizeRowsToContents()
+ table.setFixedHeight(400)
+
+ _vbox2.addWidget(table)
+ _vbox2.setAlignment(Qt.AlignCenter|Qt.AlignTop)
+ _vbox2_widget.setLayout(_vbox2)
+
+ self._group_box.setLayout(_vbox2)
+ self._group_box.setContentsMargins(20, 20, 20, 20)
+ self._group_box.setAlignment(Qt.AlignTop)
+
+ self._group_box.setFixedWidth(table.width() + 20)
+ return self._group_box
+
+
+class QHDFDockWidget(QDockWidget):
+
+ def __init__(self, title=None, parent=None):
+ super().__init__(title, parent)
+ self.parent = parent
+ self.is_docked = True
+ self.geometry_from_qsettings = self.parent.application_geometry
+ self.topLevelChanged.connect(self._top_level_changed)
+ self.setVisible(False)
+ self.setFloating(False)
+ self.geometry_from_qsettings = self.parent.geometry()
+
+ def closeEvent(self, event: QCloseEvent):
+ super().closeEvent(event)
+
+ self.parent.setGeometry(self.geometry_from_qsettings)
+ self.setGeometry(self.geometry_from_qsettings)
+ QApplication.processEvents()
+
+ self.parent.setGeometry(self.geometry_from_qsettings)
+
+ def changeEvent(self, event):
+ pass
+
+ def _top_level_changed(self, is_floating):
+ pass
+
+
+class QNoDockWidget(QDockWidget):
+
+ def __init__(self, title=None, parent=None):
+ super().__init__(title, parent)
+ self.parent = parent
+ self.is_docked = True
+ self.geometry_from_qsettings = self.parent.application_geometry
+ self.topLevelChanged.connect(self._top_level_changed)
+ self.setVisible(False)
+ self.setFloating(True)
+
+ def changeEvent(self, event):
+ if "QAbstractButton" in str(self.sender()):
+ self.geometry_from_qsettings = self.parent.geometry()
+
+ def _top_level_changed(self): #, is_floating):
+ self.setVisible(False)
+ self.setFloating(True)
+ #ResetGeometry
+ self.parent.setGeometry(self.geometry_from_qsettings)
+ QApplication.processEvents()
+
+
+
+class CAQStripChart(PlotWidget):
+ '''Channel access enabled pyqtgraph.PlotWidget'''
+
+ def __init__(self, parent=None, pv_list: list = ['PV_NAME_NOT_GIVEN'],
+ monitor_callback=None, pv_within_daq_group: bool = False,
+ color_mode=None, show_units: bool = False, prefix: str = "",
+ suffix: str = "", notify_freq_hz: int = 0, title: str = "",
+ ylabel: str = ""):
+ super().__init__()
+
+ self.no_channels = len(pv_list)
+
+ self.found = False
+ self.time_zero = [0] * self.no_channels
+ self.time_delta = [0] * self.no_channels
+ self.pv_list = pv_list
+ self.pv2item_dict = {}
+ self.pv_gateway = [None] * self.no_channels
+
+ self.pvd_previous_list = [None] * self.no_channels
+ self.val_previous = [None] * self.no_channels
+
+ self.curve = [None] * self.no_channels
+
+ for i in range(0, len(self.pv_list)):
+ self.pv_gateway[i] = PVGateway(
+ parent, pv_list[i], monitor_callback, pv_within_daq_group,
+ color_mode, show_units, prefix, suffix,
+ #connect_callback=self.py_connect_callback,
+ connect_triggers=False, notify_freq_hz=notify_freq_hz,
+ monitor_dbr_time=True)
+
+ self.pv_gateway[i].is_initialize_complete()
+
+ self.pvd_previous_list[i] = self.pv_gateway[i].pvd
+
+ self.pv_gateway[i].trigger_connect.connect(
+ self.receive_connect_update)
+
+ self.pv_gateway[i].trigger_monitor_str.connect(
+ self.receive_monitor_update)
+ self.pv_gateway[i].trigger_monitor_int.connect(
+ self.receive_monitor_update)
+ self.pv_gateway[i].trigger_monitor_float.connect(
+ self.receive_monitor_update)
+ self.pv_gateway[i].trigger_monitor.connect(
+ self.receive_monitor_dbr_time)
+
+ self.pv_gateway[i].widget_class = "PlotWidget"
+ self.pv2item_dict[self.pv_gateway[i]] = i
+
+ self.cafe = self.pv_gateway[0].cafe
+ self.cyca = self.pv_gateway[0].cyca
+ for i in range(0, len(self.pv_gateway)):
+ if self.cafe.isConnected(self.pv_gateway[i].pv_name):
+ self.pv_gateway[i].trigger_connect.emit(
+ self.pv_gateway[i].handle, str(self.pv_gateway[i].pv_name),
+ self.pv_gateway[i].cyca.ICAFE_CS_CONN)
+
+ for i in range(0, len(self.pv_gateway)):
+ if not self.pv_gateway[i].pv_within_daq_group:
+ self.pv_gateway[i].monitor_start()
+
+ sampleinterval = 0.2
+ ##timewindow = 1800.0
+
+ self.ts_delta_max = 0.6
+
+ # Data stuff
+ self._interval = int(sampleinterval*1000)
+ self._bufsize = 9000 #int(timewindow/0.33)
+ self._bufsize2 = 9000 # int(timewindow/1.33)
+ self.databuffer = [None] * self.no_channels
+ self.timebuffer = [None] * self.no_channels
+ self.x = [None] * self.no_channels
+ self.y = [None] * self.no_channels
+ self.x_shifted = [None] * self.no_channels
+
+ self.idx = [0] * self.no_channels
+
+ for i in range(0, self.no_channels):
+ bsize = self._bufsize if i == 0 else self._bufsize2
+ self.databuffer[i] = collections.deque([None]*bsize, bsize)
+ self.timebuffer[i] = collections.deque([0]*bsize, bsize)
+ self.x[i] = np.zeros(bsize, dtype=np.float)
+ self.y[i] = np.zeros(bsize, dtype=np.float)
+
+ ##_long_size=20
+ #self.data_series_buffer = collections.deque([0]*_long_size, _long_size)
+ #self.time_series_buffer = collections.deque([0]*_long_size, _long_size)
+
+ #self.data_series = [] * self.no_channels
+ #self.time_series = [] * self.no_channels
+
+ self.iflag_series = 0
+
+ #self.x = np.linspace(-timewindow, 0.0, self._bufsize)
+ #self.x_series = np.zeros(_long_size, dtype=np.float)
+ #self.y_series = np.zeros(_long_size, dtype=np.float)
+ if title is not None:
+ self.setTitle(str(title)) #self.pv_gateway[0].pv_name)
+ self.showGrid(x=True, y=True)
+ self.setLabel('left', ylabel, self.pv_gateway[0].units)
+ self.setLabel('bottom', 'time', 's')
+ self.setBackground((60, 60, 60)) #247, 236, 249))
+ self.setLimits(yMin=-0.11)
+
+ self.plotItem.setMouseEnabled(y=False) # Only allow zoom in X-axis
+ self.plotItem.setMouseEnabled(x=True) # Only allow zoom in Y-axis
+
+ pen_list = [(125, 249, 255), (255, 255, 0)]
+
+ for i in range(0, len(self.pv_gateway)):
+ self.curve[i] = self.plot(self.x[0], self.y[0], pen=pen_list[i])
+
+ l = pg.LegendItem(offset=(0., 0.5), colCount=1)
+ l.setParentItem(self.graphicsItem())
+
+ l.setLabelTextColor((255, 255, 255))
+
+ for curv, pv in zip(self.curve, self.pv_gateway):
+ l.addItem(curv, pv.pv_name)
+
+ QApplication.processEvents()
+
+ @Slot(object, int)
+ def receive_monitor_dbr_time(self, pvdata, alarm_severity):
+
+ #Check on alarm_severity??
+
+ _row = self.pv2item_dict[self.sender()]
+
+ ts_now = pvdata.ts[0] + pvdata.ts[1] * 10**(-9)
+ ts_previous = (self.pvd_previous_list[_row].ts[0] +
+ self.pvd_previous_list[_row].ts[1] * 10**(-9))
+ ##ts_delta = ts_now - ts_previous
+
+ if (pvdata.ts[0] == self.pvd_previous_list[_row].ts[0]) and (
+ pvdata.ts[1] == self.pvd_previous_list[_row].ts[1]):
+ pvdata.show()
+ self.pvd_previous_list[_row].show()
+ return
+
+ value = pvdata.value[0]
+ #discard first callbacks
+ #if ts_delta > 2.0:
+ # self.pvd_previous_list[_row] = _pvd
+ # return;
+ self.pvd_previous_list[_row] = pvdata
+ self.val_previous[_row] = value
+ #self.pvd_previous_list[_row].ts[0] = _pvd.ts[0]
+ #self.pvd_previous_list[_row].ts[1] = _pvd.ts[1]
+
+ self.databuffer[_row].append(value)
+ self.timebuffer[_row].append(self.time_delta[_row])
+
+ highest_ts = self.timebuffer[0][0] \
+ if self.timebuffer[0][0] is not None else 0
+ for i in range(1, len(self.timebuffer)):
+ if self.timebuffer[i][0] is None:
+ continue
+ elif self.timebuffer[i][0] > highest_ts:
+ highest_ts = self.timebuffer[i][0]
+
+ if self.timebuffer[_row][0] is not None:
+ for i, val in enumerate(self.timebuffer[_row]):
+ if val > highest_ts:
+ self.idx[_row] = i - 1
+ break
+
+ self.y[_row][:] = self.databuffer[_row]
+ self.x[_row][:] = self.timebuffer[_row]
+
+ idx = self.idx[_row]
+ self.x_shifted[_row] = list(
+ map(lambda m: (m - self.time_delta[_row]), self.x[_row][idx:]))
+
+ self.curve[_row].setData(self.x_shifted[_row], self.y[_row][idx:])
+
+ self.time_delta[_row] = (
+ pvdata.ts[0] + pvdata.ts[1]*10**(-9)) - self.time_zero[0]
+
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+
+ #self.pv_gateway.receive_monitor_update(value, status, alarm_severity)
+ _row = self.pv2item_dict[self.sender()]
+
+ #print("row, value===>", _row, value, self.pv_gateway[_row].pv_name)
+ _pvd = self.pv_gateway[_row].cafe.getPVCache(
+ self.pv_gateway[_row].handle)
+
+ #print("value", _pvd.value[0], self.pvd_previous_list[_row].value[0])
+
+ _pvd2 = self.pv_gateway[_row].pvd
+
+ print("val", value, _pvd2.value[0], _pvd.value[0],
+ self.pvd_previous_list[_row].value[0])
+
+ ts_now = _pvd.ts[0] + _pvd.ts[1] * 10**(-9)
+
+ ts_previous = (self.pvd_previous_list[_row].ts[0] +
+ self.pvd_previous_list[_row].ts[1] * 10**(-9))
+ ts_delta = ts_now - ts_previous
+
+ if value == self.val_previous[_row]:
+ _pvd.show()
+
+ return
+
+
+ #discard first callbacks
+ #if ts_delta > 2.0:
+ # self.pvd_previous_list[_row] = _pvd
+ # return;
+ self.pvd_previous_list[_row] = _pvd2
+ self.val_previous[_row] = value
+ #self.pvd_previous_list[_row].ts[0] = _pvd.ts[0]
+ #self.pvd_previous_list[_row].ts[1] = _pvd.ts[1]
+
+ self.databuffer[_row].append(value)
+ self.timebuffer[_row].append(self.time_delta[_row])
+
+ highest_ts = self.timebuffer[0][0] \
+ if self.timebuffer[0][0] is not None else 0
+ for i in range(1, len(self.timebuffer)):
+ if self.timebuffer[i][0] is None:
+ continue
+ elif self.timebuffer[i][0] > highest_ts:
+ highest_ts = self.timebuffer[i][0]
+
+
+ if self.timebuffer[_row][0] is not None:
+ for i, val in enumerate(self.timebuffer[_row]):
+ if val > highest_ts:
+ self.idx[_row] = i - 1
+ break
+
+
+ #for i in range(1, self.timebuffer):
+ # if self.timebuffer[i][0] is not None:
+ # a = self.timebuffer[0][0]
+ # for i, val in enumerate(self.timebuffer[_row]):
+ # if val > a:
+ # idx = i - 1
+ # break
+
+
+ self.y[_row][:] = self.databuffer[_row]
+ self.x[_row][:] = self.timebuffer[_row]
+
+
+ #self.y[_row][:] = self.databuffer[_row]
+ #self.x[_row][:] = self.timebuffer[_row]
+
+ '''
+ #print(ts_delta, value, self.pvd_previous.value[0])
+ #if (ts_delta < self.ts_delta_max) and (value <
+ #self.pvd_previous.value[0]) :
+ if (value < self.pvd_previous.value[0]) :
+ self.data_series_buffer.append(value)
+ self.time_series_buffer.append(ts_now - self.time_zero )
+ self.y_series[:] = self.data_series_buffer
+ self.x_series[:] = self.time_series_buffer
+ #print(self.x_series, self.y_series)
+ #elif ts_delta < 1.0:
+ if len(self.data_series_buffer) > 15:
+ #x_series = np.array(self.time_series, dtype=np.float)
+ #y_series = np.array(self.data_series, dtype=np.float)
+ _x=self.x_series.reshape((-1, 1))
+
+ model = LinearRegression()
+ model.fit(_x, self.y_series)
+ r_sq = model.score(_x, self.y_series)
+ ###JCprint('coefficient of determination:',
+ ##r_sq, "slope", model.coef_ , "lifetime:",
+ ###self.y_series[0]/model.coef_ / 3600)
+ #print('intercept:', model.intercept_)
+ #print('slope:', model.coef_)
+ #print('max value', y_series[0], y_series[1])
+ if r_sq > 0.995:
+ _I = self.y_series[0]
+ ###JCprint("lifetime:", _I/model.coef_ / 3600)
+
+
+ y_pred = model.predict(_x)
+ #print("len, y_pred, _x", len(y_pred),
+ #len(self.y_series), len(_x))
+ #print('predicted response:', y_pred, sep='\n')
+ m_sq_error = mean_squared_error(self.y_series, y_pred)
+ #print('Mean squared error: {0:.9f}'.format(
+ # mean_squared_error(y_series, y_pred)))
+ #print('Coefficient of determination: {0:.9f}'.format(
+ # r2_score(y_series, y_pred)))
+
+
+
+ self.trigger_series_sequence.emit(self.x_series,
+ self.y_series)
+ #print("emit")
+ self.data_series = []
+ self.time_series = []
+ #print(len(self.x_series), len(self.y_series))
+ else:
+ self.data_series = []
+ self.time_series = []
+
+ '''
+
+
+ #dt = (self.x[-1] - self.x[-2])
+ #print("dt", dt)
+ #Lowet IPCT before trigger is set to t=0
+ idx = self.idx[_row]
+ self.x_shifted[_row] = list(
+ map(lambda m: (m - self.time_delta[_row]), self.x[_row][idx:]))
+
+ ##self.y = np.where(self.y != self.y, 0, self.y) #test for nan
+
+ #print("row len len ", _row, self.time_delta[0], self.time_delta[1])
+
+
+ self.curve[_row].setData(self.x_shifted[_row], self.y[_row][idx:])
+
+ self.time_delta[_row] = (
+ _pvd.ts[0] + _pvd.ts[1]*10**(-9)) - self.time_zero[0]
+
+
+ '''
+ LOOK_BACK = -800
+ if 'ARIDI-PCT2:CURRENT' in self.pv_gateway[_row].pv_name:
+ LOOK_BACK = -250
+
+ if value > self.y[-2]:
+ if not self.found:
+ #print(x_shifted[-240:], self.y[-240:])
+ #self.y = np.where(self.y != self.y, 0, self.y) #test for nan
+ max_index = self.y[LOOK_BACK:].argmax()
+
+ if max_index == 0:
+ return
+ print("max index=", max_index)
+
+ #print(x_shifted[-600+max_index:], self.x[-600+max_index:])
+ #print(self.y[-600+max_index:-2])
+ self.found = True
+ #print("Are Signals blocked??", self.signalsBlocked())
+ self.trigger_decay_sequence.emit(np.array(
+ x_shifted[LOOK_BACK+max_index+9:-2]),
+ self.y[LOOK_BACK+max_index+9:-2])
+ else:
+ self.found = False
+ '''
+
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ print("pv_name==>", pv_name)
+
+ _row = self.pv2item_dict[self.sender()]
+ self.pv_gateway[_row].receive_connect_update(handle, pv_name, status,
+ post_display=False)
+
+ #self.pv_gateway.receive_connect_update(handle, pv_name, status)
+ _pvd = self.pv_gateway[_row].cafe.getPVCache(
+ self.pv_gateway[_row].handle)
+ if self.time_zero[_row] == 0:
+ self.time_zero[_row] = _pvd.ts[0] + _pvd.ts[1]*10**(-9)
+
+ self.pvd_previous = _pvd
+
+
+ #renove highlighting which persists after mouse leaves
+ def mouseMoveEvent(self, event):
+ pass
+
+ def leaveEvent(self, event):
+ self.clearFocus()
+ del event
+
+
+class CAQPCTChart(PlotWidget):
+ '''Channel access enabled pyqtgraph.PlotWidget'''
+ #trigger_monitor_float = Signal(float, int, int)
+ #trigger_monitor_int = Signal(int, int, int)
+ #trigger_monitor_str = Signal(str, int, int)
+
+ #trigger_connect = Signal(int, str, int)
+
+ trigger_decay_sequence = Signal(np.ndarray, np.ndarray)
+ trigger_series_sequence = Signal(np.ndarray, np.ndarray)
+ #def py_connect_callback(self, handle, pvname, status):
+ # self.trigger_connect.emit(int(handle), str(pvname), int(status))
+ # print("py connect callback", handle, pvname, status)
+
+ def daq_start(self):
+ self.blockSignals(False)
+
+ def daq_pause(self):
+ self.blockSignals(True)
+
+ def daq_stop(self):
+ self.blockSignals(True)
+
+ def __init__(self, parent=None, pv_list: list = ['PV_NAME_NOT_GIVEN'],
+ monitor_callback=None, pv_within_daq_group: bool = False,
+ color_mode=None, show_units: bool = False, prefix: str = "",
+ suffix: str = "", notify_freq_hz: int = 0):
+ super().__init__()
+
+ self.found = False
+ self.time_zero = 0
+ self.time_delta = 0
+ self.pv_list = pv_list
+ self.pv2item_dict = {}
+ self.pv_gateway = [None] * len(self.pv_list)
+ self.pvd_previous = None
+
+ for i in range(0, len(self.pv_list)):
+ self.pv_gateway[i] = PVGateway(
+ parent, pv_list[i], monitor_callback, pv_within_daq_group,
+ color_mode, show_units, prefix, suffix,
+ #connect_callback=self.py_connect_callback,
+ connect_triggers=False, notify_freq_hz=notify_freq_hz)
+
+ self.pv_gateway[i].is_initialize_complete()
+
+ self.pv_gateway[i].trigger_connect.connect(
+ self.receive_connect_update)
+
+ self.pv_gateway[i].trigger_monitor_str.connect(
+ self.receive_monitor_update)
+ self.pv_gateway[i].trigger_monitor_int.connect(
+ self.receive_monitor_update)
+ self.pv_gateway[i].trigger_monitor_float.connect(
+ self.receive_monitor_update)
+
+ self.pv_gateway[i].widget_class = "PlotWidget"
+
+ self.pv2item_dict[self.pv_gateway[i]] = i
+
+ self.cafe = self.pv_gateway[0].cafe
+ self.cyca = self.pv_gateway[0].cyca
+ for i in range(0, len(self.pv_gateway)):
+ if self.cafe.isConnected(self.pv_gateway[i].pv_name):
+ self.pv_gateway[i].trigger_connect.emit(
+ self.pv_gateway[i].handle, str(self.pv_gateway[i].pv_name),
+ self.pv_gateway[i].cyca.ICAFE_CS_CONN)
+
+ for i in range(0, len(self.pv_gateway)):
+ if not self.pv_gateway[i].pv_within_daq_group:
+ self.pv_gateway[i].monitor_start()
+
+ sampleinterval = 0.333
+ timewindow = 1800.0
+
+ self.ts_delta_max = 0.6
+
+ # Data stuff
+ self._interval = int(sampleinterval*1000)
+ self._bufsize = int(timewindow/sampleinterval)
+ self.databuffer = collections.deque([None]*self._bufsize, self._bufsize)
+ self.timebuffer = collections.deque([0]*self._bufsize, self._bufsize)
+
+ _long_size = 20
+ self.data_series_buffer = collections.deque([0]*_long_size, _long_size)
+ self.time_series_buffer = collections.deque([0]*_long_size, _long_size)
+
+ self.data_series = []
+ self.time_series = []
+
+ self.iflag_series = 0
+
+ #self.x = np.linspace(-timewindow, 0.0, self._bufsize)
+ self.x = np.zeros(self._bufsize, dtype=np.float)
+ self.y = np.zeros(self._bufsize, dtype=np.float)
+
+ self.x_series = np.zeros(_long_size, dtype=np.float)
+ self.y_series = np.zeros(_long_size, dtype=np.float)
+
+ self.setTitle("PCT(t)") #self.pv_gateway[0].pv_name)
+ self.showGrid(x=True, y=True)
+ self.setLabel('left', 'I', 'mA')
+ self.setLabel('bottom', 'time', 's')
+ self.setBackground((60, 60, 60)) #247, 236, 249))
+ self.setLimits(yMin=-0.11)
+
+ self.plotItem.setMouseEnabled(y=False) # Only allow zoom in X-axis
+ self.plotItem.setMouseEnabled(x=True) # Only allow zoom in Y-axis
+
+ self.curve = self.plot(self.x, self.y, pen=(125, 249, 255))
+ #self.curve2 = self.plot(self.x, self.y, pen=(255,255,0))
+
+ l = pg.LegendItem(offset=(0., 0.5))
+ l.setParentItem(self.graphicsItem())
+ l.setLabelTextColor((125, 249, 255))
+
+ l.addItem(self.curve, str(self.pv_gateway[0].pv_name))
+ '''
+ l2=self.addLegend()
+ l2.setLabelTextColor('g')
+ l2.setOffset(10)
+ l2.addItem(self.curve2, str(self.pv_gateway[0].pv_name))
+ '''
+ self.daq_stop()
+ print(self._bufsize)
+ print(len(self.x), len(self.y))
+
+
+
+ @Slot(str, int, int)
+ @Slot(int, int, int)
+ @Slot(float, int, int)
+ def receive_monitor_update(self, value, status, alarm_severity):
+
+ #self.pv_gateway.receive_monitor_update(value, status, alarm_severity)
+ _row = self.pv2item_dict[self.sender()]
+ #print("value===>", value, self.pv_gateway[_row].pv_name)
+ _pvd = self.pv_gateway[_row].cafe.getPVCache(
+ self.pv_gateway[_row].handle)
+
+ ts_now = _pvd.ts[0] + _pvd.ts[1] * 10**(-9)
+ ts_previous = self.pvd_previous.ts[0] + self.pvd_previous.ts[1]*10**(-9)
+ ts_delta = ts_now - ts_previous
+
+ if (_pvd.ts[0] == self.pvd_previous.ts[0]) and (
+ _pvd.ts[1] == self.pvd_previous.ts[1]):
+ #_pvd.show()
+ return
+
+ #discard first callbacks
+ if ts_delta > 2.0:
+ self.pvd_previous = _pvd
+ return
+
+ self.databuffer.append(value)
+ self.y[:] = self.databuffer
+ self.timebuffer.append(self.time_delta)
+ self.x[:] = self.timebuffer
+
+ #print(ts_delta, value, self.pvd_previous.value[0])
+ #if (ts_delta < self.ts_delta_max) and (value <
+ # self.pvd_previous.value[0]):
+ if value < self.pvd_previous.value[0]:
+ self.data_series_buffer.append(value)
+ self.time_series_buffer.append(ts_now - self.time_zero)
+ self.y_series[:] = self.data_series_buffer
+ self.x_series[:] = self.time_series_buffer
+ #print(self.x_series, self.y_series)
+ #elif ts_delta < 1.0:
+ if len(self.data_series_buffer) > 15:
+ #x_series = np.array(self.time_series, dtype=np.float)
+ #y_series = np.array(self.data_series, dtype=np.float)
+ _x = self.x_series.reshape((-1, 1))
+
+ model = LinearRegression()
+ model.fit(_x, self.y_series)
+ r_sq = model.score(_x, self.y_series)
+ ###JCprint('coefficient of determination:',
+ ###r_sq, "slope", model.coef_ , "lifetime:",
+ ###self.y_series[0]/model.coef_ / 3600)
+ #print('intercept:', model.intercept_)
+ #print('slope:', model.coef_)
+ #print('max value', y_series[0], y_series[1])
+ if r_sq > 0.995:
+ #_I = self.y_series[0]
+
+
+ ###JCprint("lifetime:", _I/model.coef_ / 3600)
+
+ ####y_pred = model.predict(_x)
+ #print("len, y_pred, _x", len(y_pred), len(self.y_series),
+ # len(_x))
+ #print('predicted response:', y_pred, sep='\n')
+ ##m_sq_error = mean_squared_error(self.y_series, y_pred)
+ #print('Mean squared error: {0:.9f}'.format(
+ # mean_squared_error(y_series, y_pred)))
+ #print('Coefficient of determination: {0:.9f}'.format(
+ # r2_score(y_series, y_pred)))
+
+
+ self.trigger_series_sequence.emit(self.x_series,
+ self.y_series)
+ #print("emit")
+ self.data_series = []
+ self.time_series = []
+ #print(len(self.x_series), len(self.y_series))
+ else:
+ self.data_series = []
+ self.time_series = []
+
+
+ self.pvd_previous = _pvd
+
+ #dt = (self.x[-1] - self.x[-2])
+ #print("dt", dt)
+ #Lowet IPCT before trigger is set to t=0
+ x_shifted = list(map(lambda m: (m - self.time_delta), self.x))
+
+ ##self.y = np.where(self.y != self.y, 0, self.y) #test for nan
+ self.curve.setData(x_shifted, self.y)
+
+ self.time_delta = (
+ _pvd.ts[0] + _pvd.ts[1]*10**(-9)) - self.time_zero
+ #x_shifted2= list(map(lambda m : m -self.time_delta-1 , self.x))
+ #self.curve2.setData(x_shifted2, self.y)
+ #QApplication.processEvents()
+ #print(type(x_shifted), type(self.y), type([1.1]), type(1.1))
+
+ LOOK_BACK = -800
+ if 'ARIDI-PCT2:CURRENT' in self.pv_gateway[_row].pv_name:
+ LOOK_BACK = -250
+
+ if value > self.y[-2]:
+ if not self.found:
+ #print(x_shifted[-240:], self.y[-240:])
+ #self.y = np.where(self.y != self.y, 0, self.y) #test for nan
+ max_index = self.y[LOOK_BACK:].argmax()
+
+ if max_index == 0:
+ return
+ print("max index=", max_index)
+
+ #print(x_shifted[-600+max_index:], self.x[-600+max_index:])
+ #print(self.y[-600+max_index:-2])
+ self.found = True
+ #print("Are Signals blocked??", self.signalsBlocked())
+ self.trigger_decay_sequence.emit(
+ np.array(x_shifted[LOOK_BACK+max_index+9:-2]),
+ self.y[LOOK_BACK+max_index+9:-2])
+ else:
+ self.found = False
+
+
+
+
+ @Slot(int, str, int)
+ def receive_connect_update(self, handle: int, pv_name: str, status: int):
+ '''Triggered by connect signal'''
+ print("pv_name==>", pv_name)
+
+ _row = self.pv2item_dict[self.sender()]
+ self.pv_gateway[_row].receive_connect_update(handle, pv_name, status,
+ post_display=False)
+
+ #self.pv_gateway.receive_connect_update(handle, pv_name, status)
+ _pvd = self.pv_gateway[_row].cafe.getPVCache(
+ self.pv_gateway[_row].handle)
+ if self.time_zero == 0:
+ self.time_zero = _pvd.ts[0] + _pvd.ts[1]*10**(-9)
+ #print(self.time_zero)
+ self.pvd_previous = _pvd
+
+
+ #renove highlighting which persists after mouse leaves
+ def mouseMoveEvent(self, event):
+ pass
+
+ def leaveEvent(self, event):
+ self.clearFocus()
+ del event