diff --git a/guiframe.py b/guiframe.py index 8fdb0e9..04db387 100644 --- a/guiframe.py +++ b/guiframe.py @@ -10,7 +10,9 @@ from matplotlib.backends.backend_qt5agg import ( FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar) from qtpy.QtCore import QSize, Qt, Slot -from qtpy.QtGui import QColor, QFont, QFontMetricsF, QIcon, QPainter, QPixmap +from qtpy.QtGui import ( + QColor, QDoubleValidator, QFont, QFontMetricsF, QIcon, QIntValidator, + QPainter, QPixmap) from qtpy.QtWidgets import ( QCheckBox, QComboBox, QDoubleSpinBox, QFrame, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit, QPushButton, QRadioButton, QSpinBox, @@ -1121,32 +1123,206 @@ class GUIFrame(QWidget): self.nav[i] = None - def prepare_qlineedit(self, line, value): + def prepare_qlineread(self, line, value): + line.setObjectName("Read") + line.setFixedHeight(24) + line.setText(str(value)) + line.setAlignment(Qt.AlignRight | Qt.AlignBottom) + line.setStyleSheet("QLabel{text-align: right}") + fm = QFontMetricsF(line.font()) + param_width = fm.maxWidth() * (len(str(value))*0.5) + 20 + line.setMaximumWidth(param_width) + + def prepare_qlineedit(self, line, value, link, title, key): + has_min = False + has_max = False + is_float = False + is_int = False + no_decimals = 0 + try: + min_val = self.settings.data[link][title][key]["data"]["min"] + has_min = True + if isinstance(min_val, float): + is_float = True + d = abs(Decimal(str(min_val)).as_tuple().exponent) + no_decimals = max(no_decimals, d) + elif isinstance(min_val, int): + is_int = True + except KeyError: + pass + try: + max_val = self.settings.data[link][title][key]["data"]["max"] + has_max = True + if isinstance(max_val, float): + is_float = True + d = abs(Decimal(str(max_val)).as_tuple().exponent) + no_decimals = max(no_decimals, d) + elif isinstance(max_val, int): + is_int = True + except KeyError: + pass + + + validator = None + #QDoubleValidate only controls the number of digits + if is_float: + validator = QDoubleValidator() + validator.setNotation(QDoubleValidator.StandardNotation) + validator.setDecimals(no_decimals) + elif is_int: + validator = QIntValidator() + if validator: + validator_str = "Valid range " + if has_min: + validator.setBottom(min_val) + validator_str += "from {} ".format(min_val) + if has_max: + validator.setTop(max_val) + validator_str += "to {}".format(max_val) + line.setToolTip(validator_str) + line.setValidator(validator) + line.setObjectName("Write") line.setFixedHeight(24) line.setText(str(value)) fm = QFontMetricsF(line.font()) - param_width = fm.width(str(value)) - extra_width = 22 if len(str(value)) < 16 else 26 - line.setFixedWidth(param_width + extra_width) + #param_width = fm.width(str(value)) + #extra_width = 22 if len(str(value)) < 16 else 26 + #line.setFixedWidth(param_width + extra_width) + param_width = fm.maxWidth() * (len(str(value))*0.5) + 20 + line.setMaximumWidth(param_width) return line def prepare_qspinbox(self, line, value, link, title, key): - print(link, title, key) min_val = self.settings.data[link][title][key]["data"]["min"] max_val = self.settings.data[link][title][key]["data"]["max"] - step = self.settings.data[link][title][key]["data"]["step"] - + step = self.settings.data[link][title][key]["data"]["step"] + line.setObjectName("Write") + line.setFixedHeight(24) line.setRange(min_val, max_val) line.setSingleStep(step) line.setValue(value) - line.setAlignment(Qt.AlignRight) + line.setAlignment(Qt.AlignRight) #required fm = QFontMetricsF(line.font()) - param_width = fm.maxWidth() * len(str(max_val)) - print("param_width", param_width, flush=True) + param_width = fm.maxWidth() * (len(str(max_val)) + 0.2) line.setMaximumWidth(param_width) return line + def prepare_qdoublespinbox(self, line, value, link, title, key): + min_val = self.settings.data[link][title][key]["data"]["min"] + max_val = self.settings.data[link][title][key]["data"]["max"] + step = self.settings.data[link][title][key]["data"]["step"] + line.setObjectName("Write") + line.setFixedHeight(24) + decimal = Decimal(str(step)) + line.setDecimals(abs(decimal.as_tuple().exponent)) #precision + line.setRange(min_val, max_val) + line.setSingleStep(step) + line.setValue(value) + line.setAlignment(Qt.AlignRight) #required + fm = QFontMetricsF(line.font()) + param_width = fm.maxWidth() * (len(str(max_val))) # + len(str(step))) + line.setMaximumWidth(param_width) + return line + + def prepare_qcombobox(self, line, value, link, title, key): + line.clear() + line.addItems(value) + line.setFixedHeight(26) + line.setObjectName("Write") + value_for_width = max(value, key=len) + fm = QFontMetricsF(line.font()) + param_width = fm.maxWidth() * (len(str(value_for_width)) * 0.5) # + len(str(step))) + line.setFixedWidth(param_width) + + return line + + def prepare_qcheckbox(self, line, value, link, title, key): + + line.setChecked(Qt.Checked if value else Qt.Unchecked) + line.setMaximumWidth(320) + line.setMinimumWidth(100) + line.setFixedHeight(28) + try: + orientation = self.settings.data[link][title][key]["data"][ + "orientation"] + orientation = Qt.RightToLeft if "RightToLeft" in orientation \ + else Qt.LeftToRight + line.setLayoutDirection(orientation) + except KeyError as ex: + print(ex, flush=True) + pass + return line + + def prepare_qradiobutton(self, value, link, title, key): + widget = QWidget() + layout = QGridLayout() + layout.setContentsMargins(5, 0, 5, 20) + widget.setMaximumWidth(len(value)*100) + + target_list = value + color_list = ["#894961", "teal", "darkblue"] + width_list = [70, 70, 70] + self.radiobutton = [None] * len(target_list) + + for i, name in enumerate(target_list): + self.radiobutton[i] = QRadioButton(name) + + full_width = len(target_list) + 1 + + layout.addWidget(QHLine(), 0, 0, 1, full_width) + layout.addWidget(QLabel( + self.settings.data[link][title][key]['data']['text']), 1, 0, 1, + full_width, Qt.AlignCenter) + + for i, (radio, target, color, width) in enumerate( + zip(self.radiobutton, target_list, color_list, width_list)): + + radio.setFont(self.font_pts10) + radio.setStyleSheet("color : {0};".format(color)) + radio.val = target + radio.title = title + radio.key = key + + layout.addWidget(radio, 2, i, 1, 1, Qt.AlignCenter) + + layout.addWidget(QHLine(), 3, 0, 1, layout.columnCount()) + layout.setSpacing(0) + + widget.setLayout(layout) + + return widget + + ''' + line.setContentsMargins(0, 0, 0, 0) + + rblist = line.findChildren(QRadioButton) + + for rb in rblist: + rb.toggled.connect(on_radiobutton_change) + + wgt_grid.addWidget(line, irow, 0, 1, 4, Qt.AlignLeft) + + _idx=0 + + try: + if key in self.settings.data["Expert"].keys(): + top_key = "Expert" + elif key in self.settings.data["Parameters"].keys(): + top_key = "Parameters" + _choice =self.settings.data[top_key][key]['data']['default'] + _values = self.settings.data[top_key][key]['data']['value'] + if isinstance(_choice, (str, bytes)): + _idx = _values.index(_choice) + elif isinstance(_choice, int): + _idx = _choice if _choice < len(_values) else 0 + except KeyError: + pass + + rblist[_idx].setChecked(True) + rblist[_idx].toggled.emit(True) + return line + ''' def qtab_wgt(self, key, irow, wgt_grid): @@ -1160,81 +1336,164 @@ class GUIFrame(QWidget): line.key = inner_key line.val = value + def check_cb(new_value): + sender= self.sender() + self.parent.input_parameters[sender.title][sender.key] = new_value + def line_cb(new_value): sender= self.sender() self.parent.input_parameters[sender.title][sender.key] = new_value + + def on_radiobutton_change(new_value): + sender = self.sender() + #print("rb callback ", new_value, sender.isChecked(), sender.val, flush=True) + if sender.isChecked(): + self.parent.input_parameters[sender.title][sender.key] = sender.val + def on_tab_change(new_idx): sender_tab = self.sender().qtab_widget_dict[new_idx].title meas_line.setText(sender_tab) try: - _color = self.settings.data[top_key][key]["data"]["color"][new_idx] + _color = self.settings.data[top_key][key]["data"]["color"][ + new_idx] except KeyError as ex: _color = "black" except IndexError as ex: _color = "black" meas_line.setStyleSheet("color: {0}".format(_color)) - self.sender().tabBar().setTabTextColor(new_idx, QColor(_color)) + + for i in range (0, self.sender().count()): + if i == new_idx: + self.sender().tabBar().setTabTextColor(new_idx, + QColor(_color)) + else: + self.sender().tabBar().setTabTextColor(i, QColor("black")) self.parent.input_parameters[key] = sender_tab - qtab_widget = QTabWidget() - qtab_widget_dict = {} itab = 0 longest_title = "short" - link = self.settings.data[top_key][key]["data"]["link"] for title, tab in self.settings.data[link].items(): lo = QGridLayout() - qw = QWidget() _irow = 0 + _irowspan = 1 + tab_height = 0 if len(title) > len(longest_title): longest_title = title self.parent.input_parameters[title] = {} + for inner_key, param in tab.items(): + tab_height +=32 wgt_type = param["data"]["widget"] text = param["data"]["text"] - value = param["data"]["value"] - label = QLabel(text) - label.setFixedHeight(24) - label.setFont(self.font_pts10) - label.setContentsMargins(5, 0, 0, 0) + if "value" in param["data"]: + value = param["data"]["value"] + elif "link" in param["data"]: + link_list = param["data"]["link"] + print("ll", link_list, flush=True) + if len(link_list) == 1: + value = self.settings.data[link_list[0]] + print("values", value, flush=True) + else: + bottom_leaf = self.settings.data[link_list[0]] + for link_item in link_list[1:]: + bottom_leaf = bottom_leaf[link_item] + value = bottom_leaf + + label = None + def add_label(): + label = QLabel(text) + label.setFixedHeight(24) + label.setFont(self.font_pts10) + label.setContentsMargins(5, 0, 0, 0) + return label if "QSpinBox" in wgt_type: line = QSpinBox() + label = add_label() emboss() - print("top key", top_key, inner_key, flush=True) self.prepare_qspinbox(line, value, link, title, inner_key) line.valueChanged.connect(line_cb) line.valueChanged.emit(value) - + elif "QDoubleSpinBox" in wgt_type: + line = QDoubleSpinBox() + label = add_label() + emboss() + self.prepare_qdoublespinbox(line, value, link, title, + inner_key) + line.valueChanged.connect(line_cb) + line.valueChanged.emit(value) elif "QLineEdit" in wgt_type: line = QLineEdit() + label = add_label() emboss() - self.prepare_qlineedit(line, value) + self.prepare_qlineedit(line, value, link, title, inner_key) line.textEdited.connect(line_cb) line.textEdited.emit(str(value)) + elif "QLineRead" in wgt_type: + line = QLineEdit() + label = add_label() + emboss() + self.prepare_qlineread(line, value) + line.textEdited.connect(line_cb) + line.textEdited.emit(str(value)) + elif "QCheckBox" in wgt_type: + line = QCheckBox(text) + emboss() + self.prepare_qcheckbox(line, value, link, title, inner_key) + line.stateChanged.connect(check_cb) + line.stateChanged.emit(bool(value)) + elif "QComboBox" in wgt_type: + line = QComboBox() + label = add_label() + emboss() + self.prepare_qcombobox(line, value, link, title, inner_key) + line.currentTextChanged.connect(line_cb) + line.setCurrentIndex(0) + line.currentTextChanged.emit(value[0]) + elif "QRadioButton" in wgt_type: + tab_height += 32 + _irowspan = 2 + line = self.prepare_qradiobutton(value, link, title, + inner_key) + #emboss() + rblist = line.findChildren(QRadioButton) + for rb in rblist: + rb.toggled.connect(on_radiobutton_change) + if "startIdx" in param["data"]: + start_idx = param["data"]["startIdx"] + else: + start_idx = 0 + rblist[start_idx].setChecked(True) + rblist[start_idx].toggled.emit(True) - lo.addWidget(label, _irow, 0, 1, 1, Qt.AlignLeft | Qt.AlignVCenter) - lo.addWidget(line, _irow, 1, 1, 4, Qt.AlignLeft | Qt.AlignVCenter) + icol = 0 + icolspan = 3 + if label: + lo.addWidget(label, _irow, icol, _irowspan, 1, + Qt.AlignLeft | Qt.AlignVCenter) + icol += 1 + lo.addWidget(line, _irow, icol, _irowspan, icolspan, + Qt.AlignLeft | Qt.AlignVCenter) #print(title, tab) #print(key, line, icolumn, wgt_type, text, flush=True) _irow += 1 - qw.setLayout(lo) + qw.setLayout(lo) + qw.setFixedHeight(tab_height) qtab_widget_dict[itab] = qw qtab_widget_dict[itab].title = title qtab_widget.addTab(qtab_widget_dict[itab], title) itab += 1 qtab_widget.qtab_widget_dict = qtab_widget_dict - - meas_label = QLabel(text) meas_label.setFixedHeight(24) meas_label.setFont(self.font_pts10) @@ -1244,10 +1503,11 @@ class GUIFrame(QWidget): meas_line.setObjectName("Read") fm = QFontMetricsF(meas_line.font()) - param_width = fm.width(longest_title) - extra_width = 22 if len(str(value)) < 15 else 28 - meas_line.setFixedWidth(param_width + extra_width) - + #param_width = fm.width(longest_title) + #extra_width = 22 if len(str(value)) < 15 else 28 + #meas_line.setFixedWidth(param_width + extra_width) + param_width = fm.maxWidth() * len(longest_title) + meas_line.setMaximumWidth(param_width*0.5) qtab_widget.setContentsMargins(0, 40, 0, 0) qtab_widget.currentChanged.connect(on_tab_change) @@ -1343,16 +1603,12 @@ class GUIFrame(QWidget): pv_local = pv monitor_pv = None - def cb_pv_selector(value): - value_str = radio_buddy_text_dict[self.sender()].text() - #print("cb_pv_selector", value_str) #Find which group the radio button belongs to: #Find which stack the group belongs to: #Set this to the current stack? - for k, v in self.stack_radiolist_dict.items(): if self.sender() in v: #get groupbox from key @@ -1425,7 +1681,7 @@ class GUIFrame(QWidget): monitor_pv = CAQLabel(self, pv_name=pv, monitor_callback=mon_cb, show_units=True) - monitor_pv.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + monitor_pv.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) monitor_pv.setFont(self.font_pts10) monitor_pv.setFixedHeight(self.widget_height) monitor_pv.setFixedWidth(122) @@ -1443,8 +1699,6 @@ class GUIFrame(QWidget): if init_value is not None: val = read_pv.format_display_value(init_value) - #else: - # val = 0.0 read_pv.setText(str(val)) read_pv.setFixedHeight(self.widget_height) read_pv.setFixedWidth(122) @@ -1887,8 +2141,8 @@ class GUIFrame(QWidget): else: wgt_list = ["QCheckBox", "QComboBox", "QDoubleSpinBox", "QFrame", "QHLine", "QLabel", "QLineEdit", "QLineRead", - "QRadioButton", "QSpinBox", "QPhaseStackedWidget", - "QEnergyStackedWidget"] + "QRadioButton", "QSpinBox", "QTabWidget", + "QPhaseStackedWidget", "QEnergyStackedWidget"] print("Widget {0} is not Supported".format(buddy)) print("Supported widgets are: {0}".format(wgt_list)) pass