File indexing completed on 2024-05-12 03:47:27

0001 /*
0002     File                 : Project.cpp
0003     Project              : LabPlot
0004     Description          : Represents a LabPlot project.
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2021 Stefan Gerlach <stefan.gerlach@uni.kn>
0007     SPDX-FileCopyrightText: 2011-2023 Alexander Semke <alexander.semke@web.de>
0008     SPDX-FileCopyrightText: 2007-2008 Tilman Benkert <thzs@gmx.net>
0009     SPDX-FileCopyrightText: 2007 Knut Franke <knut.franke@gmx.de>
0010 
0011     SPDX-License-Identifier: GPL-2.0-or-later
0012 */
0013 #include "backend/core/Project.h"
0014 #include "backend/lib/XmlStreamReader.h"
0015 #include "backend/lib/commandtemplates.h"
0016 #include "backend/spreadsheet/Spreadsheet.h"
0017 #include "backend/worksheet/InfoElement.h"
0018 #include "backend/worksheet/Worksheet.h"
0019 #include "backend/worksheet/plots/cartesian/BarPlot.h"
0020 #include "backend/worksheet/plots/cartesian/BoxPlot.h"
0021 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
0022 #include "backend/worksheet/plots/cartesian/ErrorBar.h"
0023 #include "backend/worksheet/plots/cartesian/Histogram.h"
0024 #include "backend/worksheet/plots/cartesian/KDEPlot.h"
0025 #include "backend/worksheet/plots/cartesian/LollipopPlot.h"
0026 #include "backend/worksheet/plots/cartesian/QQPlot.h"
0027 #include "backend/worksheet/plots/cartesian/Value.h"
0028 #include "backend/worksheet/plots/cartesian/XYFitCurve.h"
0029 
0030 #ifdef HAVE_LIBORIGIN
0031 #include "backend/datasources/projects/OriginProjectParser.h"
0032 #endif
0033 
0034 #ifndef SDK
0035 #include "backend/datapicker/DatapickerCurve.h"
0036 #include "backend/datasources/LiveDataSource.h"
0037 #ifdef HAVE_MQTT
0038 #include "backend/datasources/MQTTClient.h"
0039 #endif
0040 #endif
0041 
0042 #include <KCompressionDevice>
0043 #include <KConfig>
0044 #include <KConfigGroup>
0045 #include <KLocalizedString>
0046 #include <KMessageBox>
0047 #include <kwidgetsaddons_version.h>
0048 
0049 #include <QBuffer>
0050 #include <QDateTime>
0051 #include <QFile>
0052 #include <QFileInfo>
0053 #include <QMenu>
0054 #include <QMimeData>
0055 #include <QThreadPool>
0056 #include <QUndoStack>
0057 
0058 namespace {
0059 // xmlVersion of this labplot version
0060 // the project version will compared with this.
0061 // if you make any compatibilty changes to the xmlfile
0062 // or the function in labplot, increase this number
0063 int buildXmlVersion = 11;
0064 }
0065 
0066 /**
0067  * \class Project
0068  * \ingroup core
0069  * \brief Represents a project.
0070  *
0071  * Project represents the root node of all objects created during the runtime of the program.
0072  * Manages also the undo stack.
0073  */
0074 
0075 /**
0076  * \enum Project::MdiWindowVisibility
0077  * \brief MDI subwindow visibility setting
0078  */
0079 /**
0080  * \var Project::folderOnly
0081  * \brief only show MDI windows corresponding to Parts in the current folder
0082  */
0083 /**
0084  * \var Project::foldAndSubfolders
0085  * \brief show MDI windows corresponding to Parts in the current folder and its subfolders
0086  */
0087 /**
0088  * \var Project::allMdiWindows
0089  * \brief show MDI windows for all Parts in the project simultaneously
0090  */
0091 
0092 class ProjectPrivate {
0093 public:
0094     explicit ProjectPrivate(Project* owner)
0095         : modificationTime(QDateTime::currentDateTime())
0096         , q(owner) {
0097         setVersion(QStringLiteral(LVERSION));
0098     }
0099     QString name() const {
0100         return q->name();
0101     }
0102 
0103     bool setVersion(const QString& v) const {
0104         versionString = v;
0105         auto l = v.split(QLatin1Char('.'));
0106         const int count = l.count();
0107         int major = 0;
0108         int minor = 0;
0109         int patch = 0;
0110         bool ok;
0111 
0112         if (count > 0) {
0113             major = l.at(0).toInt(&ok);
0114             if (!ok)
0115                 return false;
0116         }
0117 
0118         if (count > 1) {
0119             minor = l.at(1).toInt(&ok);
0120             if (!ok)
0121                 return false;
0122         }
0123 
0124         if (count > 2) {
0125             patch = l.at(2).toInt(&ok);
0126             if (!ok)
0127                 return false;
0128         }
0129 
0130         m_versionNumber = QT_VERSION_CHECK(major, minor, patch);
0131         return true;
0132     }
0133 
0134     static QString version() {
0135         return versionString;
0136     }
0137 
0138     static int versionNumber() {
0139         return m_versionNumber;
0140     }
0141 
0142     static int xmlVersion() {
0143         return mXmlVersion;
0144     }
0145 
0146     Project::DockVisibility dockVisibility{Project::DockVisibility::folderOnly};
0147     bool changed{false};
0148     bool aspectAddedSignalSuppressed{false};
0149 
0150     static int m_versionNumber;
0151     static int mXmlVersion;
0152     static QString versionString;
0153 
0154     QDateTime modificationTime;
0155     Project* const q;
0156     QString fileName;
0157     QString windowState;
0158     QString author;
0159     bool saveCalculations{true};
0160     QUndoStack undo_stack;
0161 };
0162 
0163 int ProjectPrivate::m_versionNumber = 0;
0164 QString ProjectPrivate::versionString = QString();
0165 int ProjectPrivate::mXmlVersion = buildXmlVersion;
0166 
0167 Project::Project()
0168     : Folder(i18n("Project"), AspectType::Project)
0169     , d_ptr(new ProjectPrivate(this)) {
0170     Q_D(Project);
0171     // load default values for name, comment and author from config
0172     KConfig config;
0173     KConfigGroup group = config.group(QStringLiteral("Project"));
0174 
0175     QString user = qEnvironmentVariable("USER"); // !Windows
0176     if (user.isEmpty())
0177         user = qEnvironmentVariable("USERNAME"); // Windows
0178     d->author = group.readEntry(QStringLiteral("Author"), user);
0179 
0180     // we don't have direct access to the members name and comment
0181     //->temporary disable the undo stack and call the setters
0182     setUndoAware(false);
0183     setIsLoading(true);
0184     setName(group.readEntry(QStringLiteral("Name"), i18n("Project")));
0185     setComment(group.readEntry(QStringLiteral("Comment"), QString()));
0186     setUndoAware(true);
0187     setIsLoading(false);
0188     d->changed = false;
0189 
0190     connect(this, &Project::aspectDescriptionChanged, this, &Project::descriptionChanged);
0191     connect(this, &Project::childAspectAdded, this, &Project::aspectAddedSlot);
0192 }
0193 
0194 Project::~Project() {
0195     Q_D(Project);
0196     Q_EMIT aboutToClose();
0197 #ifndef SDK
0198     // if the project is being closed and the live data sources still continue reading the data,
0199     // the dependent objects (columns, etc.), which are already deleted maybe here,  are still being notified about the changes.
0200     //->stop reading the live data sources prior to deleting all objects.
0201     for (auto* lds : children<LiveDataSource>())
0202         lds->pauseReading();
0203 
0204 #ifdef HAVE_MQTT
0205     for (auto* client : children<MQTTClient>())
0206         client->pauseReading();
0207 #endif
0208 #endif
0209     // if the project is being closed, in Worksheet the scene items are being removed and the selection in the view can change.
0210     // don't react on these changes since this can lead crashes (worksheet object is already in the destructor).
0211     //->notify all worksheets about the project being closed.
0212     for (auto* w : children<Worksheet>(ChildIndexFlag::Recursive))
0213         w->setIsClosing();
0214 
0215     d->undo_stack.clear();
0216     delete d;
0217 }
0218 
0219 QString Project::version() {
0220     return ProjectPrivate::version();
0221 }
0222 
0223 int Project::versionNumber() {
0224     return ProjectPrivate::versionNumber();
0225 }
0226 
0227 int Project::xmlVersion() {
0228     return ProjectPrivate::xmlVersion();
0229 }
0230 
0231 void Project::setXmlVersion(int version) {
0232     ProjectPrivate::mXmlVersion = version;
0233 }
0234 
0235 int Project::currentBuildXmlVersion() {
0236     return buildXmlVersion;
0237 }
0238 
0239 QUndoStack* Project::undoStack() const {
0240     // Q_D(const Project);
0241     return &d_ptr->undo_stack;
0242 }
0243 
0244 QMenu* Project::createContextMenu() {
0245     QMenu* menu = AbstractAspect::createContextMenu();
0246 
0247     // add close action
0248     menu->addSeparator();
0249     menu->addAction(QIcon::fromTheme(QLatin1String("document-close")), i18n("Close"), this, SIGNAL(closeRequested()));
0250 
0251     // add the actions from MainWin
0252     Q_EMIT requestProjectContextMenu(menu);
0253 
0254     return menu;
0255 }
0256 
0257 QMenu* Project::createFolderContextMenu(const Folder* folder) {
0258     QMenu* menu = const_cast<Folder*>(folder)->AbstractAspect::createContextMenu();
0259     Q_EMIT requestFolderContextMenu(folder, menu);
0260     return menu;
0261 }
0262 
0263 void Project::setDockVisibility(DockVisibility visibility) {
0264     Q_D(Project);
0265     d->dockVisibility = visibility;
0266     Q_EMIT mdiWindowVisibilityChanged();
0267 }
0268 
0269 Project::DockVisibility Project::dockVisibility() const {
0270     Q_D(const Project);
0271     return d->dockVisibility;
0272 }
0273 
0274 CLASS_D_ACCESSOR_IMPL(Project, QString, fileName, FileName, fileName)
0275 CLASS_D_ACCESSOR_IMPL(Project, QString, windowState, WindowState, windowState)
0276 BASIC_D_READER_IMPL(Project, QString, author, author)
0277 CLASS_D_ACCESSOR_IMPL(Project, QDateTime, modificationTime, ModificationTime, modificationTime)
0278 BASIC_D_READER_IMPL(Project, bool, saveCalculations, saveCalculations)
0279 
0280 STD_SETTER_CMD_IMPL_S(Project, SetAuthor, QString, author)
0281 void Project::setAuthor(const QString& author) {
0282     Q_D(Project);
0283     if (author != d->author)
0284         exec(new ProjectSetAuthorCmd(d, author, ki18n("%1: set author")));
0285 }
0286 
0287 STD_SETTER_CMD_IMPL_S(Project, SetSaveCalculations, bool, saveCalculations)
0288 void Project::setSaveCalculations(bool save) {
0289     Q_D(Project);
0290     if (save != d->saveCalculations)
0291         exec(new ProjectSetSaveCalculationsCmd(d, save, ki18n("%1: save calculation changed")));
0292 }
0293 
0294 void Project::setChanged(const bool value) {
0295     if (isLoading())
0296         return;
0297 
0298     Q_D(Project);
0299 
0300     d->changed = value;
0301 
0302     if (value)
0303         Q_EMIT changed();
0304 }
0305 
0306 void Project::setSuppressAspectAddedSignal(bool value) {
0307     Q_D(Project);
0308     d->aspectAddedSignalSuppressed = value;
0309 }
0310 
0311 bool Project::aspectAddedSignalSuppressed() const {
0312     Q_D(const Project);
0313     return d->aspectAddedSignalSuppressed;
0314 }
0315 
0316 bool Project::hasChanged() const {
0317     Q_D(const Project);
0318     return d->changed;
0319 }
0320 
0321 /*!
0322  * \brief Project::descriptionChanged
0323  * This function is called, when an object changes its name. When a column changed its name and wasn't connected
0324  * before to the curve/column(formula), this is updated in this function.
0325  * \param aspect
0326  */
0327 void Project::descriptionChanged(const AbstractAspect* aspect) {
0328     if (isLoading())
0329         return;
0330 
0331     // when the name of a column is being changed, it can match again the names being used in the plots, etc.
0332     // and we need to update the dependencies
0333     const auto* column = dynamic_cast<const AbstractColumn*>(aspect);
0334     if (column)
0335         updateColumnDependencies(column);
0336 
0337     Q_D(Project);
0338     d->changed = true;
0339     Q_EMIT changed();
0340 }
0341 
0342 /*!
0343  * \brief Project::aspectAddedSlot
0344  * When adding new columns, these should be connected to the corresponding curves
0345  * \param aspect
0346  */
0347 void Project::aspectAddedSlot(const AbstractAspect* aspect) {
0348     if (isLoading())
0349         return;
0350 
0351     if (aspect->inherits(AspectType::AbstractColumn)) {
0352         // check whether new columns were added and if yes,
0353         // update the dependencies in the project
0354         QVector<const AbstractColumn*> columns;
0355         const auto* column = static_cast<const AbstractColumn*>(aspect);
0356         if (column)
0357             columns.append(column);
0358         else {
0359             for (auto* child : aspect->children<Column>(ChildIndexFlag::Recursive))
0360                 columns.append(static_cast<const AbstractColumn*>(child));
0361         }
0362 
0363         if (!columns.isEmpty()) {
0364             // if a new column was addded, check whether the column name matches the missing
0365             // names in the plots, etc. and update the dependencies
0366             for (auto column : columns)
0367                 updateColumnDependencies(column);
0368         }
0369     } else if (aspect->inherits(AspectType::Spreadsheet)) {
0370         // if a new spreadsheet was addded, check whether the spreadsheet name matches the missing
0371         // name in a linked spreadsheet, etc. and update the dependencies
0372         const auto* newSpreadsheet = static_cast<const Spreadsheet*>(aspect);
0373         updateSpreadsheetDependencies(newSpreadsheet);
0374 
0375         connect(static_cast<const Spreadsheet*>(aspect), &Spreadsheet::aboutToResize, [this]() {
0376             const auto& wes = children<WorksheetElement>(AbstractAspect::ChildIndexFlag::Recursive);
0377             for (auto* we : wes)
0378                 we->setSuppressRetransform(true);
0379         });
0380         connect(static_cast<const Spreadsheet*>(aspect), &Spreadsheet::resizeFinished, [this]() {
0381             const auto& wes = children<WorksheetElement>(AbstractAspect::ChildIndexFlag::Recursive);
0382             for (auto* we : wes)
0383                 we->setSuppressRetransform(false);
0384         });
0385     }
0386 }
0387 
0388 void Project::updateSpreadsheetDependencies(const Spreadsheet* spreadsheet) const {
0389     const QString& spreadsheetPath = spreadsheet->path();
0390     const auto& spreadsheets = children<Spreadsheet>(ChildIndexFlag::Recursive);
0391 
0392     for (auto* sh : spreadsheets) {
0393         sh->setUndoAware(false);
0394         if (sh->linkedSpreadsheetPath() == spreadsheetPath)
0395             sh->setLinkedSpreadsheet(spreadsheet);
0396         sh->setUndoAware(true);
0397     }
0398 }
0399 
0400 /*!
0401  * in case the column \c column was added or renamed, update all dependent objects in the project accordingly.
0402  */
0403 void Project::updateColumnDependencies(const AbstractColumn* column) const {
0404     // update the dependencies in the plots
0405     const auto& plots = children<Plot>(ChildIndexFlag::Recursive);
0406     for (auto* plot : plots)
0407         plot->updateColumnDependencies(column);
0408 
0409     // update the dependencies in the column formulas
0410     const QString& columnPath = column->path();
0411     const auto& columns = children<Column>(ChildIndexFlag::Recursive);
0412     for (auto* tempColumn : columns) {
0413         for (int i = 0; i < tempColumn->formulaData().count(); i++) {
0414             auto path = tempColumn->formulaData().at(i).columnName();
0415             if (path == columnPath)
0416                 tempColumn->setFormulVariableColumn(i, const_cast<Column*>(static_cast<const Column*>(column)));
0417         }
0418     }
0419 }
0420 
0421 void Project::navigateTo(const QString& path) {
0422     Q_EMIT requestNavigateTo(path);
0423 }
0424 
0425 /*!
0426  * returns \c true if the project file \fileName has a supported format and can be openned in LabPlot directly,
0427  * returns \c false otherwise.
0428  */
0429 bool Project::isSupportedProject(const QString& fileName) {
0430     bool open = Project::isLabPlotProject(fileName);
0431 #ifdef HAVE_LIBORIGIN
0432     if (!open)
0433         open = OriginProjectParser::isOriginProject(fileName);
0434 #endif
0435 
0436 #ifdef HAVE_CANTOR_LIBS
0437     if (!open) {
0438         QFileInfo fi(fileName);
0439         open = (fi.completeSuffix() == QLatin1String("cws")) || (fi.completeSuffix() == QLatin1String("ipynb"));
0440     }
0441 #endif
0442 
0443     return open;
0444 }
0445 
0446 bool Project::isLabPlotProject(const QString& fileName) {
0447     return fileName.endsWith(QStringLiteral(".lml"), Qt::CaseInsensitive) || fileName.endsWith(QStringLiteral(".lml.gz"), Qt::CaseInsensitive)
0448         || fileName.endsWith(QStringLiteral(".lml.bz2"), Qt::CaseInsensitive) || fileName.endsWith(QStringLiteral(".lml.xz"), Qt::CaseInsensitive);
0449 }
0450 
0451 QString Project::supportedExtensions() {
0452     static const QString extensions = QStringLiteral("*.lml *.lml.gz *.lml.bz2 *.lml.xz *.LML *.LML.GZ *.LML.BZ2 *.LML.XZ");
0453     return extensions;
0454 }
0455 
0456 QVector<quintptr> Project::droppedAspects(const QMimeData* mimeData) {
0457     auto data = mimeData->data(QLatin1String("labplot-dnd"));
0458     QDataStream stream(&data, QIODevice::ReadOnly);
0459 
0460     // read the project pointer first
0461     quintptr project = 0;
0462     stream >> project;
0463 
0464     // read the pointers of the dragged aspects
0465     QVector<quintptr> vec;
0466     stream >> vec;
0467 
0468     return vec;
0469 }
0470 
0471 // ##############################################################################
0472 // ##################  Serialization/Deserialization  ###########################
0473 // ##############################################################################
0474 
0475 void Project::save(const QPixmap& thumbnail, QXmlStreamWriter* writer) {
0476     Q_D(Project);
0477     // set the version and the modification time to the current values
0478     d->setVersion(QStringLiteral(LVERSION));
0479     d->modificationTime = QDateTime::currentDateTime();
0480 
0481     writer->setAutoFormatting(true);
0482     writer->writeStartDocument();
0483     writer->writeDTD(QStringLiteral("<!DOCTYPE LabPlotXML>"));
0484 
0485     writer->writeStartElement(QStringLiteral("project"));
0486     writer->writeAttribute(QStringLiteral("version"), version());
0487     writer->writeAttribute(QStringLiteral("xmlVersion"), QString::number(buildXmlVersion));
0488     writer->writeAttribute(QStringLiteral("modificationTime"), modificationTime().toString(QStringLiteral("yyyy-dd-MM hh:mm:ss:zzz")));
0489     writer->writeAttribute(QStringLiteral("author"), author());
0490     writer->writeAttribute(QStringLiteral("saveCalculations"), QString::number(d->saveCalculations));
0491     writer->writeAttribute(QStringLiteral("windowState"), d->windowState);
0492 
0493     QString image;
0494     if (!thumbnail.isNull()) {
0495         QByteArray bArray;
0496         QBuffer buffer(&bArray);
0497         buffer.open(QIODevice::WriteOnly);
0498         QPixmap scaledThumbnail = thumbnail.scaled(512, 512, Qt::KeepAspectRatio);
0499         scaledThumbnail.save(&buffer, "JPEG");
0500         image = QString::fromLatin1(bArray.toBase64().data());
0501     }
0502 
0503     writer->writeAttribute(QStringLiteral("thumbnail"), image);
0504     writeBasicAttributes(writer);
0505     writeCommentElement(writer);
0506     save(writer);
0507 }
0508 
0509 /**
0510  * \brief Save as XML
0511  */
0512 void Project::save(QXmlStreamWriter* writer) const {
0513     // save all children
0514     const auto& children = this->children<AbstractAspect>(ChildIndexFlag::IncludeHidden);
0515     for (auto* child : children) {
0516         writer->writeStartElement(QStringLiteral("child_aspect"));
0517         child->save(writer);
0518         writer->writeEndElement();
0519     }
0520 
0521     // save the state of the views (visible, maximized/minimized/geometry)
0522     // and the state of the project explorer (expanded items, currently selected item)
0523     Q_EMIT requestSaveState(writer);
0524 
0525     writer->writeEndElement();
0526     writer->writeEndDocument();
0527     Q_EMIT saved();
0528 }
0529 
0530 bool Project::load(const QString& filename, bool preview) {
0531     setFileName(filename);
0532     DEBUG(Q_FUNC_INFO << ", LOADING file " << STDSTRING(filename))
0533     QIODevice* file;
0534     if (filename.endsWith(QLatin1String(".lml"), Qt::CaseInsensitive)) {
0535         DEBUG(Q_FUNC_INFO << ", filename ends with .lml")
0536 
0537         // check compression
0538         file = new QFile(filename);
0539         if (!file->open(QIODevice::ReadOnly)) {
0540             KMessageBox::error(nullptr, i18n("Sorry. Could not open file for reading."));
0541             delete file;
0542             return false;
0543         }
0544         QDataStream in(file);
0545         quint16 magic;
0546         in >> magic;
0547         file->close();
0548         delete file;
0549 
0550         if (!magic) {
0551             KMessageBox::error(nullptr, i18n("The project file is empty."), i18n("Error opening project"));
0552             return false;
0553         }
0554 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
0555         QDEBUG(Q_FUNC_INFO << ", got magic: " << magic << Qt::hex << "0x" << magic)
0556 #else
0557         QDEBUG(Q_FUNC_INFO << ", got magic: " << magic << hex << "0x" << magic)
0558 #endif
0559 
0560         if (magic == 0xfd37) // XZ compressed data
0561             file = new KCompressionDevice(filename, KCompressionDevice::Xz);
0562         else // gzip or not compressed data
0563             file = new KCompressionDevice(filename, KCompressionDevice::GZip);
0564     } else { // opens filename using file ending
0565         // DEBUG(Q_FUNC_INFO << ", filename does not end with .lml. Guessing by extension")
0566         file = new KCompressionDevice(filename);
0567         DEBUG(Q_FUNC_INFO << ", found compression type " << ((KCompressionDevice*)file)->compressionType())
0568     }
0569 
0570     if (!file)
0571         file = new QFile(filename);
0572 
0573     if (!file->open(QIODevice::ReadOnly)) {
0574         KMessageBox::error(nullptr, i18n("Sorry. Could not open file for reading."));
0575         return false;
0576     }
0577 
0578     char c;
0579     bool rc = file->getChar(&c);
0580     if (!rc) {
0581         KMessageBox::error(nullptr, i18n("The project file is empty."), i18n("Error opening project"));
0582         file->close();
0583         delete file;
0584         return false;
0585     }
0586     file->seek(0);
0587 
0588     // parse XML
0589     XmlStreamReader reader(file);
0590     setIsLoading(true);
0591     ProjectPrivate::mXmlVersion =
0592         0; // set the version temporarily to 0, the actual project version will be read in the file, if available, and used in load() functions
0593     rc = this->load(&reader, preview);
0594     ProjectPrivate::mXmlVersion = buildXmlVersion; // set the version back to the current XML version
0595     setIsLoading(false);
0596     if (rc == false) {
0597         RESET_CURSOR;
0598         QString msg = reader.errorString();
0599         if (msg.isEmpty())
0600             msg = i18n("Unknown error when opening the project %1.", filename);
0601         KMessageBox::error(nullptr, msg, i18n("Error when opening the project"));
0602         file->close();
0603         delete file;
0604         return false;
0605     }
0606 
0607     if (reader.hasWarnings()) {
0608         qWarning("The following problems occurred when loading the project file:");
0609         const QStringList& warnings = reader.warningStrings();
0610         for (const auto& str : warnings)
0611             qWarning() << qUtf8Printable(str);
0612 
0613         // TODO: show warnings in a kind of "log window" but not in message box
0614         //          KMessageBox::error(this, msg, i18n("Project loading partly failed"));
0615     }
0616 
0617     if (reader.hasMissingCASWarnings()) {
0618         RESET_CURSOR;
0619 
0620         const QString& msg = i18n(
0621             "The project has content written with %1. "
0622             "Your installation of LabPlot lacks the support for it.\n\n "
0623             "You won't be able to see this part of the project. "
0624             "If you modify and save the project, the CAS content will be lost.\n\n"
0625             "Do you want to continue?",
0626             reader.missingCASWarning());
0627 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
0628         auto status = KMessageBox::warningTwoActions(nullptr, msg, i18n("Missing Support for CAS"), KStandardGuiItem::cont(), KStandardGuiItem::cancel());
0629         if (status == KMessageBox::SecondaryAction) {
0630 #else
0631         auto status = KMessageBox::warningYesNo(nullptr, msg, i18n("Missing Support for CAS"));
0632         if (status == KMessageBox::No) {
0633 #endif
0634             file->close();
0635             delete file;
0636             return false;
0637         }
0638     }
0639 
0640     file->close();
0641     delete file;
0642 
0643     return true;
0644 }
0645 
0646 /**
0647  * \brief Load from XML
0648  */
0649 bool Project::load(XmlStreamReader* reader, bool preview) {
0650     Q_D(Project);
0651     while (!(reader->isStartDocument() || reader->atEnd()))
0652         reader->readNext();
0653 
0654     bool stateAttributeFound = false;
0655     if (!(reader->atEnd())) {
0656         if (!reader->skipToNextTag())
0657             return false;
0658 
0659         if (reader->name() == QLatin1String("project")) {
0660             QString version = reader->attributes().value(QStringLiteral("version")).toString();
0661             if (version.isEmpty())
0662                 reader->raiseWarning(i18n("Attribute 'version' is missing."));
0663             else
0664                 d->setVersion(version);
0665 
0666             QString c = reader->attributes().value(QStringLiteral("xmlVersion")).toString();
0667             if (c.isEmpty())
0668                 d->mXmlVersion = 0;
0669             else
0670                 d->mXmlVersion = c.toInt();
0671 
0672             if (!readBasicAttributes(reader))
0673                 return false;
0674             if (!readProjectAttributes(reader))
0675                 return false;
0676 
0677             while (!reader->atEnd()) {
0678                 reader->readNext();
0679 
0680                 if (reader->isEndElement())
0681                     break;
0682 
0683                 if (reader->isStartElement()) {
0684                     if (reader->name() == QLatin1String("comment")) {
0685                         if (!readCommentElement(reader))
0686                             return false;
0687                     } else if (reader->name() == QLatin1String("child_aspect")) {
0688                         if (!readChildAspectElement(reader, preview))
0689                             return false;
0690                     } else if (reader->name() == QLatin1String("state")) {
0691                         // load the state of the views (visible, maximized/minimized/geometry)
0692                         // and the state of the project explorer (expanded items, currently selected item).
0693                         //"state" is read at the very end of XML, restore the pointers here so the current index
0694                         // can be properly selected in ProjectExplorer after requestLoadState() is called.
0695                         // Restore pointers and retransform elements before loading the state,
0696                         // otherwise curves don't have column pointers assigned and therefore calculations
0697                         // in the docks might be wrong
0698                         stateAttributeFound = true;
0699                         restorePointers(this);
0700                         if (!preview)
0701                             retransformElements(this);
0702                         Q_EMIT requestLoadState(reader);
0703                     } else {
0704                         if (!preview)
0705                             reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString()));
0706                         if (!reader->skipToEndElement())
0707                             return false;
0708                     }
0709                 }
0710             }
0711         } else // no project element
0712             reader->raiseError(i18n("no project element found"));
0713     } else // no start document
0714         reader->raiseError(i18n("no valid XML document found"));
0715 
0716     if (!stateAttributeFound) {
0717         // No state attribute available, means no project explorer reacted on the signal
0718         restorePointers(this);
0719         if (!preview)
0720             retransformElements(this);
0721     }
0722 
0723     Q_EMIT loaded();
0724 
0725     return !reader->hasError();
0726 }
0727 
0728 void Project::retransformElements(AbstractAspect* aspect) {
0729     bool hasChildren = aspect->childCount<AbstractAspect>();
0730 
0731     // recalculate all analysis curves if the results of the calculations were not saved in the project
0732     if (!aspect->project()->saveCalculations()) {
0733         for (auto* curve : aspect->children<XYAnalysisCurve>(ChildIndexFlag::Recursive))
0734             curve->recalculate();
0735     }
0736 
0737     // set "isLoading" to false for all worksheet elements
0738     for (auto* child : aspect->children<WorksheetElement>(ChildIndexFlag::Recursive | ChildIndexFlag::IncludeHidden))
0739         child->setIsLoading(false);
0740 
0741     for (auto& column : aspect->project()->children<Column>(ChildIndexFlag::Recursive))
0742         column->setIsLoading(false);
0743 
0744     // all data was read:
0745     // call retransform() to every element
0746     if (hasChildren && aspect->type() == AspectType::Worksheet) {
0747         const auto& elements = aspect->children<WorksheetElement>(ChildIndexFlag::Recursive | ChildIndexFlag::IncludeHidden);
0748         for (auto* e : elements)
0749             e->retransform();
0750     } else if (hasChildren && aspect->type() != AspectType::CartesianPlot) {
0751         for (const auto* w : aspect->children<Worksheet>(ChildIndexFlag::Recursive | ChildIndexFlag::IncludeHidden)) {
0752             // retransform all elements in the worksheet (labels, images, plots)
0753             // the plots will then recursive retransform the childs of them
0754             const auto& elements = w->children<WorksheetElement>(ChildIndexFlag::IncludeHidden);
0755             for (auto* e : elements)
0756                 e->retransform();
0757         }
0758     } else {
0759         QVector<CartesianPlot*> plots;
0760         if (aspect->type() == AspectType::CartesianPlot)
0761             plots << static_cast<CartesianPlot*>(aspect);
0762         else if (dynamic_cast<Plot*>(aspect))
0763             plots << static_cast<CartesianPlot*>(aspect->parentAspect());
0764 
0765         if (!plots.isEmpty()) {
0766             for (auto* plot : plots)
0767                 plot->retransform();
0768         } else {
0769             // worksheet element is being copied alone without its parent plot object
0770             // so the plot retransform is not called above. we need to call it for the aspect.
0771             auto* e = dynamic_cast<WorksheetElement*>(aspect);
0772             if (e)
0773                 e->retransform();
0774         }
0775     }
0776 
0777 #ifndef SDK
0778     QVector<XYCurve*> curves;
0779     if (hasChildren)
0780         curves = aspect->children<XYCurve>(ChildIndexFlag::Recursive);
0781     // all data was read in live-data sources:
0782     // call CartesianPlot::dataChanged() to notify affected plots about the new data.
0783     // this needs to be done here since in LiveDataSource::finalizeImport() called above
0784     // where the data is read the column pointers are not restored yes in curves.
0785     QVector<CartesianPlot*> plots;
0786     for (auto* source : aspect->children<LiveDataSource>(ChildIndexFlag::Recursive)) {
0787         for (int n = 0; n < source->columnCount(); ++n) {
0788             Column* column = source->column(n);
0789 
0790             // determine the plots where the column is consumed
0791             for (const auto* curve : curves) {
0792                 if (curve->xColumn() == column || curve->yColumn() == column) {
0793                     auto* plot = static_cast<CartesianPlot*>(curve->parentAspect());
0794                     if (plots.indexOf(plot) == -1) {
0795                         plots << plot;
0796                         plot->setSuppressRetransform(true);
0797                     }
0798                 }
0799             }
0800 
0801             column->setChanged();
0802         }
0803     }
0804 #endif
0805 
0806     // loop over all affected plots and retransform them
0807     for (auto* plot : plots) {
0808         plot->setSuppressRetransform(false);
0809         plot->dataChanged(-1, -1);
0810     }
0811 }
0812 
0813 /*!
0814  * this function is used to restore the pointers to the columns in xy-curves etc.
0815  * from the stored column paths. This function is called after the project was loaded
0816  * and when an aspect is being pasted. In both cases we deserialized from XML and need
0817  * to restore the pointers.
0818  */
0819 void Project::restorePointers(AbstractAspect* aspect) {
0820     // wait until all columns are decoded from base64-encoded data
0821     QThreadPool::globalInstance()->waitForDone();
0822 
0823     bool hasChildren = aspect->childCount<AbstractAspect>();
0824     const auto& columns = aspect->project()->children<Column>(ChildIndexFlag::Recursive);
0825     const auto& histograms = aspect->project()->children<Histogram>(ChildIndexFlag::Recursive); // needed for fit curves only. why a better implementation?
0826 
0827 #ifndef SDK
0828     // LiveDataSource:
0829     // call finalizeLoad() to replace relative with absolute paths if required
0830     // and to create columns during the initial read
0831     for (auto* source : aspect->children<LiveDataSource>(ChildIndexFlag::Recursive)) {
0832         if (!source)
0833             continue;
0834         source->finalizeLoad();
0835     }
0836 #endif
0837 
0838     // xy-curves
0839     //  cannot be removed by the column observer, because it does not react
0840     //  on curve changes
0841     QVector<XYCurve*> curves;
0842     if (hasChildren)
0843         curves = aspect->children<XYCurve>(ChildIndexFlag::Recursive);
0844     else if (aspect->inherits(AspectType::XYCurve) || aspect->inherits(AspectType::XYAnalysisCurve))
0845         // the object doesn't have any children -> one single aspect is being pasted.
0846         // check whether the object being pasted is a XYCurve and add it to the
0847         // list of curves to be retransformed
0848         curves << static_cast<XYCurve*>(aspect);
0849 
0850     for (auto* curve : qAsConst(curves)) {
0851         if (!curve)
0852             continue;
0853         curve->setSuppressRetransform(true);
0854 
0855         auto* analysisCurve = dynamic_cast<XYAnalysisCurve*>(curve);
0856         if (analysisCurve) {
0857             RESTORE_COLUMN_POINTER(analysisCurve, xDataColumn, XDataColumn);
0858             RESTORE_COLUMN_POINTER(analysisCurve, yDataColumn, YDataColumn);
0859             RESTORE_COLUMN_POINTER(analysisCurve, y2DataColumn, Y2DataColumn);
0860             auto* fitCurve = dynamic_cast<XYFitCurve*>(curve);
0861             if (fitCurve) {
0862                 RESTORE_COLUMN_POINTER(fitCurve, xErrorColumn, XErrorColumn);
0863                 RESTORE_COLUMN_POINTER(fitCurve, yErrorColumn, YErrorColumn);
0864                 RESTORE_POINTER(fitCurve, dataSourceHistogram, DataSourceHistogram, Histogram, histograms);
0865             }
0866         } else {
0867             RESTORE_COLUMN_POINTER(curve, xColumn, XColumn);
0868             RESTORE_COLUMN_POINTER(curve, yColumn, YColumn);
0869             RESTORE_COLUMN_POINTER(curve, valuesColumn, ValuesColumn);
0870             RESTORE_COLUMN_POINTER(curve->xErrorBar(), plusColumn, PlusColumn);
0871             RESTORE_COLUMN_POINTER(curve->xErrorBar(), minusColumn, MinusColumn);
0872             RESTORE_COLUMN_POINTER(curve->yErrorBar(), plusColumn, PlusColumn);
0873             RESTORE_COLUMN_POINTER(curve->yErrorBar(), minusColumn, MinusColumn);
0874         }
0875 
0876         if (analysisCurve)
0877             RESTORE_POINTER(analysisCurve, dataSourceCurve, DataSourceCurve, XYCurve, curves);
0878 
0879         curve->setSuppressRetransform(false);
0880     }
0881 
0882     // assign to all markers the curves they need
0883     QVector<InfoElement*> elements;
0884     if (aspect->type() == AspectType::InfoElement) // check for the type first. InfoElement has children, but they are not relevant here
0885         elements << static_cast<InfoElement*>(aspect);
0886     else if (hasChildren)
0887         elements = aspect->children<InfoElement>(ChildIndexFlag::Recursive);
0888 
0889     for (auto* element : elements)
0890         element->assignCurve(curves);
0891 
0892     // axes
0893     QVector<Axis*> axes;
0894     if (hasChildren)
0895         axes = aspect->children<Axis>(ChildIndexFlag::Recursive);
0896     else if (aspect->type() == AspectType::Axis)
0897         axes << static_cast<Axis*>(aspect);
0898 
0899     for (auto* axis : axes) {
0900         if (!axis)
0901             continue;
0902         RESTORE_COLUMN_POINTER(axis, majorTicksColumn, MajorTicksColumn);
0903         RESTORE_COLUMN_POINTER(axis, minorTicksColumn, MinorTicksColumn);
0904         RESTORE_COLUMN_POINTER(axis, labelsTextColumn, LabelsTextColumn);
0905     }
0906 
0907     // histograms
0908     QVector<Histogram*> hists;
0909     if (hasChildren)
0910         hists = aspect->children<Histogram>(ChildIndexFlag::Recursive);
0911     else if (aspect->type() == AspectType::Histogram)
0912         hists << static_cast<Histogram*>(aspect);
0913 
0914     for (auto* hist : hists) {
0915         if (!hist)
0916             continue;
0917         RESTORE_COLUMN_POINTER(hist, dataColumn, DataColumn);
0918         auto* value = hist->value();
0919         RESTORE_COLUMN_POINTER(value, column, Column);
0920         RESTORE_COLUMN_POINTER(hist->errorBar(), plusColumn, PlusColumn);
0921         RESTORE_COLUMN_POINTER(hist->errorBar(), minusColumn, MinusColumn);
0922     }
0923 
0924     // QQ-plots
0925     QVector<QQPlot*> qqPlots;
0926     if (hasChildren)
0927         qqPlots = aspect->children<QQPlot>(ChildIndexFlag::Recursive);
0928     else if (aspect->type() == AspectType::QQPlot)
0929         qqPlots << static_cast<QQPlot*>(aspect);
0930 
0931     for (auto* plot : qqPlots) {
0932         if (!plot)
0933             continue;
0934         RESTORE_COLUMN_POINTER(plot, dataColumn, DataColumn);
0935     }
0936 
0937     // KDE-plots
0938     QVector<KDEPlot*> kdePlots;
0939     if (hasChildren)
0940         kdePlots = aspect->children<KDEPlot>(ChildIndexFlag::Recursive);
0941     else if (aspect->type() == AspectType::KDEPlot)
0942         kdePlots << static_cast<KDEPlot*>(aspect);
0943 
0944     for (auto* plot : kdePlots) {
0945         if (!plot)
0946             continue;
0947         RESTORE_COLUMN_POINTER(plot, dataColumn, DataColumn);
0948     }
0949 
0950     // box plots
0951     QVector<BoxPlot*> boxPlots;
0952     if (hasChildren)
0953         boxPlots = aspect->children<BoxPlot>(ChildIndexFlag::Recursive);
0954     else if (aspect->type() == AspectType::BoxPlot)
0955         boxPlots << static_cast<BoxPlot*>(aspect);
0956 
0957     for (auto* boxPlot : boxPlots) {
0958         if (!boxPlot)
0959             continue;
0960 
0961         // initialize the array for the column pointers
0962         int count = boxPlot->dataColumnPaths().count();
0963         QVector<const AbstractColumn*> dataColumns;
0964         dataColumns.resize(count);
0965 
0966         // restore the pointers
0967         for (int i = 0; i < count; ++i) {
0968             dataColumns[i] = nullptr;
0969             const auto& path = boxPlot->dataColumnPaths().at(i);
0970             for (Column* column : columns) {
0971                 if (!column)
0972                     continue;
0973                 if (column->path() == path) {
0974                     dataColumns[i] = column;
0975                     break;
0976                 }
0977             }
0978         }
0979 
0980         boxPlot->setDataColumns(dataColumns);
0981     }
0982 
0983     // bar plots
0984     QVector<BarPlot*> barPlots;
0985     if (hasChildren)
0986         barPlots = aspect->children<BarPlot>(ChildIndexFlag::Recursive);
0987     else if (aspect->type() == AspectType::BarPlot)
0988         barPlots << static_cast<BarPlot*>(aspect);
0989 
0990     for (auto* barPlot : barPlots) {
0991         if (!barPlot)
0992             continue;
0993 
0994         // initialize the array for the column pointers
0995         int count = barPlot->dataColumnPaths().count();
0996         QVector<const AbstractColumn*> dataColumns;
0997         dataColumns.resize(count);
0998 
0999         // restore the pointers
1000         for (int i = 0; i < count; ++i) {
1001             dataColumns[i] = nullptr;
1002             const auto& path = barPlot->dataColumnPaths().at(i);
1003             for (Column* column : columns) {
1004                 if (!column)
1005                     continue;
1006                 if (column->path() == path) {
1007                     dataColumns[i] = column;
1008                     break;
1009                 }
1010             }
1011         }
1012 
1013         barPlot->setDataColumns(dataColumns);
1014 
1015         RESTORE_COLUMN_POINTER(barPlot, xColumn, XColumn);
1016     }
1017 
1018     // lollipop plots
1019     QVector<LollipopPlot*> lollipopPlots;
1020     if (hasChildren)
1021         lollipopPlots = aspect->children<LollipopPlot>(ChildIndexFlag::Recursive);
1022     else if (aspect->type() == AspectType::BoxPlot)
1023         lollipopPlots << static_cast<LollipopPlot*>(aspect);
1024 
1025     for (auto* lollipopPlot : lollipopPlots) {
1026         if (!lollipopPlot)
1027             continue;
1028 
1029         // initialize the array for the column pointers
1030         int count = lollipopPlot->dataColumnPaths().count();
1031         QVector<const AbstractColumn*> dataColumns;
1032         dataColumns.resize(count);
1033 
1034         // restore the pointers
1035         for (int i = 0; i < count; ++i) {
1036             dataColumns[i] = nullptr;
1037             const auto& path = lollipopPlot->dataColumnPaths().at(i);
1038             for (Column* column : columns) {
1039                 if (!column)
1040                     continue;
1041                 if (column->path() == path) {
1042                     dataColumns[i] = column;
1043                     break;
1044                 }
1045             }
1046         }
1047 
1048         lollipopPlot->setDataColumns(dataColumns);
1049 
1050         RESTORE_COLUMN_POINTER(lollipopPlot, xColumn, XColumn);
1051     }
1052 
1053     // data picker curves
1054 #ifndef SDK
1055     QVector<DatapickerCurve*> dataPickerCurves;
1056     if (hasChildren)
1057         dataPickerCurves = aspect->children<DatapickerCurve>(ChildIndexFlag::Recursive);
1058     else if (aspect->type() == AspectType::DatapickerCurve)
1059         dataPickerCurves << static_cast<DatapickerCurve*>(aspect);
1060 
1061     for (auto* dataPickerCurve : dataPickerCurves) {
1062         if (!dataPickerCurve)
1063             continue;
1064         RESTORE_COLUMN_POINTER(dataPickerCurve, posXColumn, PosXColumn);
1065         RESTORE_COLUMN_POINTER(dataPickerCurve, posYColumn, PosYColumn);
1066         RESTORE_COLUMN_POINTER(dataPickerCurve, plusDeltaXColumn, PlusDeltaXColumn);
1067         RESTORE_COLUMN_POINTER(dataPickerCurve, minusDeltaXColumn, MinusDeltaXColumn);
1068         RESTORE_COLUMN_POINTER(dataPickerCurve, plusDeltaYColumn, PlusDeltaYColumn);
1069         RESTORE_COLUMN_POINTER(dataPickerCurve, minusDeltaYColumn, MinusDeltaYColumn);
1070     }
1071 #endif
1072 
1073     // spreadsheet
1074     QVector<Spreadsheet*> spreadsheets;
1075     if (hasChildren)
1076         spreadsheets = aspect->children<Spreadsheet>(ChildIndexFlag::Recursive);
1077     for (auto* linkingSpreadsheet : spreadsheets) {
1078         if (!linkingSpreadsheet->linking())
1079             continue;
1080         for (const auto* toLinkedSpreadsheet : spreadsheets) {
1081             if (linkingSpreadsheet->linkedSpreadsheetPath() == toLinkedSpreadsheet->path()) {
1082                 linkingSpreadsheet->setLinkedSpreadsheet(toLinkedSpreadsheet, true);
1083             }
1084         }
1085     }
1086 
1087     // if a column was calculated via a formula, restore the pointers to the variable columns defining the formula
1088     for (auto* col : columns) {
1089         for (Column* c : columns)
1090             col->setFormulaVariableColumn(c);
1091         col->finalizeLoad();
1092     }
1093 
1094     if (hasChildren && Project::xmlVersion() < 9) {
1095         const auto& plots = aspect->children<CartesianPlot>(ChildIndexFlag::Recursive);
1096         for (const auto* plot : plots) {
1097             const auto& axes = plot->children<Axis>(ChildIndexFlag::Recursive);
1098             for (auto* axis : axes) {
1099                 const auto cSystem = plot->coordinateSystem(axis->coordinateSystemIndex());
1100                 RangeT::Scale scale{RangeT::Scale::Linear};
1101                 switch (axis->orientation()) {
1102                 case Axis::Orientation::Horizontal:
1103                     scale = plot->range(Dimension::X, cSystem->index(Dimension::X)).scale();
1104                     break;
1105                 case Axis::Orientation::Vertical:
1106                     scale = plot->range(Dimension::Y, cSystem->index(Dimension::Y)).scale();
1107                     break;
1108                 case Axis::Orientation::Both:
1109                     continue;
1110                 }
1111                 if (axis->scale() == scale) {
1112                     axis->setUndoAware(false);
1113                     axis->setRangeScale(true);
1114                     axis->setUndoAware(true);
1115                 }
1116             }
1117         }
1118     }
1119 }
1120 
1121 bool Project::readProjectAttributes(XmlStreamReader* reader) {
1122     Q_D(Project);
1123     const auto& attribs = reader->attributes();
1124     auto str = attribs.value(QStringLiteral("modificationTime")).toString();
1125     auto modificationTime = QDateTime::fromString(str, QStringLiteral("yyyy-dd-MM hh:mm:ss:zzz"));
1126     if (str.isEmpty() || !modificationTime.isValid()) {
1127         reader->raiseWarning(i18n("Invalid project modification time. Using current time."));
1128         d->modificationTime = QDateTime::currentDateTime();
1129     } else
1130         d->modificationTime = modificationTime;
1131 
1132     d->author = attribs.value(QStringLiteral("author")).toString();
1133     d->saveCalculations = attribs.value(QStringLiteral("saveCalculations")).toInt();
1134     d->windowState = attribs.value(QStringLiteral("windowState")).toString();
1135 
1136     return true;
1137 }