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