File indexing completed on 2025-07-13 03:32:48

0001 /*
0002     File             : XYEquationCurveDock.cpp
0003     Project          : LabPlot
0004     Description      : widget for editing properties of equation curves
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2014-2024 Alexander Semke <alexander.semke@web.de>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "XYEquationCurveDock.h"
0012 #include "backend/gsl/ExpressionParser.h"
0013 #include "backend/worksheet/plots/cartesian/XYEquationCurve.h"
0014 #include "kdefrontend/widgets/ConstantsWidget.h"
0015 #include "kdefrontend/widgets/FunctionsWidget.h"
0016 
0017 #include <QCompleter>
0018 #include <QKeyEvent>
0019 #include <QMenu>
0020 #include <QWidgetAction>
0021 
0022 #include <KLocalizedString>
0023 
0024 /*!
0025   \class XYEquationCurveDock
0026   \brief  Provides a widget for editing the properties of the XYEquationCurves
0027         (2D-curves defined by a mathematical equation) currently selected in
0028         the project explorer.
0029 
0030   If more than one curves are set, the properties of the first column are shown.
0031   The changes of the properties are applied to all curves.
0032   The exclusions are the name, the comment and the datasets (columns) of
0033   the curves  - these properties can only be changed if there is only one single curve.
0034 
0035   \ingroup kdefrontend
0036 */
0037 
0038 XYEquationCurveDock::XYEquationCurveDock(QWidget* parent)
0039     : XYCurveDock(parent) {
0040     // remove the tab "Error bars"
0041     ui.tabWidget->removeTab(5);
0042 }
0043 
0044 /*!
0045  *  // Tab "General"
0046  */
0047 void XYEquationCurveDock::setupGeneral() {
0048     auto* generalTab = new QWidget(ui.tabGeneral);
0049     uiGeneralTab.setupUi(generalTab);
0050     setPlotRangeCombobox(uiGeneralTab.cbPlotRanges);
0051     setBaseWidgets(uiGeneralTab.leName, uiGeneralTab.teComment);
0052     setVisibilityWidgets(uiGeneralTab.chkVisible, uiGeneralTab.chkLegendVisible);
0053 
0054     auto* gridLayout = dynamic_cast<QGridLayout*>(generalTab->layout());
0055     if (gridLayout) {
0056         gridLayout->setContentsMargins(2, 2, 2, 2);
0057         gridLayout->setHorizontalSpacing(2);
0058         gridLayout->setVerticalSpacing(2);
0059     }
0060 
0061     auto* layout = new QHBoxLayout(ui.tabGeneral);
0062     layout->setContentsMargins(0, 0, 0, 0);
0063     layout->addWidget(generalTab);
0064 
0065     uiGeneralTab.tbConstants1->setIcon(QIcon::fromTheme(QStringLiteral("labplot-format-text-symbol")));
0066     uiGeneralTab.tbFunctions1->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font")));
0067 
0068     uiGeneralTab.tbConstants2->setIcon(QIcon::fromTheme(QStringLiteral("labplot-format-text-symbol")));
0069     uiGeneralTab.tbFunctions2->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font")));
0070 
0071     uiGeneralTab.cbType->addItem(i18n("Cartesian"));
0072     uiGeneralTab.cbType->addItem(i18n("Polar"));
0073     uiGeneralTab.cbType->addItem(i18n("Parametric"));
0074     //  uiGeneralTab.cbType->addItem(i18n("Implicit"));
0075 
0076     uiGeneralTab.pbRecalculate->setIcon(QIcon::fromTheme(QStringLiteral("run-build")));
0077 
0078     uiGeneralTab.teEquation2->setExpressionType(XYEquationCurve::EquationType::Parametric);
0079 
0080     //  uiGeneralTab.teEquation1->setMaximumHeight(uiGeneralTab.leName->sizeHint().height()*2);
0081     //  uiGeneralTab.teEquation2->setMaximumHeight(uiGeneralTab.leName->sizeHint().height()*2);
0082     uiGeneralTab.teMin->setMaximumHeight(uiGeneralTab.leName->sizeHint().height());
0083     uiGeneralTab.teMax->setMaximumHeight(uiGeneralTab.leName->sizeHint().height());
0084 
0085     // Slots
0086     connect(uiGeneralTab.cbType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &XYEquationCurveDock::typeChanged);
0087     connect(uiGeneralTab.teEquation1, &ExpressionTextEdit::expressionChanged, this, &XYEquationCurveDock::enableRecalculate);
0088     connect(uiGeneralTab.teEquation2, &ExpressionTextEdit::expressionChanged, this, &XYEquationCurveDock::enableRecalculate);
0089     connect(uiGeneralTab.tbConstants1, &QToolButton::clicked, this, &XYEquationCurveDock::showConstants);
0090     connect(uiGeneralTab.tbFunctions1, &QToolButton::clicked, this, &XYEquationCurveDock::showFunctions);
0091     connect(uiGeneralTab.tbConstants2, &QToolButton::clicked, this, &XYEquationCurveDock::showConstants);
0092     connect(uiGeneralTab.tbFunctions2, &QToolButton::clicked, this, &XYEquationCurveDock::showFunctions);
0093     connect(uiGeneralTab.teMin, &ExpressionTextEdit::expressionChanged, this, &XYEquationCurveDock::enableRecalculate);
0094     connect(uiGeneralTab.teMax, &ExpressionTextEdit::expressionChanged, this, &XYEquationCurveDock::enableRecalculate);
0095     connect(uiGeneralTab.sbCount, QOverload<int>::of(&QSpinBox::valueChanged), this, &XYEquationCurveDock::enableRecalculate);
0096     connect(uiGeneralTab.pbRecalculate, &QPushButton::clicked, this, &XYEquationCurveDock::recalculateClicked);
0097 }
0098 
0099 void XYEquationCurveDock::initGeneralTab() {
0100     // show the properties of the first curve
0101     const auto* equationCurve = static_cast<const XYEquationCurve*>(m_curve);
0102     Q_ASSERT(equationCurve);
0103     const XYEquationCurve::EquationData& data = equationCurve->equationData();
0104     uiGeneralTab.cbType->setCurrentIndex(static_cast<int>(data.type));
0105     this->typeChanged(static_cast<int>(data.type));
0106     uiGeneralTab.teEquation1->setText(data.expression1);
0107     uiGeneralTab.teEquation2->setText(data.expression2);
0108     uiGeneralTab.teMin->setText(data.min);
0109     uiGeneralTab.teMax->setText(data.max);
0110     uiGeneralTab.sbCount->setValue(data.count);
0111 
0112     uiGeneralTab.chkLegendVisible->setChecked(m_curve->legendVisible());
0113     uiGeneralTab.chkVisible->setChecked(m_curve->isVisible());
0114 
0115     // Slots
0116     connect(m_equationCurve, &XYEquationCurve::equationDataChanged, this, &XYEquationCurveDock::curveEquationDataChanged);
0117 }
0118 
0119 /*!
0120   sets the curves. The properties of the curves in the list \c list can be edited in this widget.
0121 */
0122 void XYEquationCurveDock::setCurves(QList<XYCurve*> list) {
0123     CONDITIONAL_LOCK_RETURN;
0124     m_curvesList = list;
0125     m_curve = list.first();
0126     setAspects(list);
0127     m_equationCurve = static_cast<XYEquationCurve*>(m_curve);
0128     Q_ASSERT(m_equationCurve);
0129     XYCurveDock::setModel();
0130     initGeneralTab();
0131     initTabs();
0132     setSymbols(list);
0133 
0134     updatePlotRangeList();
0135 
0136     uiGeneralTab.pbRecalculate->setEnabled(false);
0137 }
0138 
0139 //*************************************************************
0140 //**** SLOTs for changes triggered in XYEquationCurveDock *****
0141 //*************************************************************
0142 void XYEquationCurveDock::typeChanged(int index) {
0143     const auto type{XYEquationCurve::EquationType(index)};
0144     if (type == XYEquationCurve::EquationType::Cartesian) {
0145         uiGeneralTab.lEquation1->setText(QStringLiteral("y=f(x)"));
0146         uiGeneralTab.lEquation2->hide();
0147         uiGeneralTab.teEquation2->hide();
0148         uiGeneralTab.tbFunctions2->hide();
0149         uiGeneralTab.tbConstants2->hide();
0150         uiGeneralTab.lMin->show();
0151         uiGeneralTab.lMax->show();
0152         uiGeneralTab.teMin->show();
0153         uiGeneralTab.teMax->show();
0154         uiGeneralTab.lMin->setText(i18n("x, min"));
0155         uiGeneralTab.lMax->setText(i18n("x, max"));
0156     } else if (type == XYEquationCurve::EquationType::Polar) {
0157         uiGeneralTab.lEquation1->setText(QString::fromUtf8("r(φ)"));
0158         uiGeneralTab.lEquation2->hide();
0159         uiGeneralTab.teEquation2->hide();
0160         uiGeneralTab.tbFunctions2->hide();
0161         uiGeneralTab.tbConstants2->hide();
0162         uiGeneralTab.lMin->show();
0163         uiGeneralTab.lMax->show();
0164         uiGeneralTab.teMin->show();
0165         uiGeneralTab.teMax->show();
0166         uiGeneralTab.lMin->setText(i18n("φ, min"));
0167         uiGeneralTab.lMax->setText(i18n("φ, max"));
0168     } else if (type == XYEquationCurve::EquationType::Parametric) {
0169         uiGeneralTab.lEquation1->setText(QStringLiteral("x=f(t)"));
0170         uiGeneralTab.lEquation2->setText(QStringLiteral("y=f(t)"));
0171         uiGeneralTab.lEquation2->show();
0172         uiGeneralTab.teEquation2->show();
0173         uiGeneralTab.tbFunctions2->show();
0174         uiGeneralTab.tbConstants2->show();
0175         uiGeneralTab.lMin->show();
0176         uiGeneralTab.lMax->show();
0177         uiGeneralTab.teMin->show();
0178         uiGeneralTab.teMax->show();
0179         uiGeneralTab.lMin->setText(i18n("t, min"));
0180         uiGeneralTab.lMax->setText(i18n("t, max"));
0181     } else if (type == XYEquationCurve::EquationType::Implicit) {
0182         uiGeneralTab.lEquation1->setText(QStringLiteral("f(x,y)"));
0183         uiGeneralTab.lEquation2->hide();
0184         uiGeneralTab.teEquation2->hide();
0185         uiGeneralTab.tbFunctions2->hide();
0186         uiGeneralTab.tbConstants2->hide();
0187         uiGeneralTab.lMin->hide();
0188         uiGeneralTab.lMax->hide();
0189         uiGeneralTab.teMin->hide();
0190         uiGeneralTab.teMax->hide();
0191     }
0192 
0193     uiGeneralTab.teEquation1->setExpressionType(type);
0194     this->enableRecalculate();
0195 }
0196 
0197 void XYEquationCurveDock::recalculateClicked() {
0198     XYEquationCurve::EquationData data;
0199     data.type = (XYEquationCurve::EquationType)uiGeneralTab.cbType->currentIndex();
0200     data.expression1 = uiGeneralTab.teEquation1->document()->toPlainText();
0201     data.expression2 = uiGeneralTab.teEquation2->document()->toPlainText();
0202     data.min = uiGeneralTab.teMin->document()->toPlainText();
0203     data.max = uiGeneralTab.teMax->document()->toPlainText();
0204     data.count = uiGeneralTab.sbCount->value();
0205 
0206     for (auto* curve : m_curvesList)
0207         static_cast<XYEquationCurve*>(curve)->setEquationData(data);
0208 
0209     uiGeneralTab.pbRecalculate->setEnabled(false);
0210     updatePlotRangeList(); // axes range may change when range on auto scale
0211 }
0212 
0213 void XYEquationCurveDock::showConstants() {
0214     QMenu menu;
0215     ConstantsWidget constants(&menu);
0216 
0217     if (QObject::sender() == uiGeneralTab.tbConstants1)
0218         connect(&constants, &ConstantsWidget::constantSelected, this, &XYEquationCurveDock::insertConstant1);
0219     else
0220         connect(&constants, &ConstantsWidget::constantSelected, this, &XYEquationCurveDock::insertConstant2);
0221 
0222     connect(&constants, &ConstantsWidget::constantSelected, &menu, &QMenu::close);
0223     connect(&constants, &ConstantsWidget::canceled, &menu, &QMenu::close);
0224 
0225     auto* widgetAction = new QWidgetAction(this);
0226     widgetAction->setDefaultWidget(&constants);
0227     menu.addAction(widgetAction);
0228 
0229     if (QObject::sender() == uiGeneralTab.tbConstants1) {
0230         QPoint pos(-menu.sizeHint().width() + uiGeneralTab.tbConstants1->width(), -menu.sizeHint().height());
0231         menu.exec(uiGeneralTab.tbConstants1->mapToGlobal(pos));
0232     } else {
0233         QPoint pos(-menu.sizeHint().width() + uiGeneralTab.tbConstants2->width(), -menu.sizeHint().height());
0234         menu.exec(uiGeneralTab.tbConstants2->mapToGlobal(pos));
0235     }
0236 }
0237 
0238 void XYEquationCurveDock::showFunctions() {
0239     QMenu menu;
0240     FunctionsWidget functions(&menu);
0241     if (QObject::sender() == uiGeneralTab.tbFunctions1)
0242         connect(&functions, &FunctionsWidget::functionSelected, this, &XYEquationCurveDock::insertFunction1);
0243     else
0244         connect(&functions, &FunctionsWidget::functionSelected, this, &XYEquationCurveDock::insertFunction2);
0245 
0246     connect(&functions, &FunctionsWidget::functionSelected, &menu, &QMenu::close);
0247     connect(&functions, &FunctionsWidget::canceled, &menu, &QMenu::close);
0248 
0249     auto* widgetAction = new QWidgetAction(this);
0250     widgetAction->setDefaultWidget(&functions);
0251     menu.addAction(widgetAction);
0252 
0253     if (QObject::sender() == uiGeneralTab.tbFunctions1) {
0254         QPoint pos(-menu.sizeHint().width() + uiGeneralTab.tbFunctions1->width(), -menu.sizeHint().height());
0255         menu.exec(uiGeneralTab.tbFunctions1->mapToGlobal(pos));
0256     } else {
0257         QPoint pos(-menu.sizeHint().width() + uiGeneralTab.tbFunctions2->width(), -menu.sizeHint().height());
0258         menu.exec(uiGeneralTab.tbFunctions2->mapToGlobal(pos));
0259     }
0260 }
0261 
0262 void XYEquationCurveDock::insertFunction1(const QString& functionName) {
0263     const auto type{XYEquationCurve::EquationType(uiGeneralTab.cbType->currentIndex())};
0264 
0265     uiGeneralTab.teEquation1->insertPlainText(functionName + ExpressionParser::functionArgumentString(functionName, type));
0266 }
0267 
0268 void XYEquationCurveDock::insertConstant1(const QString& constantsName) {
0269     uiGeneralTab.teEquation1->insertPlainText(constantsName);
0270 }
0271 
0272 void XYEquationCurveDock::insertFunction2(const QString& functionName) {
0273     uiGeneralTab.teEquation1->insertPlainText(functionName + ExpressionParser::functionArgumentString(functionName, XYEquationCurve::EquationType::Parametric));
0274 }
0275 
0276 void XYEquationCurveDock::insertConstant2(const QString& constantsName) {
0277     uiGeneralTab.teEquation2->insertPlainText(constantsName);
0278 }
0279 
0280 void XYEquationCurveDock::enableRecalculate() {
0281     CONDITIONAL_RETURN_NO_LOCK;
0282 
0283     // check whether the formula expressions are correct
0284     bool valid = false;
0285     const auto type = XYEquationCurve::EquationType(uiGeneralTab.cbType->currentIndex());
0286     if (type != XYEquationCurve::EquationType::Parametric)
0287         valid = uiGeneralTab.teEquation1->isValid();
0288     else
0289         valid = (uiGeneralTab.teEquation1->isValid() && uiGeneralTab.teEquation2->isValid());
0290 
0291     valid = (valid && uiGeneralTab.teMin->isValid() && uiGeneralTab.teMax->isValid());
0292     uiGeneralTab.pbRecalculate->setEnabled(valid);
0293 
0294     updatePlotRangeList();
0295 }
0296 
0297 //*************************************************************
0298 //*********** SLOTs for changes triggered in XYCurve **********
0299 //*************************************************************
0300 // General-Tab
0301 void XYEquationCurveDock::curveEquationDataChanged(const XYEquationCurve::EquationData& data) {
0302     CONDITIONAL_LOCK_RETURN;
0303     uiGeneralTab.cbType->setCurrentIndex(static_cast<int>(data.type));
0304     uiGeneralTab.teEquation1->setText(data.expression1);
0305     uiGeneralTab.teEquation2->setText(data.expression2);
0306     uiGeneralTab.teMin->setText(data.min);
0307     uiGeneralTab.teMax->setText(data.max);
0308     uiGeneralTab.sbCount->setValue(data.count);
0309 }