nmreval/src/gui_qt/fit/fit_forms.py
dominik 8d148b639b BUGFIX: VFT;
change to src layout
2022-10-20 17:23:15 +02:00

461 lines
15 KiB
Python

from __future__ import annotations
from nmreval.utils.text import convert
from ..Qt import QtCore, QtWidgets, QtGui
from .._py.fitmodelwidget import Ui_FitParameter
from .._py.save_fitmodel_dialog import Ui_SaveDialog
from ..lib import get_icon
class FitModelWidget(QtWidgets.QWidget, Ui_FitParameter):
value_requested = QtCore.pyqtSignal(object)
value_changed = QtCore.pyqtSignal(str)
state_changed = QtCore.pyqtSignal()
def __init__(self, label: str = 'Fitparameter', parent=None, fixed: bool = False):
super().__init__(parent)
self.setupUi(self)
self.parametername.setText(label + ' ')
validator = QtGui.QDoubleValidator()
validator.setDecimals(9)
self.parameter_line.setValidator(validator)
self.parameter_line.setText('1')
self.parameter_line.setMaximumWidth(60)
self.lineEdit.setMaximumWidth(60)
self.lineEdit_2.setMaximumWidth(60)
self.label_3.setText(f'< {label} <')
self.checkBox.stateChanged.connect(self.enableBounds)
self.global_checkbox.stateChanged.connect(lambda: self.state_changed.emit())
self.parameter_line.values_requested.connect(lambda: self.value_requested.emit(self))
self.parameter_line.editingFinished.connect(lambda: self.value_changed.emit(self.parameter_line.text()))
self.fixed_check.toggled.connect(self.set_fixed)
if fixed:
self.fixed_check.hide()
self.menu = QtWidgets.QMenu(self)
self.add_links()
self.is_linked = None
self.parameter_pos = None
self.func_idx = None
self._linetext = '1'
@property
def name(self):
return convert(self.parametername.text().strip(), old='html', new='str')
def set_parameter_string(self, p: str):
self.parameter_line.setText(p)
self.parameter_line.setToolTip(p)
def set_bounds(self, lb: float, ub: float, cbox: bool = True):
self.checkBox.setCheckState(QtCore.Qt.Checked if cbox else QtCore.Qt.Unchecked)
for val, bds_line in [(lb, self.lineEdit), (ub, self.lineEdit_2)]:
if val is not None:
bds_line.setText(str(val))
else:
bds_line.setText('')
def enableBounds(self, value: int):
self.lineEdit.setEnabled(value == 2)
self.lineEdit_2.setEnabled(value == 2)
def set_parameter(self, p: float | None, bds: tuple[float, float, bool] = None,
fixed: bool = None, glob: bool = None):
if p is None:
# bad hack: linked parameter return (None, linked parameter)
# if p is None -> parameter is linked to argument given by bds
self.link_parameter(linkto=bds)
else:
ptext = f'{p:.4g}'
self.set_parameter_string(ptext)
if bds is not None:
self.set_bounds(*bds)
if fixed is not None:
self.fixed_check.setCheckState(QtCore.Qt.Unchecked if fixed else QtCore.Qt.Checked)
if glob is not None:
self.global_checkbox.setCheckState(QtCore.Qt.Checked if glob else QtCore.Qt.Unchecked)
def get_parameter(self):
if self.is_linked:
try:
p = float(self._linetext)
except ValueError:
p = 1.0
else:
try:
p = float(self.parameter_line.text().replace(',', '.'))
except ValueError:
_ = QtWidgets.QMessageBox().warning(self, 'Invalid value',
f'{self.parametername.text()} contains invalid values',
QtWidgets.QMessageBox.Cancel)
return None
if self.checkBox.isChecked():
try:
lb = float(self.lineEdit.text().replace(',', '.'))
except ValueError:
lb = None
try:
rb = float(self.lineEdit_2.text().replace(',', '.'))
except ValueError:
rb = None
else:
lb = rb = None
bounds = (lb, rb)
return p, bounds, not self.fixed_check.isChecked(), self.global_checkbox.isChecked(), self.is_linked
@QtCore.pyqtSlot(bool)
def set_fixed(self, state: bool):
# self.global_checkbox.setVisible(not state)
self.frame.setVisible(not state)
def add_links(self, parameter: dict = None):
if parameter is None:
parameter = {}
self.menu.clear()
ac = QtWidgets.QAction('Link to...', self)
ac.triggered.connect(self.link_parameter)
self.menu.addAction(ac)
for model_key, model_funcs in parameter.items():
m = QtWidgets.QMenu('Model ' + model_key, self)
for func_name, func_params in model_funcs.items():
m2 = QtWidgets.QMenu(func_name, m)
for p_name, idx in func_params:
ac = QtWidgets.QAction(p_name, m2)
ac.setData((model_key, *idx))
ac.triggered.connect(self.link_parameter)
m2.addAction(ac)
m.addMenu(m2)
self.menu.addMenu(m)
self.toolButton.setMenu(self.menu)
@QtCore.pyqtSlot()
def link_parameter(self, linkto=None):
if linkto is None:
action = self.sender()
else:
action = False
for m in self.menu.actions():
if m.menu():
for a in m.menu().actions():
if a.data() == linkto:
action = a
break
if action:
break
if (self.func_idx, self.parameter_pos) == action.data():
return
try:
new_text = f'Linked to {action.parentWidget().title()}.{action.text()}'
self._linetext = self.parameter_line.text()
self.parameter_line.setText(new_text)
self.parameter_line.setEnabled(False)
self.global_checkbox.hide()
self.global_checkbox.blockSignals(True)
self.global_checkbox.setCheckState(QtCore.Qt.Checked)
self.global_checkbox.blockSignals(False)
self.frame.hide()
self.is_linked = action.data()
except AttributeError:
self.parameter_line.setText(self._linetext)
self.parameter_line.setEnabled(True)
if self.fixed_check.isEnabled():
self.global_checkbox.show()
self.frame.show()
self.is_linked = None
self.state_changed.emit()
class QSaveModelDialog(QtWidgets.QDialog, Ui_SaveDialog):
def __init__(self, types=None, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
if types is None:
types = []
self.comboBox.blockSignals(True)
self.comboBox.addItems(types)
self.comboBox.addItem('New group...')
self.comboBox.blockSignals(False)
self.frame.hide()
@QtCore.pyqtSlot(int, name='on_comboBox_currentIndexChanged')
def new_group(self, idx: int):
if idx == self.comboBox.count() - 1:
self.frame.show()
else:
self.lineEdit_2.clear()
self.frame.hide()
@QtCore.pyqtSlot(name='on_toolButton_clicked')
def accept_group(self):
self.comboBox.insertItem(self.comboBox.count() - 1, self.lineEdit_2.text())
self.comboBox.setCurrentIndex(self.comboBox.count() - 2)
def accept(self):
if self.lineEdit.text():
self.close()
class FitModelTree(QtWidgets.QTreeWidget):
icons = ['plus', 'mal_icon', 'minus_icon', 'geteilt_icon']
treeChanged = QtCore.pyqtSignal()
itemRemoved = QtCore.pyqtSignal(int)
counterRole = QtCore.Qt.UserRole + 1
operatorRole = QtCore.Qt.UserRole + 2
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setHeaderHidden(True)
self.setDragEnabled(True)
self.setDragDropMode(QtWidgets.QTreeWidget.InternalMove)
self.setDefaultDropAction(QtCore.Qt.MoveAction)
self.itemSelectionChanged.connect(lambda: self.treeChanged.emit())
def keyPressEvent(self, evt):
operators = [QtCore.Qt.Key_Plus, QtCore.Qt.Key_Asterisk,
QtCore.Qt.Key_Minus, QtCore.Qt.Key_Slash]
if evt.key() == QtCore.Qt.Key_Delete:
for item in self.selectedItems():
self.remove_function(item)
elif evt.key() == QtCore.Qt.Key_Space:
for item in self.treeWidget.selectedItems():
item.setCheckState(0, QtCore.Qt.Checked) if item.checkState(
0) == QtCore.Qt.Unchecked else item.setCheckState(0, QtCore.Qt.Unchecked)
elif evt.key() in operators:
idx = operators.index(evt.key())
for item in self.selectedItems():
item.setData(0, self.operatorRole, idx)
item.setIcon(0, get_icon(self.icons[idx]))
else:
super().keyPressEvent(evt)
def dropEvent(self, evt: QtGui.QDropEvent):
super().dropEvent(evt)
self.treeChanged.emit()
def remove_function(self, item: QtWidgets.QTreeWidgetItem):
"""
Remove function and children from tree and dictionary
"""
while item.childCount():
self.remove_function(item.child(0))
if item.parent():
item.parent().removeChild(item)
else:
self.invisibleRootItem().removeChild(item)
idx = item.data(0, self.counterRole)
self.itemRemoved.emit(idx)
def add_function(self, idx: int, cnt: int, op: int, name: str, color: QtGui.QColor | str | tuple,
parent: QtWidgets.QTreeWidgetItem = None, children: list = None, active: bool = True, **kwargs):
"""
Add function to tree and dictionary of functions.
"""
if not isinstance(color, QtGui.QColor):
if isinstance(color, tuple):
color = QtGui.QColor.fromRgbF(*color)
else:
color = QtGui.QColor(color)
it = QtWidgets.QTreeWidgetItem()
it.setData(0, QtCore.Qt.UserRole, idx)
it.setData(0, self.counterRole, cnt)
it.setData(0, self.operatorRole, op)
it.setText(0, name)
it.setForeground(0, QtGui.QBrush(color))
it.setIcon(0, get_icon(self.icons[op]))
it.setCheckState(0, QtCore.Qt.Checked if active else QtCore.Qt.Unchecked)
if parent is None:
self.addTopLevelItem(it)
else:
parent.addChild(it)
if children is not None:
for c in children:
self.add_function(**c, parent=it)
self.setCurrentIndex(self.indexFromItem(it, 0))
def sizeHint(self):
w = super().sizeHint().width()
return QtCore.QSize(w, 100)
def get_selected(self):
try:
it = self.selectedItems()[0]
function_nr = it.data(0, QtCore.Qt.UserRole)
idx = it.data(0, self.counterRole)
except IndexError:
function_nr = None
idx = None
return function_nr, idx
def get_functions(self, full: bool = True, pos: int = -1, return_pos: bool = False, parent=None):
"""
Create nested list of functions in tree. Parameters saved are idx (Index of function in list of all functions),
cnt (counter of number to associate with functione values), ops (+, -, *, /), and maybe children.
"""
if parent is None:
parent = self.invisibleRootItem()
funcs = []
for i in range(parent.childCount()):
pos += 1
it = parent.child(i)
child = {
'idx': it.data(0, QtCore.Qt.UserRole),
'op': it.data(0, self.operatorRole),
'pos': pos,
'active': (it.checkState(0) == QtCore.Qt.Checked),
'children': []
}
if full:
child['name'] = it.text(0)
child['cnt'] = it.data(0, self.counterRole)
child['color'] = it.foreground(0).color().getRgbF()
if it.childCount():
child['children'], pos = self.get_functions(full=full, parent=it, pos=pos, return_pos=True)
funcs.append(child)
if return_pos:
return funcs, pos
else:
return funcs
class FitTableWidget(QtWidgets.QTableWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.horizontalHeader().hide()
self.verticalHeader().hide()
self.setColumnCount(2)
self.setSelectionBehavior(QtWidgets.QTableWidget.SelectRows)
self.horizontalHeader().setStretchLastSection(True)
self.hideColumn(1)
def add_model(self, idx: str):
model_count = 0
for r in range(self.rowCount()):
cb = self.cellWidget(r, 1)
cb.addItem('Model ' + str(idx), userData=idx)
model_count = cb.count()
if model_count > 2:
if self.isColumnHidden(1):
self.showColumn(1)
self.resizeColumnToContents(0)
self.setColumnWidth(1, self.columnWidth(0) - self.columnWidth(1))
def remove_model(self, idx: str):
model_count = 0
for r in range(self.rowCount()):
cb = self.cellWidget(r, 1)
if cb.currentData() == idx:
cb.setCurrentIndex(0)
cb.removeItem(cb.findData(idx))
model_count = cb.count()
if model_count == 2:
self.hideColumn(1)
self.resizeColumnToContents(0)
def load(self, set_ids: list[str]):
self.blockSignals(True)
while self.rowCount():
self.removeRow(0)
self.setColumnCount(2)
self.hideColumn(1)
for (sid, name) in set_ids:
item = QtWidgets.QTableWidgetItem(name)
item.setCheckState(QtCore.Qt.Checked)
item.setData(QtCore.Qt.UserRole+1, sid)
row = self.rowCount()
self.setRowCount(row+1)
self.setItem(row, 0, item)
item2 = QtWidgets.QTableWidgetItem('')
self.setItem(row, 1, item2)
cb = QtWidgets.QComboBox(parent=self)
cb.addItem('Default')
self.setCellWidget(row, 1, cb)
self.blockSignals(False)
def collect_data(self, default: str = None, include_name: bool = False) -> dict:
data = {}
for i in range(self.rowCount()):
item = self.item(i, 0)
if item.checkState() == QtCore.Qt.Checked:
mod = self.cellWidget(i, 1).currentData()
if mod is None:
mod = default
if include_name:
arg = (item.data(QtCore.Qt.UserRole+1), item.text())
else:
arg = item.data(QtCore.Qt.UserRole+1)
if mod not in data:
data[mod] = []
data[mod].append(arg)
return data
def data_list(self, include_name: bool = True) -> list:
ret_val = []
for i in range(self.rowCount()):
item = self.item(i, 0)
if include_name:
ret_val.append((item.data(QtCore.Qt.UserRole+1), item.text()))
else:
ret_val.append(item.data(QtCore.Qt.UserRole+1))
return ret_val