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

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2009 Alexander Rieder <alexanderrieder@gmail.com>
0004     SPDX-FileCopyrightText: 2019 Alexander Semke <alexander.semke@web.de>
0005 */
0006 
0007 #include "session.h"
0008 using namespace Cantor;
0009 
0010 #include <map>
0011 
0012 #include "backend.h"
0013 #include "textresult.h"
0014 
0015 #include <QTimer>
0016 #include <QQueue>
0017 #include <QDebug>
0018 #include <KMessageBox>
0019 #include <KLocalizedString>
0020 
0021 class Cantor::SessionPrivate
0022 {
0023   public:
0024     SessionPrivate() { }
0025 
0026     Backend* backend{nullptr};
0027     Session::Status status{Session::Disable};
0028     bool typesettingEnabled{false};
0029     int expressionCount{0};
0030     QList<Cantor::Expression*> expressionQueue;
0031     DefaultVariableModel* variableModel{nullptr};
0032     QList<GraphicPackage> usableGraphicPackages;
0033     QList<GraphicPackage> enabledGraphicPackages;
0034     QList<QString> ignorableGraphicPackageIds;
0035     bool needUpdate{false};
0036 };
0037 
0038 Session::Session(Backend* backend ) : QObject(backend), d(new SessionPrivate)
0039 {
0040     d->backend = backend;
0041 
0042 #ifdef WITH_EPS
0043     if (Cantor::LatexRenderer::isLatexAvailable())
0044         d->typesettingEnabled = Settings::self()->typesetDefault();
0045 #endif
0046 }
0047 
0048 Session::Session(Backend* backend, DefaultVariableModel* model) : QObject(backend), d(new SessionPrivate)
0049 {
0050     d->backend = backend;
0051     d->variableModel = model;
0052 
0053 #ifdef WITH_EPS
0054     if (Cantor::LatexRenderer::isLatexAvailable())
0055         d->typesettingEnabled = Settings::self()->typesetDefault();
0056 #endif
0057 }
0058 
0059 Session::~Session()
0060 {
0061     delete d;
0062 }
0063 
0064 void Cantor::Session::testGraphicsPackages(QList<GraphicPackage> packages)
0065 {
0066     std::map<QString, bool> handlingStatus;
0067 
0068     QEventLoop loop;
0069     for (GraphicPackage& package : packages)
0070     {
0071         if (GraphicPackage::findById(package, d->usableGraphicPackages) != -1)
0072             continue;
0073 
0074         handlingStatus[package.id()] = false;
0075         Expression* expr = package.isAvailable(this);
0076 
0077         connect(expr, &Expression::expressionFinished, [this, expr, &package, &loop, &handlingStatus](Expression::Status status) {
0078             if (status == Expression::Status::Done) {
0079                 if (expr->result() != nullptr
0080                     && expr->result()->type() == TextResult::Type
0081                     && expr->result()->data().toString() == QLatin1String("1")) {
0082                     this->d->usableGraphicPackages.push_back(package);
0083                 }
0084             } else {
0085                 qDebug() << "test presence command for" << package.id() << "finished because of" << (status == Expression::Error ? "error" : "interrupt");
0086                 if (status == Expression::Error && expr)
0087                     qDebug() << "error message:" << expr->errorMessage();
0088             }
0089 
0090             handlingStatus[package.id()] = true;
0091 
0092             bool allExpersionsFinished = true;
0093             for (auto& iter : handlingStatus)
0094             {
0095                 if (iter.second == false)
0096                 {
0097                     allExpersionsFinished = false;
0098                     break;
0099                 }
0100             }
0101 
0102             if (allExpersionsFinished)
0103                 loop.exit();
0104         });
0105     }
0106     // If handlingStatus size is empty (it means, that no connections have been done), then we will stay in the 'loop' event loop forever
0107     if (handlingStatus.size() != 0)
0108         loop.exec();
0109 }
0110 
0111 void Session::logout()
0112 {
0113     if (d->status == Session::Running)
0114         interrupt();
0115 
0116     if (d->variableModel)
0117     {
0118         d->variableModel->clearVariables();
0119         d->variableModel->clearFunctions();
0120     }
0121 
0122     d->expressionCount = 0;
0123     changeStatus(Status::Disable);
0124 
0125     // Clean graphic package state
0126     d->enabledGraphicPackages.clear();
0127     d->ignorableGraphicPackageIds.clear();
0128     d->usableGraphicPackages.clear();
0129 }
0130 
0131 QList<Expression*>& Cantor::Session::expressionQueue() const
0132 {
0133     return d->expressionQueue;
0134 }
0135 
0136 void Session::enqueueExpression(Expression* expr)
0137 {
0138     d->expressionQueue.append(expr);
0139 
0140     //run the newly added expression immediately if it's the only one in the queue
0141     if (d->expressionQueue.size() == 1)
0142     {
0143         changeStatus(Cantor::Session::Running);
0144         runFirstExpression();
0145     }
0146     else
0147         expr->setStatus(Cantor::Expression::Queued);
0148 }
0149 
0150 void Session::runFirstExpression()
0151 {
0152 
0153 }
0154 
0155 void Session::finishFirstExpression(bool setDoneAfterUpdate)
0156 {
0157     if (!d->expressionQueue.isEmpty())
0158     {
0159         auto first = d->expressionQueue.takeFirst();
0160         d->needUpdate |= !first->isInternal() && !first->isHelpRequest();
0161     }
0162 
0163     if (d->expressionQueue.isEmpty())
0164         if (d->variableModel && d->needUpdate)
0165         {
0166             d->variableModel->update();
0167             d->needUpdate = false;
0168 
0169             // Some variable models could update internal lists without running expressions
0170             // or don't need to be updated at all like for Maxima being in Lisp-mode.
0171             // So, if after update queue still empty, set status to Done
0172             // setDoneAfterUpdate used for compatibility with some backends, like R - TODO: check why this is requried
0173             if (setDoneAfterUpdate && d->expressionQueue.isEmpty())
0174                 changeStatus(Done);
0175             else if (d->expressionQueue.isEmpty())
0176                 changeStatus(Done);
0177         }
0178         else
0179             changeStatus(Done);
0180     else
0181         runFirstExpression();
0182 }
0183 
0184 void Session::currentExpressionStatusChanged(Cantor::Expression::Status status)
0185 {
0186     auto* expression = expressionQueue().first();
0187     qDebug() << "expression status changed: command = " << expression->command() << ", status = " << status;
0188 
0189     switch (status)
0190     {
0191     case Cantor::Expression::Done:
0192     case Cantor::Expression::Error:
0193         qDebug()<<"################################## EXPRESSION END ###############################################";
0194         disconnect(expression, &Cantor::Expression::statusChanged, this, &Session::currentExpressionStatusChanged);
0195         finishFirstExpression();
0196         break;
0197     default:
0198         break;
0199     }
0200 }
0201 
0202 Backend* Session::backend()
0203 {
0204     return d->backend;
0205 }
0206 
0207 Cantor::Session::Status Session::status()
0208 {
0209     return d->status;
0210 }
0211 
0212 void Session::changeStatus(Session::Status newStatus)
0213 {
0214     d->status = newStatus;
0215     emit statusChanged(newStatus);
0216 }
0217 
0218 void Session::setTypesettingEnabled(bool enable)
0219 {
0220     d->typesettingEnabled = enable;
0221 }
0222 
0223 bool Session::isTypesettingEnabled()
0224 {
0225     return d->typesettingEnabled;
0226 }
0227 
0228 void Session::setWorksheetPath(const QString&) { }
0229 
0230 CompletionObject* Session::completionFor(const QString&, int)
0231 {
0232     //Return nullptr per default, so Backends not offering tab completions don't have
0233     //to reimplement this. This method should only be called on backends with
0234     //the Completion Capability flag
0235 
0236     return nullptr;
0237 }
0238 
0239 SyntaxHelpObject* Session::syntaxHelpFor(const QString&)
0240 {
0241     //Return nullptr per default, so Backends not offering tab completions don't have
0242     //to reimplement this. This method should only be called on backends with
0243     //the SyntaxHelp Capability flag
0244     return nullptr;
0245 }
0246 
0247 QSyntaxHighlighter* Session::syntaxHighlighter(QObject*)
0248 {
0249     return nullptr;
0250 }
0251 
0252 DefaultVariableModel* Session::variableModel() const
0253 {
0254     //By default, there is variableModel in session, used by syntax higlighter for variable analyzing
0255     //The model store only variable names by default.
0256     //In backends with VariableManagement Capability flag, this model also used for Cantor variable doc panel
0257     return d->variableModel;
0258 }
0259 
0260 QAbstractItemModel* Session::variableDataModel() const
0261 {
0262     return variableModel();
0263 }
0264 
0265 void Session::updateVariables()
0266 {
0267     if (d->variableModel)
0268     {
0269         d->variableModel->update();
0270         d->needUpdate = false;
0271     }
0272 }
0273 
0274 void Cantor::Session::setVariableModel(Cantor::DefaultVariableModel* model)
0275 {
0276     d->variableModel = model;
0277 }
0278 
0279 int Session::nextExpressionId()
0280 {
0281     return d->expressionCount++;
0282 }
0283 
0284 QString Session::locateCantorFile(const QString& partialPath, QStandardPaths::LocateOptions options)
0285 {
0286     QString file = QStandardPaths::locate(QStandardPaths::AppDataLocation, partialPath, options);
0287 
0288     if (file.isEmpty())
0289         file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("cantor/") + partialPath, options);
0290 
0291     return file;
0292 }
0293 
0294 QStringList Session::locateAllCantorFiles(const QString& partialPath, QStandardPaths::LocateOptions options)
0295 {
0296     QStringList files = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, partialPath, options);
0297 
0298     if (files.isEmpty())
0299         files = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QLatin1String("cantor/") + partialPath, options);
0300 
0301     return files;
0302 }
0303 
0304 void Cantor::Session::reportSessionCrash(const QString& additionalInfo)
0305 {
0306     // Reporting about crashing backend in session without backend has not sense
0307     if (d->backend == nullptr)
0308         return;
0309 
0310     if (additionalInfo.isEmpty())
0311         KMessageBox::error(nullptr, i18n("%1 process has died unexpectedly. All calculation results are lost.", d->backend->name()), i18n("Error - Cantor"));
0312     else
0313         KMessageBox::error(nullptr, i18n("%1 process has died unexpectedly with message \"%2\". All calculation results are lost.", d->backend->name(), additionalInfo), i18n("Error - Cantor"));
0314     logout();
0315 }
0316 
0317 QList<Cantor::GraphicPackage> Cantor::Session::usableGraphicPackages()
0318 {
0319     return d->usableGraphicPackages;
0320 }
0321 
0322 const QList<Cantor::GraphicPackage>& Cantor::Session::enabledGraphicPackages() const
0323 {
0324     return d->enabledGraphicPackages;
0325 }
0326 
0327 QString Cantor::Session::graphicPackageErrorMessage(QString packageId) const
0328 {
0329     Q_UNUSED(packageId);
0330     return QString();
0331 }
0332 
0333 void Cantor::Session::updateEnabledGraphicPackages(const QList<Cantor::GraphicPackage>& newEnabledPackages, const QString& additionalInfo)
0334 {
0335     if (newEnabledPackages.isEmpty())
0336     {
0337         if (!d->enabledGraphicPackages.isEmpty())
0338         {
0339             for (const GraphicPackage& package : d->enabledGraphicPackages)
0340                 evaluateExpression(package.disableSupportCommand(), Cantor::Expression::DeleteOnFinish, true);
0341         }
0342         d->enabledGraphicPackages.clear();
0343     }
0344     else
0345     {
0346         QList<GraphicPackage> packagesExceptIgnored;
0347         for (const GraphicPackage& package : newEnabledPackages)
0348             if (d->ignorableGraphicPackageIds.contains(package.id()) == false)
0349                 packagesExceptIgnored.append(package);
0350 
0351         testGraphicsPackages(packagesExceptIgnored);
0352 
0353         QList<GraphicPackage> unavailablePackages;
0354         QList<GraphicPackage> willEnabledPackages;
0355 
0356         for (const GraphicPackage& package : packagesExceptIgnored)
0357         {
0358             if (GraphicPackage::findById(package, usableGraphicPackages()) != -1)
0359                 willEnabledPackages.append(package);
0360             else
0361                 unavailablePackages.append(package);
0362         }
0363 
0364         for (const GraphicPackage& package : d->enabledGraphicPackages)
0365             if (GraphicPackage::findById(package, willEnabledPackages) == -1)
0366                 evaluateExpression(package.disableSupportCommand(), Cantor::Expression::DeleteOnFinish, true);
0367 
0368         for (const GraphicPackage& newPackage : willEnabledPackages)
0369             if (GraphicPackage::findById(newPackage, d->enabledGraphicPackages) == -1)
0370                 evaluateExpression(newPackage.enableSupportCommand(additionalInfo), Cantor::Expression::DeleteOnFinish, true);
0371 
0372         d->enabledGraphicPackages = willEnabledPackages;
0373 
0374         for (const Cantor::GraphicPackage& notEnabledPackage : unavailablePackages)
0375         {
0376             if (d->ignorableGraphicPackageIds.contains(notEnabledPackage.id()) == false)
0377             {
0378                 KMessageBox::information(nullptr, i18n(
0379                     "You choose support for %1 graphic package, but the support can't be "\
0380                     "activated due to the missing requirements, so integration for this package will be disabled. %2",
0381                     notEnabledPackage.name(), graphicPackageErrorMessage(notEnabledPackage.id())), i18n("Cantor")
0382                 );
0383 
0384                 d->ignorableGraphicPackageIds.append(notEnabledPackage.id());
0385             }
0386         }
0387     }
0388 }