File indexing completed on 2024-06-02 03:49:27

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