File indexing completed on 2024-12-01 09:37:32

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2012 Filipe Saraiva <filipe@kde.org>
0004 */
0005 
0006 #include "pythonexpression.h"
0007 
0008 #include <config-cantorlib.h>
0009 
0010 #include "textresult.h"
0011 #include "imageresult.h"
0012 #include "helpresult.h"
0013 #include "session.h"
0014 #include "settings.h"
0015 
0016 #include <QDebug>
0017 #include <QDir>
0018 #include <QTemporaryFile>
0019 #include <QFileSystemWatcher>
0020 
0021 #include "pythonsession.h"
0022 
0023 PythonExpression::PythonExpression(Cantor::Session* session, bool internal) : Cantor::Expression(session, internal)
0024 {
0025 }
0026 
0027 PythonExpression::~PythonExpression() {
0028     delete m_tempFile;
0029 }
0030 
0031 void PythonExpression::evaluate()
0032 {
0033     if(m_tempFile) {
0034         delete m_tempFile;
0035         m_tempFile = nullptr;
0036     }
0037 
0038     session()->enqueueExpression(this);
0039 }
0040 
0041 QString PythonExpression::internalCommand()
0042 {
0043     QString cmd = command();
0044 
0045     // handle matplotlib's show() command
0046     if((PythonSettings::integratePlots()) && (command().contains(QLatin1String("show()"))))
0047     {
0048         QString extension;
0049         if (PythonSettings::inlinePlotFormat() == 0)
0050             extension = QLatin1String("pdf");
0051         else if (PythonSettings::inlinePlotFormat() == 1)
0052             extension = QLatin1String("svg");
0053         else if (PythonSettings::inlinePlotFormat() == 2)
0054             extension = QLatin1String("png");
0055 
0056         m_tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/cantor_python-XXXXXX.%1").arg(extension));
0057         m_tempFile->open();
0058         QString saveFigCommand = QLatin1String("savefig('%1')");
0059         cmd.replace(QLatin1String("show()"), saveFigCommand.arg(m_tempFile->fileName()));
0060 
0061         // set the plot size in inches
0062         // TODO: matplotlib is usually imported via "import matplotlib.pyplot as plt" and we set
0063         // plot size for plt below but it's still possible to name the module differently and we
0064         // somehow need to handle such cases, too.
0065         const double w = PythonSettings::plotWidth() / 2.54;
0066         const double h = PythonSettings::plotHeight() / 2.54;
0067         cmd += QLatin1String("\nplt.figure(figsize=(%1, %2))").arg(QString::number(w), QString::number(h));
0068 
0069         QFileSystemWatcher* watcher = fileWatcher();
0070         watcher->removePaths(watcher->files());
0071         watcher->addPath(m_tempFile->fileName());
0072         connect(watcher, &QFileSystemWatcher::fileChanged, this, &PythonExpression::imageChanged,  Qt::UniqueConnection);
0073     }
0074     // TODO: handle other plotting frameworks
0075 
0076     QStringList commandLine = cmd.split(QLatin1String("\n"));
0077     QString commandProcessing;
0078 
0079     for(const QString& command : commandLine)
0080     {
0081         const QString firstLineWord = command.trimmed().replace(QLatin1String("("), QLatin1String(" "))
0082             .split(QLatin1String(" ")).at(0);
0083 
0084         // Ignore comments
0085         if (firstLineWord.length() != 0 && firstLineWord[0] == QLatin1Char('#')){
0086             commandProcessing += command + QLatin1String("\n");
0087             continue;
0088         }
0089 
0090         if(firstLineWord.contains(QLatin1String("execfile"))){
0091             commandProcessing += command;
0092             continue;
0093         }
0094 
0095         commandProcessing += command + QLatin1String("\n");
0096     }
0097 
0098     return commandProcessing;
0099 }
0100 
0101 void PythonExpression::parseOutput(const QString& output)
0102 {
0103     qDebug() << "expression output: " << output;
0104     if(command().simplified().startsWith(QLatin1String("help(")))
0105     {
0106         QString resultStr = output;
0107         setResult(new Cantor::HelpResult(resultStr.remove(output.lastIndexOf(QLatin1String("None")), 4)));
0108     } else {
0109         if (!output.isEmpty())
0110             addResult(new Cantor::TextResult(output));
0111      }
0112 
0113     setStatus(Cantor::Expression::Done);
0114 }
0115 
0116 void PythonExpression::parseError(const QString& error)
0117 {
0118     qDebug() << "expression error: " << error;
0119     setErrorMessage(error);
0120 
0121     setStatus(Cantor::Expression::Error);
0122 }
0123 
0124 void PythonExpression::parseWarning(const QString& warning)
0125 {
0126     if (!warning.isEmpty())
0127     {
0128         auto* result = new Cantor::TextResult(warning);
0129         result->setStdErr(true);
0130         addResult(result);
0131     }
0132 }
0133 
0134 void PythonExpression::imageChanged()
0135 {
0136     if(m_tempFile->size() <= 0)
0137         return;
0138 
0139     auto* newResult = new Cantor::ImageResult(QUrl::fromLocalFile(m_tempFile->fileName()));
0140     if (result() == nullptr)
0141         setResult(newResult);
0142     else
0143     {
0144         bool found = false;
0145         for (int i = 0; i < results().size(); i++)
0146             if (results()[i]->type() == newResult->type())
0147             {
0148                 replaceResult(i, newResult);
0149                 found = true;
0150             }
0151         if (!found)
0152             addResult(newResult);
0153     }
0154     setStatus(Done);
0155 }