File indexing completed on 2024-05-12 03:47:20
0001 /* 0002 File : CantorWorksheet.cpp 0003 Project : LabPlot 0004 Description : Aspect providing a Cantor Worksheets for Multiple backends 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2015 Garvit Khatri <garvitdelhi@gmail.com> 0007 SPDX-FileCopyrightText: 2016-2023 Alexander Semke <alexander.semke@web.de> 0008 SPDX-FileCopyrightText: 2022 Stefan Gerlach <stefan.gerlach@uni.kn> 0009 SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 0012 #include "CantorWorksheet.h" 0013 #include "VariableParser.h" 0014 #include "backend/core/Project.h" 0015 #include "backend/core/Settings.h" 0016 #include "backend/core/column/Column.h" 0017 #include "backend/core/column/ColumnPrivate.h" 0018 #include "backend/lib/XmlStreamReader.h" 0019 #include "commonfrontend/cantorWorksheet/CantorWorksheetView.h" 0020 0021 #include "3rdparty/cantor/cantor_part.h" 0022 #include <cantor/cantorlibs_version.h> 0023 #include <cantor/worksheetaccess.h> 0024 0025 #ifdef HAVE_NEW_CANTOR_LIBS 0026 #include <cantor/panelplugin.h> 0027 #include <cantor/panelpluginhandler.h> 0028 #else 0029 #include "3rdparty/cantor/panelplugin.h" 0030 #include "3rdparty/cantor/panelpluginhandler.h" 0031 #endif 0032 0033 #include <KConfigGroup> 0034 #include <KLocalizedString> 0035 #include <KParts/ReadWritePart> 0036 #include <KPluginFactory> 0037 #include <KPluginMetaData> 0038 #include <kcoreaddons_version.h> 0039 0040 #include <QAction> 0041 #include <QFileInfo> 0042 #include <QModelIndex> 0043 0044 CantorWorksheet::CantorWorksheet(const QString& name, bool loading) 0045 : AbstractPart(name, AspectType::CantorWorksheet) 0046 , m_backendName(name) { 0047 if (!loading) 0048 init(); 0049 } 0050 0051 /*! 0052 initializes Cantor's part and plugins 0053 */ 0054 bool CantorWorksheet::init(QByteArray* content) { 0055 DEBUG(Q_FUNC_INFO) 0056 0057 #if KCOREADDONS_VERSION < QT_VERSION_CHECK(5, 86, 0) 0058 KPluginLoader loader(QLatin1String("kf5/parts/cantorpart")); 0059 KPluginLoader oldLoader(QLatin1String("cantorpart")); // old path 0060 KPluginFactory* factory = loader.factory(); 0061 0062 if (!factory) { // try old path 0063 WARN("Failed to load Cantor plugins; file name: " << STDSTRING(loader.fileName())) 0064 WARN("Error message: " << STDSTRING(loader.errorString())) 0065 factory = oldLoader.factory(); 0066 } 0067 if (!factory) { 0068 // we can only get to this here if we open a project having Cantor content and Cantor plugins were not found. 0069 // return false here, a proper error message will be created in load() and propagated further. 0070 WARN("Failed to load Cantor plugins; file name: " << STDSTRING(oldLoader.fileName())) 0071 WARN("Error message: " << STDSTRING(oldLoader.errorString())) 0072 m_error = i18n("Couldn't find the dynamic library 'cantorpart'. Please check your installation."); 0073 return false; 0074 } else { 0075 m_part = factory->create<KParts::ReadWritePart>(this, QVariantList() << m_backendName << QLatin1String("--noprogress")); 0076 0077 #else 0078 const auto result = KPluginFactory::instantiatePlugin<KParts::ReadWritePart>(KPluginMetaData(QStringLiteral("kf5/parts/cantorpart")), 0079 this, 0080 QVariantList() << m_backendName << QLatin1String("--noprogress")); 0081 0082 if (!result) { 0083 WARN("Could not find cantorpart part"); 0084 return false; 0085 } else { 0086 m_part = result.plugin; 0087 #endif 0088 if (!m_part) { 0089 WARN("Could not create the Cantor Part for backend " << STDSTRING(m_backendName)) 0090 m_error = i18n("Couldn't find the plugin for %1. Please check your installation.", m_backendName); 0091 return false; 0092 } 0093 0094 m_worksheetAccess = m_part->findChild<Cantor::WorksheetAccessInterface*>(Cantor::WorksheetAccessInterface::Name); 0095 if (!m_worksheetAccess) 0096 return false; 0097 0098 // load worksheet content if available 0099 if (content) 0100 m_worksheetAccess->loadWorksheetFromByteArray(content); 0101 0102 connect(m_worksheetAccess, SIGNAL(modified()), this, SLOT(modified())); 0103 0104 // Cantor's session 0105 #ifdef HAVE_CANTOR_LIBS 0106 m_session = m_worksheetAccess->session(); 0107 if (m_session) { 0108 connect(m_session, &Cantor::Session::statusChanged, this, &CantorWorksheet::statusChanged); 0109 0110 // variable model 0111 m_variableModel = m_session->variableDataModel(); 0112 connect(m_variableModel, &QAbstractItemModel::dataChanged, this, &CantorWorksheet::dataChanged); 0113 connect(m_variableModel, &QAbstractItemModel::rowsInserted, this, &CantorWorksheet::rowsInserted); 0114 connect(m_variableModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &CantorWorksheet::rowsAboutToBeRemoved); 0115 connect(m_variableModel, &QAbstractItemModel::modelReset, this, &CantorWorksheet::modelReset); 0116 } 0117 #endif 0118 0119 // default settings 0120 const KConfigGroup group = Settings::group(QStringLiteral("Settings_Notebook")); 0121 0122 // TODO: right now we don't have the direct accces to Cantor's worksheet and to all its public methods 0123 // and we need to go through the actions provided in cantor_part. 0124 //-> redesign this! expose Cantor's Worksheet directly and add more settings here. 0125 auto* action = m_part->action("enable_highlighting"); 0126 if (action) { 0127 bool value = group.readEntry(QLatin1String("SyntaxHighlighting"), false); 0128 action->setChecked(value); 0129 } 0130 0131 action = m_part->action("enable_completion"); 0132 if (action) { 0133 bool value = group.readEntry(QLatin1String("SyntaxCompletion"), false); 0134 action->setChecked(value); 0135 } 0136 0137 action = m_part->action("enable_expression_numbers"); 0138 if (action) { 0139 bool value = group.readEntry(QLatin1String("LineNumbers"), false); 0140 action->setChecked(value); 0141 } 0142 0143 action = m_part->action("enable_typesetting"); 0144 if (action) { 0145 bool value = group.readEntry(QLatin1String("LatexTypesetting"), false); 0146 action->setChecked(value); 0147 } 0148 0149 action = m_part->action("enable_animations"); 0150 if (action) { 0151 bool value = group.readEntry(QLatin1String("Animations"), false); 0152 action->setChecked(value); 0153 } 0154 0155 // bool value = group.readEntry(QLatin1String("ReevaluateEntries"), false); 0156 // value = group.readEntry(QLatin1String("AskConfirmation"), true); 0157 } 0158 0159 return true; 0160 } 0161 0162 const QString& CantorWorksheet::error() const { 0163 return m_error; 0164 } 0165 0166 // SLots 0167 void CantorWorksheet::dataChanged(const QModelIndex& index) { 0168 parseData(index.row()); 0169 } 0170 0171 void CantorWorksheet::rowsInserted(const QModelIndex& /*parent*/, int first, int last) { 0172 for (int i = first; i <= last; ++i) 0173 parseData(i); 0174 0175 project()->setChanged(true); 0176 } 0177 0178 void CantorWorksheet::parseData(int row) { 0179 const QString& name = m_variableModel->data(m_variableModel->index(row, 0)).toString(); 0180 QVariant dataValue = m_variableModel->data(m_variableModel->index(row, 1), 257); 0181 if (dataValue.isNull()) 0182 dataValue = m_variableModel->data(m_variableModel->index(row, 1)); 0183 0184 const QString& value = dataValue.toString(); 0185 VariableParser parser(m_backendName, value); 0186 0187 if (parser.isParsed()) { 0188 auto* col = child<Column>(name); 0189 if (col) { 0190 switch (parser.dataType()) { 0191 case AbstractColumn::ColumnMode::Integer: 0192 col->setColumnMode(AbstractColumn::ColumnMode::Integer); 0193 col->setIntegers(parser.integers()); 0194 break; 0195 case AbstractColumn::ColumnMode::BigInt: 0196 col->setColumnMode(AbstractColumn::ColumnMode::BigInt); 0197 col->setBigInts(parser.bigInt()); 0198 break; 0199 case AbstractColumn::ColumnMode::Double: 0200 col->setColumnMode(AbstractColumn::ColumnMode::Double); 0201 col->setValues(parser.doublePrecision()); 0202 break; 0203 case AbstractColumn::ColumnMode::Month: 0204 case AbstractColumn::ColumnMode::Day: 0205 case AbstractColumn::ColumnMode::DateTime: 0206 col->setColumnMode(AbstractColumn::ColumnMode::DateTime); 0207 col->setDateTimes(parser.dateTime()); 0208 break; 0209 case AbstractColumn::ColumnMode::Text: 0210 col->setColumnMode(AbstractColumn::ColumnMode::Text); 0211 col->setText(parser.text()); 0212 break; 0213 } 0214 } else { 0215 // Column doesn't exist for this variable yet either because it was not defined yet or 0216 // because its values was changed now to an array-like structure after the initial definition. 0217 // -> create a new column for the current variable 0218 switch (parser.dataType()) { 0219 case AbstractColumn::ColumnMode::Integer: 0220 col = new Column(name, parser.integers()); 0221 break; 0222 case AbstractColumn::ColumnMode::BigInt: 0223 col = new Column(name, parser.bigInt()); 0224 break; 0225 case AbstractColumn::ColumnMode::Double: 0226 col = new Column(name, parser.doublePrecision()); 0227 break; 0228 case AbstractColumn::ColumnMode::Month: 0229 case AbstractColumn::ColumnMode::Day: 0230 case AbstractColumn::ColumnMode::DateTime: 0231 col = new Column(name, parser.dateTime(), parser.dataType()); 0232 break; 0233 case AbstractColumn::ColumnMode::Text: 0234 col = new Column(name, parser.text()); 0235 break; 0236 } 0237 col->setUndoAware(false); 0238 col->setFixed(true); 0239 addChild(col); 0240 0241 // TODO: Cantor currently ignores the order of variables in the worksheets 0242 // and adds new variables at the last position in the model. 0243 // Fix this in Cantor and switch to insertChildBefore here later. 0244 // insertChildBefore(col, child<Column>(i)); 0245 } 0246 } else { 0247 // the already existing variable doesn't contain any numerical values -> remove it 0248 Column* col = child<Column>(name); 0249 if (col) 0250 removeChild(col); 0251 } 0252 } 0253 0254 void CantorWorksheet::modified() { 0255 project()->setChanged(true); 0256 } 0257 0258 void CantorWorksheet::modelReset() { 0259 for (auto* column : children<Column>()) 0260 column->remove(); 0261 } 0262 0263 void CantorWorksheet::rowsAboutToBeRemoved(const QModelIndex& /*parent*/, int first, int last) { 0264 for (int i = first; i <= last; ++i) { 0265 const QString& name = m_variableModel->data(m_variableModel->index(first, 0)).toString(); 0266 Column* column = child<Column>(name); 0267 if (column) 0268 column->remove(); 0269 } 0270 } 0271 0272 QList<Cantor::PanelPlugin*> CantorWorksheet::getPlugins() { 0273 if (!m_pluginsLoaded) { 0274 #ifdef HAVE_NEW_CANTOR_LIBS 0275 auto* handler = new Cantor::PanelPluginHandler(this); 0276 handler->loadPlugins(); 0277 m_plugins = handler->activePluginsForSession(m_session, Cantor::PanelPluginHandler::PanelStates()); 0278 for (auto* plugin : m_plugins) 0279 plugin->connectToShell(m_part); 0280 #else 0281 auto* handler = m_part->findChild<Cantor::PanelPluginHandler*>(QLatin1String("PanelPluginHandler")); 0282 if (!handler) { 0283 m_error = i18n("Couldn't find panel plugins. Please check your installation."); 0284 return false; 0285 } 0286 m_plugins = handler->plugins(); 0287 #endif 0288 0289 m_pluginsLoaded = true; 0290 } 0291 0292 return m_plugins; 0293 } 0294 0295 KParts::ReadWritePart* CantorWorksheet::part() { 0296 return m_part; 0297 } 0298 0299 QIcon CantorWorksheet::icon() const { 0300 #ifdef HAVE_CANTOR_LIBS 0301 if (m_session) 0302 return QIcon::fromTheme(m_session->backend()->icon()); 0303 #endif 0304 return {}; 0305 } 0306 0307 QWidget* CantorWorksheet::view() const { 0308 if (!m_partView) { 0309 m_view = new CantorWorksheetView(const_cast<CantorWorksheet*>(this)); 0310 m_view->setBaseSize(1500, 1500); 0311 m_partView = m_view; 0312 connect(this, &CantorWorksheet::viewAboutToBeDeleted, [this]() { 0313 m_view = nullptr; 0314 }); 0315 // connect(m_view, SIGNAL(statusInfo(QString)), this, SIGNAL(statusInfo(QString))); 0316 0317 // set the current path in the session to the path of the project file 0318 #ifdef HAVE_CANTOR_LIBS 0319 if (m_session) { 0320 const Project* project = const_cast<CantorWorksheet*>(this)->project(); 0321 const QString& fileName = project->fileName(); 0322 if (!fileName.isEmpty()) { 0323 QFileInfo fi(fileName); 0324 m_session->setWorksheetPath(fi.filePath()); 0325 } 0326 } 0327 #endif 0328 } 0329 return m_partView; 0330 } 0331 0332 //! Return a new context menu. 0333 /** 0334 * The caller takes ownership of the menu. 0335 */ 0336 QMenu* CantorWorksheet::createContextMenu() { 0337 QMenu* menu = AbstractPart::createContextMenu(); 0338 Q_ASSERT(menu); 0339 Q_EMIT requestProjectContextMenu(menu); 0340 return menu; 0341 } 0342 0343 void CantorWorksheet::fillColumnContextMenu(QMenu* menu, Column* column) { 0344 if (m_view) 0345 m_view->fillColumnContextMenu(menu, column); 0346 } 0347 0348 QString CantorWorksheet::backendName() { 0349 return this->m_backendName; 0350 } 0351 0352 bool CantorWorksheet::exportView() const { 0353 // TODO: file_export_pdf exists starting with Cantor 23.12, 0354 // remove this check later once 23.12 is the minimal 0355 // supported version of Cantor. 0356 auto* action = m_part->action("file_export_pdf"); 0357 if (action) { 0358 action->trigger(); 0359 return true; 0360 } else 0361 return false; 0362 } 0363 0364 bool CantorWorksheet::printView() { 0365 m_part->action("file_print")->trigger(); 0366 return true; 0367 } 0368 0369 bool CantorWorksheet::printPreview() const { 0370 m_part->action("file_print_preview")->trigger(); 0371 return true; 0372 } 0373 0374 void CantorWorksheet::evaluate() { 0375 m_part->action("evaluate_worksheet")->trigger(); 0376 } 0377 0378 void CantorWorksheet::restart() { 0379 m_part->action("restart_backend")->trigger(); 0380 } 0381 0382 // ############################################################################## 0383 // ################## Serialization/Deserialization ########################### 0384 // ############################################################################## 0385 0386 //! Save as XML 0387 void CantorWorksheet::save(QXmlStreamWriter* writer) const { 0388 writer->writeStartElement(QStringLiteral("cantorWorksheet")); 0389 writeBasicAttributes(writer); 0390 writeCommentElement(writer); 0391 0392 // general 0393 writer->writeStartElement(QStringLiteral("general")); 0394 writer->writeAttribute(QStringLiteral("backend_name"), m_backendName); 0395 // TODO: save worksheet settings 0396 writer->writeEndElement(); 0397 0398 // save the content of Cantor's worksheet 0399 QByteArray content = m_worksheetAccess->saveWorksheetToByteArray(); 0400 writer->writeStartElement(QStringLiteral("worksheet")); 0401 writer->writeAttribute(QStringLiteral("content"), QLatin1String(content.toBase64())); 0402 writer->writeEndElement(); 0403 0404 // save columns(variables) 0405 for (auto* col : children<Column>(ChildIndexFlag::IncludeHidden)) 0406 col->save(writer); 0407 0408 writer->writeEndElement(); // close "cantorWorksheet" section 0409 } 0410 0411 //! Load from XML 0412 bool CantorWorksheet::load(XmlStreamReader* reader, bool preview) { 0413 // reset the status of the reader differentiating between 0414 //"failed because of the missing CAS" and "failed because of the broken XML" 0415 reader->setFailedCASMissing(false); 0416 0417 if (!readBasicAttributes(reader)) 0418 return false; 0419 0420 QXmlStreamAttributes attribs; 0421 bool rc = false; 0422 0423 while (!reader->atEnd()) { 0424 reader->readNext(); 0425 if (reader->isEndElement() && reader->name() == QLatin1String("cantorWorksheet")) 0426 break; 0427 0428 if (!reader->isStartElement()) 0429 continue; 0430 0431 if (reader->name() == QLatin1String("comment")) { 0432 if (!readCommentElement(reader)) 0433 return false; 0434 } else if (!preview && reader->name() == QLatin1String("general")) { 0435 attribs = reader->attributes(); 0436 0437 m_backendName = attribs.value(QStringLiteral("backend_name")).toString().trimmed(); 0438 if (m_backendName.isEmpty()) 0439 reader->raiseMissingAttributeWarning(QStringLiteral("backend_name")); 0440 } else if (!preview && reader->name() == QLatin1String("worksheet")) { 0441 attribs = reader->attributes(); 0442 0443 QString str = attribs.value(QStringLiteral("content")).toString().trimmed(); 0444 if (str.isEmpty()) 0445 reader->raiseMissingAttributeWarning(QStringLiteral("content")); 0446 0447 QByteArray content = QByteArray::fromBase64(str.toLatin1()); 0448 rc = init(&content); 0449 if (!rc) { 0450 reader->raiseMissingCASWarning(m_backendName); 0451 0452 // failed to load this object because of the missing CAS plugin 0453 // and not because of the broken project XML. Set this flag to 0454 // handle this case correctly. 0455 // TODO: we also can fail in the limit in cases where Cantor's content is broken 0456 // and not because of the missing CAS plugin. This also needs to be treated accrodingly... 0457 reader->setFailedCASMissing(true); 0458 return false; 0459 } 0460 } else if (!preview && reader->name() == QLatin1String("column")) { 0461 Column* column = new Column(QString()); 0462 column->setUndoAware(false); 0463 if (!column->load(reader, preview)) { 0464 delete column; 0465 return false; 0466 } 0467 column->setFixed(true); 0468 addChild(column); 0469 } else { // unknown element 0470 reader->raiseUnknownElementWarning(); 0471 if (!reader->skipToEndElement()) 0472 return false; 0473 } 0474 } 0475 0476 return true; 0477 }