File indexing completed on 2025-07-13 03:32:51
0001 /* 0002 File : MatrixFunctionDialog.cpp 0003 Project : LabPlot 0004 Description : Dialog for generating matrix values from a mathematical function 0005 ------------------------------------------------------------------------ 0006 SPDX-FileCopyrightText: 2015-2019 Alexander Semke <alexander.semke@web.de> 0007 SPDX-FileCopyrightText: 2016 Stefan Gerlach <stefan.gerlach@uni.kn> 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 #include "MatrixFunctionDialog.h" 0012 #include "backend/core/Settings.h" 0013 #include "backend/gsl/ExpressionParser.h" 0014 #include "backend/lib/macros.h" 0015 #include "backend/matrix/Matrix.h" 0016 #include "kdefrontend/widgets/ConstantsWidget.h" 0017 #include "kdefrontend/widgets/FunctionsWidget.h" 0018 0019 #include <KLocalizedString> 0020 #include <KWindowConfig> 0021 0022 #include <QDialogButtonBox> 0023 #include <QMenu> 0024 #include <QPushButton> 0025 #include <QThreadPool> 0026 #include <QWidgetAction> 0027 #include <QWindow> 0028 #ifndef NDEBUG 0029 #include <QElapsedTimer> 0030 #endif 0031 0032 #include "backend/gsl/parser.h" 0033 #include <cmath> 0034 0035 /*! 0036 \class MatrixFunctionDialog 0037 \brief Dialog for generating matrix values from a mathematical function. 0038 0039 \ingroup kdefrontend 0040 */ 0041 MatrixFunctionDialog::MatrixFunctionDialog(Matrix* m, QWidget* parent) 0042 : QDialog(parent) 0043 , m_matrix(m) { 0044 Q_ASSERT(m_matrix); 0045 setWindowTitle(i18nc("@title:window", "Function Values")); 0046 0047 ui.setupUi(this); 0048 setAttribute(Qt::WA_DeleteOnClose); 0049 ui.tbConstants->setIcon(QIcon::fromTheme(QStringLiteral("labplot-format-text-symbol"))); 0050 ui.tbFunctions->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font"))); 0051 0052 QStringList vars; 0053 vars << QStringLiteral("x") << QStringLiteral("y"); 0054 ui.teEquation->setVariables(vars); 0055 ui.teEquation->setFocus(); 0056 ui.teEquation->setMaximumHeight(QLineEdit().sizeHint().height() * 2); 0057 0058 const auto numberLocale = QLocale(); 0059 QString info = QStringLiteral("[") + numberLocale.toString(m_matrix->xStart()) + QStringLiteral(", ") + numberLocale.toString(m_matrix->xEnd()) 0060 + QStringLiteral("], ") + i18np("%1 value", "%1 values", m_matrix->columnCount()); 0061 ui.lXInfo->setText(info); 0062 info = QStringLiteral("[") + numberLocale.toString(m_matrix->yStart()) + QStringLiteral(", ") + numberLocale.toString(m_matrix->yEnd()) 0063 + QStringLiteral("], ") + i18np("%1 value", "%1 values", m_matrix->rowCount()); 0064 ui.lYInfo->setText(info); 0065 0066 ui.teEquation->setPlainText(m_matrix->formula()); 0067 auto* btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); 0068 0069 ui.gridLayout_2->addWidget(btnBox, 3, 0, 1, 3); 0070 m_okButton = btnBox->button(QDialogButtonBox::Ok); 0071 0072 connect(btnBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &MatrixFunctionDialog::close); 0073 connect(btnBox, &QDialogButtonBox::accepted, this, &MatrixFunctionDialog::accept); 0074 connect(btnBox, &QDialogButtonBox::rejected, this, &MatrixFunctionDialog::reject); 0075 0076 m_okButton->setText(i18n("&Generate")); 0077 m_okButton->setToolTip(i18n("Generate function values")); 0078 0079 connect(ui.teEquation, &ExpressionTextEdit::expressionChanged, this, &MatrixFunctionDialog::checkValues); 0080 connect(ui.tbConstants, &QToolButton::clicked, this, &MatrixFunctionDialog::showConstants); 0081 connect(ui.tbFunctions, &QToolButton::clicked, this, &MatrixFunctionDialog::showFunctions); 0082 connect(m_okButton, &QPushButton::clicked, this, &MatrixFunctionDialog::generate); 0083 0084 // restore saved settings if available 0085 create(); // ensure there's a window created 0086 KConfigGroup conf = Settings::group(QStringLiteral("MatrixFunctionDialog")); 0087 if (conf.exists()) { 0088 KWindowConfig::restoreWindowSize(windowHandle(), conf); 0089 resize(windowHandle()->size()); // workaround for QTBUG-40584 0090 } else 0091 resize(QSize(300, 0).expandedTo(minimumSize())); 0092 } 0093 0094 MatrixFunctionDialog::~MatrixFunctionDialog() { 0095 KConfigGroup conf = Settings::group(QStringLiteral("MatrixFunctionDialog")); 0096 KWindowConfig::saveWindowSize(windowHandle(), conf); 0097 } 0098 0099 void MatrixFunctionDialog::checkValues() { 0100 if (!ui.teEquation->isValid()) { 0101 m_okButton->setEnabled(false); 0102 return; 0103 } 0104 0105 m_okButton->setEnabled(true); 0106 } 0107 0108 void MatrixFunctionDialog::showConstants() { 0109 QMenu menu; 0110 ConstantsWidget constants(&menu); 0111 connect(&constants, &ConstantsWidget::constantSelected, this, &MatrixFunctionDialog::insertConstant); 0112 connect(&constants, &ConstantsWidget::constantSelected, &menu, &QMenu::close); 0113 connect(&constants, &ConstantsWidget::canceled, &menu, &QMenu::close); 0114 0115 auto* widgetAction = new QWidgetAction(this); 0116 widgetAction->setDefaultWidget(&constants); 0117 menu.addAction(widgetAction); 0118 0119 QPoint pos(-menu.sizeHint().width() + ui.tbConstants->width(), -menu.sizeHint().height()); 0120 menu.exec(ui.tbConstants->mapToGlobal(pos)); 0121 } 0122 0123 void MatrixFunctionDialog::showFunctions() { 0124 QMenu menu; 0125 FunctionsWidget functions(&menu); 0126 connect(&functions, &FunctionsWidget::functionSelected, this, &MatrixFunctionDialog::insertFunction); 0127 connect(&functions, &FunctionsWidget::functionSelected, &menu, &QMenu::close); 0128 connect(&functions, &FunctionsWidget::canceled, &menu, &QMenu::close); 0129 0130 auto* widgetAction = new QWidgetAction(this); 0131 widgetAction->setDefaultWidget(&functions); 0132 menu.addAction(widgetAction); 0133 0134 QPoint pos(-menu.sizeHint().width() + ui.tbFunctions->width(), -menu.sizeHint().height()); 0135 menu.exec(ui.tbFunctions->mapToGlobal(pos)); 0136 } 0137 0138 void MatrixFunctionDialog::insertFunction(const QString& functionName) const { 0139 ui.teEquation->insertPlainText(functionName + ExpressionParser::functionArgumentString(functionName, XYEquationCurve::EquationType::Cartesian)); 0140 } 0141 0142 void MatrixFunctionDialog::insertConstant(const QString& constantsName) const { 0143 ui.teEquation->insertPlainText(constantsName); 0144 } 0145 0146 /* task class for parallel fill (not used) */ 0147 class GenerateValueTask : public QRunnable { 0148 public: 0149 GenerateValueTask(int startCol, int endCol, QVector<QVector<double>>& matrixData, double xStart, double yStart, double xStep, double yStep, char* func) 0150 : m_startCol(startCol) 0151 , m_endCol(endCol) 0152 , m_matrixData(matrixData) 0153 , m_xStart(xStart) 0154 , m_yStart(yStart) 0155 , m_xStep(xStep) 0156 , m_yStep(yStep) 0157 , m_func(func) { 0158 } 0159 0160 void run() override { 0161 const int rows = m_matrixData[m_startCol].size(); 0162 double x = m_xStart; 0163 double y = m_yStart; 0164 DEBUG("FILL col" << m_startCol << "-" << m_endCol << " x/y =" << x << '/' << y << " steps =" << m_xStep << '/' << m_yStep << " rows =" << rows) 0165 0166 parser_var vars[] = {{"x", x}, {"y", y}}; 0167 for (int col = m_startCol; col < m_endCol; ++col) { 0168 vars[0].value = x; 0169 for (int row = 0; row < rows; ++row) { 0170 vars[1].value = y; 0171 double z = parse_with_vars(m_func, vars, 2, qPrintable(QLocale().name())); 0172 // DEBUG(" z =" << z); 0173 m_matrixData[col][row] = z; 0174 y += m_yStep; 0175 } 0176 0177 y = m_yStart; 0178 x += m_xStep; 0179 } 0180 } 0181 0182 private: 0183 int m_startCol; 0184 int m_endCol; 0185 QVector<QVector<double>>& m_matrixData; 0186 double m_xStart; 0187 double m_yStart; 0188 double m_xStep; 0189 double m_yStep; 0190 char* m_func; 0191 }; 0192 0193 void MatrixFunctionDialog::generate() { 0194 WAIT_CURSOR; 0195 0196 m_matrix->beginMacro(i18n("%1: fill matrix with function values", m_matrix->name())); 0197 0198 // TODO: data types 0199 auto* new_data = static_cast<QVector<QVector<double>>*>(m_matrix->data()); 0200 0201 // check if rows or cols == 1 0202 double diff = m_matrix->xEnd() - m_matrix->xStart(); 0203 double xStep = 0.0; 0204 if (m_matrix->columnCount() > 1) 0205 xStep = diff / double(m_matrix->columnCount() - 1); 0206 0207 diff = m_matrix->yEnd() - m_matrix->yStart(); 0208 double yStep = 0.0; 0209 if (m_matrix->rowCount() > 1) 0210 yStep = diff / double(m_matrix->rowCount() - 1); 0211 0212 #ifndef NDEBUG 0213 QElapsedTimer timer; 0214 timer.start(); 0215 #endif 0216 0217 // TODO: too slow because every parser thread needs an own symbol_table 0218 // idea: use pool->maxThreadCount() symbol tables and reuse them? 0219 /* double yStart = m_matrix->yStart(); 0220 const int cols = m_matrix->columnCount(); 0221 QThreadPool* pool = QThreadPool::globalInstance(); 0222 int range = ceil(double(cols)/pool->maxThreadCount()); 0223 DEBUG("Starting" << pool->maxThreadCount() << "threads. cols =" << cols << ": range =" << range); 0224 0225 for (int i = 0; i < pool->maxThreadCount(); ++i) { 0226 const int start = i*range; 0227 int end = (i+1)*range; 0228 if (end > cols) end = cols; 0229 qDebug() << "start/end: " << start << end; 0230 const double xStart = m_matrix->xStart() + xStep*start; 0231 GenerateValueTask* task = new GenerateValueTask(start, end, new_data, xStart, yStart, xStep, yStep, 0232 qPrintable(ui.teEquation->toPlainText())); 0233 task->setAutoDelete(false); 0234 pool->start(task); 0235 } 0236 pool->waitForDone(); 0237 */ 0238 double x = m_matrix->xStart(); 0239 double y = m_matrix->yStart(); 0240 parser_var vars[] = {{"x", x}, {"y", y}}; 0241 for (int col = 0; col < m_matrix->columnCount(); ++col) { 0242 vars[0].value = x; 0243 for (int row = 0; row < m_matrix->rowCount(); ++row) { 0244 vars[1].value = y; 0245 (*new_data)[col][row] = parse_with_vars(qPrintable(ui.teEquation->toPlainText()), vars, 2, qPrintable(QLocale().name())); 0246 y += yStep; 0247 } 0248 y = m_matrix->yStart(); 0249 x += xStep; 0250 } 0251 0252 // Timing 0253 #ifndef NDEBUG 0254 DEBUG("elapsed time =" << timer.elapsed() << "ms") 0255 #endif 0256 0257 m_matrix->setFormula(ui.teEquation->toPlainText()); 0258 m_matrix->setData(new_data); 0259 0260 m_matrix->endMacro(); 0261 RESET_CURSOR; 0262 }