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 }