diff --git a/src/backends/maxima/maximasession.cpp b/src/backends/maxima/maximasession.cpp index aaa37601..7bfbb00a 100644 --- a/src/backends/maxima/maximasession.cpp +++ b/src/backends/maxima/maximasession.cpp @@ -1,342 +1,343 @@ /* 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-2012 Alexander Rieder Copyright (C) 2017-2018 Alexander Semke (alexander.semke@web.de) */ #include "maximasession.h" #include "maximaexpression.h" #include "maximacompletionobject.h" #include "maximasyntaxhelpobject.h" #include "maximahighlighter.h" #include "maximavariablemodel.h" #include "result.h" #include "settings.h" #include #include #include #include #include #ifndef Q_OS_WIN #include #endif //NOTE: the \\s in the expressions is needed, because Maxima seems to sometimes insert newlines/spaces between the letters //maybe this is caused by some behaviour if the Prompt is split into multiple "readStdout" calls //the Expressions are encapsulated in () to allow capturing for the text const QRegExp MaximaSession::MaximaOutputPrompt=QRegExp(QLatin1String("(\\(\\s*%\\s*o\\s*[0-9\\s]*\\))")); //Text, maxima outputs, before any output const QRegExp MaximaSession::MaximaInputPrompt = QRegExp(QLatin1String("(\\(\\s*%\\s*i\\s*[0-9\\s]*\\))")); MaximaSession::MaximaSession( Cantor::Backend* backend ) : Session(backend), m_process(nullptr), m_variableModel(new MaximaVariableModel(this)), m_justRestarted(false) { } void MaximaSession::login() { qDebug()<<"login"; if (m_process) return; //TODO: why do we call login() again?!? emit loginStarted(); QStringList arguments; arguments << QLatin1String("--quiet"); //Suppress Maxima start-up message const QString initFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("cantor/maximabackend/cantor-initmaxima.lisp")); arguments << QLatin1String("--init-lisp=") + initFile; //Set the name of the Lisp initialization file m_process = new QProcess(this); m_process->start(MaximaSettings::self()->path().toLocalFile(), arguments); m_process->waitForStarted(); m_process->waitForReadyRead(); qDebug()<readAllStandardOutput(); connect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(restartMaxima())); connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(readStdOut())); connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(readStdErr())); connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(reportProcessError(QProcess::ProcessError))); if(!MaximaSettings::self()->autorunScripts().isEmpty()){ QString autorunScripts = MaximaSettings::self()->autorunScripts().join(QLatin1String(";")); autorunScripts.append(QLatin1String(";kill(labels)")); // Reset labels after running autorun scripts evaluateExpression(autorunScripts, MaximaExpression::DeleteOnFinish, true); } changeStatus(Session::Done); emit loginDone(); qDebug()<<"login done"; } void MaximaSession::logout() { qDebug()<<"logout"; if(!m_process) return; disconnect(m_process, nullptr, this, nullptr); // if(status()==Cantor::Session::Running) //TODO: terminate the running expressions first write(QLatin1String("quit();\n")); qDebug()<<"waiting for maxima to finish"; m_process->waitForFinished(); qDebug()<<"maxima exit finished"; if(m_process->state() != QProcess::NotRunning) { m_process->kill(); qDebug()<<"maxima still running, process kill enforced"; } expressionQueue().clear(); delete m_process; m_process = nullptr; + m_variableModel->clear(); changeStatus(Status::Disable); qDebug()<<"logout done"; } Cantor::Expression* MaximaSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { qDebug() << "evaluating: " << cmd; MaximaExpression* expr = new MaximaExpression(this, internal); expr->setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } void MaximaSession::readStdErr() { qDebug()<<"reading stdErr"; if (!m_process) return; QString out=QLatin1String(m_process->readAllStandardError()); if(expressionQueue().size()>0) { MaximaExpression* expr = static_cast(expressionQueue().first()); expr->parseError(out); } } void MaximaSession::readStdOut() { QString out = QLatin1String(m_process->readAllStandardOutput()); m_cache += out; //collect the multi-line output until Maxima has finished the calculation and returns a new promt if ( !out.contains(QLatin1String("")) ) return; if(expressionQueue().isEmpty()) { //queue is empty, interrupt was called, nothing to do here qDebug()<(expressionQueue().first()); if (!expr) return; //should never happen qDebug()<<"output: " << m_cache; expr->parseOutput(m_cache); m_cache.clear(); } void MaximaSession::reportProcessError(QProcess::ProcessError e) { qDebug()<<"process error"<command())&&!exp2.exactMatch(expression->command())) { m_variableModel->checkForNewFunctions(); m_variableModel->checkForNewVariables(); }else { changeStatus(Cantor::Session::Done); } }else { runFirstExpression(); } } } void MaximaSession::runFirstExpression() { qDebug()<<"running next expression"; if (!m_process) return; if(!expressionQueue().isEmpty()) { MaximaExpression* expr = static_cast(expressionQueue().first()); QString command=expr->internalCommand(); connect(expr, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionChangedStatus(Cantor::Expression::Status))); expr->setStatus(Cantor::Expression::Computing); if(command.isEmpty()) { qDebug()<<"empty command"; expr->forceDone(); } else { m_cache.clear(); write(command + QLatin1Char('\n')); } } } void MaximaSession::interrupt() { 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 } expressionQueue().first()->interrupt(); expressionQueue().removeFirst(); foreach (Cantor::Expression* expression, expressionQueue()) expression->setStatus(Cantor::Expression::Done); expressionQueue().clear(); qDebug()<<"done interrupting"; } changeStatus(Cantor::Session::Done); m_cache.clear(); } void MaximaSession::sendInputToProcess(const QString& input) { write(input); } void MaximaSession::restartMaxima() { qDebug()<<"restarting maxima cooldown: "<write(exp.toUtf8()); } diff --git a/src/backends/maxima/maximavariablemodel.cpp b/src/backends/maxima/maximavariablemodel.cpp index 580821d1..3dae7a15 100644 --- a/src/backends/maxima/maximavariablemodel.cpp +++ b/src/backends/maxima/maximavariablemodel.cpp @@ -1,269 +1,278 @@ /* 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) 2012 Alexander Rieder */ #include "maximavariablemodel.h" #include "maximasession.h" #include "maximaexpression.h" #include "textresult.h" #include "latexresult.h" #include #include //command used to inspect a maxima variable. %1 is the name of that variable const QString MaximaVariableModel::inspectCommand=QLatin1String(":lisp($disp $%1)"); const QString MaximaVariableModel::variableInspectCommand=QLatin1String(":lisp(cantor-inspect $%1)"); MaximaVariableModel::MaximaVariableModel( MaximaSession* session) : Cantor::DefaultVariableModel(session) { } +void MaximaVariableModel::clear() +{ + emit functionsRemoved(functionNames()); + emit variablesRemoved(variableNames()); + m_functions.clear(); + m_variables.clear(); + DefaultVariableModel::clearVariables(); +} + void MaximaVariableModel::checkForNewVariables() { qDebug()<<"checking for new variables"; const QString& cmd=variableInspectCommand.arg(QLatin1String("values")); Cantor::Expression* expr=session()->evaluateExpression(cmd, Cantor::Expression::FinishingBehavior::DoNotDelete, true); connect(expr, &Cantor::Expression::statusChanged, this, &MaximaVariableModel::parseNewVariables); } void MaximaVariableModel::checkForNewFunctions() { qDebug()<<"checking for new functions"; const QString& cmd=inspectCommand.arg(QLatin1String("functions")); Cantor::Expression* expr=session()->evaluateExpression(cmd, Cantor::Expression::FinishingBehavior::DoNotDelete, true); connect(expr, &Cantor::Expression::statusChanged, this, &MaximaVariableModel::parseNewFunctions); } QList parse(MaximaExpression* expr) { if(!expr || expr->status()!=Cantor::Expression::Done || expr->results().isEmpty()) { return QList(); } //for parsing of names and values below (old code) we need to combine multiple results back to one string QString text; for (auto* result : expr->results()) { if(result->type()==Cantor::TextResult::Type) text += dynamic_cast(result)->plain(); else if(expr->result()->type()==Cantor::LatexResult::Type) text += dynamic_cast(result)->plain(); } const int nameIndex=text.indexOf(QLatin1Char(']')); QString namesString=text.left(nameIndex); //namesString.chop(1); namesString=namesString.mid(1); namesString=namesString.trimmed(); qDebug()<<"variable names: "<(); QStringList variableNames; QString valuesString; bool hasValues = false; QStringList variableValues; if ( namesString.contains(QLatin1Char(')')) ) { //function definition(s): e.g //text = "[f1(x),f2(x,y),f3(x,y,z)]\n$DONE" //nameString = f1(x),f2(x,y),f3(x,y,z) //variableString = "\n$DONE" variableNames = namesString.split(QLatin1String("),")); } else { //variable definition(s): e.g. //text = "[a,b]\n1\n\"-cantor-value-separator-\"\n2\n\"-cantor-value-separator-\"\n($A $B)" //nameString = "[a,b]" //variableString = "\n1\n\"-cantor-value-separator-\"\n2\n\"-cantor-value-separator-\"\n($A $B)" variableNames = namesString.split(QLatin1Char(',')); valuesString = text.mid(nameIndex+1).trimmed(); valuesString = valuesString.remove(QLatin1String("\n")); //lists with many elements have line breaks, remove them variableValues = valuesString.split(QLatin1String("\"-cantor-value-separator-\"")); hasValues = variableValues.isEmpty(); } qDebug()< variables; variables.reserve(variableNames.size()); for(int i=0;ii) { var.value=variableValues.at(i).trimmed(); var.value=var.value.remove(QLatin1String("\n")); //lists with many elements have line breaks, remove them } else var.value=QLatin1String("unknown"); variables<(sender()); QList newVars=parse(expr); QStringList addedVars; QStringList removedVars; //remove the old variables for (const Variable& var : m_variables) { //check if this var is present in the new variables bool found=false; for (const Variable& var2 : newVars) { if(var.name==var2.name) { found=true; break; } } if(!found) { removeVariable(var); removedVars<deleteLater(); emit variablesAdded(addedVars); emit variablesRemoved(removedVars); } void MaximaVariableModel::parseNewFunctions(Cantor::Expression::Status status) { if (status != Cantor::Expression::Done && status != Cantor::Expression::Error) return; qDebug()<<"parsing functions"; MaximaExpression* expr=dynamic_cast(sender()); QList newVars=parse(expr); QStringList addedVars; QStringList removedVars; //remove the old variables for (const Variable& var : m_functions) { //check if this var is present in the new variables bool found=false; for (const Variable& var2 : newVars) { if(var.name==var2.name) { found=true; break; } } if(!found) { removeVariable(var); removedVars<deleteLater(); emit functionsAdded(addedVars); emit functionsRemoved(removedVars); } MaximaSession* MaximaVariableModel::maximaSession() { return static_cast (session()); } QList MaximaVariableModel::variables() { return m_variables; } QList MaximaVariableModel::functions() { return m_functions; } QStringList MaximaVariableModel::variableNames() { QStringList names; for (const Cantor::DefaultVariableModel::Variable& var : m_variables) names< */ #ifndef _MAXIMAVARIABLEMODEL_H #define _MAXIMAVARIABLEMODEL_H #include "defaultvariablemodel.h" #include class MaximaSession; class MaximaVariableModel : public Cantor::DefaultVariableModel { Q_OBJECT public: static const QString inspectCommand; static const QString variableInspectCommand; explicit MaximaVariableModel( MaximaSession* session); ~MaximaVariableModel() override = default; + void clear(); + QList variables(); QList functions(); QStringList variableNames(); QStringList functionNames(bool stripParameters=false); public Q_SLOTS: void checkForNewVariables(); void checkForNewFunctions(); private Q_SLOTS: void parseNewVariables(Cantor::Expression::Status status); void parseNewFunctions(Cantor::Expression::Status status); Q_SIGNALS: void variablesAdded(const QStringList variables); void variablesRemoved(const QStringList variables); void functionsAdded(const QStringList variables); void functionsRemoved(const QStringList variables); private: MaximaSession* maximaSession(); private: QList m_variables; QList m_functions; }; #endif /* _MAXIMAVARIABLEMODEL_H */ diff --git a/src/lib/defaultvariablemodel.cpp b/src/lib/defaultvariablemodel.cpp index 2efdb011..29c55bfe 100644 --- a/src/lib/defaultvariablemodel.cpp +++ b/src/lib/defaultvariablemodel.cpp @@ -1,197 +1,193 @@ /* 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) 2010 Miha Čančula */ #include "defaultvariablemodel.h" #include #include #include "extension.h" #include "backend.h" namespace Cantor { class DefaultVariableModelPrivate { public: QList variables; Session* session; VariableManagementExtension* extension; }; DefaultVariableModel::DefaultVariableModel(Session* session): QAbstractTableModel(session), d_ptr(new DefaultVariableModelPrivate) { Q_D(DefaultVariableModel); d->session = session; if (session) { d->extension = dynamic_cast(session->backend()->extension(QLatin1String("VariableManagementExtension"))); } qDebug() << d->session << d->extension; } int DefaultVariableModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); return ColumnCount; } int DefaultVariableModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } else { Q_D(const DefaultVariableModel); return d->variables.size(); } } + QVariant DefaultVariableModel::headerData(int section, Qt::Orientation orientation, int role) const { if(role==Qt::DisplayRole && orientation==Qt::Horizontal) { switch(section) { case NameColumn: return i18nc("@title:column", "Name"); case ValueColumn: return i18nc("@title:column", "Value"); break; } } return QVariant(); } Qt::ItemFlags DefaultVariableModel::flags(const QModelIndex& index) const { return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; } - QVariant DefaultVariableModel::data(const QModelIndex& index, int role) const { if (role != Qt::DisplayRole || !index.isValid()) { return QVariant(); } Q_D(const DefaultVariableModel); switch (index.column()) { case NameColumn: return QVariant(d->variables[index.row()].name); case ValueColumn: return QVariant(d->variables[index.row()].value); default: return QVariant(); } } bool DefaultVariableModel::setData(const QModelIndex& index, const QVariant& value, int role) { if(role!=Qt::EditRole || !value.isValid() || !index.isValid()) { return false; } Q_D(const DefaultVariableModel); if(index.column() == ValueColumn) { // Changing values QString name = data(index.sibling(index.row(), NameColumn)).toString(); d->session->evaluateExpression(d->extension->setValue(name, value.toString()), Expression::DeleteOnFinish); return true; } else if(index.column() == NameColumn) { // Renaming => copy it, then delete the old one QString oldName = data(index).toString(); QString variableValue = data(index.sibling(index.row(), ValueColumn)).toString(); d->session->evaluateExpression(d->extension->addVariable(value.toString(), variableValue), Expression::DeleteOnFinish); d->session->evaluateExpression(d->extension->removeVariable(oldName), Expression::DeleteOnFinish); return true; } return false; } - void DefaultVariableModel::addVariable(const QString& name, const QString& value) { Variable v; v.name = name; v.value = value; addVariable(v); } void DefaultVariableModel::addVariable(const Cantor::DefaultVariableModel::Variable& variable) { Q_D(DefaultVariableModel); if ( d->variables.contains(variable) ) { removeVariable(variable); } beginInsertRows(QModelIndex(), d->variables.size(), d->variables.size()); d->variables.append(variable); endInsertRows(); } void DefaultVariableModel::removeVariable(const QString& name) { Variable v; v.name = name; removeVariable(v); } void DefaultVariableModel::removeVariable(const Cantor::DefaultVariableModel::Variable& variable) { Q_D(DefaultVariableModel); int row = d->variables.indexOf(variable); if(row==-1) return; beginRemoveRows(QModelIndex(), row, row); d->variables.removeAt(row); endRemoveRows(); } void DefaultVariableModel::clearVariables() { Q_D(DefaultVariableModel); beginResetModel(); d->variables.clear(); endResetModel(); } Session* DefaultVariableModel::session() const { Q_D(const DefaultVariableModel); return d->session; } bool operator==(const Cantor::DefaultVariableModel::Variable& one, const Cantor::DefaultVariableModel::Variable& other) { return one.name == other.name; } } - - -