Warning, file /education/kmplot/kmplot/kmplotio.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     KmPlot - a math. function plotter for the KDE-Desktop
0003 
0004     SPDX-FileCopyrightText: 1998, 1999, 2000, 2002 Klaus-Dieter Möller <kd.moeller@t-online.de>
0005 
0006     This file is part of the KDE Project.
0007     KmPlot is part of the KDE-EDU Project.
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 
0011 */
0012 
0013 #include "kmplotio.h"
0014 
0015 // Qt includes
0016 #include <QDebug>
0017 #include <QDomDocument>
0018 #include <QDomElement>
0019 #include <QDomNode>
0020 #include <QDomText>
0021 #include <QFile>
0022 #include <QTemporaryFile>
0023 #include <QTextStream>
0024 
0025 // KDE includes
0026 #include <KIO/StoredTransferJob>
0027 #include <KJobWidgets>
0028 #include <KLocalizedString>
0029 #include <KMessageBox>
0030 
0031 // ANSI-C includes
0032 #include <stdlib.h>
0033 
0034 // local includes
0035 #include "maindlg.h"
0036 #include "settings.h"
0037 #include "xparser.h"
0038 
0039 static QString CurrentVersionString("4");
0040 
0041 class XParser;
0042 
0043 KmPlotIO::KmPlotIO()
0044 {
0045     KmPlotIO::version = CurrentVersionString.toInt();
0046     lengthScaler = 1.0;
0047 }
0048 
0049 KmPlotIO::~KmPlotIO()
0050 {
0051 }
0052 
0053 QDomDocument KmPlotIO::currentState()
0054 {
0055     // saving as xml by a QDomDocument
0056     QDomDocument doc("kmpdoc");
0057     // the root tag
0058     QDomElement root = doc.createElement("kmpdoc");
0059     root.setAttribute("version", CurrentVersionString);
0060     doc.appendChild(root);
0061 
0062     // the axes tag
0063     QDomElement tag = doc.createElement("axes");
0064 
0065     tag.setAttribute("color", Settings::axesColor().name());
0066     tag.setAttribute("width", Settings::axesLineWidth());
0067     tag.setAttribute("tic-width", Settings::ticWidth());
0068     tag.setAttribute("tic-legth", Settings::ticLength());
0069 
0070     addTag(doc, tag, "show-axes", Settings::showAxes() ? "1" : "-1");
0071     addTag(doc, tag, "show-arrows", Settings::showArrows() ? "1" : "-1");
0072     addTag(doc, tag, "show-label", Settings::showLabel() ? "1" : "-1");
0073 
0074     addTag(doc, tag, "xmin", Settings::xMin());
0075     addTag(doc, tag, "xmax", Settings::xMax());
0076 
0077     addTag(doc, tag, "ymin", Settings::yMin());
0078     addTag(doc, tag, "ymax", Settings::yMax());
0079 
0080     root.appendChild(tag);
0081 
0082     tag = doc.createElement("grid");
0083 
0084     tag.setAttribute("color", Settings::gridColor().name());
0085     tag.setAttribute("width", Settings::gridLineWidth());
0086 
0087     addTag(doc, tag, "mode", QString::number(Settings::gridStyle()));
0088 
0089     root.appendChild(tag);
0090 
0091     tag = doc.createElement("scale");
0092 
0093     addTag(doc, tag, "tic-x-mode", QString::number(Settings::xScalingMode()));
0094     addTag(doc, tag, "tic-y-mode", QString::number(Settings::yScalingMode()));
0095     addTag(doc, tag, "tic-x", Settings::xScaling());
0096     addTag(doc, tag, "tic-y", Settings::yScaling());
0097 
0098     root.appendChild(tag);
0099 
0100     for (Function *f : qAsConst(XParser::self()->m_ufkt))
0101         addFunction(doc, root, f);
0102 
0103     addConstants(doc, root);
0104 
0105     tag = doc.createElement("fonts");
0106     addTag(doc, tag, "axes-font", Settings::axesFont().family());
0107     addTag(doc, tag, "label-font", Settings::labelFont().family());
0108     addTag(doc, tag, "header-table-font", Settings::headerTableFont().family());
0109     root.appendChild(tag);
0110 
0111     return doc;
0112 }
0113 
0114 bool KmPlotIO::save(const QUrl &url)
0115 {
0116     QDomDocument doc = currentState();
0117 
0118     if (!url.isLocalFile()) {
0119         QTemporaryFile tmpfile;
0120         if (!tmpfile.open()) {
0121             qWarning() << "Could not open " << QUrl(tmpfile.fileName()).toLocalFile() << " for writing.\n";
0122             return false;
0123         }
0124         QTextStream ts(&tmpfile);
0125         doc.save(ts, 4);
0126         ts.flush();
0127 
0128         Q_CONSTEXPR int permission = -1;
0129         QFile file(tmpfile.fileName());
0130         file.open(QIODevice::ReadOnly);
0131         KIO::StoredTransferJob *putjob = KIO::storedPut(file.readAll(), url, permission, KIO::JobFlag::Overwrite);
0132         if (!putjob->exec()) {
0133             qWarning() << "Could not open " << url.toString() << " for writing (" << putjob->errorString() << ").\n";
0134             return false;
0135         }
0136         file.close();
0137     } else {
0138         QFile xmlfile(url.toLocalFile());
0139         if (!xmlfile.open(QIODevice::WriteOnly)) {
0140             qWarning() << "Could not open " << url.path() << " for writing.\n";
0141             return false;
0142         }
0143         QTextStream ts(&xmlfile);
0144         doc.save(ts, 4);
0145         xmlfile.close();
0146         return true;
0147     }
0148     return true;
0149 }
0150 
0151 void KmPlotIO::addConstants(QDomDocument &doc, QDomElement &root)
0152 {
0153     ConstantList constants = XParser::self()->constants()->list(Constant::Document);
0154 
0155     for (ConstantList::iterator it = constants.begin(); it != constants.end(); ++it) {
0156         QDomElement tag = doc.createElement("constant");
0157         root.appendChild(tag);
0158         tag.setAttribute("name", it.key());
0159         tag.setAttribute("value", it.value().value.expression());
0160     }
0161 }
0162 
0163 void KmPlotIO::addFunction(QDomDocument &doc, QDomElement &root, Function *function)
0164 {
0165     QDomElement tag = doc.createElement("function");
0166 
0167     QString names[] = {"f0", "f1", "f2", "integral"};
0168     PlotAppearance *plots[] = {&function->plotAppearance(Function::Derivative0),
0169                                &function->plotAppearance(Function::Derivative1),
0170                                &function->plotAppearance(Function::Derivative2),
0171                                &function->plotAppearance(Function::Integral)};
0172 
0173     for (int i = 0; i < 4; ++i) {
0174         tag.setAttribute(QString("%1-width").arg(names[i]), plots[i]->lineWidth);
0175         tag.setAttribute(QString("%1-color").arg(names[i]), plots[i]->color.name());
0176         tag.setAttribute(QString("%1-use-gradient").arg(names[i]), plots[i]->useGradient);
0177         tag.setAttribute(QString("%1-gradient").arg(names[i]), gradientToString(plots[i]->gradient.stops()));
0178         tag.setAttribute(QString("%1-show-tangent-field").arg(names[i]), plots[i]->showTangentField);
0179         tag.setAttribute(QString("%1-visible").arg(names[i]), plots[i]->visible);
0180         tag.setAttribute(QString("%1-style").arg(names[i]), PlotAppearance::penStyleToString(plots[i]->style));
0181         tag.setAttribute(QString("%1-show-extrema").arg(names[i]), plots[i]->showExtrema);
0182         tag.setAttribute(QString("%1-show-plot-name").arg(names[i]), plots[i]->showPlotName);
0183     }
0184 
0185     // BEGIN parameters
0186     tag.setAttribute("use-parameter-slider", function->m_parameters.useSlider);
0187     tag.setAttribute("parameter-slider", function->m_parameters.sliderID);
0188 
0189     tag.setAttribute("use-parameter-list", function->m_parameters.useList);
0190     QStringList str_parameters;
0191     for (const Value &k : qAsConst(function->m_parameters.list))
0192         str_parameters << k.expression();
0193 
0194     if (!str_parameters.isEmpty())
0195         addTag(doc, tag, "parameter-list", str_parameters.join(";"));
0196     // END parameters
0197 
0198     tag.setAttribute("type", Function::typeToString(function->type()));
0199     for (int i = 0; i < function->eq.size(); ++i) {
0200         Equation *equation = function->eq[i];
0201 
0202         QString fstr = equation->fstr();
0203         if (fstr.isEmpty())
0204             continue;
0205         QDomElement element = addTag(doc, tag, QString("equation-%1").arg(i), fstr);
0206         element.setAttribute("step", equation->differentialStates.step().expression());
0207 
0208         for (int i = 0; i < equation->differentialStates.size(); ++i) {
0209             DifferentialState *state = &equation->differentialStates[i];
0210 
0211             QDomElement differential = doc.createElement("differential");
0212             element.appendChild(differential);
0213 
0214             bool first = true;
0215             QString ys;
0216             for (const Value &y : qAsConst(state->y0)) {
0217                 if (!first)
0218                     ys += ';';
0219                 first = false;
0220                 ys += y.expression();
0221             }
0222 
0223             differential.setAttribute("x", state->x0.expression());
0224             differential.setAttribute("y", ys);
0225         }
0226     }
0227 
0228     addTag(doc, tag, "arg-min", function->dmin.expression()).setAttribute("use", function->usecustomxmin);
0229     addTag(doc, tag, "arg-max", function->dmax.expression()).setAttribute("use", function->usecustomxmax);
0230 
0231     root.appendChild(tag);
0232 }
0233 
0234 QDomElement KmPlotIO::addTag(QDomDocument &doc, QDomElement &parentTag, const QString &tagName, const QString &tagValue)
0235 {
0236     QDomElement tag = doc.createElement(tagName);
0237     QDomText value = doc.createTextNode(tagValue);
0238     tag.appendChild(value);
0239     parentTag.appendChild(tag);
0240     return tag;
0241 }
0242 
0243 bool KmPlotIO::restore(const QDomDocument &doc)
0244 {
0245     // temporary measure: for now, delete all previous functions
0246     XParser::self()->removeAllFunctions();
0247 
0248     QDomElement element = doc.documentElement();
0249     QString versionString = element.attribute("version");
0250     if (versionString.isNull()) // an old kmplot-file
0251     {
0252         MainDlg::oldfileversion = true;
0253         for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) {
0254             version = 0;
0255             lengthScaler = 0.1;
0256 
0257             if (n.nodeName() == "axes")
0258                 parseAxes(n.toElement());
0259             if (n.nodeName() == "grid")
0260                 parseGrid(n.toElement());
0261             if (n.nodeName() == "scale")
0262                 parseScale(n.toElement());
0263             if (n.nodeName() == "function")
0264                 oldParseFunction(n.toElement());
0265         }
0266     } else if (versionString == "1" || versionString == "2" || versionString == "3" || versionString == "4") {
0267         MainDlg::oldfileversion = false;
0268         version = versionString.toInt();
0269         lengthScaler = (version < 3) ? 0.1 : 1.0;
0270 
0271         for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) {
0272             if (n.nodeName() == "axes")
0273                 parseAxes(n.toElement());
0274             else if (n.nodeName() == "grid")
0275                 parseGrid(n.toElement());
0276             else if (n.nodeName() == "scale")
0277                 parseScale(n.toElement());
0278             else if (n.nodeName() == "constant")
0279                 parseConstant(n.toElement());
0280             else if (n.nodeName() == "function") {
0281                 if (version < 3)
0282                     oldParseFunction2(n.toElement());
0283                 else
0284                     parseFunction(n.toElement());
0285             }
0286         }
0287     } else {
0288         KMessageBox::error(nullptr, i18n("The file had an unknown version number"));
0289         return false;
0290     }
0291 
0292     // Because we may not have loaded the constants / functions in the right order
0293     // to account for dependencies
0294     XParser::self()->reparseAllFunctions();
0295 
0296     return true;
0297 }
0298 
0299 bool KmPlotIO::load(const QUrl &url)
0300 {
0301     QDomDocument doc("kmpdoc");
0302     QFile f;
0303     bool downloadedFile = false;
0304     if (!url.isLocalFile()) {
0305         if (!MainDlg::fileExists(url)) {
0306             KMessageBox::error(nullptr, i18n("The file does not exist."));
0307             return false;
0308         }
0309         downloadedFile = true;
0310         KIO::StoredTransferJob *transferjob = KIO::storedGet(url);
0311         KJobWidgets::setWindow(transferjob, nullptr);
0312         if (!transferjob->exec()) {
0313             KMessageBox::error(nullptr, i18n("An error appeared when opening this file (%1)", transferjob->errorString()));
0314             return false;
0315         }
0316         QTemporaryFile file;
0317         file.setAutoRemove(false);
0318         file.open();
0319         file.write(transferjob->data());
0320         f.setFileName(file.fileName());
0321         file.close();
0322     } else
0323         f.setFileName(url.toLocalFile());
0324 
0325     if (!f.open(QIODevice::ReadOnly)) {
0326         KMessageBox::error(nullptr, i18n("%1 could not be opened", f.fileName()));
0327         return false;
0328     }
0329     QString errorMessage;
0330     int errorLine, errorColumn;
0331     if (!doc.setContent(&f, &errorMessage, &errorLine, &errorColumn)) {
0332         KMessageBox::error(nullptr, i18n("%1 could not be loaded (%2 at line %3, column %4)", f.fileName(), errorMessage, errorLine, errorColumn));
0333         f.close();
0334         return false;
0335     }
0336     f.close();
0337 
0338     if (!restore(doc))
0339         return false;
0340 
0341     if (downloadedFile)
0342         QFile::remove(f.fileName());
0343     return true;
0344 }
0345 
0346 void KmPlotIO::parseConstant(const QDomElement &n)
0347 {
0348     QString name = n.attribute("name");
0349     QString value = n.attribute("value");
0350 
0351     /// \todo how to handle overwriting constants, etc?
0352     Constant c;
0353     c.value.updateExpression(value);
0354     c.type = Constant::Document;
0355 
0356     if (XParser::self()->constants()->list(Constant::Global).contains(name))
0357         c.type |= Constant::Global;
0358 
0359     XParser::self()->constants()->add(name, c);
0360 }
0361 
0362 void KmPlotIO::parseAxes(const QDomElement &n)
0363 {
0364     Settings::setAxesLineWidth(n.attribute("width", (version < 3) ? "2" : "0.2").toDouble() * lengthScaler);
0365     Settings::setAxesColor(QColor(n.attribute("color", "#000000")));
0366     Settings::setTicWidth(n.attribute("tic-width", (version < 3) ? "3" : "0.3").toDouble() * lengthScaler);
0367     Settings::setTicLength(n.attribute("tic-length", (version < 3) ? "5" : "0.5").toDouble() * lengthScaler);
0368 
0369     if (version < 1) {
0370         Settings::setShowAxes(true);
0371         Settings::setShowArrows(true);
0372         Settings::setShowLabel(true);
0373     } else {
0374         Settings::setShowAxes(n.namedItem("show-axes").toElement().text().toInt() == 1);
0375         Settings::setShowArrows(n.namedItem("show-arrows").toElement().text().toInt() == 1);
0376         Settings::setShowLabel(n.namedItem("show-label").toElement().text().toInt() == 1);
0377     }
0378 
0379     Settings::setXMin(n.namedItem("xmin").toElement().text());
0380     Settings::setXMax(n.namedItem("xmax").toElement().text());
0381     Settings::setYMin(n.namedItem("ymin").toElement().text());
0382     Settings::setYMax(n.namedItem("ymax").toElement().text());
0383 }
0384 
0385 void KmPlotIO::parseGrid(const QDomElement &n)
0386 {
0387     Settings::setGridColor(QColor(n.attribute("color", "#c0c0c0")));
0388     Settings::setGridLineWidth(n.attribute("width", (version < 3) ? "1" : "0.1").toDouble() * lengthScaler);
0389 
0390     Settings::setGridStyle(n.namedItem("mode").toElement().text().toInt());
0391 }
0392 
0393 int unit2index(const QString &unit)
0394 {
0395     QString units[9] = {"10", "5", "2", "1", "0.5", "pi/2", "pi/3", "pi/4", i18n("automatic")};
0396     int index = 0;
0397     while ((index < 9) && (unit != units[index]))
0398         index++;
0399     if (index == 9)
0400         index = -1;
0401     return index;
0402 }
0403 
0404 void KmPlotIO::parseScale(const QDomElement &n)
0405 {
0406 #if 0
0407     if ( version < 1 )
0408     {
0409         Settings::setXScaling( unit2index( n.namedItem( "tic-x" ).toElement().text() ) );
0410         Settings::setYScaling( unit2index( n.namedItem( "tic-y" ).toElement().text() ) );
0411         Settings::setXPrinting( unit2index( n.namedItem( "print-tic-x" ).toElement().text() ) );
0412         Settings::setYPrinting( unit2index( n.namedItem( "print-tic-y" ).toElement().text() ) );
0413     }
0414     else
0415     {
0416         Settings::setXScaling( n.namedItem( "tic-x" ).toElement().text().toInt() );
0417         Settings::setYScaling( n.namedItem( "tic-y" ).toElement().text().toInt() );
0418         Settings::setXPrinting( n.namedItem( "print-tic-x" ).toElement().text().toInt()  );
0419         Settings::setYPrinting( n.namedItem( "print-tic-y" ).toElement().text().toInt() );
0420     }
0421 #endif
0422 
0423     if (version >= 4) {
0424         Settings::setXScalingMode(n.namedItem("tic-x-mode").toElement().text().toInt());
0425         Settings::setYScalingMode(n.namedItem("tic-y-mode").toElement().text().toInt());
0426         Settings::setXScaling(n.namedItem("tic-x").toElement().text());
0427         Settings::setYScaling(n.namedItem("tic-y").toElement().text());
0428     }
0429 }
0430 
0431 void KmPlotIO::parseFunction(const QDomElement &n, bool allowRename)
0432 {
0433     QDomElement equation0 = n.namedItem("equation-0").toElement();
0434     QDomElement equation1 = n.namedItem("equation-1").toElement();
0435 
0436     QString eq0 = equation0.text();
0437     QString eq1 = equation1.text();
0438 
0439     Function::Type type = Function::stringToType(n.attribute("type"));
0440 
0441     if (allowRename) {
0442         switch (type) {
0443         case Function::Polar:
0444             XParser::self()->fixFunctionName(eq0, Equation::Polar, -1);
0445             break;
0446 
0447         case Function::Parametric:
0448             XParser::self()->fixFunctionName(eq0, Equation::ParametricX, -1);
0449             XParser::self()->fixFunctionName(eq1, Equation::ParametricY, -1);
0450             break;
0451 
0452         case Function::Cartesian:
0453             XParser::self()->fixFunctionName(eq0, Equation::Cartesian, -1);
0454             break;
0455 
0456         case Function::Implicit:
0457             XParser::self()->fixFunctionName(eq0, Equation::Implicit, -1);
0458             break;
0459 
0460         case Function::Differential:
0461             XParser::self()->fixFunctionName(eq0, Equation::Differential, -1);
0462             break;
0463         }
0464     }
0465 
0466     int functionID = XParser::self()->Parser::addFunction(eq0, eq1, type, true);
0467     if (functionID == -1) {
0468         qWarning() << "Could not create function!\n";
0469         return;
0470     }
0471     Function *function = XParser::self()->functionWithID(functionID);
0472 
0473     parseDifferentialStates(equation0, function->eq[0]);
0474     if (function->eq.size() > 1)
0475         parseDifferentialStates(equation1, function->eq[1]);
0476 
0477     PlotAppearance *plots[] = {&function->plotAppearance(Function::Derivative0),
0478                                &function->plotAppearance(Function::Derivative1),
0479                                &function->plotAppearance(Function::Derivative2),
0480                                &function->plotAppearance(Function::Integral)};
0481 
0482     QString names[] = {"f0", "f1", "f2", "integral"};
0483 
0484     for (int i = 0; i < 4; ++i) {
0485         plots[i]->lineWidth = n.attribute(QString("%1-width").arg(names[i])).toDouble() * lengthScaler;
0486         plots[i]->color = n.attribute(QString("%1-color").arg(names[i]));
0487         plots[i]->useGradient = n.attribute(QString("%1-use-gradient").arg(names[i])).toInt();
0488         plots[i]->gradient.setStops(stringToGradient(n.attribute(QString("%1-gradient").arg(names[i]))));
0489         plots[i]->visible = n.attribute(QString("%1-visible").arg(names[i])).toInt();
0490         plots[i]->style = PlotAppearance::stringToPenStyle(n.attribute(QString("%1-style").arg(names[i])));
0491         plots[i]->showExtrema = n.attribute(QString("%1-show-extrema").arg(names[i])).toInt();
0492         plots[i]->showTangentField = n.attribute(QString("%1-show-tangent-field").arg(names[i])).toInt();
0493         plots[i]->showPlotName = n.attribute(QString("%1-show-plot-name").arg(names[i])).toInt();
0494     }
0495 
0496     // BEGIN parameters
0497     parseParameters(n, function);
0498 
0499     function->m_parameters.useSlider = n.attribute("use-parameter-slider").toInt();
0500     function->m_parameters.sliderID = n.attribute("parameter-slider").toInt();
0501     function->m_parameters.useList = n.attribute("use-parameter-list").toInt();
0502     // END parameters
0503 
0504     QDomElement minElement = n.namedItem("arg-min").toElement();
0505     QString expression = minElement.text();
0506     if (expression.isEmpty())
0507         function->usecustomxmin = false;
0508     else {
0509         function->dmin.updateExpression(expression);
0510         function->usecustomxmin = minElement.attribute("use", "1").toInt();
0511     }
0512 
0513     QDomElement maxElement = n.namedItem("arg-max").toElement();
0514     expression = maxElement.text();
0515     if (expression.isEmpty())
0516         function->usecustomxmax = false;
0517     else {
0518         function->dmax.updateExpression(expression);
0519         function->usecustomxmax = maxElement.attribute("use", "1").toInt();
0520     }
0521 }
0522 
0523 void KmPlotIO::parseParameters(const QDomElement &n, Function *function)
0524 {
0525     QChar separator = (version < 1) ? ',' : ';';
0526     QString tagName = (version < 4) ? "parameterlist" : "parameter-list";
0527 
0528     const QStringList str_parameters = n.namedItem(tagName).toElement().text().split(separator, Qt::SkipEmptyParts);
0529     for (QStringList::const_iterator it = str_parameters.constBegin(); it != str_parameters.constEnd(); ++it)
0530         function->m_parameters.list.append(Value(*it));
0531 }
0532 
0533 void KmPlotIO::parseDifferentialStates(const QDomElement &n, Equation *equation)
0534 {
0535     equation->differentialStates.setStep(n.attribute("step"));
0536 
0537     QDomNode node = n.firstChild();
0538 
0539     while (!node.isNull()) {
0540         if (node.isElement()) {
0541             QDomElement e = node.toElement();
0542 
0543             QString x = e.attribute("x");
0544             const QStringList y = e.attribute("y").split(';');
0545 
0546             DifferentialState *state = equation->differentialStates.add();
0547             if (state->y0.size() != y.size()) {
0548                 qWarning() << "Invalid y count!\n";
0549                 return;
0550             }
0551 
0552             state->x0.updateExpression(x);
0553 
0554             int at = 0;
0555             for (const QString &f : y)
0556                 state->y0[at++] = f;
0557         }
0558         node = node.nextSibling();
0559     }
0560 }
0561 
0562 void KmPlotIO::oldParseFunction2(const QDomElement &n)
0563 {
0564     Function::Type type;
0565     QString eq0, eq1;
0566 
0567     eq0 = n.namedItem("equation").toElement().text();
0568 
0569     switch (eq0[0].unicode()) {
0570     case 'r':
0571         type = Function::Polar;
0572         break;
0573 
0574     case 'x':
0575         parametricXEquation = eq0;
0576         return;
0577 
0578     case 'y':
0579         type = Function::Parametric;
0580         eq1 = eq0;
0581         eq0 = parametricXEquation;
0582         break;
0583 
0584     default:
0585         type = Function::Cartesian;
0586         break;
0587     }
0588 
0589     Function ufkt(type);
0590     ufkt.eq[0]->setFstr(eq0, nullptr, nullptr, true);
0591     if (!eq1.isEmpty())
0592         ufkt.eq[1]->setFstr(eq1, nullptr, nullptr, true);
0593 
0594     PlotAppearance *plots[] = {&ufkt.plotAppearance(Function::Derivative0),
0595                                &ufkt.plotAppearance(Function::Derivative1),
0596                                &ufkt.plotAppearance(Function::Derivative2),
0597                                &ufkt.plotAppearance(Function::Integral)};
0598 
0599     plots[0]->visible = n.attribute("visible").toInt();
0600     plots[0]->color = QColor(n.attribute("color"));
0601     plots[0]->lineWidth = n.attribute("width").toDouble() * lengthScaler;
0602 
0603     plots[1]->visible = n.attribute("visible-deriv", "0").toInt();
0604     plots[1]->color = QColor(n.attribute("deriv-color"));
0605     plots[1]->lineWidth = n.attribute("deriv-width").toDouble() * lengthScaler;
0606 
0607     plots[2]->visible = n.attribute("visible-2nd-deriv", "0").toInt();
0608     plots[2]->color = QColor(n.attribute("deriv2nd-color"));
0609     plots[2]->lineWidth = n.attribute("deriv2nd-width").toDouble() * lengthScaler;
0610 
0611     plots[3]->visible = n.attribute("visible-integral", "0").toInt();
0612     plots[3]->color = QColor(n.attribute("integral-color"));
0613     plots[3]->lineWidth = n.attribute("integral-width").toDouble() * lengthScaler;
0614 
0615     // BEGIN parameters
0616     parseParameters(n, &ufkt);
0617 
0618     int use_slider = n.attribute("use-slider").toInt();
0619     ufkt.m_parameters.useSlider = (use_slider >= 0);
0620     ufkt.m_parameters.sliderID = use_slider;
0621 
0622     ufkt.m_parameters.useList = !ufkt.m_parameters.list.isEmpty();
0623     // END parameters
0624 
0625     if (type == Function::Cartesian) {
0626         DifferentialState *state = &ufkt.eq[0]->differentialStates[0];
0627         state->x0.updateExpression(n.attribute("integral-startx"));
0628         state->y0[0].updateExpression(n.attribute("integral-starty"));
0629     }
0630 
0631     QDomElement minElement = n.namedItem("arg-min").toElement();
0632     QString expression = minElement.text();
0633     if (expression.isEmpty())
0634         ufkt.usecustomxmin = false;
0635     else {
0636         ufkt.dmin.updateExpression(expression);
0637         ufkt.usecustomxmin = minElement.attribute("use", "1").toInt();
0638     }
0639 
0640     QDomElement maxElement = n.namedItem("arg-max").toElement();
0641     expression = maxElement.text();
0642     if (expression.isEmpty())
0643         ufkt.usecustomxmax = false;
0644     else {
0645         ufkt.dmax.updateExpression(expression);
0646         ufkt.usecustomxmax = maxElement.attribute("use", "1").toInt();
0647     }
0648 
0649     QString fstr = ufkt.eq[0]->fstr();
0650     if (!fstr.isEmpty()) {
0651         int const i = fstr.indexOf(';');
0652         QString str;
0653         if (i == -1)
0654             str = fstr;
0655         else
0656             str = fstr.left(i);
0657 
0658         int id = XParser::self()->Parser::addFunction(str, eq1, type, true);
0659 
0660         Function *added_function = XParser::self()->m_ufkt[id];
0661         added_function->copyFrom(ufkt);
0662     }
0663 }
0664 
0665 void KmPlotIO::oldParseFunction(const QDomElement &n)
0666 {
0667     QString tmp_fstr = n.namedItem("equation").toElement().text();
0668     if (tmp_fstr.isEmpty()) {
0669         qWarning() << "tmp_fstr is empty!\n";
0670         return;
0671     }
0672 
0673     Function::Type type;
0674     switch (tmp_fstr[0].unicode()) {
0675     case 'r':
0676         type = Function::Polar;
0677         break;
0678 
0679     case 'x':
0680         parametricXEquation = tmp_fstr;
0681         return;
0682 
0683     case 'y':
0684         type = Function::Parametric;
0685         break;
0686 
0687     default:
0688         type = Function::Cartesian;
0689         break;
0690     }
0691 
0692     Function ufkt(type);
0693 
0694     ufkt.plotAppearance(Function::Derivative0).visible = n.attribute("visible").toInt();
0695     ufkt.plotAppearance(Function::Derivative1).visible = n.attribute("visible-deriv").toInt();
0696     ufkt.plotAppearance(Function::Derivative2).visible = n.attribute("visible-2nd-deriv").toInt();
0697     ufkt.plotAppearance(Function::Derivative0).lineWidth = n.attribute("width").toDouble() * lengthScaler;
0698     ufkt.plotAppearance(Function::Derivative0).color = ufkt.plotAppearance(Function::Derivative1).color = ufkt.plotAppearance(Function::Derivative2).color =
0699         ufkt.plotAppearance(Function::Integral).color = QColor(n.attribute("color"));
0700 
0701     QString expression = n.namedItem("arg-min").toElement().text();
0702     ufkt.dmin.updateExpression(expression);
0703     ufkt.usecustomxmin = !expression.isEmpty();
0704 
0705     expression = n.namedItem("arg-max").toElement().text();
0706     ufkt.dmax.updateExpression(expression);
0707     ufkt.usecustomxmax = !expression.isEmpty();
0708 
0709     if (ufkt.usecustomxmin && ufkt.usecustomxmax && ufkt.dmin.expression() == ufkt.dmax.expression()) {
0710         ufkt.usecustomxmin = false;
0711         ufkt.usecustomxmax = false;
0712     }
0713 
0714     const int pos = tmp_fstr.indexOf(';');
0715     if (pos == -1)
0716         ufkt.eq[0]->setFstr(tmp_fstr, nullptr, nullptr, true);
0717     else {
0718         ufkt.eq[0]->setFstr(tmp_fstr.left(pos), nullptr, nullptr, true);
0719         if (!XParser::self()->getext(&ufkt, tmp_fstr)) {
0720             KMessageBox::error(nullptr, i18n("The function %1 could not be loaded", ufkt.eq[0]->fstr()));
0721             return;
0722         }
0723     }
0724 
0725     QString fstr = ufkt.eq[0]->fstr();
0726     if (!fstr.isEmpty()) {
0727         int const i = fstr.indexOf(';');
0728         QString str;
0729         if (i == -1)
0730             str = fstr;
0731         else
0732             str = fstr.left(i);
0733 
0734         int id;
0735         if (type == Function::Parametric)
0736             id = XParser::self()->Parser::addFunction(str, parametricXEquation, type, true);
0737         else
0738             id = XParser::self()->Parser::addFunction(str, nullptr, type, true);
0739 
0740         Function *added_function = XParser::self()->m_ufkt[id];
0741         added_function->copyFrom(ufkt);
0742     }
0743 }
0744 
0745 // static
0746 QString KmPlotIO::gradientToString(const QGradientStops &stops)
0747 {
0748     QString string;
0749     for (const QGradientStop &stop : qAsConst(stops))
0750         string += QString("%1;%2,").arg(stop.first).arg(stop.second.name());
0751     return string;
0752 }
0753 
0754 // static
0755 QGradientStops KmPlotIO::stringToGradient(const QString &string)
0756 {
0757     const QStringList stopStrings = string.split(',', Qt::SkipEmptyParts);
0758 
0759     QGradientStops stops;
0760     for (const QString &stopString : stopStrings) {
0761         QString pos = stopString.section(';', 0, 0);
0762         QString color = stopString.section(';', 1, 1);
0763 
0764         QGradientStop stop;
0765         stop.first = pos.toDouble();
0766         stop.second = color;
0767         stops << stop;
0768     }
0769 
0770     return stops;
0771 }