from numpy import inf from nmreval.utils.text import convert from ..Qt import QtGui, QtCore, QtWidgets from .._py.mean_form import Ui_mean_form class QDelayWidget(QtWidgets.QWidget): get_data = QtCore.pyqtSignal(str) newDelay = QtCore.pyqtSignal() def __init__(self, parent=None): super().__init__(parent=parent) self.vals = '' self.dim = 0 self.plainTextEdit.setVisible(False) def __call__(self): self.vals = '' self.dim = 0 self.comboBox.clear() self.comboBox.blockSignals(True) self.comboBox.addItem('New list...') self.comboBox.blockSignals(True) self.plainTextEdit.setVisible(False) def add_items(self, item): self.comboBox.blockSignals(True) if isinstance(item, list): self.comboBox.insertItems(0, item) else: self.comboBox.insertItem(0, item) self.comboBox.setCurrentIndex(0) self.comboBox.blockSignals(False) @QtCore.pyqtSlot(name='on_toolButton_clicked') def show_values(self): if self.plainTextEdit.isVisible(): self.plainTextEdit.clear() self.plainTextEdit.setVisible(False) elif self.comboBox.currentText() == 'New list...': self.newDelay.emit() else: self.get_data.emit(self.comboBox.currentText()) self.plainTextEdit.setVisible(True) self.plainTextEdit.setPlainText(self.vals) class LineEdit(QtWidgets.QLineEdit): values_requested = QtCore.pyqtSignal() def __init__(self, parent=None): super().__init__(parent=parent) def contextMenuEvent(self, evt): menu = self.createStandardContextMenu() request_action = menu.addAction('Use value of sets') action = menu.exec(evt.globalPos()) if action == request_action: self.values_requested.emit() class LineEditPost(QtWidgets.QLineEdit): values_requested = QtCore.pyqtSignal() def __init__(self, parent=None): super().__init__(parent=parent) self.suffix = '' self.prefix = '' self.editingFinished.connect(self.add_fixes) def add_fixes(self): self.setText(self.prefix+super().text()+self.suffix) def text(self): text = super().text() if text.startswith(self.prefix): text = text[len(self.prefix):] if text.endswith(self.suffix): text = text[:-len(self.suffix)] return text class FormWidget(QtWidgets.QWidget): types = { 'float': (float, QtGui.QDoubleValidator), 'int': (int, QtGui.QIntValidator), 'str': (str, lambda: 0), } valueChanged = QtCore.pyqtSignal(object) stateChanged = QtCore.pyqtSignal(bool) def __init__(self, name: str, validator: str = 'float', fixable: bool = False, parent=None): super().__init__(parent=parent) self._init_ui() self._name = name self._type = FormWidget.types[validator][0] self.vals.setValidator(FormWidget.types[validator][1]()) self.vals.textChanged.connect(lambda: self.valueChanged.emit(self.value) if self.value is not None else 0) self.label.setText(convert(name)) self._checkable = fixable self.checkBox.setVisible(fixable) self.checkBox.stateChanged.connect(lambda x: self.stateChanged.emit(True if x else False)) def _init_ui(self): layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(1, 1, 1, 1) layout.setSpacing(3) self.label = QtWidgets.QLabel(self) layout.addWidget(self.label) layout.addSpacerItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)) self.vals = QtWidgets.QLineEdit(self) layout.addWidget(self.vals) self.checkBox = QtWidgets.QCheckBox('Fix?', self) layout.addWidget(self.checkBox) @property def value(self): try: return self._type(self.vals.text().replace(',', '.')) except ValueError: return {float: 0.0, int: 0, str: ''}[self._type] @value.setter def value(self, val): if self._type == 'str': self.vals.setText(val) elif self._type == 'int': self.vals.setText(f'{float(val):.0f}') else: self.vals.setText(f'{float(val):.5g}') def setChecked(self, enable): if self._checkable: self.checkBox.setCheckState(QtCore.Qt.Checked if enable else QtCore.Qt.Unchecked) else: print(f'Parameter {self._name} is not variable') def isChecked(self): return self.checkBox.isChecked() class SelectionWidget(QtWidgets.QWidget): selectionChanged = QtCore.pyqtSignal(str, object) def __init__(self, label: str, argname: str, opts: dict, parent=None): super().__init__(parent=parent) self._init_ui() self.label.setText(convert(label)) for k in opts.keys(): self.comboBox.addItem(k) self.argname = argname self.options = opts self.comboBox.currentIndexChanged.connect(lambda idx: self.selectionChanged.emit(self.argname, self.value)) def _init_ui(self): layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(1, 1, 1, 1) layout.setSpacing(2) self.label = QtWidgets.QLabel(self) layout.addWidget(self.label) layout.addSpacerItem(QtWidgets.QSpacerItem(65, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)) self.comboBox = QtWidgets.QComboBox(self) layout.addWidget(self.comboBox) self.setLayout(layout) @property def value(self): return {self.argname: self.options[self.comboBox.currentText()]} def get_parameter(self): return str(self.comboBox.currentText()) @value.setter def value(self, val): if isinstance(val, dict): val = list(val.values())[0] key = [k for k, v in self.options.items() if v == val][0] self.comboBox.setCurrentIndex(self.comboBox.findText(key)) class Widget(QtWidgets.QWidget, Ui_mean_form): valueChanged = QtCore.pyqtSignal() def __init__(self, name: str, tree: dict, collapsing: bool = False, parent=None): super().__init__(parent=parent) self.setupUi(self) self._tree = {} self._collapse = collapsing self.label.setText(convert(name)) self.lineEdit.setValidator(QtGui.QDoubleValidator()) self.set_graphs(tree) self.change_graph(0) if self._collapse: self.digit_checkbox.hide() self.frame.hide() self.data_checkbox.stateChanged.connect(self.collapse_widgets) else: self.data_checkbox.stateChanged.connect(self.change_mode) self.digit_checkbox.stateChanged.connect(self.change_mode) def set_graphs(self, graph: dict): self.graph_combobox.blockSignals(True) self._tree.clear() for key, (name, _) in graph.items(): self.graph_combobox.addItem(name, userData=key) self.graph_combobox.blockSignals(False) self._tree.update(graph) self.change_graph(0) @QtCore.pyqtSlot(int, name='on_graph_combobox_currentIndexChanged') def change_graph(self, idx: int): self.set_combobox.clear() key = self.graph_combobox.itemData(idx, QtCore.Qt.UserRole) if key is not None: for set_key, set_name in self._tree[key][1]: self.set_combobox.addItem(set_name, userData=set_key) def on_lineEdit_textChanged(self, text=''): if text: self.valueChanged.emit() @property def value(self): if self.data_checkbox.isChecked(): return self.set_combobox.currentData() else: try: return float(self.lineEdit.text()) except ValueError: return @QtCore.pyqtSlot(int) def change_mode(self, state: int): box = self.sender() other_box = self.data_checkbox if box == self.digit_checkbox else self.digit_checkbox if (state == QtCore.Qt.Unchecked) and (other_box.checkState() == QtCore.Qt.Unchecked): box.blockSignals(True) box.setCheckState(QtCore.Qt.Checked) box.blockSignals(False) return other_box.blockSignals(True) other_box.setChecked(False) other_box.blockSignals(False) self.valueChanged.emit() @QtCore.pyqtSlot(int) def collapse_widgets(self, state: int): data_is_checked = state == QtCore.Qt.Checked self.frame.setVisible(data_is_checked) self.lineEdit.setVisible(not data_is_checked) class CheckBoxHeader(QtWidgets.QHeaderView): clicked = QtCore.pyqtSignal(int, bool) _x_offset = 3 _y_offset = 0 # This value is calculated later, based on the height of the paint rect _width = 20 _height = 20 def __init__(self, column_indices, orientation=QtCore.Qt.Horizontal, parent=None): super().__init__(orientation, parent) self.setSectionResizeMode(QtWidgets.QHeaderView.Stretch) # self.setClickable(True) if isinstance(column_indices, list) or isinstance(column_indices, tuple): self.column_indices = column_indices elif isinstance(column_indices, int): self.column_indices = [column_indices] else: raise RuntimeError('column_indices must be a list, tuple or integer') self.isChecked = {} for column in self.column_indices: self.isChecked[column] = 0 def paintSection(self, painter, rect, logicalIndex): painter.save() super().paintSection(painter, rect, logicalIndex) painter.restore() self._y_offset = int((rect.height()-self._width)/2.) if logicalIndex in self.column_indices: option = QtWidgets.QStyleOptionButton() option.rect = QtCore.QRect(rect.x() + self._x_offset, rect.y() + self._y_offset, self._width, self._height) option.state = QtWidgets.QStyle.State_Enabled | QtWidgets.QStyle.State_Active if self.isChecked[logicalIndex] == 2: option.state |= QtWidgets.QStyle.State_NoChange elif self.isChecked[logicalIndex]: option.state |= QtWidgets.QStyle.State_On else: option.state |= QtWidgets.QStyle.State_Off self.style().drawControl(QtWidgets.QStyle.CE_CheckBox,option,painter) def updateCheckState(self, index, state): self.isChecked[index] = state self.viewport().update() def mousePressEvent(self, event): index = self.logicalIndexAt(event.pos()) if 0 <= index < self.count(): x = self.sectionPosition(index) if x + self._x_offset < event.pos().x() < x + self._x_offset + self._width and self._y_offset < event.pos().y() < self._y_offset + self._height: if self.isChecked[index] == 1: self.isChecked[index] = 0 else: self.isChecked[index] = 1 self.clicked.emit(index, self.isChecked[index]) self.viewport().update() else: super().mousePressEvent(event) else: super().mousePressEvent(event) class CustomRangeDialog(QtWidgets.QDialog): """ Simple dialog to enter x and y limits to fit regions""" def __init__(self, parent=None): super().__init__(parent=parent) self.gl = QtWidgets.QGridLayout() self.gl.addWidget(QtWidgets.QLabel('Leave empty for complete range'), 0, 0, 1, 4) self._limits = [[], []] for i, orient in enumerate(['x', 'y'], start=1): self.gl.addWidget(QtWidgets.QLabel(orient + ' from'), i, 0) self.gl.addWidget(QtWidgets.QLabel('to'), i, 2) for j in [0, 1]: lim = QtWidgets.QLineEdit() lim.setValidator(QtGui.QDoubleValidator()) self._limits[i-1].append(lim) self.gl.addWidget(lim, i, 2*j+1) self.buttonbox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok) self.buttonbox.accepted.connect(self.accept) self.gl.addWidget(self.buttonbox, 3, 0, 1, 4) self.setLayout(self.gl) @property def limits(self): ret_val = [] for orient in self._limits: for i, w in enumerate(orient): val = w.text() if val == '': ret_val.append(-inf if i == 0 else inf) else: ret_val.append(float(val)) return ret_val class ElideComboBox(QtWidgets.QComboBox): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.view().setTextElideMode(QtCore.Qt.ElideRight) def paintEvent(self, evt: QtGui.QPaintEvent): opt = QtWidgets.QStyleOptionComboBox() self.initStyleOption(opt) painter = QtWidgets.QStylePainter(self) painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, opt) rect = self.style().subControlRect(QtWidgets.QStyle.CC_ComboBox, opt, QtWidgets.QStyle.SC_ComboBoxEditField, self) opt.currentText = painter.fontMetrics().elidedText(opt.currentText, QtCore.Qt.ElideRight, rect.width()) painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, opt)