From 3c408ebcbd405f462af5a68c315f94752f04cf35 Mon Sep 17 00:00:00 2001 From: chrin Date: Thu, 1 Jun 2023 12:14:26 +0200 Subject: [PATCH] add standby functionality to table wgt --- pvgateway.py | 47 ++++---- pvwidgets.py | 318 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 303 insertions(+), 62 deletions(-) diff --git a/pvgateway.py b/pvgateway.py index b48b775..89c0704 100644 --- a/pvgateway.py +++ b/pvgateway.py @@ -121,6 +121,8 @@ class PVGateway(QWidget): self.color_mode = None + self.check_rtyp = False + if color_mode is not None: if color_mode in (self.ACT_ON_BEAM, self.NOT_ACT_ON_BEAM, @@ -148,9 +150,13 @@ class PVGateway(QWidget): self.cafe = self.parent.cafe self.cyca = self.parent.cyca + self.url_archiver = None + self.url_databuffer = None + 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"] + if "databuffer" in self.parent.settings.data["url"]: + 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"][ @@ -163,10 +169,10 @@ class PVGateway(QWidget): "fgAlarmNoAlarm"] else: #self.settings = ReadJSON(self.parent.appname) - self.url_archiver = ("https://ui-data-api.psi.ch/prepare?channel=" + + self.url_archiver = ("https://data-ui.psi.ch/preselect?c1=" + "sf-archiverappliance/") self.url_databuffer \ - = "https://ui-data-api.psi.ch/prepare?channel=sf-databuffer/" + = "https://data-ui.psi.ch/preselect?c1=sf-databuffer/" self.daq_group_name = self._DAQ_CAFE_SG_NAME self.desc = None @@ -272,23 +278,20 @@ class PVGateway(QWidget): action1 = QAction("Text Info", self) action1.triggered.connect(self.pv_status_text) - + self.context_menu.addAction(action1) action2 = QAction("Lookup in Archiver", self) action2.triggered.connect(self.lookup_archiver) - - action3 = QAction("Lookup in Databuffer", self) - action3.triggered.connect(self.lookup_databuffer) - + self.context_menu.addAction(action2) + if self.url_databuffer is not None: + action3 = QAction("Lookup in Databuffer", self) + action3.triggered.connect(self.lookup_databuffer) + self.context_menu.addAction(action3) action4 = QAction("Strip Chart (PShell)", self) action4.triggered.connect(self.strip_chart) - + self.context_menu.addAction(action4) 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) @@ -385,7 +388,7 @@ class PVGateway(QWidget): if self.pv_info is None: self.pv_info = self.cafe.getChannelInfo(self.pv_name) - if "Not Supported" in self.pv_info.className: + if "Not Supported" in self.pv_info.className and self.check_rtyp: _rtype = self.cafe.get(self.pv_name.split(".")[0] + ".RTYP") self.record_type = _rtype if _rtype is not None else \ self.pv_info.className @@ -418,8 +421,9 @@ class PVGateway(QWidget): _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)) + #DisplayLimit preferred over ControlLimit as latter n/a for ao + _lower_control_abs = abs(int(self.pv_ctrl.lowerDisplayLimit)) + _upper_control_abs = abs(int(self.pv_ctrl.upperDisplayLimit)) _max_control_abs = max(_lower_control_abs, _upper_control_abs) if _max_control_abs is None: _max_control_abs = 0 @@ -494,7 +498,7 @@ class PVGateway(QWidget): time.sleep(0.01) self.initialize_meta_data() icount += 1 - if icount > 50: + if icount > 5: #50 return False return True @@ -583,7 +587,7 @@ class PVGateway(QWidget): 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: + if "Not Supported" in self.pv_info.className and self.check_rtyp: _rtype = self.cafe.get(self.pv_name.split(".")[0] + ".RTYP") self.record_type = _rtype if _rtype is not None else \ self.pv_info.className @@ -1342,7 +1346,7 @@ class PVGateway(QWidget): 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: + if "Not Supported" in self.pv_info.className and self.check_rtyp: _rtype = self.cafe.get(self.pv_name.split(".")[0] + ".RTYP") self.record_type = _rtype if _rtype is not None else \ self.pv_info.className @@ -1386,7 +1390,7 @@ class PVGateway(QWidget): 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: + if "Not Supported" in self.pv_info.className and self.check_rtyp: _rtype = self.cafe.get(self.pv_name.split(".")[0] + ".RTYP") self.record_type = _rtype if _rtype is not None else \ self.pv_info.className @@ -1491,8 +1495,7 @@ class PVGateway(QWidget): self.pv_status_text_display_limits()) self.pv_message_in_a_box.setText( - self.pv_status_text_header(source=_source) + _text_data - ) + self.pv_status_text_header(source=_source) + _text_data) QApplication.processEvents() self.pv_message_in_a_box.exec() diff --git a/pvwidgets.py b/pvwidgets.py index 414eebd..8a70f48 100644 --- a/pvwidgets.py +++ b/pvwidgets.py @@ -12,18 +12,17 @@ from sklearn.linear_model import LinearRegression from distutils.version import LooseVersion from functools import reduce as func_reduce -from qtpy.QtCore import (QEventLoop, QMutex, QPoint, Qt, QThread, QTimer, - Signal, Slot) -from qtpy.QtGui import (QCloseEvent, QColor, QCursor, QFont, QFontMetricsF, - QIcon, QKeySequence) +from qtpy.QtCore import ( + QEventLoop, QMutex, 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) +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 @@ -132,6 +131,7 @@ class CAQLineEdit(QLineEdit, PVGateway): _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) @@ -287,7 +287,6 @@ class CAQMenu(QComboBox, PVGateway): def post_display_value(self, value): - '''Convert value to index''' if "setCurrentIndex" in dir(self): @@ -297,7 +296,6 @@ class CAQMenu(QComboBox, PVGateway): if isinstance(value, str): self.setCurrentIndex(self.cafe.getEnumFromString(self.handle, value)) - elif isinstance(value, int): self.setCurrentIndex(value) #Should not happen @@ -990,6 +988,8 @@ class CAQDoubleSpinBox(QDoubleSpinBox, PVGateway): def mouseReleaseEvent(self, event): self.clearFocus() + self.lineEdit().clearFocus() + def setValue(self, value): self.currentValue = self.value() @@ -1047,14 +1047,18 @@ class CAQDoubleSpinBox(QDoubleSpinBox, PVGateway): self.qt_style_polish() self.clearFocus() + self.lineEdit().clearFocus() + self.lineEdit().setFocusPolicy(Qt.NoFocus) 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() + self.lineEdit().clearFocus() + self.lineEdit().setFocusPolicy(Qt.NoFocus) + self.setFocusPolicy(Qt.NoFocus) elif event.key() in (Qt.Key_Up, Qt.Key_Down): QDoubleSpinBox.keyPressEvent(self, event) else: @@ -1101,16 +1105,29 @@ class reconnectQPushButton(QPushButton, QThread): def reconnect(self): QApplication.processEvents() + print("Reconnect") self.isdirty = True if self._handles_to_reconnect: + print("handles to reconnect", self._handles_to_reconnect, flush=True) self.parent.cafe.reconnect(self._handles_to_reconnect) + print("handles reconnected", self._handles_to_reconnect, flush=True) self.isdirty = False #Uncheck reconnected channels for i in range(0, len(self.parent.pv_gateway)): + #print(i, len(self.parent.pv_gateway), flush=True) + #print( "ischecked", self.parent.item( + # i, self.parent.no_columns-1).checkState(), Qt.Checked ) + #print("Connected", self.parent.cafe.isConnected( + # self.parent.pv_gateway[i].handle), flush=True) if self.parent.item( i, self.parent.no_columns-1).checkState() == Qt.Checked: if self.parent.cafe.isConnected( self.parent.pv_gateway[i].handle): + #print("isConnected", flush=True) + self.parent.item( + i, self.parent.no_columns-1).setCheckState(False) + else: + #Even if not connected - uncheck box as action is complete self.parent.item( i, self.parent.no_columns-1).setCheckState(False) @@ -1196,7 +1213,6 @@ class CAQTableWidget(QTableWidget): def widget_update(self): - for _row, pvgate in enumerate(self.pv_gateway): if not pvgate.notify_unison: @@ -1313,6 +1329,8 @@ class CAQTableWidget(QTableWidget): color_mode=None, show_units: bool = True, prefix: str = "", suffix: str = "", ts_res: str = "milli", init_column: bool = False, init_list: list = [], + standby_column: bool = False, standby_list: list = [], + standby_values: list = [], set_delay: float = 0.0, 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): @@ -1332,6 +1350,10 @@ class CAQTableWidget(QTableWidget): if init_column: self.columns_dict['Init'] = _column_dict_value _column_dict_value += 1 + + if standby_column: + self.columns_dict['Standby'] = _column_dict_value + _column_dict_value += 1 self.columns_dict['Value'] = _column_dict_value @@ -1346,11 +1368,17 @@ class CAQTableWidget(QTableWidget): 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.standby_column = standby_column + self.standby_list = standby_list + if self.standby_column and not self.standby_list: + self.standby_list = pv_list + self.standby_values = standby_values + self.set_delay = set_delay + self.icount = 0 self.notify_freq_hz = abs(notify_freq_hz) self.notify_freq_hz_default = self.notify_freq_hz @@ -1434,6 +1462,7 @@ class CAQTableWidget(QTableWidget): self.timer.singleShot(0, self.widget_update) self.timer.start(self.notify_milliseconds) + self.reconnect_button = None self.configure_widget() #Connect only deals with colours - only helps on reconnect @@ -1452,6 +1481,9 @@ class CAQTableWidget(QTableWidget): if init_column: self.update_init_values() + if standby_column: + self.update_standby_values() + self.configure_context_menu() @@ -1473,7 +1505,108 @@ class CAQTableWidget(QTableWidget): QApplication.processEvents() + + def set_standby_values(self, pv_list: list = []): + if self.init_column: + self.init_value_button.setEnabled(False) + self.restore_value_button.setEnabled(False) + + _text = self.standby_value_button.text() + self.standby_value_button.setText("Downing") + self.standby_value_button.setEnabled(False) + + if self.reconnect_button is not None: + self.reconnect_button.setEnabled(False) + + + _set_values_dict = self.get_standby_values() + + #return 1,2,3 + + 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]) + + #self.standby_value_button.setEnabled(False) + #QApplication.processEvents(QEventLoop.AllEvents, 1.0) + + if self.set_delay <= 0: + status, status_list = self.cafe.setScalarList(_pvs_to_set, + _values_to_set) + else: + status_list = [self.cyca.ICAFE_NORMAL] * len(_pvs_to_set) + status = self.cyca.ICAFE_NORMAL + for i, (pv, val) in enumerate(zip(_pvs_to_set, _values_to_set)): + status_list[i] = self.cafe.set(pv, val) + if status_list[i] != self.cyca.ICAFE_NORMAL: + status = status_list[i] + + ##self.standby_value_button.setEnabled(False) + ##QApplication.processEvents(QEventLoop.AllEvents, 1.0) + time.sleep(self.set_delay) + ##self.standby_value_button.setEnabled(False) + #QApplication.sendPostedEvents() + QApplication.processEvents(QEventLoop.AllEvents, 1.0) + + if status != self.cyca.ICAFE_NORMAL: + _mess = ("The following devices 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.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) + #qm.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + #qm.setFixedWidth(800) + ##self.standby_value_button.setEnabled(False) + ##QApplication.sendPostedEvents() + qm.exec() + #QApplication.processEvents() + + + if self.init_column: + self.init_value_button.setEnabled(True) + self.restore_value_button.setEnabled(True) + + + self.standby_value_button.setText(_text) + self.standby_value_button.setEnabled(True) + if self.reconnect_button is not None: + self.reconnect_button.setEnabled(True) + + return status, status_list, _pvs_to_set + + + + def restore_init_values(self, pv_list: list = []): + + _text = self.restore_value_button.text() + self.restore_value_button.setText("Restoring") + self.restore_value_button.setEnabled(False) + + if self.init_column: + self.init_value_button.setEnabled(False) + + if self.standby_column: + self.standby_value_button.setEnabled(False) + + if self.reconnect_button is not None: + self.reconnect_button.setEnabled(False) + _set_values_dict = self.get_init_values() if not pv_list: @@ -1489,8 +1622,19 @@ class CAQTableWidget(QTableWidget): _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 self.set_delay <= 0: + status, status_list = self.cafe.setScalarList(_pvs_to_set, + _values_to_set) + else: + status_list = [self.cyca.ICAFE_NORMAL] * len(_pvs_to_set) + status = self.cyca.ICAFE_NORMAL + for i, (pv, val) in enumerate(zip(_pvs_to_set, _values_to_set)): + status_list[i] = self.cafe.set(pv, val) + if status_list[i] != self.cyca.ICAFE_NORMAL: + status = status_list[i] + time.sleep(self.set_delay) + QApplication.processEvents(QEventLoop.AllEvents, 1.0) if status != self.cyca.ICAFE_NORMAL: _mess = ("The following device(s) reported an error " + @@ -1503,12 +1647,23 @@ class CAQTableWidget(QTableWidget): " " + self.cafe.getStatusInfo(status_value)) qm = QMessageBox() qm.setText(_mess) - + qm.exec() QApplication.processEvents() - self.init_value_button.setEnabled(True) + if self.init_column: + self.init_value_button.setEnabled(True) + + self.restore_value_button.setText(_text) + self.restore_value_button.setEnabled(True) + if self.standby_column: + self.standby_value_button.setEnabled(True) + + if self.reconnect_button is not None: + self.reconnect_button.setEnabled(True) + + return status, status_list, _pvs_to_set def is_same_as_init_values(self): _init_values_dict = self.get_column_values(self.columns_dict['Init']) @@ -1551,6 +1706,8 @@ class CAQTableWidget(QTableWidget): if _pvs[_row] in self.pv_list_show: _values_dict[self.pv_gateway[_row].pv_name] = _values[_row] + else: + _values_dict[_row] = _values[_row] return _values_dict #_pvs_to_set, _values_to_set @@ -1558,6 +1715,9 @@ class CAQTableWidget(QTableWidget): def get_init_values(self): return self.get_column_values(self.columns_dict['Init']) + def get_standby_values(self): + return self.get_column_values(self.columns_dict['Standby']) + def get_init_values_previous(self): _set_values_dict = {} _start = 0 @@ -1585,21 +1745,69 @@ class CAQTableWidget(QTableWidget): _set_values_dict[ self.pv_gateway[_row].pv_name] = _values_to_set[_row] + + return _set_values_dict - - def update_init_values(self): + + def update_standby_values(self): _start = 0 - _end = len(self.pv_gateway) + _end = len(self.standby_values) + if 'Standby' in self.columns_dict: + _column_no = self.columns_dict['Standby'] + else: + return + + for _row in range(_start, _end): + _value = self.standby_values[_row] + + 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, _column_no, qtwi) + self.item(_row, _column_no).setTextAlignment(Qt.AlignRight | + Qt.AlignVCenter) + + + def set_init_values(self, values): + _start = 0 + _end = min(len(self.pv_gateway), len(values)) + if 'Init' in self.columns_dict: _column_no = self.columns_dict['Init'] else: return for _row in range(_start, _end): + _value = self.pv_gateway[_row].format_display_value(values[_row]) + qtwi = QTableWidgetItem(str(_value)+ " ") + _f = qtwi.font() + _f.setPointSize(8) + qtwi.setFont(_f) + self.setItem(_row, _column_no, qtwi) + self.item(_row, _column_no).setTextAlignment(Qt.AlignRight | + Qt.AlignVCenter) + + + def update_init_values(self): + _start = 0 + _end = len(self.pv_gateway) + + if 'Init' in self.columns_dict: + _column_no = self.columns_dict['Init'] + else: + return + + 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 @@ -1609,10 +1817,11 @@ class CAQTableWidget(QTableWidget): _f = qtwi.font() _f.setPointSize(8) qtwi.setFont(_f) - self.setItem(_row, 1, qtwi) + self.setItem(_row, _column_no, qtwi) + self.item(_row, _column_no).setTextAlignment(Qt.AlignRight | Qt.AlignVCenter) - + def configure_widget(self): @@ -1672,7 +1881,7 @@ class CAQTableWidget(QTableWidget): if self.init_column: self.init_widget = QWidget() - _init_layout = QHBoxLayout(self.init_widget) + self.init_layout = QHBoxLayout(self.init_widget) self.init_value_button = QPushButton() self.init_value_button.setText("Update") _f = self.init_value_button.font() @@ -1683,14 +1892,18 @@ class CAQTableWidget(QTableWidget): self.init_value_button.setToolTip( ("Stores initial, pre-measurement value. Update is also " + "typically executed automatically before analysis procedure.")) - _init_layout.addWidget(self.init_value_button) - _init_layout.setAlignment(Qt.AlignCenter) - _init_layout.setContentsMargins(1, 1, 1, 0) #Required - self.init_widget.setLayout(_init_layout) - self.setCellWidget(len(self.pv_gateway), 1, self.init_widget) + self.init_layout.addWidget(self.init_value_button) + self.init_layout.setAlignment(Qt.AlignCenter) + self.init_layout.setContentsMargins(1, 1, 1, 0) #Required + self.init_widget.setLayout(self.init_layout) + + if 'PV' in self.columns_dict: + self.setCellWidget(len(self.pv_gateway), 0, self.init_widget) + else: + self.setCellWidget(len(self.pv_gateway), self.columns_dict['Init'], self.init_widget) - _restore_widget = QWidget() - _restore_layout = QHBoxLayout(_restore_widget) + self.restore_widget = QWidget() + self.restore_layout = QHBoxLayout(self.restore_widget) self.restore_value_button = QPushButton() self.restore_value_button.setStyleSheet( "QPushButton{background-color: rgb(212, 219, 157);}") @@ -1702,11 +1915,36 @@ class CAQTableWidget(QTableWidget): 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.AlignCenter) - _restore_layout.setContentsMargins(1, 1, 0, 0) - _restore_widget.setLayout(_restore_layout) - self.setCellWidget(len(self.pv_gateway), 2, _restore_widget) + self.restore_layout.addWidget(self.restore_value_button) + self.restore_layout.setAlignment(Qt.AlignCenter) + self.restore_layout.setContentsMargins(1, 1, 0, 0) + self.restore_widget.setLayout(self.restore_layout) + if 'PV' in self.columns_dict: + self.setCellWidget(len(self.pv_gateway), + self.columns_dict['Init'], + self.restore_widget) + + if self.standby_column: + + _standby_widget = QWidget() + _standby_layout = QHBoxLayout(_standby_widget) + self.standby_value_button = QPushButton() + self.standby_value_button.setStyleSheet( + "QPushButton{background-color: rgb(212, 219, 157);}") + self.standby_value_button.setText("Standby") + _f = self.standby_value_button.font() + _f.setPointSize(8) + self.standby_value_button.setFont(_f) + self.standby_value_button.setFixedWidth(80) + self.standby_value_button.clicked.connect(self.set_standby_values) + self.standby_value_button.setToolTip( + ("Set devices to their pre-set standby values")) + _standby_layout.addWidget(self.standby_value_button) + _standby_layout.setAlignment(Qt.AlignCenter) + _standby_layout.setContentsMargins(1, 1, 0, 0) + _standby_widget.setLayout(_standby_layout) + self.setCellWidget(len(self.pv_gateway), self.columns_dict['Standby'], _standby_widget) + #Do not display no for last row (Reconnect button) _row_digit_last_cell = QTableWidgetItem(str("")) @@ -1729,7 +1967,7 @@ class CAQTableWidget(QTableWidget): self.reconnect_button.setFont(f) self.reconnect_button.setText("Reconnect") - + self.reconnect_button.setToolTip("Reconnect selected/checked channels") _layout = QHBoxLayout(_qwb) _layout.addWidget(self.reconnect_button) _layout.setAlignment(Qt.AlignCenter)