File indexing completed on 2024-05-12 15:28:18
0001 /*************************************************************************** 0002 File : FitParametersWidget.cc 0003 Project : LabPlot 0004 Description : widget for editing fit parameters 0005 -------------------------------------------------------------------- 0006 Copyright : (C) 2014-2016 Alexander Semke (alexander.semke@web.de) 0007 Copyright : (C) 2016-2018 Stefan Gerlach (stefan.gerlach@uni.kn) 0008 0009 ***************************************************************************/ 0010 0011 /*************************************************************************** 0012 * * 0013 * This program is free software; you can redistribute it and/or modify * 0014 * it under the terms of the GNU General Public License as published by * 0015 * the Free Software Foundation; either version 2 of the License, or * 0016 * (at your option) any later version. * 0017 * * 0018 * This program is distributed in the hope that it will be useful, * 0019 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0020 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0021 * GNU General Public License for more details. * 0022 * * 0023 * You should have received a copy of the GNU General Public License * 0024 * along with this program; if not, write to the Free Software * 0025 * Foundation, Inc., 51 Franklin Street, Fifth Floor, * 0026 * Boston, MA 02110-1301 USA * 0027 * * 0028 ***************************************************************************/ 0029 #include "FitParametersWidget.h" 0030 0031 #include <KLocalizedString> 0032 0033 #include <QLineEdit> 0034 #include <QCheckBox> 0035 #include <QKeyEvent> 0036 #include <QScrollBar> 0037 0038 /*! 0039 \class FitParametersWidget 0040 \brief Widget for editing fit parameters. For predefined models the number of parameters, 0041 their names and default values are given - the user can change the start values. 0042 For custom models the user has to define here the parameter names and their start values. 0043 0044 \ingroup kdefrontend 0045 */ 0046 FitParametersWidget::FitParametersWidget(QWidget* parent) : QWidget(parent) { 0047 ui.setupUi(this); 0048 0049 ui.tableWidget->setColumnCount(5); 0050 0051 auto* headerItem = new QTableWidgetItem(); 0052 headerItem->setText(i18n("Name")); 0053 ui.tableWidget->setHorizontalHeaderItem(0, headerItem); 0054 0055 headerItem = new QTableWidgetItem(); 0056 headerItem->setText(i18n("Start value")); 0057 ui.tableWidget->setHorizontalHeaderItem(1, headerItem); 0058 0059 headerItem = new QTableWidgetItem(); 0060 headerItem->setText(i18n("Fixed")); 0061 ui.tableWidget->setHorizontalHeaderItem(2, headerItem); 0062 0063 headerItem = new QTableWidgetItem(); 0064 headerItem->setText(i18n("Lower limit")); 0065 ui.tableWidget->setHorizontalHeaderItem(3, headerItem); 0066 0067 headerItem = new QTableWidgetItem(); 0068 headerItem->setText(i18n("Upper limit")); 0069 ui.tableWidget->setHorizontalHeaderItem(4, headerItem); 0070 0071 ui.tableWidget->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); 0072 ui.tableWidget->horizontalHeader()->setStretchLastSection(true); 0073 0074 ui.tableWidget->installEventFilter(this); 0075 0076 connect(ui.tableWidget, SIGNAL(cellChanged(int, int)), this, SLOT(changed()) ); 0077 updateTableSize(); 0078 } 0079 0080 void FitParametersWidget::setFitData(XYFitCurve::FitData* data) { 0081 DEBUG("FitParametersWidget::setFitData()"); 0082 m_initializing = true; 0083 m_fitData = data; 0084 0085 int np = m_fitData->paramNames.size(); 0086 DEBUG("# params = " << np); 0087 DEBUG("# start values = " << m_fitData->paramStartValues.size()); 0088 SET_NUMBER_LOCALE 0089 if (m_fitData->modelCategory != nsl_fit_model_custom) { // pre-defined models 0090 ui.tableWidget->setRowCount(np); 0091 0092 for (int i = 0; i < np; ++i) { 0093 // name 0094 auto* item = new QTableWidgetItem(m_fitData->paramNamesUtf8.at(i)); 0095 item->setFlags(item->flags() ^ Qt::ItemIsEditable); 0096 item->setBackground(QApplication::palette().color(QPalette::Window)); 0097 ui.tableWidget->setItem(i, 0, item); 0098 0099 // start value 0100 auto* le = new QLineEdit(ui.tableWidget); 0101 le->setValidator(new QDoubleValidator(le)); 0102 le->setFrame(false); 0103 le->insert(numberLocale.toString(m_fitData->paramStartValues.at(i))); 0104 ui.tableWidget->setCellWidget(i, 1, le); 0105 connect(le, SIGNAL(textChanged(QString)), this, SLOT(startValueChanged()) ); 0106 0107 // fixed 0108 QWidget* widget = new QWidget(); 0109 auto* cb = new QCheckBox(); 0110 cb->setChecked(m_fitData->paramFixed.at(i)); 0111 auto* cbl = new QHBoxLayout(widget); 0112 cbl->addWidget(cb); 0113 cbl->setAlignment(Qt::AlignCenter); 0114 cbl->setContentsMargins(0, 0, 0, 0); 0115 widget->setLayout(cbl); 0116 ui.tableWidget->setCellWidget(i, 2, widget); 0117 connect(cb, SIGNAL(stateChanged(int)), this, SLOT(changed()) ); 0118 0119 // limits 0120 le = new QLineEdit(ui.tableWidget); 0121 le->setValidator(new QDoubleValidator(le)); 0122 le->setFrame(false); 0123 if (m_fitData->paramLowerLimits.at(i) > -std::numeric_limits<double>::max()) 0124 le->insert(numberLocale.toString(m_fitData->paramLowerLimits.at(i))); 0125 ui.tableWidget->setCellWidget(i, 3, le); 0126 connect(le, SIGNAL(textChanged(QString)), this, SLOT(lowerLimitChanged()) ); 0127 0128 le = new QLineEdit(ui.tableWidget); 0129 le->setValidator(new QDoubleValidator(le)); 0130 le->setFrame(false); 0131 if (m_fitData->paramUpperLimits.at(i) < std::numeric_limits<double>::max()) 0132 le->insert(numberLocale.toString(m_fitData->paramUpperLimits.at(i))); 0133 ui.tableWidget->setCellWidget(i, 4, le); 0134 connect(le, SIGNAL(textChanged(QString)), this, SLOT(upperLimitChanged()) ); 0135 } 0136 ui.tableWidget->setCurrentCell(0, 1); 0137 } else { // custom model 0138 if (!m_fitData->paramNames.isEmpty()) { // parameters for the custom model are already available -> show them 0139 ui.tableWidget->setRowCount(np); 0140 0141 for (int i = 0; i < np; ++i) { 0142 // name 0143 auto* item = new QTableWidgetItem(m_fitData->paramNames.at(i)); 0144 item->setBackground(QApplication::palette().color(QPalette::Window)); 0145 ui.tableWidget->setItem(i, 0, item); 0146 0147 // start value 0148 auto* le = new QLineEdit(ui.tableWidget); 0149 le->setValidator(new QDoubleValidator(le)); 0150 le->setFrame(false); 0151 le->insert(numberLocale.toString(m_fitData->paramStartValues.at(i))); 0152 ui.tableWidget->setCellWidget(i, 1, le); 0153 connect(le, SIGNAL(textChanged(QString)), this, SLOT(startValueChanged()) ); 0154 0155 // fixed 0156 QWidget* widget = new QWidget(); 0157 auto* cb = new QCheckBox(); 0158 cb->setChecked(m_fitData->paramFixed.at(i)); 0159 auto* cbl = new QHBoxLayout(widget); 0160 cbl->addWidget(cb); 0161 cbl->setAlignment(Qt::AlignCenter); 0162 cbl->setContentsMargins(0, 0, 0, 0); 0163 widget->setLayout(cbl); 0164 ui.tableWidget->setCellWidget(i, 2, widget); 0165 connect(cb, SIGNAL(stateChanged(int)), this, SLOT(changed()) ); 0166 0167 // limits 0168 le = new QLineEdit(ui.tableWidget); 0169 le->setValidator(new QDoubleValidator(le)); 0170 le->setFrame(false); 0171 if (m_fitData->paramLowerLimits.at(i) > -std::numeric_limits<double>::max()) 0172 le->insert(numberLocale.toString(m_fitData->paramLowerLimits.at(i))); 0173 ui.tableWidget->setCellWidget(i, 3, le); 0174 connect(le, SIGNAL(textChanged(QString)), this, SLOT(lowerLimitChanged()) ); 0175 0176 le = new QLineEdit(ui.tableWidget); 0177 le->setValidator(new QDoubleValidator(le)); 0178 le->setFrame(false); 0179 if (m_fitData->paramUpperLimits.at(i) < std::numeric_limits<double>::max()) 0180 le->insert(numberLocale.toString(m_fitData->paramUpperLimits.at(i))); 0181 ui.tableWidget->setCellWidget(i, 4, le); 0182 connect(le, SIGNAL(textChanged(QString)), this, SLOT(upperLimitChanged()) ); 0183 } 0184 } else { // no parameters available yet -> create the first row in the table for the first parameter 0185 ui.tableWidget->setRowCount(1); 0186 // name 0187 auto* item = new QTableWidgetItem(); 0188 item->setBackground(QApplication::palette().color(QPalette::Window)); 0189 ui.tableWidget->setItem(0, 0, item); 0190 0191 // start value 0192 auto* le = new QLineEdit(ui.tableWidget); 0193 le->setValidator(new QDoubleValidator(le)); 0194 le->setFrame(false); 0195 ui.tableWidget->setCellWidget(0, 1, le); 0196 connect(le, SIGNAL(textChanged(QString)), this, SLOT(startValueChanged()) ); 0197 0198 // fixed 0199 QWidget* widget = new QWidget(); 0200 auto* cb = new QCheckBox(); 0201 auto* cbl = new QHBoxLayout(widget); 0202 cbl->addWidget(cb); 0203 cbl->setAlignment(Qt::AlignCenter); 0204 cbl->setContentsMargins(0, 0, 0, 0); 0205 widget->setLayout(cbl); 0206 ui.tableWidget->setCellWidget(0, 2, widget); 0207 connect(cb, SIGNAL(stateChanged(int)), this, SLOT(changed()) ); 0208 0209 // limits 0210 le = new QLineEdit(ui.tableWidget); 0211 le->setValidator(new QDoubleValidator(le)); 0212 le->setFrame(false); 0213 ui.tableWidget->setCellWidget(0, 3, le); 0214 connect(le, SIGNAL(textChanged(QString)), this, SLOT(lowerLimitChanged()) ); 0215 0216 le = new QLineEdit(ui.tableWidget); 0217 le->setValidator(new QDoubleValidator(le)); 0218 le->setFrame(false); 0219 ui.tableWidget->setCellWidget(0, 4, le); 0220 connect(le, SIGNAL(textChanged(QString)), this, SLOT(upperLimitChanged()) ); 0221 } 0222 ui.tableWidget->setCurrentCell(0, 0); 0223 } 0224 m_initializing = false; 0225 0226 updateTableSize(); 0227 } 0228 0229 bool FitParametersWidget::eventFilter(QObject* watched, QEvent* event) { 0230 if (watched == ui.tableWidget) { 0231 if (event->type() == QEvent::KeyPress) { 0232 auto* keyEvent = static_cast<QKeyEvent*>(event); 0233 if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { 0234 //on the second column with the values is editable. 0235 //navigate to the next cell in the second column 0236 if (ui.tableWidget->currentRow() == ui.tableWidget->rowCount() - 1) 0237 ui.tableWidget->clearSelection(); 0238 else 0239 ui.tableWidget->setCurrentCell(ui.tableWidget->currentRow() + 1, 1); 0240 0241 return true; 0242 } 0243 } 0244 } 0245 0246 return QWidget::eventFilter(watched, event); 0247 } 0248 0249 void FitParametersWidget::resizeEvent(QResizeEvent*) { 0250 updateTableSize(); 0251 } 0252 0253 void FitParametersWidget::updateTableSize() { 0254 if (m_resizing) 0255 return; 0256 0257 m_resizing = true; 0258 auto horHeader = ui.tableWidget->horizontalHeader(); 0259 auto vertHeader = ui.tableWidget->verticalHeader(); 0260 int count = vertHeader->count(); 0261 0262 //show the vertrical scrollbar if we have more than 5 rows and limit the max size to 5 rows 0263 if (count <= 5) 0264 ui.tableWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0265 else { 0266 ui.tableWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); 0267 count = 5; 0268 } 0269 0270 //set the size of the table to the minimum possible 0271 int h = horHeader->height(); 0272 h += vertHeader->sectionSize(0) * count; 0273 if (ui.tableWidget->horizontalScrollBar()->isVisible()) 0274 h += ui.tableWidget->horizontalScrollBar()->height(); 0275 setMinimumSize(16777215, h); 0276 setMaximumSize(16777215, h); 0277 m_resizing = false; 0278 } 0279 0280 0281 void FitParametersWidget::changed() { 0282 DEBUG("FitParametersWidget::changed()"); 0283 if (!m_initializing) { 0284 apply(); 0285 emit parametersChanged(false); 0286 } 0287 } 0288 0289 /* 0290 * Apply parameter settings by setting m_fitData 0291 */ 0292 void FitParametersWidget::apply() { 0293 DEBUG("FitParametersWidget::apply()"); 0294 if (m_fitData->modelCategory != nsl_fit_model_custom) { // pre-defined models 0295 for (int i = 0; i < ui.tableWidget->rowCount(); ++i) { 0296 SET_DOUBLE_FROM_LE(m_fitData->paramStartValues[i], (QLineEdit *)ui.tableWidget->cellWidget(i, 1)) 0297 0298 QWidget *widget = ui.tableWidget->cellWidget(i, 2)->layout()->itemAt(0)->widget(); 0299 m_fitData->paramFixed[i] = (qobject_cast<QCheckBox *>(widget))->isChecked(); 0300 0301 if ( !((QLineEdit *)ui.tableWidget->cellWidget(i, 3))->text().isEmpty() ) 0302 SET_DOUBLE_FROM_LE(m_fitData->paramLowerLimits[i], (QLineEdit *)ui.tableWidget->cellWidget(i, 3)) 0303 else 0304 m_fitData->paramLowerLimits[i] = -std::numeric_limits<double>::max(); 0305 if ( !((QLineEdit *)ui.tableWidget->cellWidget(i, 4))->text().isEmpty() ) 0306 SET_DOUBLE_FROM_LE(m_fitData->paramUpperLimits[i], (QLineEdit *)ui.tableWidget->cellWidget(i, 4)) 0307 else 0308 m_fitData->paramUpperLimits[i] = std::numeric_limits<double>::max(); 0309 } 0310 } else { // custom model 0311 m_fitData->paramNames.clear(); 0312 m_fitData->paramNamesUtf8.clear(); 0313 m_fitData->paramStartValues.clear(); 0314 m_fitData->paramFixed.clear(); 0315 m_fitData->paramLowerLimits.clear(); 0316 m_fitData->paramUpperLimits.clear(); 0317 0318 SET_NUMBER_LOCALE 0319 for (int i = 0; i < ui.tableWidget->rowCount(); ++i) { 0320 // skip those rows where either the name or the value is empty 0321 if ( !ui.tableWidget->item(i, 0)->text().simplified().isEmpty() 0322 && !((QLineEdit *)ui.tableWidget->cellWidget(i, 1))->text().simplified().isEmpty() ) { 0323 m_fitData->paramNames.append( ui.tableWidget->item(i, 0)->text() ); 0324 m_fitData->paramNamesUtf8.append( ui.tableWidget->item(i, 0)->text() ); 0325 m_fitData->paramStartValues.append( numberLocale.toDouble(((QLineEdit *)ui.tableWidget->cellWidget(i, 1))->text()) ); 0326 0327 QWidget *widget = ui.tableWidget->cellWidget(i, 2)->layout()->itemAt(0)->widget(); 0328 m_fitData->paramFixed.append( (qobject_cast<QCheckBox *>(widget))->isChecked() ); 0329 0330 if ( !((QLineEdit *)ui.tableWidget->cellWidget(i, 3))->text().isEmpty() ) 0331 m_fitData->paramLowerLimits.append( numberLocale.toDouble(((QLineEdit *)ui.tableWidget->cellWidget(i, 3))->text()) ); 0332 else 0333 m_fitData->paramLowerLimits.append(-std::numeric_limits<double>::max()); 0334 if ( !((QLineEdit *)ui.tableWidget->cellWidget(i, 4))->text().isEmpty() ) 0335 m_fitData->paramUpperLimits.append( numberLocale.toDouble(((QLineEdit *)ui.tableWidget->cellWidget(i, 4))->text()) ); 0336 else 0337 m_fitData->paramUpperLimits.append(std::numeric_limits<double>::max()); 0338 } 0339 } 0340 } 0341 } 0342 0343 /* 0344 * called when a start value is changed 0345 */ 0346 void FitParametersWidget::startValueChanged() { 0347 DEBUG("FitParametersWidget::startValueChanged()"); 0348 const int row = ui.tableWidget->currentRow(); 0349 double value{1}; 0350 SET_DOUBLE_FROM_LE(value, (QLineEdit *)ui.tableWidget->cellWidget(row, 1)) 0351 0352 double lowerLimit{-std::numeric_limits<double>::max()}, upperLimit{std::numeric_limits<double>::max()}; 0353 if ( !((QLineEdit *)ui.tableWidget->cellWidget(row, 3))->text().isEmpty() ) 0354 SET_DOUBLE_FROM_LE(lowerLimit, (QLineEdit *)ui.tableWidget->cellWidget(row, 3)) 0355 if ( !((QLineEdit *)ui.tableWidget->cellWidget(row, 4))->text().isEmpty() ) 0356 SET_DOUBLE_FROM_LE(upperLimit, (QLineEdit *)ui.tableWidget->cellWidget(row, 4)) 0357 0358 const bool invalid = (value < lowerLimit || value > upperLimit); 0359 highlightInvalid(row, 1, invalid); 0360 if (invalid) 0361 m_invalidRanges = true; 0362 0363 if (m_rehighlighting) 0364 return; 0365 0366 //start value was changed -> check whether the lower and upper limits are valid and highlight them if not 0367 m_invalidRanges = invalid; 0368 m_rehighlighting = true; 0369 lowerLimitChanged(); 0370 upperLimitChanged(); 0371 m_rehighlighting = false; 0372 0373 changed(); 0374 } 0375 0376 // check if lower limit fits to start value and upper limit 0377 void FitParametersWidget::lowerLimitChanged() { 0378 DEBUG("FitParametersWidget::lowerLimitChanged()"); 0379 const int row = ui.tableWidget->currentRow(); 0380 0381 double value{1}; 0382 SET_DOUBLE_FROM_LE(value, (QLineEdit *)ui.tableWidget->cellWidget(row, 1)) 0383 0384 double lowerLimit{-std::numeric_limits<double>::max()}, upperLimit{std::numeric_limits<double>::max()}; 0385 if ( !((QLineEdit *)ui.tableWidget->cellWidget(row, 3))->text().isEmpty() ) 0386 SET_DOUBLE_FROM_LE(lowerLimit, (QLineEdit *)ui.tableWidget->cellWidget(row, 3)) 0387 if ( !((QLineEdit *)ui.tableWidget->cellWidget(row, 4))->text().isEmpty() ) 0388 SET_DOUBLE_FROM_LE(upperLimit, (QLineEdit *)ui.tableWidget->cellWidget(row, 4)) 0389 0390 const bool invalid = (lowerLimit > value || lowerLimit > upperLimit); 0391 highlightInvalid(row, 3, invalid); 0392 if (invalid) 0393 m_invalidRanges = true; 0394 0395 if (m_rehighlighting) 0396 return; 0397 0398 //lower limit was changed -> check whether the start value and the upper limit are valid and highlight them if not 0399 m_invalidRanges = invalid; 0400 m_rehighlighting = true; 0401 startValueChanged(); 0402 upperLimitChanged(); 0403 m_rehighlighting = false; 0404 0405 changed(); 0406 } 0407 0408 // check if upper limit fits to start value and lower limit 0409 void FitParametersWidget::upperLimitChanged() { 0410 DEBUG("FitParametersWidget::upperLimitChanged()"); 0411 const int row = ui.tableWidget->currentRow(); 0412 0413 double value{1}; 0414 SET_DOUBLE_FROM_LE(value, (QLineEdit *)ui.tableWidget->cellWidget(row, 1)) 0415 0416 double lowerLimit{-std::numeric_limits<double>::max()}, upperLimit{std::numeric_limits<double>::max()}; 0417 if ( !((QLineEdit *)ui.tableWidget->cellWidget(row, 3))->text().isEmpty() ) 0418 SET_DOUBLE_FROM_LE(lowerLimit, (QLineEdit *)ui.tableWidget->cellWidget(row, 3)) 0419 if ( !((QLineEdit *)ui.tableWidget->cellWidget(row, 4))->text().isEmpty() ) 0420 SET_DOUBLE_FROM_LE(upperLimit, (QLineEdit *)ui.tableWidget->cellWidget(row, 4)) 0421 0422 const bool invalid = (upperLimit < value || upperLimit < lowerLimit); 0423 highlightInvalid(row, 4, invalid); 0424 if (invalid) 0425 m_invalidRanges = true; 0426 0427 if (m_rehighlighting) 0428 return; 0429 0430 //upper limit was changed -> check whether the start value and the lower limit are valid and highlight them if not 0431 m_invalidRanges = invalid; 0432 m_rehighlighting = true; 0433 startValueChanged(); 0434 lowerLimitChanged(); 0435 m_rehighlighting = false; 0436 0437 changed(); 0438 } 0439 0440 void FitParametersWidget::highlightInvalid(int row, int col, bool invalid) { 0441 QLineEdit* le = ((QLineEdit*)ui.tableWidget->cellWidget(row, col)); 0442 if (invalid) 0443 le->setStyleSheet("QLineEdit{background: red;}"); 0444 else 0445 le->setStyleSheet(QString()); 0446 0447 if (m_invalidRanges) 0448 emit parametersValid(false); 0449 else 0450 emit parametersValid(true); 0451 }