diff --git a/src/backends/octave/octaveexpression.cpp b/src/backends/octave/octaveexpression.cpp index b8a92b8d..5327be57 100644 --- a/src/backends/octave/octaveexpression.cpp +++ b/src/backends/octave/octaveexpression.cpp @@ -1,186 +1,223 @@ /* Copyright (C) 2010 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "octaveexpression.h" #include "octavesession.h" #include "defaultvariablemodel.h" #include "textresult.h" #include #include +#include +#include +#include #include -static const QLatin1String printCommand("cantor_print();"); +static const QLatin1String printCommandBegin("cantor_print('"); +static const QLatin1String printCommandEnd("');"); +static const QStringList plotCommands({ + QLatin1String("plot"), QLatin1String("semilogx"), QLatin1String("semilogy"), + QLatin1String("loglog"), QLatin1String("polar"), QLatin1String("contour"), + QLatin1String("bar"), QLatin1String("stairs"), QLatin1String("errorbar"), + QLatin1String("sombrero"), QLatin1String("hist"), QLatin1String("fplot"), + QLatin1String("imshow"), QLatin1String("stem"), QLatin1String("stem3"), + QLatin1String("scatter"), QLatin1String("pareto"), QLatin1String("rose"), + QLatin1String("pie"), QLatin1String("quiver"), QLatin1String("compass"), + QLatin1String("feather"), QLatin1String("pcolor"), QLatin1String("area"), + QLatin1String("fill"), QLatin1String("comet"), QLatin1String("plotmatrix"), + /* 3d-plots */ + QLatin1String("plot3"), QLatin1String("mesh"), QLatin1String("meshc"), + QLatin1String("meshz"), QLatin1String("surf"), QLatin1String("surfc"), + QLatin1String("surfl"), QLatin1String("surfnorm"), QLatin1String("isosurface"), + QLatin1String("isonormals"), QLatin1String("isocaps"), + /* 3d-plots defined by a function */ + QLatin1String("ezplot3"), QLatin1String("ezmesh"), QLatin1String("ezmeshc"), + QLatin1String("ezsurf"), QLatin1String("ezsurfc"), QLatin1String("cantor_plot2d"), + QLatin1String("cantor_plot3d")}); + +OctaveExpression::OctaveExpression(Cantor::Session* session, bool internal): Expression(session, internal) +{ +} -OctaveExpression::OctaveExpression(Cantor::Session* session, bool internal): Expression(session, internal), - m_plotPending(false), - m_finished(false) +OctaveExpression::~OctaveExpression() { - m_plotCommands << QLatin1String("plot") << QLatin1String("semilogx") << QLatin1String("semilogy") << QLatin1String("loglog") - << QLatin1String("polar") << QLatin1String("contour") << QLatin1String("bar") - << QLatin1String("stairs") << QLatin1String("errorbar") << QLatin1String("sombrero") - << QLatin1String("hist") << QLatin1String("fplot") << QLatin1String("imshow") - << QLatin1String("stem") << QLatin1String("stem3") << QLatin1String("scatter") << QLatin1String("pareto") << QLatin1String("rose") - << QLatin1String("pie") << QLatin1String("quiver") << QLatin1String("compass") << QLatin1String("feather") - << QLatin1String("pcolor") << QLatin1String("area") << QLatin1String("fill") << QLatin1String("comet") - << QLatin1String("plotmatrix") - /* 3d-plots */ - << QLatin1String("plot3") - << QLatin1String("mesh") << QLatin1String("meshc") << QLatin1String("meshz") - << QLatin1String("surf") << QLatin1String("surfc") << QLatin1String("surfl") << QLatin1String("surfnorm") - << QLatin1String("isosurface")<< QLatin1String("isonormals") << QLatin1String("isocaps") - /* 3d-plots defined by a function */ - << QLatin1String("ezplot3") << QLatin1String("ezmesh") << QLatin1String("ezmeshc") << QLatin1String("ezsurf") << QLatin1String("ezsurfc"); - m_plotCommands << QLatin1String("cantor_plot2d") << QLatin1String("cantor_plot3d"); + if(m_tempFile) { + delete m_tempFile; + m_tempFile = nullptr; + } } void OctaveExpression::interrupt() { qDebug() << "interrupt"; setStatus(Interrupted); } void OctaveExpression::evaluate() { + if(m_tempFile) { + delete m_tempFile; + m_tempFile = nullptr; + } + qDebug() << "evaluate"; QString cmd = command(); QStringList cmdWords = cmd.split(QRegExp(QLatin1String("\\b")), QString::SkipEmptyParts); if (!cmdWords.contains(QLatin1String("help")) && !cmdWords.contains(QLatin1String("completion_matches"))) { - foreach (const QString& plotCmd, m_plotCommands) + for (const QString& plotCmd : plotCommands) { if (cmdWords.contains(plotCmd)) { - setPlotPending(true); qDebug() << "Executing a plot command"; +#ifdef WITH_EPS + QLatin1String ext(".eps"); +#else + QLatin1String ext(".png"); +#endif + m_tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/cantor_octave-XXXXXX")+ext); + m_tempFile->open(); + + qDebug() << "plot temp file" << m_tempFile->fileName(); + + QFileSystemWatcher* watcher = fileWatcher(); + if (!watcher->files().isEmpty()) + watcher->removePaths(watcher->files()); + watcher->addPath(m_tempFile->fileName()); + + connect(watcher, &QFileSystemWatcher::fileChanged, this, &OctaveExpression::imageChanged, Qt::UniqueConnection); + + m_plotPending = true; break; } } } m_finished = false; session()->enqueueExpression(this); } QString OctaveExpression::internalCommand() { QString cmd = command(); - if (m_plotPending && !cmd.contains(QLatin1String("cantor_plot")) && !cmd.contains(printCommand)) + if (m_plotPending) { if (!cmd.endsWith(QLatin1Char(';')) && !cmd.endsWith(QLatin1Char(','))) cmd += QLatin1Char(','); - cmd += printCommand; + cmd += printCommandBegin + m_tempFile->fileName() + printCommandEnd; } // We need remove all comments here, because below we merge all strings to one long string // Otherwise, all code after line with comment will be commented out after merging // So, this small state machine remove all comments // FIXME better implementation QString tmp; // 0 - command mode, 1 - string mode for ', 2 - string mode for ", 3 - comment mode int status = 0; for (int i = 0; i < cmd.size(); i++) { const char ch = cmd[i].toLatin1(); if (status == 0 && (ch == '#' || ch == '%')) status = 3; else if (status == 0 && ch == '\'') status = 1; else if (status == 0 && ch == '"') status = 2; else if (status == 1 && ch == '\'') status = 0; else if (status == 2 && ch == '"') status = 0; else if (status == 3 && ch == '\n') status = 0; if (status != 3) tmp += cmd[i]; } cmd = tmp; cmd.replace(QLatin1String(";\n"), QLatin1String(";")); cmd.replace(QLatin1Char('\n'), QLatin1Char(',')); cmd += QLatin1Char('\n'); return cmd; } void OctaveExpression::parseOutput(const QString& output) { qDebug() << "parseOutput: " << output; if (!output.trimmed().isEmpty()) { // TODO: what about help in comment? printf with '... help ...'? // This must be corrected. if (command().contains(QLatin1String("help"))) { addResult(new Cantor::HelpResult(output)); } else { addResult(new Cantor::TextResult(output)); } } m_finished = true; if (!m_plotPending) setStatus(Done); } void OctaveExpression::parseError(const QString& error) { if (error.startsWith(QLatin1String("warning: "))) { // It's warning, so add as result addResult(new Cantor::TextResult(error)); } else { setErrorMessage(error); setStatus(Error); } } -void OctaveExpression::parsePlotFile(const QString& file) +void OctaveExpression::imageChanged() { - qDebug() << "parsePlotFile"; - if (QFile::exists(file)) - { - qDebug() << "OctaveExpression::parsePlotFile: " << file; - - addResult(new OctavePlotResult(QUrl::fromLocalFile(file))); - setPlotPending(false); + if(m_tempFile->size() <= 0) + return; - if (m_finished) + OctavePlotResult* newResult = new OctavePlotResult(QUrl::fromLocalFile(m_tempFile->fileName())); + bool found = false; + for (int i = 0; i < results().size(); i++) + if (results()[i]->type() == newResult->type()) { - setStatus(Done); + replaceResult(i, newResult); + found = true; } - } -} + if (!found) + addResult(newResult); -void OctaveExpression::setPlotPending(bool plot) -{ - m_plotPending = plot; + m_plotPending = false; + + if (m_finished && status() != Expression::Done) + { + setStatus(Done); + } } diff --git a/src/backends/octave/octaveexpression.h b/src/backends/octave/octaveexpression.h index 761fdb36..27f8ae83 100644 --- a/src/backends/octave/octaveexpression.h +++ b/src/backends/octave/octaveexpression.h @@ -1,61 +1,61 @@ /* Copyright (C) 2010 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef OCTAVEEXPRESSION_H #define OCTAVEEXPRESSION_H #include #include #include #ifdef WITH_EPS #include "epsresult.h" using OctavePlotResult = Cantor::EpsResult; #else #include "imageresult.h" typedef Cantor::ImageResult OctavePlotResult; #endif +class QTemporaryFile; class OctaveExpression : public Cantor::Expression { Q_OBJECT public: explicit OctaveExpression(Cantor::Session*, bool internal = false); + ~OctaveExpression(); void interrupt() override; void evaluate() override; QString internalCommand() override; void parseOutput(const QString&); void parseError(const QString&); - void parsePlotFile(const QString&); - - void setPlotPending(bool); + void imageChanged(); private: QString m_resultString; - bool m_plotPending; - bool m_finished; - QStringList m_plotCommands; + bool m_finished = false; + bool m_plotPending = false; + QTemporaryFile* m_tempFile = nullptr; }; #endif // OCTAVEEXPRESSION_H diff --git a/src/backends/octave/octavesession.cpp b/src/backends/octave/octavesession.cpp index 65348aa9..cc2761b2 100644 --- a/src/backends/octave/octavesession.cpp +++ b/src/backends/octave/octavesession.cpp @@ -1,387 +1,337 @@ /* Copyright (C) 2010 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "octavesession.h" #include "octaveexpression.h" #include "octavecompletionobject.h" #include "octavesyntaxhelpobject.h" #include "octavehighlighter.h" #include "result.h" #include "textresult.h" #include "settings.h" #include "octave-backend-config.h" #include "octavehighlighter.h" #include #include #include #include #include #include #include #ifndef Q_OS_WIN #include #endif #include "octavevariablemodel.h" const QRegExp OctaveSession::PROMPT_UNCHANGEABLE_COMMAND = QRegExp(QLatin1String("(,|;)+")); OctaveSession::OctaveSession ( Cantor::Backend* backend ) : Session ( backend), m_process(nullptr), m_prompt(QLatin1String("CANTOR_OCTAVE_BACKEND_PROMPT:([0-9]+)> ")), m_subprompt(QLatin1String("CANTOR_OCTAVE_BACKEND_SUBPROMPT:([0-9]+)> ")), m_previousPromptNumber(1), -m_watch(nullptr), m_syntaxError(false) { setVariableModel(new OctaveVariableModel(this)); qDebug() << octaveScriptInstallDir; } void OctaveSession::login() { qDebug() << "login"; emit loginStarted(); m_process = new KProcess ( this ); QStringList args; args << QLatin1String("--silent"); args << QLatin1String("--interactive"); args << QLatin1String("--persist"); // Setting prompt and subprompt args << QLatin1String("--eval"); args << QLatin1String("PS1('CANTOR_OCTAVE_BACKEND_PROMPT:\\#> ');"); args << QLatin1String("--eval"); args << QLatin1String("PS2('CANTOR_OCTAVE_BACKEND_SUBPROMPT:\\#> ');"); // Add the cantor script directory to octave script search path bool isScriptDirExists = QDir(octaveScriptInstallDir).exists(); if (isScriptDirExists) args << QLatin1String("--eval") <addDir(m_tempDir, KDirWatch::WatchFiles); - } - loginFinished = true; - } - } - connect ( m_process, SIGNAL (readyReadStandardOutput()), SLOT (readOutput()) ); connect ( m_process, SIGNAL (readyReadStandardError()), SLOT (readError()) ); connect ( m_process, SIGNAL (error(QProcess::ProcessError)), SLOT (processError()) ); if(!OctaveSettings::self()->autorunScripts().isEmpty()){ QString autorunScripts = OctaveSettings::self()->autorunScripts().join(QLatin1String("\n")); evaluateExpression(autorunScripts, OctaveExpression::DeleteOnFinish, true); updateVariables(); } changeStatus(Cantor::Session::Done); emit loginDone(); qDebug()<<"login done"; } void OctaveSession::logout() { qDebug()<<"logout"; if(!m_process) return; disconnect(m_process, nullptr, this, nullptr); // if(status()==Cantor::Session::Running) // TODO: terminate the running expressions first m_process->write("exit\n"); - qDebug()<<"waiting for octave to finish"; - m_process->waitForFinished(); - qDebug()<<"octave exit finished"; + qDebug()<<"send exit command to octave"; - if(m_process->state() != QProcess::NotRunning) + if(!m_process->waitForFinished(1000)) { m_process->kill(); qDebug()<<"octave still running, process kill enforced"; } expressionQueue().clear(); delete m_process; m_process = nullptr; - - m_tempDir.clear(); m_output.clear(); m_previousPromptNumber = 1; variableModel()->clearVariables(); changeStatus(Status::Disable); qDebug()<<"logout done"; } void OctaveSession::interrupt() { + qDebug() << expressionQueue().size(); if(!expressionQueue().isEmpty()) { qDebug()<<"interrupting " << expressionQueue().first()->command(); if(m_process->state() != QProcess::NotRunning) { #ifndef Q_OS_WIN const int pid=m_process->pid(); kill(pid, SIGINT); #else ; //TODO: interrupt the process on windows #endif } foreach (Cantor::Expression* expression, expressionQueue()) expression->setStatus(Cantor::Expression::Interrupted); expressionQueue().clear(); // Cleanup inner state and call octave prompt printing // If we move this code for interruption to Session, we need add function for // cleaning before setting Done status m_output.clear(); m_process->write("\n"); qDebug()<<"done interrupting"; } changeStatus(Cantor::Session::Done); } void OctaveSession::processError() { qDebug() << "processError"; emit error(m_process->errorString()); } Cantor::Expression* OctaveSession::evaluateExpression ( const QString& command, Cantor::Expression::FinishingBehavior finishingBehavior, bool internal ) { qDebug() << "evaluating: " << command; OctaveExpression* expression = new OctaveExpression ( this, internal); expression->setCommand ( command ); expression->setFinishingBehavior ( finishingBehavior ); expression->evaluate(); return expression; } void OctaveSession::runFirstExpression() { OctaveExpression* expression = static_cast(expressionQueue().first()); connect(expression, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionStatusChanged(Cantor::Expression::Status))); QString command = expression->internalCommand(); expression->setStatus(Cantor::Expression::Computing); if (isDoNothingCommand(command)) expression->setStatus(Cantor::Expression::Done); else { m_process->write ( command.toLocal8Bit() ); } } void OctaveSession::readError() { qDebug() << "readError"; QString error = QString::fromLocal8Bit(m_process->readAllStandardError()); if (!expressionQueue().isEmpty() && !error.isEmpty()) { OctaveExpression* const exp = static_cast(expressionQueue().first()); if (m_syntaxError) { m_syntaxError = false; exp->parseError(i18n("Syntax Error")); } else exp->parseError(error); m_output.clear(); } } void OctaveSession::readOutput() { qDebug() << "readOutput"; while (m_process->bytesAvailable() > 0) { QString line = QString::fromLocal8Bit(m_process->readLine()); qDebug()<<"start parsing " << " " << line; if (line.contains(m_prompt)) { const int promptNumber = m_prompt.cap(1).toInt(); // Add all text before prompt, if exists m_output += QStringRef(&line, 0, line.indexOf(m_prompt)).toString(); if (!expressionQueue().isEmpty()) { const QString& command = expressionQueue().first()->command(); if (m_previousPromptNumber + 1 == promptNumber || isSpecialOctaveCommand(command)) { if (!expressionQueue().isEmpty()) { readError(); static_cast(expressionQueue().first())->parseOutput(m_output); } } else { // Error command don't increase octave prompt number (usually, but not always) readError(); } } m_previousPromptNumber = promptNumber; m_output.clear(); } else if (line.contains(m_subprompt) && m_subprompt.cap(1).toInt() == m_previousPromptNumber) { // User don't write finished octave statement (for example, write 'a = [1,2, ' only), so // octave print subprompt and waits input finish. m_syntaxError = true; qDebug() << "subprompt catch"; m_process->write(")]'\"\n"); // forse exit from subprompt m_output.clear(); } else m_output += line; } } void OctaveSession::currentExpressionStatusChanged(Cantor::Expression::Status status) { - qDebug() << "currentExpressionStatusChanged"; + qDebug() << "currentExpressionStatusChanged" << status << expressionQueue().first()->command(); switch (status) { case Cantor::Expression::Done: case Cantor::Expression::Error: finishFirstExpression(); break; default: break; } } -void OctaveSession::plotFileChanged(const QString& filename) -{ - if (!QFile::exists(filename) || !filename.split(QLatin1Char('/')).last().contains(QLatin1String("c-ob-"))) - { - return; - } - - if (!expressionQueue().isEmpty()) - { - static_cast(expressionQueue().first())->parsePlotFile(filename); - } -} - Cantor::CompletionObject* OctaveSession::completionFor ( const QString& cmd, int index ) { return new OctaveCompletionObject ( cmd, index, this ); } Cantor::SyntaxHelpObject* OctaveSession::syntaxHelpFor ( const QString& cmd ) { return new OctaveSyntaxHelpObject ( cmd, this ); } QSyntaxHighlighter* OctaveSession::syntaxHighlighter ( QObject* parent ) { return new OctaveHighlighter ( parent, this ); } void OctaveSession::runSpecificCommands() { m_process->write("figure(1,'visible','off')"); } bool OctaveSession::isDoNothingCommand(const QString& command) { return PROMPT_UNCHANGEABLE_COMMAND.exactMatch(command) || command.isEmpty() || command == QLatin1String("\n"); } bool OctaveSession::isSpecialOctaveCommand(const QString& command) { return command.contains(QLatin1String("completion_matches")); } diff --git a/src/backends/octave/octavesession.h b/src/backends/octave/octavesession.h index a5049824..75589ebb 100644 --- a/src/backends/octave/octavesession.h +++ b/src/backends/octave/octavesession.h @@ -1,83 +1,80 @@ /* Copyright (C) 2010 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef OCTAVESESSION_H #define OCTAVESESSION_H #include #include #include #include #include namespace Cantor { class DefaultVariableModel; } class KDirWatch; class OctaveExpression; class KProcess; class OctaveSession : public Cantor::Session { Q_OBJECT public: explicit OctaveSession(Cantor::Backend* backend); ~OctaveSession() override = default; void interrupt() override; Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior finishingBehavior = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; void logout() override; void login() override; Cantor::CompletionObject* completionFor(const QString& cmd, int index=-1) override; Cantor::SyntaxHelpObject* syntaxHelpFor(const QString& cmd) override; QSyntaxHighlighter* syntaxHighlighter(QObject* parent) override; void runFirstExpression() override; private: const static QRegExp PROMPT_UNCHANGEABLE_COMMAND; private: KProcess* m_process; QTextStream m_stream; QRegExp m_prompt; QRegExp m_subprompt; int m_previousPromptNumber; - KDirWatch* m_watch; - QString m_tempDir; bool m_syntaxError; QString m_output; private: void readFromOctave(QByteArray data); bool isDoNothingCommand(const QString& command); bool isSpecialOctaveCommand(const QString& command); private Q_SLOTS: void readOutput(); void readError(); void currentExpressionStatusChanged(Cantor::Expression::Status status); void processError(); - void plotFileChanged(const QString& filename); void runSpecificCommands(); }; #endif // OCTAVESESSION_H diff --git a/src/backends/octave/scripts/cantor_plot2d.m b/src/backends/octave/scripts/cantor_plot2d.m index 3f4a4c08..f8039ca3 100644 --- a/src/backends/octave/scripts/cantor_plot2d.m +++ b/src/backends/octave/scripts/cantor_plot2d.m @@ -1,33 +1,32 @@ %{ Copyright (C) 2010 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. %} function cantor_plot2d(f_string,var,min,max) f_mod_string = f_string; for op = ['*' '/' '^'] f_mod_string = strrep(f_mod_string, op, strcat('.',op)); endfor f = inline(f_mod_string, var); x = linspace(min,max); plot (x, f(x)); xlabel(var); ylabel(f_string); - cantor_print(); -endfunction \ No newline at end of file +endfunction diff --git a/src/backends/octave/scripts/cantor_plot3d.m b/src/backends/octave/scripts/cantor_plot3d.m index bc8bed1b..91b3ee50 100644 --- a/src/backends/octave/scripts/cantor_plot3d.m +++ b/src/backends/octave/scripts/cantor_plot3d.m @@ -1,34 +1,33 @@ %{ Copyright (C) 2010 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. %} function cantor_plot3d(f_string,var1,min1,max1,var2,min2,max2) f_mod_string = f_string; for op = ['*' '/' '^'] f_mod_string = strrep(f_mod_string, op, strcat('.',op)); endfor f = inline(f_mod_string, var1, var2); [x,y] = meshgrid(linspace(min1,max1),linspace(min2,max2)); mesh(x,y,f(x,y)); xlabel(var1); ylabel(var2); zlabel(f_string); - cantor_print(); -endfunction \ No newline at end of file +endfunction diff --git a/src/backends/octave/scripts/cantor_print.m.in b/src/backends/octave/scripts/cantor_print.m.in index c3c19a15..d6b89bbb 100644 --- a/src/backends/octave/scripts/cantor_print.m.in +++ b/src/backends/octave/scripts/cantor_print.m.in @@ -1,26 +1,26 @@ %{ Copyright (C) 2010 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. %} -function cantor_print() +function cantor_print(filename) try - print('-d${PLOT_FILE_FORMAT}',strcat(tempname(tempdir,'c-ob-'),'.${PLOT_FILE_SUFFIX}'),'-S480,336','-tight'); + print('-d${PLOT_FILE_FORMAT}',filename,'-S480,336','-tight'); catch - print('-d${PLOT_FILE_FORMAT}',strcat(tempname(tempdir,'c-ob-'),'.${PLOT_FILE_SUFFIX}'),'-S480,336'); + print('-d${PLOT_FILE_FORMAT}',filename,'-S480,336'); end_try_catch endfunction diff --git a/src/backends/octave/testoctave.cpp b/src/backends/octave/testoctave.cpp index 491a3262..e4935c0e 100644 --- a/src/backends/octave/testoctave.cpp +++ b/src/backends/octave/testoctave.cpp @@ -1,241 +1,268 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #include "testoctave.h" #include "session.h" #include "backend.h" #include "expression.h" #include "result.h" #include "imageresult.h" #include "textresult.h" #include "epsresult.h" #include "completionobject.h" #include "defaultvariablemodel.h" #include "octaveexpression.h" #include QString TestOctave::backendName() { return QLatin1String("octave"); } void TestOctave::testSimpleCommand() { Cantor::Expression* e=evalExp( QLatin1String("2+2") ); QVERIFY( e!=nullptr ); QVERIFY( e->result()!=nullptr ); QCOMPARE( cleanOutput( e->result()->data().toString() ), QLatin1String("ans = 4") ); } void TestOctave::testMultilineCommand() { Cantor::Expression* e=evalExp( QLatin1String("a = 2+2, b = 3+3") ); QVERIFY( e!=nullptr ); QVERIFY( e->result()!=nullptr ); QString result=e->result()->data().toString(); QCOMPARE( cleanOutput(result ), QLatin1String("a = 4\nb = 6") ); } void TestOctave::testCommandQueue() { Cantor::Expression* e1=session()->evaluateExpression(QLatin1String("0+1")); Cantor::Expression* e2=session()->evaluateExpression(QLatin1String("1+1")); Cantor::Expression* e3=evalExp(QLatin1String("1+2")); QVERIFY(e1!=nullptr); QVERIFY(e2!=nullptr); QVERIFY(e3!=nullptr); QVERIFY(e1->result()); QVERIFY(e2->result()); QVERIFY(e3->result()); QCOMPARE(cleanOutput(e1->result()->data().toString()), QLatin1String("ans = 1")); QCOMPARE(cleanOutput(e2->result()->data().toString()), QLatin1String("ans = 2")); QCOMPARE(cleanOutput(e3->result()->data().toString()), QLatin1String("ans = 3")); } void TestOctave::testVariableDefinition() { Cantor::Expression* e = evalExp(QLatin1String("testvar = 1")); QVERIFY(e != nullptr); QVERIFY(e->result() != nullptr); QCOMPARE(cleanOutput(e->result()->data().toString()), QLatin1String("testvar = 1")); } void TestOctave::testMatrixDefinition() { Cantor::Expression* e = evalExp(QLatin1String( "M = [1, 2, 3;"\ " 4, 5, 6;"\ " 7, 8, 9;]" )); QVERIFY(e != nullptr); QVERIFY(e->result() != nullptr); QCOMPARE(e->result()->type(), (int) Cantor::TextResult::Type); Cantor::TextResult* result = static_cast(e->result()); QCOMPARE(result->plain(), QLatin1String( "M =\n"\ "\n" " 1 2 3\n"\ " 4 5 6\n"\ " 7 8 9" )); } void TestOctave::testSimpleExpressionWithComment() { Cantor::Expression* e = evalExp(QLatin1String("s = 1234 #This is comment")); QVERIFY(e != nullptr); QVERIFY(e->result() != nullptr); QCOMPARE(cleanOutput(e->result()->data().toString()), QLatin1String("s = 1234")); } void TestOctave::testCommentExpression() { Cantor::Expression* e = evalExp(QLatin1String("#Only comment")); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Status::Done); QCOMPARE(e->results().size(), 0); } void TestOctave::testMultilineCommandWithComment() { Cantor::Expression* e = evalExp(QLatin1String( "a = 2+4 \n" "6/2 % comment\n" "q = 'Str' # comment\n" "b = 4" )); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Status::Done); QVERIFY(e->result() != nullptr); Cantor::TextResult* result = static_cast(e->result()); QVERIFY(result != nullptr); QCOMPARE(cleanOutput(result->plain()), QLatin1String( "a = 6\n" "ans = 3\n" "q = Str\n" "b = 4" )); } void TestOctave::testCompletion() { Cantor::CompletionObject* help = session()->completionFor(QLatin1String("as"), 2); waitForSignal(help, SIGNAL(fetchingDone())); // Checks some completions for this request (but not all) // This correct for Octave 4.2.2 at least (and another versions, I think) const QStringList& completions = help->completions(); qDebug() << completions; QVERIFY(completions.contains(QLatin1String("asin"))); QVERIFY(completions.contains(QLatin1String("asctime"))); QVERIFY(completions.contains(QLatin1String("asec"))); QVERIFY(completions.contains(QLatin1String("assert"))); } void TestOctave::testVariablesCreatingFromCode() { QAbstractItemModel* model = session()->variableModel(); QVERIFY(model != nullptr); evalExp(QLatin1String("clear();")); Cantor::Expression* e=evalExp(QLatin1String("a = 15; b = 'S';")); QVERIFY(e!=nullptr); if(session()->status()==Cantor::Session::Running) waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); QCOMPARE(2, model->rowCount()); QCOMPARE(model->index(0,0).data().toString(), QLatin1String("a")); QCOMPARE(model->index(0,1).data().toString(), QLatin1String(" 15")); QCOMPARE(model->index(1,0).data().toString(), QLatin1String("b")); QCOMPARE(model->index(1,1).data().toString(), QLatin1String("S")); } +void TestOctave::testVariableCreatingFromCodeWithPlot() +{ + QAbstractItemModel* model = session()->variableModel(); + QVERIFY(model != nullptr); + + evalExp(QLatin1String("clear();")); + + Cantor::Expression* e = evalExp(QLatin1String( + "x = -10:0.1:10;\n" + "plot (x, sin (x));\n" + "xlabel (\"x\");\n" + "ylabel (\"sin (x)\");\n" + "title (\"Simple 2-D Plot\");\n" + )); + QVERIFY(e!=nullptr); + QCOMPARE(e->status(), Cantor::Expression::Done); + QVERIFY(e->result()); + QVERIFY(e->result()->type() != Cantor::TextResult::Type); + + if(session()->status()==Cantor::Session::Running) + waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); + + QCOMPARE(1, model->rowCount()); + + QCOMPARE(model->index(0,0).data().toString(), QLatin1String("x")); +} + void TestOctave::testVariableCleanupAfterRestart() { Cantor::DefaultVariableModel* model = session()->variableModel(); QVERIFY(model != nullptr); evalExp(QLatin1String("clear();")); Cantor::Expression* e=evalExp(QLatin1String("a = 15; b = 'S';")); QVERIFY(e!=nullptr); if(session()->status()==Cantor::Session::Running) waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); QCOMPARE(2, static_cast(model)->rowCount()); session()->logout(); session()->login(); QCOMPARE(0, static_cast(model)->rowCount()); } void TestOctave::testPlot() { Cantor::Expression* e=evalExp( QLatin1String("cantor_plot2d('sin(x)', 'x', -10,10);") ); int cnt=0; //give some time to create the image, but at most 5sec while(e->result()==nullptr||e->result()->type()!=OctavePlotResult::Type ) { QTest::qWait(250); cnt+=250; if(cnt>5000) break; } QVERIFY( e!=nullptr ); QVERIFY( e->result()!=nullptr ); QCOMPARE( e->result()->type(), (int) OctavePlotResult::Type ); QVERIFY( !e->result()->data().isNull() ); } void TestOctave::testInvalidSyntax() { Cantor::Expression* e=evalExp( QLatin1String("2+2*+.") ); QVERIFY( e!=nullptr ); QCOMPARE( e->status(), Cantor::Expression::Error ); } QTEST_MAIN( TestOctave ) diff --git a/src/backends/octave/testoctave.h b/src/backends/octave/testoctave.h index bdadbf45..4f16fe47 100644 --- a/src/backends/octave/testoctave.h +++ b/src/backends/octave/testoctave.h @@ -1,69 +1,70 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #ifndef _TESTOCTAVE_H #define _TESTOCTAVE_H #include "backendtest.h" /** This class test some of the basic functions of the Octave backend The different tests represent some general expressions, as well as expressions, that are known to have caused problems in earlier versions **/ class TestOctave : public BackendTest { Q_OBJECT private Q_SLOTS: //tests evaluating a simple command void testSimpleCommand(); //tests a command, containing more than 1 line void testMultilineCommand(); //tests if the command queue works correcly void testCommandQueue(); void testVariableDefinition(); void testMatrixDefinition(); //some tests to see if comments are working correctly void testSimpleExpressionWithComment(); void testCommentExpression(); void testMultilineCommandWithComment(); //tests a syntax error (not closing bracket) void testInvalidSyntax(); void testCompletion(); //tests variable model void testVariablesCreatingFromCode(); void testVariableCleanupAfterRestart(); + void testVariableCreatingFromCodeWithPlot(); //tests doing a plot void testPlot(); private: QString backendName() override; }; #endif /* _TESTOCTAVE_H */