File indexing completed on 2024-05-12 15:28:07

0001 /***************************************************************************
0002     File             : XYFitCurveDock.cpp
0003     Project          : LabPlot
0004     --------------------------------------------------------------------
0005     Copyright        : (C) 2014-2020 Alexander Semke (alexander.semke@web.de)
0006     Copyright        : (C) 2016-2020 Stefan Gerlach (stefan.gerlach@uni.kn)
0007     Description      : widget for editing properties of fit curves
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 
0030 #include "XYFitCurveDock.h"
0031 #include "backend/core/AspectTreeModel.h"
0032 #include "backend/core/Project.h"
0033 #include "backend/lib/macros.h"
0034 #include "backend/gsl/ExpressionParser.h"
0035 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
0036 #include "commonfrontend/widgets/TreeViewComboBox.h"
0037 #include "kdefrontend/widgets/ConstantsWidget.h"
0038 #include "kdefrontend/widgets/FunctionsWidget.h"
0039 #include "kdefrontend/widgets/FitOptionsWidget.h"
0040 #include "kdefrontend/widgets/FitParametersWidget.h"
0041 
0042 #include <KMessageWidget>
0043 
0044 #include <QMenu>
0045 #include <QWidgetAction>
0046 #include <QStandardItemModel>
0047 #include <QStandardPaths>
0048 #include <QClipboard>
0049 
0050 extern "C" {
0051 #include "backend/nsl/nsl_sf_stats.h"
0052 }
0053 
0054 /*!
0055   \class XYFitCurveDock
0056   \brief  Provides a widget for editing the properties of the XYFitCurves
0057         (2D-curves defined by a fit model) currently selected in
0058         the project explorer.
0059 
0060   If more then one curves are set, the properties of the first column are shown.
0061   The changes of the properties are applied to all curves.
0062   The exclusions are the name, the comment and the datasets (columns) of
0063   the curves  - these properties can only be changed if there is only one single curve.
0064 
0065   \ingroup kdefrontend
0066 */
0067 
0068 XYFitCurveDock::XYFitCurveDock(QWidget* parent) : XYCurveDock(parent) {
0069 }
0070 
0071 /*!
0072  *  set up "General" tab
0073  */
0074 void XYFitCurveDock::setupGeneral() {
0075     DEBUG("XYFitCurveDock::setupGeneral()");
0076     QWidget* generalTab = new QWidget(ui.tabGeneral);
0077     uiGeneralTab.setupUi(generalTab);
0078     m_leName = uiGeneralTab.leName;
0079     m_leComment = uiGeneralTab.leComment;
0080 
0081     auto* gridLayout = static_cast<QGridLayout*>(generalTab->layout());
0082     gridLayout->setContentsMargins(2, 2, 2, 2);
0083     gridLayout->setHorizontalSpacing(2);
0084     gridLayout->setVerticalSpacing(2);
0085 
0086     uiGeneralTab.cbDataSourceType->addItem(i18n("Spreadsheet"));
0087     uiGeneralTab.cbDataSourceType->addItem(i18n("XY-Curve"));
0088 
0089     cbDataSourceCurve = new TreeViewComboBox(generalTab);
0090     gridLayout->addWidget(cbDataSourceCurve, 5, 3, 1, 4);
0091 
0092     cbXDataColumn = new TreeViewComboBox(generalTab);
0093     gridLayout->addWidget(cbXDataColumn, 6, 3, 1, 4);
0094 
0095     cbXErrorColumn = new TreeViewComboBox(generalTab);
0096     cbXErrorColumn->setEnabled(false);
0097     uiGeneralTab.hlXError->addWidget(cbXErrorColumn);
0098 
0099     cbYDataColumn = new TreeViewComboBox(generalTab);
0100     gridLayout->addWidget(cbYDataColumn, 7, 3, 1, 4);
0101 
0102     cbYErrorColumn = new TreeViewComboBox(generalTab);
0103     cbYErrorColumn->setEnabled(false);
0104     uiGeneralTab.hlYWeight->addWidget(cbYErrorColumn);
0105 
0106     // X/Y-Weight
0107     for (int i = 0; i < NSL_FIT_WEIGHT_TYPE_COUNT; i++) {
0108         uiGeneralTab.cbXWeight->addItem(nsl_fit_weight_type_name[i]);
0109         uiGeneralTab.cbYWeight->addItem(nsl_fit_weight_type_name[i]);
0110     }
0111     uiGeneralTab.cbXWeight->setCurrentIndex(nsl_fit_weight_no);
0112     uiGeneralTab.cbYWeight->setCurrentIndex(nsl_fit_weight_no);
0113 
0114     for (int i = 0; i < NSL_FIT_MODEL_CATEGORY_COUNT; i++)
0115         uiGeneralTab.cbCategory->addItem(nsl_fit_model_category_name[i]);
0116 
0117     uiGeneralTab.teEquation->setMaximumHeight(uiGeneralTab.leName->sizeHint().height() * 2);
0118 
0119     fitParametersWidget = new FitParametersWidget(uiGeneralTab.frameParameters);
0120     auto* l = new QVBoxLayout();
0121     l->setContentsMargins(0, 0, 0, 0);
0122     l->addWidget(fitParametersWidget);
0123     uiGeneralTab.frameParameters->setLayout(l);
0124 
0125     //use white background in the preview label
0126     QPalette p;
0127     p.setColor(QPalette::Window, Qt::white);
0128     uiGeneralTab.lFuncPic->setAutoFillBackground(true);
0129     uiGeneralTab.lFuncPic->setPalette(p);
0130 
0131     uiGeneralTab.tbConstants->setIcon(QIcon::fromTheme("labplot-format-text-symbol"));
0132     uiGeneralTab.tbFunctions->setIcon(QIcon::fromTheme("preferences-desktop-font"));
0133     uiGeneralTab.pbRecalculate->setIcon(QIcon::fromTheme("run-build"));
0134 
0135     // TODO: setting checked background color to unchecked color
0136 //  p = uiGeneralTab.lData->palette();
0137     // QWidget::palette().color(QWidget::backgroundRole())
0138     // not working with 'transparent'
0139 //  p.setColor(QPalette::Base, Qt::transparent);
0140 //  uiGeneralTab.lData->setPalette(p);
0141     // see https://forum.qt.io/topic/41325/solved-background-of-checked-qpushbutton-with-stylesheet/2
0142     // Styles not usable (here: text color not theme dependent). see https://forum.qt.io/topic/60546/qpushbutton-default-windows-style-sheet/9
0143 //  uiGeneralTab.lData->setStyleSheet("QToolButton:checked{background-color: transparent;border: 3px transparent;padding: 3px;}");
0144 
0145 //  uiGeneralTab.lData->setAutoFillBackground(true);
0146 
0147     uiGeneralTab.twLog->setEditTriggers(QAbstractItemView::NoEditTriggers);
0148     uiGeneralTab.twParameters->setEditTriggers(QAbstractItemView::NoEditTriggers);
0149     uiGeneralTab.twGoodness->setEditTriggers(QAbstractItemView::NoEditTriggers);
0150 
0151     //don't allow word wrapping in the log-table for the multi-line iterations string
0152     uiGeneralTab.twLog->setWordWrap(false);
0153 
0154     //header labels
0155     QStringList headerLabels;
0156     headerLabels << QString() << i18n("Value") << i18n("Error") << i18n("Error, %") << i18n("t statistic") << QLatin1String("P > |t|")
0157         << i18n("Lower") << i18n("Upper");
0158     uiGeneralTab.twParameters->setHorizontalHeaderLabels(headerLabels);
0159 
0160     // show all options per default
0161     showDataOptions(true);
0162     showFitOptions(true);
0163     showWeightsOptions(true);
0164     showParameters(true);
0165     showResults(true);
0166 
0167     //CTRL+C copies only the last cell in the selection, we want to copy the whole selection.
0168     //install event filters to handle CTRL+C key events.
0169     uiGeneralTab.twParameters->installEventFilter(this);
0170     uiGeneralTab.twGoodness->installEventFilter(this);
0171     uiGeneralTab.twLog->installEventFilter(this);
0172 
0173     // context menus
0174     uiGeneralTab.twParameters->setContextMenuPolicy(Qt::CustomContextMenu);
0175     uiGeneralTab.twGoodness->setContextMenuPolicy(Qt::CustomContextMenu);
0176     uiGeneralTab.twLog->setContextMenuPolicy(Qt::CustomContextMenu);
0177     connect(uiGeneralTab.twParameters, &QTableWidget::customContextMenuRequested,
0178             this, &XYFitCurveDock::resultParametersContextMenuRequest);
0179     connect(uiGeneralTab.twGoodness, &QTableWidget::customContextMenuRequested,
0180             this, &XYFitCurveDock::resultGoodnessContextMenuRequest);
0181     connect(uiGeneralTab.twLog, &QTableWidget::customContextMenuRequested,
0182             this, &XYFitCurveDock::resultLogContextMenuRequest);
0183 
0184     uiGeneralTab.twLog->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
0185     uiGeneralTab.twGoodness->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
0186     // append symbols
0187     uiGeneralTab.twGoodness->item(0, 0)->setText(uiGeneralTab.twGoodness->item(0, 0)->text() + UTF8_QSTRING(" (χ²)"));
0188     uiGeneralTab.twGoodness->item(1, 0)->setText(uiGeneralTab.twGoodness->item(1, 0)->text() + UTF8_QSTRING(" (χ²/dof)"));
0189     uiGeneralTab.twGoodness->item(3, 0)->setText(uiGeneralTab.twGoodness->item(3, 0)->text() + UTF8_QSTRING(" (R²)"));
0190     uiGeneralTab.twGoodness->item(4, 0)->setText(uiGeneralTab.twGoodness->item(4, 0)->text() + UTF8_QSTRING(" (R̄²)"));
0191     uiGeneralTab.twGoodness->item(5, 0)->setText(UTF8_QSTRING("χ²-") + i18n("test") + UTF8_QSTRING(" ( P > χ²)"));
0192 
0193     auto* layout = new QHBoxLayout(ui.tabGeneral);
0194     layout->setMargin(0);
0195     layout->addWidget(generalTab);
0196 
0197     //Slots
0198     connect(uiGeneralTab.leName, &QLineEdit::textChanged, this, &XYFitCurveDock::nameChanged);
0199     connect(uiGeneralTab.leComment, &QLineEdit::textChanged, this, &XYFitCurveDock::commentChanged);
0200     connect(uiGeneralTab.chkVisible, &QCheckBox::clicked, this, &XYFitCurveDock::visibilityChanged);
0201     connect(uiGeneralTab.cbDataSourceType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &XYFitCurveDock::dataSourceTypeChanged);
0202     connect(uiGeneralTab.lWeights, &QPushButton::clicked, this, &XYFitCurveDock::showWeightsOptions);
0203     connect(uiGeneralTab.cbXWeight, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &XYFitCurveDock::xWeightChanged);
0204     connect(uiGeneralTab.cbYWeight, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &XYFitCurveDock::yWeightChanged);
0205     connect(uiGeneralTab.cbCategory, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &XYFitCurveDock::categoryChanged);
0206     connect(uiGeneralTab.cbModel, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &XYFitCurveDock::modelTypeChanged);
0207     connect(uiGeneralTab.sbDegree, QOverload<int>::of(&QSpinBox::valueChanged), this, &XYFitCurveDock::updateModelEquation);
0208     connect(uiGeneralTab.teEquation, &ExpressionTextEdit::expressionChanged, this, &XYFitCurveDock::expressionChanged);
0209     connect(uiGeneralTab.tbConstants, &QToolButton::clicked, this, &XYFitCurveDock::showConstants);
0210     connect(uiGeneralTab.tbFunctions, &QToolButton::clicked, this, &XYFitCurveDock::showFunctions);
0211     connect(uiGeneralTab.pbOptions, &QPushButton::clicked, this, &XYFitCurveDock::showOptions);
0212     connect(uiGeneralTab.pbRecalculate, &QPushButton::clicked, this, &XYFitCurveDock::recalculateClicked);
0213     connect(uiGeneralTab.lData, &QPushButton::clicked, this, &XYFitCurveDock::showDataOptions);
0214     connect(uiGeneralTab.lFit, &QPushButton::clicked, this, &XYFitCurveDock::showFitOptions);
0215     connect(uiGeneralTab.lParameters, &QPushButton::clicked, this, &XYFitCurveDock::showParameters);
0216     connect(uiGeneralTab.lResults, &QPushButton::clicked, this, &XYFitCurveDock::showResults);
0217 
0218     connect(cbDataSourceCurve, &TreeViewComboBox::currentModelIndexChanged, this, &XYFitCurveDock::dataSourceCurveChanged);
0219     connect(cbXDataColumn, &TreeViewComboBox::currentModelIndexChanged, this, &XYFitCurveDock::xDataColumnChanged);
0220     connect(cbYDataColumn, &TreeViewComboBox::currentModelIndexChanged, this, &XYFitCurveDock::yDataColumnChanged);
0221     connect(cbXErrorColumn, &TreeViewComboBox::currentModelIndexChanged, this, &XYFitCurveDock::xErrorColumnChanged);
0222     connect(cbYErrorColumn, &TreeViewComboBox::currentModelIndexChanged, this, &XYFitCurveDock::yErrorColumnChanged);
0223 }
0224 
0225 /*
0226  * load curve settings
0227  */
0228 void XYFitCurveDock::initGeneralTab() {
0229     //if there are more then one curve in the list, disable the tab "general"
0230     if (m_curvesList.size() == 1) {
0231         uiGeneralTab.lName->setEnabled(true);
0232         uiGeneralTab.leName->setEnabled(true);
0233         uiGeneralTab.lComment->setEnabled(true);
0234         uiGeneralTab.leComment->setEnabled(true);
0235 
0236         uiGeneralTab.leName->setText(m_curve->name());
0237         uiGeneralTab.leComment->setText(m_curve->comment());
0238     } else {
0239         uiGeneralTab.lName->setEnabled(false);
0240         uiGeneralTab.leName->setEnabled(false);
0241         uiGeneralTab.lComment->setEnabled(false);
0242         uiGeneralTab.leComment->setEnabled(false);
0243 
0244         uiGeneralTab.leName->setText(QString());
0245         uiGeneralTab.leComment->setText(QString());
0246     }
0247 
0248     auto* fitCurve = static_cast<XYFitCurve*>(m_curve);
0249     checkColumnAvailability(cbXDataColumn, fitCurve->xDataColumn(), fitCurve->xDataColumnPath());
0250     checkColumnAvailability(cbYDataColumn, fitCurve->yDataColumn(), fitCurve->yDataColumnPath());
0251     checkColumnAvailability(cbXErrorColumn, fitCurve->xErrorColumn(), fitCurve->xErrorColumnPath());
0252     checkColumnAvailability(cbYErrorColumn, fitCurve->yErrorColumn(), fitCurve->yErrorColumnPath());
0253 
0254     uiGeneralTab.cbDataSourceType->setCurrentIndex(static_cast<int>(m_fitCurve->dataSourceType()));
0255     this->dataSourceTypeChanged(uiGeneralTab.cbDataSourceType->currentIndex());
0256     XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, m_fitCurve->dataSourceCurve());
0257     XYCurveDock::setModelIndexFromAspect(cbXDataColumn, m_fitCurve->xDataColumn());
0258     XYCurveDock::setModelIndexFromAspect(cbYDataColumn, m_fitCurve->yDataColumn());
0259     XYCurveDock::setModelIndexFromAspect(cbXErrorColumn, m_fitCurve->xErrorColumn());
0260     XYCurveDock::setModelIndexFromAspect(cbYErrorColumn, m_fitCurve->yErrorColumn());
0261 
0262     int tmpModelType = m_fitData.modelType; // save type because it's reset when category changes
0263     if (m_fitData.modelCategory == nsl_fit_model_custom)
0264         uiGeneralTab.cbCategory->setCurrentIndex(uiGeneralTab.cbCategory->count() - 1);
0265     else
0266         uiGeneralTab.cbCategory->setCurrentIndex(m_fitData.modelCategory);
0267     categoryChanged(m_fitData.modelCategory);   // fill model types
0268 
0269     m_fitData.modelType = tmpModelType;
0270     if (m_fitData.modelCategory != nsl_fit_model_custom)
0271         uiGeneralTab.cbModel->setCurrentIndex(m_fitData.modelType);
0272 
0273     uiGeneralTab.cbXWeight->setCurrentIndex(m_fitData.xWeightsType);
0274     uiGeneralTab.cbYWeight->setCurrentIndex(m_fitData.yWeightsType);
0275     uiGeneralTab.sbDegree->setValue(m_fitData.degree);
0276 
0277     if (m_fitData.paramStartValues.size() > 0)
0278         DEBUG(Q_FUNC_INFO << ", start value 1 = " << m_fitData.paramStartValues.at(0));
0279 
0280     DEBUG(Q_FUNC_INFO << ", model degree = " << m_fitData.degree);
0281 
0282     uiGeneralTab.chkVisible->setChecked(m_curve->isVisible());
0283 
0284     //Slots
0285     connect(m_fitCurve, &XYFitCurve::aspectDescriptionChanged, this, &XYFitCurveDock::curveDescriptionChanged);
0286     connect(m_fitCurve, &XYFitCurve::dataSourceTypeChanged, this, &XYFitCurveDock::curveDataSourceTypeChanged);
0287     connect(m_fitCurve, &XYFitCurve::dataSourceCurveChanged, this, &XYFitCurveDock::curveDataSourceCurveChanged);
0288     connect(m_fitCurve, &XYFitCurve::xDataColumnChanged, this, &XYFitCurveDock::curveXDataColumnChanged);
0289     connect(m_fitCurve, &XYFitCurve::yDataColumnChanged, this, &XYFitCurveDock::curveYDataColumnChanged);
0290     connect(m_fitCurve, &XYFitCurve::xErrorColumnChanged, this, &XYFitCurveDock::curveXErrorColumnChanged);
0291     connect(m_fitCurve, &XYFitCurve::yErrorColumnChanged, this, &XYFitCurveDock::curveYErrorColumnChanged);
0292     connect(m_fitCurve, &XYFitCurve::fitDataChanged, this, &XYFitCurveDock::curveFitDataChanged);
0293     connect(m_fitCurve, &XYFitCurve::sourceDataChanged, this, &XYFitCurveDock::enableRecalculate);
0294     connect(m_fitCurve, QOverload<bool>::of(&XYCurve::visibilityChanged), this, &XYFitCurveDock::curveVisibilityChanged);
0295 
0296     connect(fitParametersWidget, &FitParametersWidget::parametersChanged, this, &XYFitCurveDock::parametersChanged);
0297     connect(fitParametersWidget, &FitParametersWidget::parametersValid, this, &XYFitCurveDock::parametersValid);
0298 }
0299 
0300 void XYFitCurveDock::setModel() {
0301     QList<AspectType> list{AspectType::Folder, AspectType::Datapicker, AspectType::Worksheet,
0302                             AspectType::CartesianPlot, AspectType::XYCurve, AspectType::XYAnalysisCurve};
0303     cbDataSourceCurve->setTopLevelClasses(list);
0304 
0305     QList<const AbstractAspect*> hiddenAspects;
0306     for (auto* curve : m_curvesList)
0307         hiddenAspects << curve;
0308     cbDataSourceCurve->setHiddenAspects(hiddenAspects);
0309 
0310     list = {AspectType::Folder, AspectType::Workbook, AspectType::Spreadsheet, AspectType::LiveDataSource,
0311             AspectType::Column, AspectType::CantorWorksheet, AspectType::Datapicker};
0312     cbXDataColumn->setTopLevelClasses(list);
0313     cbYDataColumn->setTopLevelClasses(list);
0314     cbXErrorColumn->setTopLevelClasses(list);
0315     cbYErrorColumn->setTopLevelClasses(list);
0316 
0317     cbDataSourceCurve->setModel(m_aspectTreeModel);
0318     cbXDataColumn->setModel(m_aspectTreeModel);
0319     cbYDataColumn->setModel(m_aspectTreeModel);
0320     cbXErrorColumn->setModel(m_aspectTreeModel);
0321     cbYErrorColumn->setModel(m_aspectTreeModel);
0322 
0323     XYCurveDock::setModel();
0324 }
0325 
0326 /*!
0327   sets the curves. The properties of the curves in the list \c list can be edited in this widget.
0328 */
0329 void XYFitCurveDock::setCurves(QList<XYCurve*> list) {
0330     m_initializing = true;
0331     m_curvesList = list;
0332     m_curve = list.first();
0333     m_aspect = m_curve;
0334     m_fitCurve = dynamic_cast<XYFitCurve*>(m_curve);
0335     m_aspectTreeModel = new AspectTreeModel(m_curve->project());
0336     this->setModel();
0337     m_fitData = m_fitCurve->fitData();
0338 
0339     DEBUG(Q_FUNC_INFO << ", model type = " << m_fitData.modelType);
0340     DEBUG(Q_FUNC_INFO << ", model = " << STDSTRING(m_fitData.model));
0341     DEBUG(Q_FUNC_INFO << ", model degree = " << m_fitData.degree);
0342     DEBUG(Q_FUNC_INFO << ", # params = " << m_fitData.paramNames.size());
0343     DEBUG(Q_FUNC_INFO << ", # start values = " << m_fitData.paramStartValues.size());
0344     //for (auto startValue: m_fitData.paramStartValues)
0345     //  DEBUG("XYFitCurveDock::setCurves()  start value = " << startValue);
0346 
0347     fitParametersWidget->setFitData(&m_fitData);
0348 
0349     initGeneralTab();
0350     initTabs();
0351 
0352     if (m_messageWidget && m_messageWidget->isVisible())
0353         m_messageWidget->close();
0354 
0355     showFitResult();
0356     enableRecalculate();
0357 
0358     m_initializing = false;
0359 
0360     //init parameter list when not available
0361     if (m_fitData.paramStartValues.size() == 0)
0362         updateModelEquation();
0363 }
0364 
0365 bool XYFitCurveDock::eventFilter(QObject* obj, QEvent* event) {
0366     if (event->type() == QEvent::KeyPress
0367         && (obj == uiGeneralTab.twParameters || obj == uiGeneralTab.twGoodness || obj == uiGeneralTab.twLog)) {
0368         auto* key_event = static_cast<QKeyEvent*>(event);
0369         if (key_event->matches(QKeySequence::Copy)) {
0370             resultCopy();
0371             return true;
0372         }
0373     }
0374     return QWidget::eventFilter(obj, event);
0375 }
0376 
0377 //*************************************************************
0378 //**** SLOTs for changes triggered in XYFitCurveDock *****
0379 //*************************************************************
0380 void XYFitCurveDock::dataSourceTypeChanged(int index) {
0381     const auto type = (XYAnalysisCurve::DataSourceType)index;
0382     if (type == XYAnalysisCurve::DataSourceType::Spreadsheet) {
0383         uiGeneralTab.lDataSourceCurve->hide();
0384         cbDataSourceCurve->hide();
0385         uiGeneralTab.lXColumn->show();
0386         cbXDataColumn->show();
0387         uiGeneralTab.lYColumn->show();
0388         cbYDataColumn->show();
0389     } else {
0390         uiGeneralTab.lDataSourceCurve->show();
0391         cbDataSourceCurve->show();
0392         uiGeneralTab.lXColumn->hide();
0393         cbXDataColumn->hide();
0394         uiGeneralTab.lYColumn->hide();
0395         cbYDataColumn->hide();
0396     }
0397 
0398     if (m_initializing)
0399         return;
0400 
0401     for (auto* curve : m_curvesList)
0402         dynamic_cast<XYFitCurve*>(curve)->setDataSourceType(type);
0403 }
0404 
0405 void XYFitCurveDock::dataSourceCurveChanged(const QModelIndex& index) {
0406     if (m_initializing)
0407         return;
0408 
0409     auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
0410     auto* dataSourceCurve = dynamic_cast<XYCurve*>(aspect);
0411 
0412     for (auto* curve : m_curvesList)
0413         dynamic_cast<XYFitCurve*>(curve)->setDataSourceCurve(dataSourceCurve);
0414 }
0415 
0416 void XYFitCurveDock::xDataColumnChanged(const QModelIndex& index) {
0417     if (m_initializing)
0418         return;
0419 
0420     auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
0421     auto* column = dynamic_cast<AbstractColumn*>(aspect);
0422 
0423     for (auto* curve : m_curvesList)
0424         dynamic_cast<XYFitCurve*>(curve)->setXDataColumn(column);
0425 
0426     // set model dependent start values from new data
0427     XYFitCurve::initStartValues(m_fitData, m_curve);
0428 
0429     // update model limits depending on number of points
0430     modelTypeChanged(uiGeneralTab.cbModel->currentIndex());
0431 
0432     cbXDataColumn->useCurrentIndexText(true);
0433     cbXDataColumn->setInvalid(false);
0434 }
0435 
0436 void XYFitCurveDock::yDataColumnChanged(const QModelIndex& index) {
0437     if (m_initializing)
0438         return;
0439 
0440     auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
0441     auto* column = dynamic_cast<AbstractColumn*>(aspect);
0442 
0443     for (auto* curve : m_curvesList)
0444         dynamic_cast<XYFitCurve*>(curve)->setYDataColumn(column);
0445 
0446     // set model dependent start values from new data
0447     XYFitCurve::initStartValues(m_fitData, m_curve);
0448 
0449     cbYDataColumn->useCurrentIndexText(true);
0450     cbYDataColumn->setInvalid(false);
0451 }
0452 
0453 void XYFitCurveDock::xErrorColumnChanged(const QModelIndex& index) {
0454     if (m_initializing)
0455         return;
0456 
0457     auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
0458     auto* column = dynamic_cast<AbstractColumn*>(aspect);
0459 
0460     for (auto* curve : m_curvesList)
0461         dynamic_cast<XYFitCurve*>(curve)->setXErrorColumn(column);
0462 
0463     cbXErrorColumn->useCurrentIndexText(true);
0464     cbXErrorColumn->setInvalid(false);
0465 }
0466 
0467 void XYFitCurveDock::yErrorColumnChanged(const QModelIndex& index) {
0468     if (m_initializing)
0469         return;
0470 
0471     auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
0472     auto* column = dynamic_cast<AbstractColumn*>(aspect);
0473 
0474     for (auto* curve : m_curvesList)
0475         dynamic_cast<XYFitCurve*>(curve)->setYErrorColumn(column);
0476 
0477     cbYErrorColumn->useCurrentIndexText(true);
0478     cbYErrorColumn->setInvalid(false);
0479 }
0480 
0481 ///////////////////////// fold/unfold options //////////////////////////////////////////////////
0482 
0483 void XYFitCurveDock::showDataOptions(bool checked) {
0484     if (checked) {
0485         uiGeneralTab.lData->setIcon(QIcon::fromTheme("arrow-down"));
0486         uiGeneralTab.lDataSourceType->show();
0487         uiGeneralTab.cbDataSourceType->show();
0488         // select options for current source type
0489         dataSourceTypeChanged(uiGeneralTab.cbDataSourceType->currentIndex());
0490     } else {
0491         uiGeneralTab.lData->setIcon(QIcon::fromTheme("arrow-right"));
0492         uiGeneralTab.lDataSourceType->hide();
0493         uiGeneralTab.cbDataSourceType->hide();
0494         uiGeneralTab.lXColumn->hide();
0495         cbXDataColumn->hide();
0496         uiGeneralTab.lYColumn->hide();
0497         cbYDataColumn->hide();
0498         uiGeneralTab.lDataSourceCurve->hide();
0499         cbDataSourceCurve->hide();
0500     }
0501 }
0502 
0503 void XYFitCurveDock::showWeightsOptions(bool checked) {
0504     if (checked) {
0505         uiGeneralTab.lWeights->setIcon(QIcon::fromTheme("arrow-down"));
0506         uiGeneralTab.lXWeight->show();
0507         uiGeneralTab.cbXWeight->show();
0508         uiGeneralTab.lXErrorCol->show();
0509         cbXErrorColumn->show();
0510         uiGeneralTab.lYWeight->show();
0511         uiGeneralTab.cbYWeight->show();
0512         uiGeneralTab.lYErrorCol->show();
0513         cbYErrorColumn->show();
0514     } else {
0515         uiGeneralTab.lWeights->setIcon(QIcon::fromTheme("arrow-right"));
0516         uiGeneralTab.lXWeight->hide();
0517         uiGeneralTab.cbXWeight->hide();
0518         uiGeneralTab.lXErrorCol->hide();
0519         cbXErrorColumn->hide();
0520         uiGeneralTab.lYWeight->hide();
0521         uiGeneralTab.cbYWeight->hide();
0522         uiGeneralTab.lYErrorCol->hide();
0523         cbYErrorColumn->hide();
0524     }
0525 }
0526 
0527 void XYFitCurveDock::showFitOptions(bool checked) {
0528     if (checked) {
0529         uiGeneralTab.lFit->setIcon(QIcon::fromTheme("arrow-down"));
0530         uiGeneralTab.lCategory->show();
0531         uiGeneralTab.cbCategory->show();
0532         uiGeneralTab.lModel->show();
0533         uiGeneralTab.cbModel->show();
0534         uiGeneralTab.lEquation->show();
0535 
0536         m_initializing = true;  // do not change start parameter
0537         modelTypeChanged(uiGeneralTab.cbModel->currentIndex());
0538         m_initializing = false;
0539     } else {
0540         uiGeneralTab.lFit->setIcon(QIcon::fromTheme("arrow-right"));
0541         uiGeneralTab.lCategory->hide();
0542         uiGeneralTab.cbCategory->hide();
0543         uiGeneralTab.lModel->hide();
0544         uiGeneralTab.cbModel->hide();
0545         uiGeneralTab.lDegree->hide();
0546         uiGeneralTab.sbDegree->hide();
0547         uiGeneralTab.lEquation->hide();
0548         uiGeneralTab.lFuncPic->hide();
0549         uiGeneralTab.teEquation->hide();
0550         uiGeneralTab.tbFunctions->hide();
0551         uiGeneralTab.tbConstants->hide();
0552     }
0553 }
0554 
0555 void XYFitCurveDock::showParameters(bool checked) {
0556     if (checked) {
0557         uiGeneralTab.lParameters->setIcon(QIcon::fromTheme("arrow-down"));
0558         uiGeneralTab.frameParameters->show();
0559     } else {
0560         uiGeneralTab.lParameters->setIcon(QIcon::fromTheme("arrow-right"));
0561         uiGeneralTab.frameParameters->hide();
0562     }
0563 }
0564 
0565 void XYFitCurveDock::showResults(bool checked) {
0566     if (checked) {
0567         uiGeneralTab.lResults->setIcon(QIcon::fromTheme("arrow-down"));
0568         uiGeneralTab.twResults->show();
0569     } else {
0570         uiGeneralTab.lResults->setIcon(QIcon::fromTheme("arrow-right"));
0571         uiGeneralTab.twResults->hide();
0572     }
0573 }
0574 
0575 ///////////////////////////////////////////////////////////////////////////
0576 
0577 void XYFitCurveDock::xWeightChanged(int index) {
0578     DEBUG(Q_FUNC_INFO << ", weight = " << nsl_fit_weight_type_name[index]);
0579 
0580     m_fitData.xWeightsType = (nsl_fit_weight_type)index;
0581 
0582     // enable/disable weight column
0583     switch ((nsl_fit_weight_type)index) {
0584     case nsl_fit_weight_no:
0585     case nsl_fit_weight_statistical:
0586     case nsl_fit_weight_statistical_fit:
0587     case nsl_fit_weight_relative:
0588     case nsl_fit_weight_relative_fit:
0589         cbXErrorColumn->setEnabled(false);
0590         uiGeneralTab.lXErrorCol->setEnabled(false);
0591         break;
0592     case nsl_fit_weight_instrumental:
0593     case nsl_fit_weight_direct:
0594     case nsl_fit_weight_inverse:
0595         cbXErrorColumn->setEnabled(true);
0596         uiGeneralTab.lXErrorCol->setEnabled(true);
0597         break;
0598     }
0599     enableRecalculate();
0600 }
0601 
0602 void XYFitCurveDock::yWeightChanged(int index) {
0603     DEBUG(Q_FUNC_INFO << ", weight = " << nsl_fit_weight_type_name[index]);
0604 
0605     m_fitData.yWeightsType = (nsl_fit_weight_type)index;
0606 
0607     // enable/disable weight column
0608     switch ((nsl_fit_weight_type)index) {
0609     case nsl_fit_weight_no:
0610     case nsl_fit_weight_statistical:
0611     case nsl_fit_weight_statistical_fit:
0612     case nsl_fit_weight_relative:
0613     case nsl_fit_weight_relative_fit:
0614         cbYErrorColumn->setEnabled(false);
0615         uiGeneralTab.lYErrorCol->setEnabled(false);
0616         break;
0617     case nsl_fit_weight_instrumental:
0618     case nsl_fit_weight_direct:
0619     case nsl_fit_weight_inverse:
0620         cbYErrorColumn->setEnabled(true);
0621         uiGeneralTab.lYErrorCol->setEnabled(true);
0622         break;
0623     }
0624     enableRecalculate();
0625 }
0626 
0627 /*!
0628  * called when the fit model category (basic functions, peak functions etc.) was changed.
0629  * In the combobox for the model type shows the model types for the current category \index and calls \c modelTypeChanged()
0630  * to update the model type dependent widgets in the general-tab.
0631  */
0632 void XYFitCurveDock::categoryChanged(int index) {
0633     if (index == nsl_fit_model_custom) {
0634         DEBUG(Q_FUNC_INFO << ", category = \"nsl_fit_model_custom\"");
0635     } else {
0636         DEBUG(Q_FUNC_INFO << ", category = \"" << nsl_fit_model_category_name[index] << "\"");
0637     }
0638 
0639     bool hasChanged = true;
0640     // nothing has changed when ...
0641     if (m_fitData.modelCategory == (nsl_fit_model_category)index || (m_fitData.modelCategory == nsl_fit_model_custom && index == uiGeneralTab.cbCategory->count() - 1) )
0642         hasChanged = false;
0643 
0644     if (uiGeneralTab.cbCategory->currentIndex() == uiGeneralTab.cbCategory->count() - 1)
0645         m_fitData.modelCategory = nsl_fit_model_custom;
0646     else
0647         m_fitData.modelCategory = (nsl_fit_model_category)index;
0648 
0649     uiGeneralTab.cbModel->clear();
0650     uiGeneralTab.cbModel->show();
0651     uiGeneralTab.lModel->show();
0652 
0653     switch (m_fitData.modelCategory) {
0654     case nsl_fit_model_basic:
0655         for (int i = 0; i < NSL_FIT_MODEL_BASIC_COUNT; i++)
0656             uiGeneralTab.cbModel->addItem(nsl_fit_model_basic_name[i]);
0657         break;
0658     case nsl_fit_model_peak: {
0659         for (int i = 0; i < NSL_FIT_MODEL_PEAK_COUNT; i++)
0660             uiGeneralTab.cbModel->addItem(nsl_fit_model_peak_name[i]);
0661 #if defined(_MSC_VER)
0662         // disable voigt model
0663         const QStandardItemModel* model = qobject_cast<const QStandardItemModel*>(uiGeneralTab.cbModel->model());
0664         QStandardItem* item = model->item(nsl_fit_model_voigt);
0665         item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled));
0666 #endif
0667         break;
0668     }
0669     case nsl_fit_model_growth:
0670         for (int i = 0; i < NSL_FIT_MODEL_GROWTH_COUNT; i++)
0671             uiGeneralTab.cbModel->addItem(nsl_fit_model_growth_name[i]);
0672         break;
0673     case nsl_fit_model_distribution: {
0674         for (int i = 0; i < NSL_SF_STATS_DISTRIBUTION_COUNT; i++)
0675             uiGeneralTab.cbModel->addItem(nsl_sf_stats_distribution_name[i]);
0676 
0677         // not-used items are disabled here
0678         const auto* model = qobject_cast<const QStandardItemModel*>(uiGeneralTab.cbModel->model());
0679 
0680         for (int i = 1; i < NSL_SF_STATS_DISTRIBUTION_COUNT; i++) {
0681             // unused distributions
0682             if (i == nsl_sf_stats_levy_alpha_stable || i == nsl_sf_stats_levy_skew_alpha_stable || i == nsl_sf_stats_bernoulli) {
0683                     QStandardItem* item = model->item(i);
0684                     item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled));
0685             }
0686         }
0687         break;
0688     }
0689     case nsl_fit_model_custom:
0690         uiGeneralTab.cbModel->addItem(i18n("Custom"));
0691         uiGeneralTab.cbModel->hide();
0692         uiGeneralTab.lModel->hide();
0693     }
0694 
0695     if (hasChanged) {
0696         //show the fit-model for the currently selected default (first) fit-model
0697         uiGeneralTab.cbModel->setCurrentIndex(0);
0698         uiGeneralTab.sbDegree->setValue(1);
0699         // when model type does not change, call it here
0700         updateModelEquation();
0701     }
0702 
0703     enableRecalculate();
0704 }
0705 
0706 /*!
0707  * called when the fit model type (depends on category) was changed.
0708  * Updates the model type dependent widgets in the general-tab and calls \c updateModelEquation() to update the preview pixmap.
0709  */
0710 void XYFitCurveDock::modelTypeChanged(int index) {
0711     DEBUG("modelTypeChanged() type = " << (unsigned int)index << ", initializing = " << m_initializing << ", current type = " << m_fitData.modelType);
0712     // leave if there is no selection
0713     if (index == -1)
0714         return;
0715 
0716     bool custom = false;
0717     if (m_fitData.modelCategory == nsl_fit_model_custom)
0718         custom = true;
0719     uiGeneralTab.teEquation->setReadOnly(!custom);
0720     uiGeneralTab.lModel->setVisible(!custom);
0721     uiGeneralTab.cbModel->setVisible(!custom);
0722     uiGeneralTab.tbFunctions->setVisible(custom);
0723     uiGeneralTab.tbConstants->setVisible(custom);
0724 
0725     // default settings
0726     uiGeneralTab.lDegree->setText(i18n("Degree:"));
0727     if (m_fitData.modelType != index)
0728         uiGeneralTab.sbDegree->setValue(1);
0729 
0730     const AbstractColumn* xColumn = nullptr;
0731     if (m_fitCurve->dataSourceType() == XYAnalysisCurve::DataSourceType::Spreadsheet) {
0732         DEBUG(" data source: Spreadsheet")
0733         //auto* aspect = static_cast<AbstractAspect*>(cbXDataColumn->currentModelIndex().internalPointer());
0734         //xColumn = dynamic_cast<AbstractColumn*>(aspect);
0735         xColumn = m_fitCurve->xDataColumn();
0736     } else {
0737         DEBUG(" data source: Curve")
0738         if (m_fitCurve->dataSourceCurve() != nullptr)
0739             xColumn = m_fitCurve->dataSourceCurve()->xColumn();
0740     }
0741     // with no xColumn: show all models (assume 100 data points)
0742     const int availableRowCount = (xColumn != nullptr) ? xColumn->availableRowCount() : 100;
0743     DEBUG(" available row count = " << availableRowCount)
0744 
0745     bool disableFit = false;
0746     switch (m_fitData.modelCategory) {
0747     case nsl_fit_model_basic:
0748         switch (index) {
0749         case nsl_fit_model_polynomial:
0750             uiGeneralTab.lDegree->setVisible(true);
0751             uiGeneralTab.sbDegree->setVisible(true);
0752             uiGeneralTab.sbDegree->setMaximum(qMin(availableRowCount - 1, 10));
0753             break;
0754         case nsl_fit_model_fourier:
0755             if (availableRowCount < 4) {    // too few data points
0756                 uiGeneralTab.lDegree->setVisible(false);
0757                 uiGeneralTab.sbDegree->setVisible(false);
0758                 disableFit = true;
0759             } else {
0760                 uiGeneralTab.lDegree->setVisible(true);
0761                 uiGeneralTab.sbDegree->setVisible(true);
0762                 uiGeneralTab.sbDegree->setMaximum(qMin(availableRowCount/2 - 1, 10));
0763             }
0764             break;
0765         case nsl_fit_model_power:
0766             uiGeneralTab.lDegree->setVisible(true);
0767             uiGeneralTab.sbDegree->setVisible(true);
0768             uiGeneralTab.sbDegree->setMaximum(2);
0769             //TODO: limit degree depending on availableRowCount
0770             break;
0771         case nsl_fit_model_exponential:
0772             uiGeneralTab.lDegree->setVisible(true);
0773             uiGeneralTab.sbDegree->setVisible(true);
0774             uiGeneralTab.sbDegree->setMaximum(10);
0775             //TODO: limit degree depending on availableRowCount
0776             break;
0777         default:
0778             uiGeneralTab.lDegree->setVisible(false);
0779             uiGeneralTab.sbDegree->setVisible(false);
0780         }
0781         break;
0782     case nsl_fit_model_peak:    // all models support multiple peaks
0783         uiGeneralTab.lDegree->setText(i18n("Number of peaks:"));
0784         uiGeneralTab.lDegree->setVisible(true);
0785         uiGeneralTab.sbDegree->setVisible(true);
0786         uiGeneralTab.sbDegree->setMaximum(9);
0787         break;
0788     case nsl_fit_model_growth:
0789     case nsl_fit_model_distribution:
0790     case nsl_fit_model_custom:
0791         uiGeneralTab.lDegree->setVisible(false);
0792         uiGeneralTab.sbDegree->setVisible(false);
0793     }
0794 
0795     m_fitData.modelType = index;
0796 
0797     updateModelEquation();
0798 
0799     if (disableFit)
0800         uiGeneralTab.pbRecalculate->setEnabled(false);
0801 }
0802 
0803 /*!
0804  * Show the preview pixmap of the fit model expression for the current model category and type.
0805  * Called when the model type or the degree of the model were changed.
0806  */
0807 void XYFitCurveDock::updateModelEquation() {
0808 
0809     if (m_fitData.modelCategory == nsl_fit_model_custom) {
0810         DEBUG("XYFitCurveDock::updateModelEquation() category = nsl_fit_model_custom, type = " << m_fitData.modelType);
0811     } else {
0812         DEBUG("XYFitCurveDock::updateModelEquation() category = " << nsl_fit_model_category_name[m_fitData.modelCategory] << ", type = " << m_fitData.modelType);
0813     }
0814 
0815     //this function can also be called when the value for the degree was changed -> update the fit data structure
0816     int degree = uiGeneralTab.sbDegree->value();
0817     if (!m_initializing) {
0818         m_fitData.degree = degree;
0819         XYFitCurve::initFitData(m_fitData);
0820         // set model dependent start values from curve data
0821         XYFitCurve::initStartValues(m_fitData, m_curve);
0822         // udpate parameter widget
0823         fitParametersWidget->setFitData(&m_fitData);
0824     }
0825 
0826     // variables/parameter that are known
0827     QStringList vars = {"x"};
0828     vars << m_fitData.paramNames;
0829     uiGeneralTab.teEquation->setVariables(vars);
0830 
0831     // set formula picture
0832     uiGeneralTab.lEquation->setText(QLatin1String("f(x) ="));
0833     QString file;
0834     switch (m_fitData.modelCategory) {
0835     case nsl_fit_model_basic: {
0836         // formula pic depends on degree
0837         QString numSuffix = QString::number(degree);
0838         if (degree > 4)
0839             numSuffix = '4';
0840         if ((nsl_fit_model_type_basic)m_fitData.modelType == nsl_fit_model_power && degree > 2)
0841             numSuffix = '2';
0842         file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/fit_models/"
0843             + QString(nsl_fit_model_basic_pic_name[m_fitData.modelType]) + numSuffix + ".png");
0844         break;
0845     }
0846     case nsl_fit_model_peak: {
0847         // formula pic depends on number of peaks
0848         QString numSuffix = QString::number(degree);
0849         if (degree > 4)
0850             numSuffix = '4';
0851         file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/fit_models/"
0852             + QString(nsl_fit_model_peak_pic_name[m_fitData.modelType]) + numSuffix + ".png");
0853         break;
0854     }
0855     case nsl_fit_model_growth:
0856         file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/fit_models/"
0857             + QString(nsl_fit_model_growth_pic_name[m_fitData.modelType]) + ".png");
0858         break;
0859     case nsl_fit_model_distribution:
0860         file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/gsl_distributions/"
0861             + QString(nsl_sf_stats_distribution_pic_name[m_fitData.modelType]) + ".png");
0862         // change label
0863         if (m_fitData.modelType == nsl_sf_stats_poisson)
0864             uiGeneralTab.lEquation->setText(QLatin1String("f(k)/A ="));
0865         else
0866             uiGeneralTab.lEquation->setText(QLatin1String("f(x)/A ="));
0867         break;
0868     case nsl_fit_model_custom:
0869         uiGeneralTab.lFuncPic->hide();
0870         uiGeneralTab.teEquation->show();
0871         uiGeneralTab.teEquation->setPlainText(m_fitData.model);
0872     }
0873 
0874     if (m_fitData.modelCategory != nsl_fit_model_custom) {
0875         DEBUG("Model pixmap path = " << STDSTRING(file));
0876         uiGeneralTab.lFuncPic->setPixmap(file);
0877         uiGeneralTab.lFuncPic->show();
0878         uiGeneralTab.teEquation->hide();
0879     }
0880 
0881     enableRecalculate();
0882 }
0883 
0884 void XYFitCurveDock::showConstants() {
0885     QMenu menu;
0886     ConstantsWidget constants(&menu);
0887 
0888     connect(&constants, &ConstantsWidget::constantSelected, this, &XYFitCurveDock::insertConstant);
0889     connect(&constants, &ConstantsWidget::constantSelected, &menu, &QMenu::close);
0890     connect(&constants, &ConstantsWidget::canceled, &menu, &QMenu::close);
0891 
0892     auto* widgetAction = new QWidgetAction(this);
0893     widgetAction->setDefaultWidget(&constants);
0894     menu.addAction(widgetAction);
0895 
0896     QPoint pos(-menu.sizeHint().width() + uiGeneralTab.tbConstants->width(), -menu.sizeHint().height());
0897     menu.exec(uiGeneralTab.tbConstants->mapToGlobal(pos));
0898 }
0899 
0900 void XYFitCurveDock::showFunctions() {
0901     QMenu menu;
0902     FunctionsWidget functions(&menu);
0903     connect(&functions, &FunctionsWidget::functionSelected, this, &XYFitCurveDock::insertFunction);
0904     connect(&functions, &FunctionsWidget::functionSelected, &menu, &QMenu::close);
0905     connect(&functions, &FunctionsWidget::canceled, &menu, &QMenu::close);
0906 
0907     auto* widgetAction = new QWidgetAction(this);
0908     widgetAction->setDefaultWidget(&functions);
0909     menu.addAction(widgetAction);
0910 
0911     QPoint pos(-menu.sizeHint().width() + uiGeneralTab.tbFunctions->width(), -menu.sizeHint().height());
0912     menu.exec(uiGeneralTab.tbFunctions->mapToGlobal(pos));
0913 }
0914 
0915 /*!
0916  * Update parameter by parsing expression
0917  * Only called for custom fit model
0918  */
0919 void XYFitCurveDock::updateParameterList() {
0920     DEBUG("XYFitCurveDock::updateParameterList()");
0921     // use current model function
0922     m_fitData.model = uiGeneralTab.teEquation->toPlainText();
0923 
0924     ExpressionParser* parser = ExpressionParser::getInstance();
0925     QStringList vars; // variables that are known
0926     vars << "x";    //TODO: generalize when we support other XYEquationCurve::EquationType
0927     m_fitData.paramNames = m_fitData.paramNamesUtf8 = parser->getParameter(m_fitData.model, vars);
0928 
0929     // if number of parameter changed
0930     int oldNumberOfParameter = m_fitData.paramStartValues.size();
0931     int numberOfParameter = m_fitData.paramNames.size();
0932     DEBUG(" old number of parameter: " << oldNumberOfParameter << " new number of parameter: " << numberOfParameter);
0933     if (numberOfParameter != oldNumberOfParameter) {
0934         m_fitData.paramStartValues.resize(numberOfParameter);
0935         m_fitData.paramFixed.resize(numberOfParameter);
0936         m_fitData.paramLowerLimits.resize(numberOfParameter);
0937         m_fitData.paramUpperLimits.resize(numberOfParameter);
0938     }
0939     if (numberOfParameter > oldNumberOfParameter) {
0940         for (int i = oldNumberOfParameter; i < numberOfParameter; ++i) {
0941             m_fitData.paramStartValues[i] = 1.0;
0942             m_fitData.paramFixed[i] = false;
0943             m_fitData.paramLowerLimits[i] = -std::numeric_limits<double>::max();
0944             m_fitData.paramUpperLimits[i] = std::numeric_limits<double>::max();
0945         }
0946     }
0947 
0948     parametersChanged();
0949 }
0950 
0951 /*!
0952  * called when parameter names and/or start values for the model were changed
0953  * also called from parameter widget
0954  */
0955 void XYFitCurveDock::parametersChanged(bool updateParameterWidget) {
0956     DEBUG("XYFitCurveDock::parametersChanged() m_initializing = " << m_initializing);
0957 
0958     //parameter names were (probably) changed -> set the new vars in ExpressionTextEdit teEquation
0959     QStringList vars{m_fitData.paramNames};
0960     vars << "x";    //TODO: generalize when we support other XYEquationCurve::EquationType
0961     uiGeneralTab.teEquation->setVariables(vars);
0962 
0963     if (m_initializing)
0964         return;
0965 
0966     if (updateParameterWidget)
0967         fitParametersWidget->setFitData(&m_fitData);
0968 
0969     enableRecalculate();
0970 }
0971 void XYFitCurveDock::parametersValid(bool valid) {
0972     DEBUG("XYFitCurveDock::parametersValid() valid = " << valid);
0973     m_parametersValid = valid;
0974 }
0975 
0976 void XYFitCurveDock::showOptions() {
0977     QMenu menu;
0978     FitOptionsWidget w(&menu, &m_fitData, m_fitCurve);
0979     connect(&w, &FitOptionsWidget::finished, &menu, &QMenu::close);
0980     connect(&w, &FitOptionsWidget::optionsChanged, this, &XYFitCurveDock::enableRecalculate);
0981 
0982     auto* widgetAction = new QWidgetAction(this);
0983     widgetAction->setDefaultWidget(&w);
0984     menu.addAction(widgetAction);
0985     menu.setTearOffEnabled(true);
0986 
0987     //menu.setWindowFlags(menu.windowFlags() & Qt::MSWindowsFixedSizeDialogHint);
0988 
0989     QPoint pos(-menu.sizeHint().width() + uiGeneralTab.pbOptions->width(), 0);
0990     menu.exec(uiGeneralTab.pbOptions->mapToGlobal(pos));
0991 }
0992 
0993 void XYFitCurveDock::insertFunction(const QString& functionName) const {
0994     uiGeneralTab.teEquation->insertPlainText(functionName + ExpressionParser::functionArgumentString(functionName, XYEquationCurve::EquationType::Cartesian));
0995 }
0996 
0997 void XYFitCurveDock::insertConstant(const QString& constantsName) const {
0998     uiGeneralTab.teEquation->insertPlainText(constantsName);
0999 }
1000 
1001 /*!
1002  * When a custom evaluate range is specified, set the plot range too.
1003  */
1004 void XYFitCurveDock::setPlotXRange() {
1005     if (m_fitData.autoEvalRange || m_curve == nullptr)
1006         return;
1007 
1008     auto* plot = dynamic_cast<CartesianPlot*>(m_curve->parentAspect());
1009     if (plot != nullptr) {
1010         const Range<double> range{ m_fitData.evalRange };
1011         const double extend{ range.size() * 0.05 }; // + 5 %
1012         if (!range.isZero()) {
1013             plot->setXMin(range.min() - extend);
1014             plot->setXMax(range.max() + extend);
1015         }
1016     }
1017 }
1018 
1019 void XYFitCurveDock::recalculateClicked() {
1020     DEBUG("XYFitCurveDock::recalculateClicked()");
1021     QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1022     m_fitData.degree = uiGeneralTab.sbDegree->value();
1023     if (m_fitData.modelCategory == nsl_fit_model_custom)
1024         updateParameterList();
1025 
1026     for (XYCurve* curve: m_curvesList)
1027         dynamic_cast<XYFitCurve*>(curve)->setFitData(m_fitData);
1028 
1029     m_fitCurve->recalculate();
1030     setPlotXRange();
1031 
1032     //update fitParametersWidget
1033     if (m_fitData.useResults) {
1034         DEBUG(" nr of param names = " << m_fitData.paramNames.size())
1035         DEBUG(" size of start values = " << m_fitData.paramStartValues.size())
1036         DEBUG(" size of param values = " << m_fitCurve->fitResult().paramValues.size())
1037         if (m_fitCurve->fitResult().paramValues.size() > 0) {   // may be 0 if fit fails
1038             for (int i = 0; i < m_fitData.paramNames.size(); i++)
1039                 m_fitData.paramStartValues[i] = m_fitCurve->fitResult().paramValues.at(i);
1040             fitParametersWidget->setFitData(&m_fitData);
1041         } else {
1042             DEBUG(" WARNING: no fit result available!")
1043         }
1044     }
1045 
1046     this->showFitResult();
1047     uiGeneralTab.pbRecalculate->setEnabled(false);
1048 
1049     //show the warning/error message, if available
1050     const XYFitCurve::FitResult& fitResult = m_fitCurve->fitResult();
1051     const QString& status = fitResult.status;
1052     if (status != i18n("Success")) {
1053         emit info(i18n("Fit status: %1", fitResult.status));
1054         if (!m_messageWidget) {
1055             m_messageWidget = new KMessageWidget(this);
1056             uiGeneralTab.gridLayout_2->addWidget(m_messageWidget, 25, 3, 1, 4);
1057         }
1058 
1059         if (!fitResult.valid)
1060             m_messageWidget->setMessageType(KMessageWidget::Error);
1061         else
1062             m_messageWidget->setMessageType(KMessageWidget::Warning);
1063         m_messageWidget->setText(status);
1064         m_messageWidget->animatedShow();
1065     } else {
1066         if (m_messageWidget && m_messageWidget->isVisible())
1067             m_messageWidget->close();
1068     }
1069 
1070     QApplication::restoreOverrideCursor();
1071     DEBUG("XYFitCurveDock::recalculateClicked() DONE");
1072 }
1073 
1074 void XYFitCurveDock::expressionChanged() {
1075     DEBUG("XYFitCurveDock::expressionChanged()");
1076     if (m_initializing)
1077         return;
1078 
1079     // update parameter list for custom model
1080     if (m_fitData.modelCategory == nsl_fit_model_custom)
1081         updateParameterList();
1082 
1083     enableRecalculate();
1084 }
1085 
1086 void XYFitCurveDock::enableRecalculate() {
1087     DEBUG("XYFitCurveDock::enableRecalculate()");
1088     if (m_initializing || m_fitCurve == nullptr)
1089         return;
1090 
1091     //no fitting possible without the x- and y-data
1092     bool hasSourceData = false;
1093     if (m_fitCurve->dataSourceType() == XYAnalysisCurve::DataSourceType::Spreadsheet) {
1094         auto* aspectX = static_cast<AbstractAspect*>(cbXDataColumn->currentModelIndex().internalPointer());
1095         auto* aspectY = static_cast<AbstractAspect*>(cbYDataColumn->currentModelIndex().internalPointer());
1096         hasSourceData = (aspectX != nullptr && aspectY != nullptr);
1097         if (aspectX) {
1098             cbXDataColumn->useCurrentIndexText(true);
1099             cbXDataColumn->setInvalid(false);
1100         }
1101         if (aspectY) {
1102             cbYDataColumn->useCurrentIndexText(true);
1103             cbYDataColumn->setInvalid(false);
1104         }
1105     } else {
1106         hasSourceData = (m_fitCurve->dataSourceCurve() != nullptr);
1107     }
1108 
1109     uiGeneralTab.pbRecalculate->setEnabled(hasSourceData && m_parametersValid);
1110 
1111     // PREVIEW as soon as recalculate is enabled (does not need source data)
1112     if (m_parametersValid && m_fitData.previewEnabled) {
1113         DEBUG(" EVALUATE WITH PREVIEW ENABLED");
1114         // use recent fit data
1115         m_fitCurve->setFitData(m_fitData);
1116         // calculate fit function
1117         m_fitCurve->evaluate(true);
1118         setPlotXRange();
1119     }
1120     else {
1121         DEBUG(" PREVIEW DISABLED");
1122     }
1123     DEBUG("XYFitCurveDock::enableRecalculate() DONE");
1124 }
1125 
1126 void XYFitCurveDock::resultCopy(bool copyAll) {
1127     QTableWidget* tw{nullptr};
1128     int currentTab = uiGeneralTab.twResults->currentIndex();
1129     if (currentTab == 0)
1130         tw = uiGeneralTab.twParameters;
1131     else if (currentTab == 1)
1132         tw = uiGeneralTab.twGoodness;
1133     else if (currentTab == 2)
1134         tw = uiGeneralTab.twLog;
1135     else
1136         return;
1137 
1138     QString str;
1139     QString rowStr;
1140 
1141     //copy the header of the parameters table if we copy everything in this table
1142     if (copyAll && tw == uiGeneralTab.twParameters) {
1143         for (int i = 1; i < tw->columnCount(); ++i)
1144             str += QLatin1Char('\t') + tw->horizontalHeaderItem(i)->text();
1145     }
1146 
1147     //copy the content of the table
1148     for (int i = 0; i < tw->rowCount(); ++i) {
1149         for (int j = 0; j < tw->columnCount(); ++j) {
1150             if (!tw->item(i, j))
1151                 continue;
1152             if (!copyAll && !tw->item(i, j)->isSelected())
1153                 continue;
1154 
1155             if (!rowStr.isEmpty())
1156                 rowStr += QLatin1Char('\t');
1157 
1158             rowStr += tw->item(i, j)->text();
1159         }
1160         if (!rowStr.isEmpty()) {
1161             if (!str.isEmpty())
1162                 str += QLatin1Char('\n');
1163             str += rowStr;
1164             rowStr.clear();
1165         }
1166     }
1167 
1168     QApplication::clipboard()->setText(str);
1169     DEBUG(STDSTRING(QApplication::clipboard()->text()));
1170 }
1171 
1172 void XYFitCurveDock::resultCopyAll() {
1173     resultCopy(true);
1174 }
1175 
1176 void XYFitCurveDock::resultParametersContextMenuRequest(QPoint pos) {
1177     auto* contextMenu = new QMenu;
1178     contextMenu->addAction(i18n("Copy Selection"), this, &XYFitCurveDock::resultCopy, QKeySequence::Copy);
1179     contextMenu->addAction(i18n("Copy All"), this, &XYFitCurveDock::resultCopyAll);
1180     contextMenu->exec(uiGeneralTab.twParameters->mapToGlobal(pos));
1181 }
1182 void XYFitCurveDock::resultGoodnessContextMenuRequest(QPoint pos) {
1183     auto* contextMenu = new QMenu;
1184     contextMenu->addAction(i18n("Copy Selection"), this, &XYFitCurveDock::resultCopy, QKeySequence::Copy);
1185     contextMenu->addAction(i18n("Copy All"), this, &XYFitCurveDock::resultCopyAll);
1186     contextMenu->exec(uiGeneralTab.twGoodness->mapToGlobal(pos));
1187 }
1188 void XYFitCurveDock::resultLogContextMenuRequest(QPoint pos) {
1189     auto* contextMenu = new QMenu;
1190     contextMenu->addAction(i18n("Copy Selection"), this, &XYFitCurveDock::resultCopy, QKeySequence::Copy);
1191     contextMenu->addAction(i18n("Copy All"), this, &XYFitCurveDock::resultCopyAll);
1192     contextMenu->exec(uiGeneralTab.twLog->mapToGlobal(pos));
1193 }
1194 
1195 /*!
1196  * show the result and details of the fit
1197  */
1198 void XYFitCurveDock::showFitResult() {
1199     //clear the previous result
1200     uiGeneralTab.twParameters->setRowCount(0);
1201     for (int row = 0; row < uiGeneralTab.twGoodness->rowCount(); ++row)
1202         uiGeneralTab.twGoodness->item(row, 1)->setText(QString());
1203     for (int row = 0; row < uiGeneralTab.twLog->rowCount(); ++row)
1204         uiGeneralTab.twLog->item(row, 1)->setText(QString());
1205 
1206     const auto& fitResult = m_fitCurve->fitResult();
1207 
1208     if (!fitResult.available) {
1209         DEBUG(Q_FUNC_INFO << ", fit result not available");
1210         return;
1211     }
1212 
1213     // Log
1214     uiGeneralTab.twLog->item(0, 1)->setText(fitResult.status);
1215 
1216     if (!fitResult.valid) {
1217         DEBUG(Q_FUNC_INFO << ", fit result not valid");
1218         return;
1219     }
1220 
1221     SET_NUMBER_LOCALE
1222     // used confidence interval
1223     double confidenceInterval{m_fitData.confidenceInterval};
1224     uiGeneralTab.twParameters->horizontalHeaderItem(6)->setToolTip(i18n("%1 % lower confidence level", numberLocale.toString(confidenceInterval, 'g', 7)));
1225     uiGeneralTab.twParameters->horizontalHeaderItem(7)->setToolTip(i18n("%1 % upper confidence level", numberLocale.toString(confidenceInterval, 'g', 7)));
1226 
1227     // log
1228     uiGeneralTab.twLog->item(1, 1)->setText(numberLocale.toString(fitResult.iterations));
1229     uiGeneralTab.twLog->item(2, 1)->setText(numberLocale.toString(m_fitData.eps));
1230     if (fitResult.elapsedTime > 1000)
1231         uiGeneralTab.twLog->item(3, 1)->setText(numberLocale.toString(fitResult.elapsedTime/1000) + " s");
1232     else
1233         uiGeneralTab.twLog->item(3, 1)->setText(numberLocale.toString(fitResult.elapsedTime) + " ms");
1234 
1235     uiGeneralTab.twLog->item(4, 1)->setText(numberLocale.toString(fitResult.dof));
1236     uiGeneralTab.twLog->item(5, 1)->setText(numberLocale.toString(fitResult.paramValues.size()));
1237     uiGeneralTab.twLog->item(6, 1)->setText(m_fitData.fitRange.toString());
1238 
1239     const int np = m_fitData.paramNames.size();
1240 
1241     // correlation matrix
1242     QString sCorr;
1243     for (const auto &s : m_fitData.paramNamesUtf8)
1244         sCorr += '\t' + s;
1245     int index{0};
1246     DEBUG(Q_FUNC_INFO << ", correlation values size = " << fitResult.correlationMatrix.size())
1247     for (int i = 0; i < np; i++) {
1248         sCorr += '\n' + m_fitData.paramNamesUtf8.at(i);
1249         for (int j = 0; j <= i; j++)
1250             sCorr += '\t' + numberLocale.toString(fitResult.correlationMatrix.at(index++), 'f');
1251     }
1252     uiGeneralTab.twLog->item(7, 1)->setText(sCorr);
1253 
1254     // iterations
1255     QString sIter;
1256     for (const auto &s : m_fitData.paramNamesUtf8)
1257         sIter += s + '\t';
1258     sIter += UTF8_QSTRING("χ²");
1259 
1260     const QStringList iterations = fitResult.solverOutput.split(';');
1261     for (const auto &s : iterations)
1262         if (!s.isEmpty())
1263             sIter += '\n' + s;
1264     uiGeneralTab.twLog->item(8, 1)->setText(sIter);
1265     uiGeneralTab.twLog->resizeRowsToContents();
1266 
1267     // Parameters
1268     uiGeneralTab.twParameters->setRowCount(np);
1269 
1270     for (int i = 0; i < np; i++) {
1271         const double paramValue = fitResult.paramValues.at(i);
1272         const double errorValue = fitResult.errorValues.at(i);
1273 
1274         auto* item = new QTableWidgetItem(m_fitData.paramNamesUtf8.at(i));
1275         item->setBackground(QApplication::palette().color(QPalette::Window));
1276         uiGeneralTab.twParameters->setItem(i, 0, item);
1277         item = new QTableWidgetItem(numberLocale.toString(paramValue));
1278         uiGeneralTab.twParameters->setItem(i, 1, item);
1279 
1280         if (!m_fitData.paramFixed.at(i)) {
1281             if (!std::isnan(errorValue)) {
1282                 item = new QTableWidgetItem(numberLocale.toString(errorValue));
1283                 uiGeneralTab.twParameters->setItem(i, 2, item);
1284                 item = new QTableWidgetItem(numberLocale.toString(100.*errorValue/std::abs(paramValue), 'g', 3));
1285                 uiGeneralTab.twParameters->setItem(i, 3, item);
1286             } else {
1287                 item = new QTableWidgetItem(UTF8_QSTRING("∞"));
1288                 uiGeneralTab.twParameters->setItem(i, 2, item);
1289                 item = new QTableWidgetItem(UTF8_QSTRING("∞"));
1290                 uiGeneralTab.twParameters->setItem(i, 3, item);
1291             }
1292 
1293             // t values
1294             QString tdistValueString;
1295             if (fitResult.tdist_tValues.at(i) < std::numeric_limits<double>::max())
1296                 tdistValueString = numberLocale.toString(fitResult.tdist_tValues.at(i), 'g', 3);
1297             else
1298                 tdistValueString = UTF8_QSTRING("∞");
1299             item = new QTableWidgetItem(tdistValueString);
1300             uiGeneralTab.twParameters->setItem(i, 4, item);
1301 
1302             // p values
1303             const double p = fitResult.tdist_pValues.at(i);
1304             item = new QTableWidgetItem(numberLocale.toString(p, 'g', 3));
1305             // color p values depending on value
1306             if (p > 0.05)
1307                 item->setForeground(QBrush(QApplication::palette().color(QPalette::LinkVisited)));
1308             else if (p > 0.01)
1309                 item->setForeground(QBrush(Qt::darkGreen));
1310             else if (p > 0.001)
1311                 item->setForeground(QBrush(Qt::darkCyan));
1312             else if (p > 0.0001)
1313                 item->setForeground(QBrush(QApplication::palette().color(QPalette::Link)));
1314             else
1315                 item->setForeground(QBrush(QApplication::palette().color(QPalette::Highlight)));
1316             uiGeneralTab.twParameters->setItem(i, 5, item);
1317 
1318             // Conf. interval
1319             if (!std::isnan(errorValue)) {
1320                 const double margin = fitResult.tdist_marginValues.at(i);
1321                 //TODO: if (fitResult.tdist_tValues.at(i) > 1.e6)
1322                 //  item = new QTableWidgetItem(i18n("too small"));
1323 
1324                 item = new QTableWidgetItem(numberLocale.toString(paramValue - margin));
1325                 uiGeneralTab.twParameters->setItem(i, 6, item);
1326                 item = new QTableWidgetItem(numberLocale.toString(paramValue + margin));
1327                 uiGeneralTab.twParameters->setItem(i, 7, item);
1328             }
1329         }
1330     }
1331 
1332     // Goodness of fit
1333     uiGeneralTab.twGoodness->item(0, 1)->setText(numberLocale.toString(fitResult.sse));
1334 
1335     if (fitResult.dof != 0) {
1336         uiGeneralTab.twGoodness->item(1, 1)->setText(numberLocale.toString(fitResult.rms));
1337         uiGeneralTab.twGoodness->item(2, 1)->setText(numberLocale.toString(fitResult.rsd));
1338 
1339         uiGeneralTab.twGoodness->item(3, 1)->setText(numberLocale.toString(fitResult.rsquare));
1340         uiGeneralTab.twGoodness->item(4, 1)->setText(numberLocale.toString(fitResult.rsquareAdj));
1341 
1342         // chi^2 and F test p-values
1343         uiGeneralTab.twGoodness->item(5, 1)->setText(numberLocale.toString(fitResult.chisq_p, 'g', 3));
1344         uiGeneralTab.twGoodness->item(6, 1)->setText(numberLocale.toString(fitResult.fdist_F, 'g', 3));
1345         uiGeneralTab.twGoodness->item(7, 1)->setText(numberLocale.toString(fitResult.fdist_p, 'g', 3));
1346         uiGeneralTab.twGoodness->item(9, 1)->setText(numberLocale.toString(fitResult.aic, 'g', 3));
1347         uiGeneralTab.twGoodness->item(10, 1)->setText(numberLocale.toString(fitResult.bic, 'g', 3));
1348     }
1349 
1350     uiGeneralTab.twGoodness->item(8, 1)->setText(numberLocale.toString(fitResult.mae));
1351 
1352     //resize the table headers to fit the new content
1353     uiGeneralTab.twLog->resizeColumnsToContents();
1354     uiGeneralTab.twParameters->resizeColumnsToContents();
1355     //twGoodness doesn't have any header -> resize sections
1356     uiGeneralTab.twGoodness->resizeColumnToContents(0);
1357     uiGeneralTab.twGoodness->resizeColumnToContents(1);
1358 
1359     //enable the "recalculate"-button if the source data was changed since the last fit
1360     uiGeneralTab.pbRecalculate->setEnabled(m_fitCurve->isSourceDataChangedSinceLastRecalc());
1361 }
1362 
1363 //*************************************************************
1364 //*********** SLOTs for changes triggered in XYCurve **********
1365 //*************************************************************
1366 //General-Tab
1367 void XYFitCurveDock::curveDescriptionChanged(const AbstractAspect* aspect) {
1368     if (m_curve != aspect)
1369         return;
1370 
1371     m_initializing = true;
1372     if (aspect->name() != uiGeneralTab.leName->text())
1373         uiGeneralTab.leName->setText(aspect->name());
1374     else if (aspect->comment() != uiGeneralTab.leComment->text())
1375         uiGeneralTab.leComment->setText(aspect->comment());
1376     m_initializing = false;
1377 }
1378 
1379 void XYFitCurveDock::curveDataSourceTypeChanged(XYAnalysisCurve::DataSourceType type) {
1380     m_initializing = true;
1381     uiGeneralTab.cbDataSourceType->setCurrentIndex(static_cast<int>(type));
1382     m_initializing = false;
1383 }
1384 
1385 void XYFitCurveDock::curveDataSourceCurveChanged(const XYCurve* curve) {
1386     m_initializing = true;
1387     XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, curve);
1388     m_initializing = false;
1389 }
1390 
1391 void XYFitCurveDock::curveXDataColumnChanged(const AbstractColumn* column) {
1392     m_initializing = true;
1393     XYCurveDock::setModelIndexFromAspect(cbXDataColumn, column);
1394     m_initializing = false;
1395 }
1396 
1397 void XYFitCurveDock::curveYDataColumnChanged(const AbstractColumn* column) {
1398     m_initializing = true;
1399     XYCurveDock::setModelIndexFromAspect(cbYDataColumn, column);
1400     m_initializing = false;
1401 }
1402 
1403 void XYFitCurveDock::curveXErrorColumnChanged(const AbstractColumn* column) {
1404     m_initializing = true;
1405     XYCurveDock::setModelIndexFromAspect(cbXErrorColumn, column);
1406     m_initializing = false;
1407 }
1408 
1409 void XYFitCurveDock::curveYErrorColumnChanged(const AbstractColumn* column) {
1410     m_initializing = true;
1411     XYCurveDock::setModelIndexFromAspect(cbYErrorColumn, column);
1412     m_initializing = false;
1413 }
1414 
1415 /*!
1416  * called when fit data of fit curve changes
1417  */
1418 void XYFitCurveDock::curveFitDataChanged(const XYFitCurve::FitData& fitData) {
1419     m_initializing = true;
1420     m_fitData = fitData;
1421 
1422     if (m_fitData.modelCategory != nsl_fit_model_custom)
1423         uiGeneralTab.cbModel->setCurrentIndex(m_fitData.modelType);
1424 
1425     uiGeneralTab.sbDegree->setValue(m_fitData.degree);
1426     m_initializing = false;
1427 }
1428 
1429 void XYFitCurveDock::dataChanged() {
1430     this->enableRecalculate();
1431 }
1432 
1433 void XYFitCurveDock::curveVisibilityChanged(bool on) {
1434     m_initializing = true;
1435     uiGeneralTab.chkVisible->setChecked(on);
1436     m_initializing = false;
1437 }