From 43743bdc07db5a543f91cd697136c5f919876616 Mon Sep 17 00:00:00 2001
From: chrin
Date: Thu, 15 Sep 2022 10:04:36 +0200
Subject: [PATCH] overdue
---
__pycache__/pvgateway.cpython-37.pyc | Bin 0 -> 37266 bytes
__pycache__/pvwidgets.cpython-37.pyc | Bin 0 -> 71261 bytes
pvgateway.py | 1568 +++++-------
pvgateway.py- | 1924 ++++++++++++++
pvgateway.py-- | 1690 +++++++++++++
pvwidgets.py | 3048 ++++++++++------------
pvwidgets.py- | 3508 ++++++++++++++++++++++++++
pvwidgets.py-- | 3112 +++++++++++++++++++++++
8 files changed, 12286 insertions(+), 2564 deletions(-)
create mode 100644 __pycache__/pvgateway.cpython-37.pyc
create mode 100644 __pycache__/pvwidgets.cpython-37.pyc
create mode 100644 pvgateway.py-
create mode 100644 pvgateway.py--
create mode 100644 pvwidgets.py-
create mode 100644 pvwidgets.py--
diff --git a/__pycache__/pvgateway.cpython-37.pyc b/__pycache__/pvgateway.cpython-37.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..af6ecc4a657c33cc640fd0256aa7f4f751e6fbbc
GIT binary patch
literal 37266
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 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