File indexing completed on 2024-04-21 11:13:56
0001 /* 0002 SPDX-FileCopyrightText: 2010 Miha Čančula <miha.cancula@gmail.com> 0003 SPDX-FileCopyrightText: 2018-2022 by Alexander Semke (alexander.semke@web.de) 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "octaveexpression.h" 0009 #include "octavesession.h" 0010 #include "defaultvariablemodel.h" 0011 #include <helpresult.h> 0012 #include "imageresult.h" 0013 #include "settings.h" 0014 #include "textresult.h" 0015 0016 #include <QApplication> 0017 #include <QDesktopWidget> 0018 #include <QDebug> 0019 #include <QFile> 0020 #include <QDir> 0021 #include <QFileSystemWatcher> 0022 #include <QRegularExpression> 0023 0024 #include <KLocalizedString> 0025 0026 static const QString printCommandTemplate = QString::fromLatin1("print(\"%1\", \"-S%2,%3\")"); 0027 0028 static const QStringList plotCommands({ 0029 QLatin1String("plot"), QLatin1String("semilogx"), QLatin1String("semilogy"), 0030 QLatin1String("loglog"), QLatin1String("polar"), QLatin1String("contour"), 0031 QLatin1String("bar"), QLatin1String("stairs"), QLatin1String("errorbar"), 0032 QLatin1String("sombrero"), QLatin1String("hist"), QLatin1String("fplot"), 0033 QLatin1String("imshow"), QLatin1String("stem"), QLatin1String("stem3"), 0034 QLatin1String("scatter"), QLatin1String("pareto"), QLatin1String("rose"), 0035 QLatin1String("pie"), QLatin1String("quiver"), QLatin1String("compass"), 0036 QLatin1String("feather"), QLatin1String("pcolor"), QLatin1String("area"), 0037 QLatin1String("fill"), QLatin1String("comet"), QLatin1String("plotmatrix"), 0038 /* 3d-plots */ 0039 QLatin1String("plot3"), QLatin1String("mesh"), QLatin1String("meshc"), 0040 QLatin1String("meshz"), QLatin1String("surf"), QLatin1String("surfc"), 0041 QLatin1String("surfl"), QLatin1String("surfnorm"), QLatin1String("isosurface"), 0042 QLatin1String("isonormals"), QLatin1String("isocaps"), 0043 /* 3d-plots defined by a function */ 0044 QLatin1String("ezplot3"), QLatin1String("ezmesh"), QLatin1String("ezmeshc"), 0045 QLatin1String("ezsurf"), QLatin1String("ezsurfc"), QLatin1String("cantor_plot2d"), 0046 QLatin1String("cantor_plot3d")}); 0047 0048 const QStringList OctaveExpression::plotExtensions({ 0049 QLatin1String("pdf"), 0050 QLatin1String("svg"), 0051 QLatin1String("png") 0052 }); 0053 0054 OctaveExpression::OctaveExpression(Cantor::Session* session, bool internal): Expression(session, internal) 0055 { 0056 } 0057 0058 OctaveExpression::~OctaveExpression() 0059 { 0060 } 0061 0062 void OctaveExpression::evaluate() 0063 { 0064 m_plotFilename.clear(); 0065 0066 m_finished = false; 0067 m_plotPending = false; 0068 0069 session()->enqueueExpression(this); 0070 } 0071 0072 QString OctaveExpression::internalCommand() 0073 { 0074 QString cmd = command(); 0075 auto* octaveSession = static_cast<OctaveSession*>(session()); 0076 0077 if (!isInternal()) 0078 { 0079 QStringList cmdWords = cmd.split(QRegularExpression(QStringLiteral("\\b")), QString::SkipEmptyParts); 0080 if (!cmdWords.contains(QLatin1String("help")) && !cmdWords.contains(QLatin1String("completion_matches"))) 0081 { 0082 for (const QString& plotCmd : plotCommands) 0083 { 0084 if (!cmdWords.contains(plotCmd)) 0085 continue; 0086 0087 if (octaveSession->isIntegratedPlotsEnabled()) 0088 { 0089 if (!cmd.endsWith(QLatin1Char(';')) && !cmd.endsWith(QLatin1Char(','))) 0090 cmd += QLatin1Char(','); 0091 0092 m_plotFilename = octaveSession->plotFilePrefixPath() + QString::number(id()) + QLatin1String(".") + plotExtensions[OctaveSettings::inlinePlotFormat()]; 0093 0094 int w, h; 0095 if (OctaveSettings::inlinePlotFormat() == 0 || OctaveSettings::inlinePlotFormat() == 1) // for vector formats like PDF and SVG the size for 'print' is provided in points 0096 { 0097 w = OctaveSettings::plotWidth() / 2.54 * 72; 0098 h = OctaveSettings::plotHeight() / 2.54 * 72; 0099 } 0100 else // for raster formats the size for 'print' is provided in pixels 0101 { 0102 w = OctaveSettings::plotWidth() / 2.54 * QApplication::desktop()->physicalDpiX(); 0103 h = OctaveSettings::plotHeight() / 2.54 * QApplication::desktop()->physicalDpiX(); 0104 } 0105 cmd += printCommandTemplate.arg(m_plotFilename, QString::number(w), QString::number(h)); 0106 0107 auto* watcher = fileWatcher(); 0108 if (!watcher->files().isEmpty()) 0109 watcher->removePaths(watcher->files()); 0110 0111 // add path works only for existing paths, so create the file 0112 QFile file(m_plotFilename); 0113 if (file.open(QFile::WriteOnly)) 0114 { 0115 file.close(); 0116 watcher->addPath(m_plotFilename); 0117 m_plotPending = true; 0118 connect(watcher, &QFileSystemWatcher::fileChanged, this, &OctaveExpression::imageChanged, Qt::UniqueConnection); 0119 } 0120 0121 cmd = QLatin1String("figure('visible','off');") + cmd; 0122 } 0123 else 0124 cmd = QLatin1String("figure('visible','on');") + cmd; 0125 0126 break; 0127 } 0128 } 0129 } 0130 0131 // We need to remove all comments here, because below we merge all strings to one long string 0132 // Otherwise, all code after line with comment will be commented out after merging 0133 // So, this small state machine removes all comments. 0134 // status: 0135 // 0 - command mode 0136 // 1 - string mode for ' 0137 // 2 - string mode for " 0138 // 3 - comment mode 0139 // 4 - comment character after ' 0140 0141 QString tmpCmd; 0142 QString tmpComment; 0143 int status = 0; 0144 for (int i = 0; i < cmd.size(); i++) 0145 { 0146 const char ch = cmd[i].toLatin1(); 0147 if (status == 0 && (ch == '#' || ch == '%')) 0148 status = 3; 0149 else if (status == 0 && ch == '\'') 0150 status = 1; 0151 else if (status == 4 && ch == '\'') 0152 { 0153 tmpCmd += tmpComment; 0154 tmpComment.clear(); 0155 status = 0; 0156 } 0157 else if (status == 0 && ch == '"') 0158 status = 2; 0159 else if (status == 1 && ch == '\'') 0160 status = 0; 0161 else if (status == 1 && (ch == '#' || ch == '%')) 0162 status = 4; 0163 else if (status == 2 && ch == '"') 0164 status = 0; 0165 else if (ch == '\n') 0166 { 0167 tmpComment.clear(); 0168 status = 0; 0169 } 0170 0171 if (status == 4) 0172 tmpComment += cmd[i]; 0173 else if (status != 3) 0174 tmpCmd += cmd[i]; 0175 } 0176 0177 // Remove "\n" in the beginning of the command, if present 0178 while(tmpCmd[0] == QLatin1Char('\n')) 0179 tmpCmd.remove(0, 1); 0180 0181 cmd = tmpCmd; 0182 cmd.replace(QLatin1String(";\n"), QLatin1String(";")); 0183 cmd.replace(QLatin1Char('\n'), QLatin1Char(',')); 0184 cmd += QLatin1Char('\n'); 0185 0186 return cmd; 0187 } 0188 0189 void OctaveExpression::parseOutput(const QString& output) 0190 { 0191 if (output.size() > 200) 0192 qDebug() << "parseOutput: " << output.left(200) << "..."; 0193 else 0194 qDebug() << "parseOutput: " << output; 0195 0196 if (!output.trimmed().isEmpty()) 0197 { 0198 // TODO: what about help in comment? printf with '... help ...'? 0199 // This must be corrected. 0200 if (command().contains(QLatin1String("help"))) 0201 addResult(new Cantor::HelpResult(output)); 0202 else 0203 addResult(new Cantor::TextResult(output)); 0204 } 0205 0206 m_finished = true; 0207 if (!m_plotPending) 0208 setStatus(Done); 0209 } 0210 0211 void OctaveExpression::parseError(const QString& error) 0212 { 0213 if (error.startsWith(QLatin1String("warning: "))) 0214 { 0215 // It's warning, so add as result 0216 addResult(new Cantor::TextResult(error)); 0217 } 0218 else 0219 { 0220 setErrorMessage(error); 0221 setStatus(Error); 0222 } 0223 } 0224 0225 void OctaveExpression::imageChanged() 0226 { 0227 QFile file(m_plotFilename); 0228 if(!file.open(QIODevice::ReadOnly)/* || file.size() <= 0*/) 0229 { 0230 m_plotPending = false; 0231 setResult(new Cantor::TextResult(i18n("Invalid image file generated."))); 0232 setStatus(Error); 0233 return; 0234 } 0235 0236 const QUrl& url = QUrl::fromLocalFile(m_plotFilename); 0237 auto* newResult = new Cantor::ImageResult(url); 0238 0239 bool found = false; 0240 for (int i = 0; i < results().size(); i++) 0241 if (results()[i]->type() == newResult->type()) 0242 { 0243 replaceResult(i, newResult); 0244 found = true; 0245 } 0246 0247 if (!found) 0248 addResult(newResult); 0249 0250 m_plotPending = false; 0251 0252 if (m_finished && status() == Expression::Computing) 0253 setStatus(Done); 0254 }