File indexing completed on 2024-05-12 15:26:40

0001 /***************************************************************************
0002     File                 : Project.cpp
0003     Project              : LabPlot
0004     Description          : Represents a LabPlot project.
0005     --------------------------------------------------------------------
0006     Copyright            : (C) 2011-2020 Alexander Semke (alexander.semke@web.de)
0007     Copyright            : (C) 2007-2008 Tilman Benkert (thzs@gmx.net)
0008     Copyright            : (C) 2007 Knut Franke (knut.franke@gmx.de)
0009  ***************************************************************************/
0010 
0011 /***************************************************************************
0012  *                                                                         *
0013  *  This program is free software; you can redistribute it and/or modify   *
0014  *  it under the terms of the GNU General Public License as published by   *
0015  *  the Free Software Foundation; either version 2 of the License, or      *
0016  *  (at your option) any later version.                                    *
0017  *                                                                         *
0018  *  This program is distributed in the hope that it will be useful,        *
0019  *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
0020  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
0021  *  GNU General Public License for more details.                           *
0022  *                                                                         *
0023  *   You should have received a copy of the GNU General Public License     *
0024  *   along with this program; if not, write to the Free Software           *
0025  *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
0026  *   Boston, MA  02110-1301  USA                                           *
0027  *                                                                         *
0028  ***************************************************************************/
0029 #include "backend/core/Project.h"
0030 #include "backend/lib/XmlStreamReader.h"
0031 #include "backend/datasources/LiveDataSource.h"
0032 #include "backend/spreadsheet/Spreadsheet.h"
0033 #include "backend/worksheet/Worksheet.h"
0034 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
0035 #include "backend/worksheet/plots/cartesian/Histogram.h"
0036 #include "backend/worksheet/plots/cartesian/XYEquationCurve.h"
0037 #include "backend/worksheet/plots/cartesian/XYFitCurve.h"
0038 #include "backend/worksheet/plots/cartesian/Axis.h"
0039 #include "backend/datapicker/DatapickerCurve.h"
0040 #ifdef HAVE_MQTT
0041 #include "backend/datasources/MQTTClient.h"
0042 #endif
0043 
0044 #include <QDateTime>
0045 #include <QFile>
0046 #include <QMenu>
0047 #include <QMimeData>
0048 #include <QThreadPool>
0049 #include <QUndoStack>
0050 #include <QBuffer>
0051 
0052 #include <KConfig>
0053 #include <KConfigGroup>
0054 #include <KFilterDev>
0055 #include <KLocalizedString>
0056 #include <KMessageBox>
0057 
0058 /**
0059  * \class Project
0060  * \ingroup core
0061  * \brief Represents a project.
0062  *
0063  * Project represents the root node of all objects created during the runtime of the program.
0064  * Manages also the undo stack.
0065  */
0066 
0067 /**
0068  * \enum Project::MdiWindowVisibility
0069  * \brief MDI subwindow visibility setting
0070  */
0071 /**
0072  * \var Project::folderOnly
0073  * \brief only show MDI windows corresponding to Parts in the current folder
0074  */
0075 /**
0076  * \var Project::foldAndSubfolders
0077  * \brief show MDI windows corresponding to Parts in the current folder and its subfolders
0078  */
0079 /**
0080  * \var Project::allMdiWindows
0081  * \brief show MDI windows for all Parts in the project simultaneously
0082  */
0083 
0084 class Project::Private {
0085 public:
0086     Private(Project* owner) :
0087         version(LVERSION),
0088         author(QString(qgetenv("USER"))),
0089         modificationTime(QDateTime::currentDateTime()),
0090         q(owner) {
0091     }
0092     QString name() const  {
0093         return q->name();
0094     }
0095 
0096     QUndoStack undo_stack;
0097     MdiWindowVisibility mdiWindowVisibility{Project::MdiWindowVisibility::folderOnly};
0098     QString fileName;
0099     QString version;
0100     QString author;
0101     QDateTime modificationTime;
0102     bool changed{false};
0103     bool aspectAddedSignalSuppressed{false};
0104     Project* const q;
0105 };
0106 
0107 Project::Project() : Folder(i18n("Project"), AspectType::Project), d(new Private(this)) {
0108     //load default values for name, comment and author from config
0109     KConfig config;
0110     KConfigGroup group = config.group("Project");
0111 
0112     d->author = group.readEntry("Author", QString());
0113 
0114     //we don't have direct access to the members name and comment
0115     //->temporary disable the undo stack and call the setters
0116     setUndoAware(false);
0117     setIsLoading(true);
0118     setName(group.readEntry("Name", i18n("Project")));
0119     setComment(group.readEntry("Comment", QString()));
0120     setUndoAware(true);
0121     setIsLoading(false);
0122     d->changed = false;
0123 
0124     connect(this, &Project::aspectDescriptionChanged,this, &Project::descriptionChanged);
0125     connect(this, &Project::aspectAdded,this, &Project::aspectAddedSlot);
0126 }
0127 
0128 Project::~Project() {
0129     //if the project is being closed and the live data sources still continue reading the data,
0130     //the dependent objects (columns, etc.), which are already deleted maybe here,  are still being notified about the changes.
0131     //->stop reading the live data sources prior to deleting all objects.
0132     for (auto* lds : children<LiveDataSource>())
0133         lds->pauseReading();
0134 
0135 #ifdef HAVE_MQTT
0136     for (auto* client : children<MQTTClient>())
0137         client->pauseReading();
0138 #endif
0139 
0140     //if the project is being closed, in Worksheet the scene items are being removed and the selection in the view can change.
0141     //don't react on these changes since this can lead crashes (worksheet object is already in the destructor).
0142     //->notify all worksheets about the project being closed.
0143     for (auto* w : children<Worksheet>(ChildIndexFlag::Recursive))
0144         w->setIsClosing();
0145 
0146     d->undo_stack.clear();
0147     delete d;
0148 }
0149 
0150 QUndoStack* Project::undoStack() const {
0151     return &d->undo_stack;
0152 }
0153 
0154 QMenu* Project::createContextMenu() {
0155     QMenu* menu = AbstractAspect::createContextMenu();
0156 
0157     //add close action
0158     menu->addSeparator();
0159     menu->addAction(QIcon::fromTheme(QLatin1String("document-close")), i18n("Close"), this, SIGNAL(closeRequested()));
0160 
0161     //add the actions from MainWin
0162     emit requestProjectContextMenu(menu);
0163 
0164     return menu;
0165 }
0166 
0167 QMenu* Project::createFolderContextMenu(const Folder* folder) {
0168     QMenu* menu = const_cast<Folder*>(folder)->AbstractAspect::createContextMenu();
0169     emit requestFolderContextMenu(folder, menu);
0170     return menu;
0171 }
0172 
0173 void Project::setMdiWindowVisibility(MdiWindowVisibility visibility) {
0174     d->mdiWindowVisibility = visibility;
0175     emit mdiWindowVisibilityChanged();
0176 }
0177 
0178 Project::MdiWindowVisibility Project::mdiWindowVisibility() const {
0179     return d->mdiWindowVisibility;
0180 }
0181 
0182 CLASS_D_ACCESSOR_IMPL(Project, QString, fileName, FileName, fileName)
0183 BASIC_D_ACCESSOR_IMPL(Project, QString, version, Version, version)
0184 CLASS_D_READER_IMPL(Project, QString, author, author)
0185 CLASS_D_ACCESSOR_IMPL(Project, QDateTime, modificationTime, ModificationTime, modificationTime)
0186 
0187 STD_SETTER_CMD_IMPL_S(Project, SetAuthor, QString, author)
0188 void Project::setAuthor(const QString& author) {
0189     if (author != d->author)
0190         exec(new ProjectSetAuthorCmd(d, author, ki18n("%1: set author")));
0191 }
0192 
0193 void Project::setChanged(const bool value) {
0194     if (isLoading())
0195         return;
0196 
0197     d->changed = value;
0198 
0199     if (value)
0200         emit changed();
0201 }
0202 
0203 void Project::setSuppressAspectAddedSignal(bool value) {
0204     d->aspectAddedSignalSuppressed = value;
0205 }
0206 
0207 bool Project::aspectAddedSignalSuppressed() const {
0208     return d->aspectAddedSignalSuppressed;
0209 }
0210 
0211 bool Project::hasChanged() const {
0212     return d->changed;
0213 }
0214 
0215 /*!
0216  * \brief Project::descriptionChanged
0217  * This function is called, when an object changes its name. When a column changed its name and wasn't connected before to the curve/column(formula) then
0218  * this is done in this function
0219  * \param aspect
0220  */
0221 void Project::descriptionChanged(const AbstractAspect* aspect) {
0222     if (isLoading())
0223         return;
0224 
0225     //when the name of a column is being changed, it can match again the names being used in the curves, etc.
0226     //and we need to update the dependencies
0227     const auto* column = dynamic_cast<const AbstractColumn*>(aspect);
0228     if (column) {
0229         const auto& curves = children<XYCurve>(ChildIndexFlag::Recursive);
0230         updateCurveColumnDependencies(curves, column);
0231 
0232         const auto& histograms = children<Histogram>(ChildIndexFlag::Recursive);
0233         updateHistogramColumnDependencies(histograms, column);
0234     }
0235 
0236     d->changed = true;
0237     emit changed();
0238 }
0239 
0240 /*!
0241  * \brief Project::aspectAddedSlot
0242  * When adding new columns, these should be connected to the corresponding curves
0243  * \param aspect
0244  */
0245 void Project::aspectAddedSlot(const AbstractAspect* aspect) {
0246     //check whether new columns were added and if yes,
0247     //update the dependencies in the project
0248     QVector<const AbstractColumn*> columns;
0249     const auto* column = dynamic_cast<const AbstractColumn*>(aspect);
0250     if (column)
0251         columns.append(column);
0252     else {
0253         for (auto* child : aspect->children<Column>(ChildIndexFlag::Recursive))
0254             columns.append(static_cast<const AbstractColumn*>(child));
0255     }
0256 
0257     if (columns.isEmpty())
0258         return;
0259 
0260     //if a new column was addded, check whether the column names match the missing
0261     //names in the curves, etc. and update the dependencies
0262     const auto& curves = children<XYCurve>(ChildIndexFlag::Recursive);
0263     for (auto column : columns)
0264         updateCurveColumnDependencies(curves, column);
0265 
0266     const auto& histograms = children<Histogram>(ChildIndexFlag::Recursive);
0267     for (auto column : columns)
0268         updateHistogramColumnDependencies(histograms, column);
0269 }
0270 
0271 void Project::updateCurveColumnDependencies(const QVector<XYCurve*>& curves, const AbstractColumn* column) const {
0272     const QString& columnPath = column->path();
0273 
0274     // setXColumnPath must not be set, because if curve->column matches column, there already exist a
0275     // signal/slot connection between the curve and the column to update this. If they are not same,
0276     // xColumnPath is set in setXColumn. Same for the yColumn.
0277     for (auto* curve : curves) {
0278         curve->setUndoAware(false);
0279         auto* analysisCurve = dynamic_cast<XYAnalysisCurve*>(curve);
0280         if (analysisCurve) {
0281             if (analysisCurve->xDataColumnPath() == columnPath)
0282                 analysisCurve->setXDataColumn(column);
0283             if (analysisCurve->yDataColumnPath() == columnPath)
0284                 analysisCurve->setYDataColumn(column);
0285             if (analysisCurve->y2DataColumnPath() == columnPath)
0286                 analysisCurve->setY2DataColumn(column);
0287 
0288             auto* fitCurve = dynamic_cast<XYFitCurve*>(curve);
0289             if (fitCurve) {
0290                 if (fitCurve->xErrorColumnPath() == columnPath)
0291                     fitCurve->setXErrorColumn(column);
0292                 if (fitCurve->yErrorColumnPath() == columnPath)
0293                     fitCurve->setYErrorColumn(column);
0294             }
0295         } else {
0296             if (curve->xColumnPath() == columnPath)
0297                 curve->setXColumn(column);
0298             if (curve->yColumnPath() == columnPath)
0299                 curve->setYColumn(column);
0300             if (curve->valuesColumnPath() == columnPath)
0301                 curve->setValuesColumn(column);
0302             if (curve->xErrorPlusColumnPath() == columnPath)
0303                 curve->setXErrorPlusColumn(column);
0304             if (curve->xErrorMinusColumnPath() == columnPath)
0305                 curve->setXErrorMinusColumn(column);
0306             if (curve->yErrorPlusColumnPath() == columnPath)
0307                 curve->setYErrorPlusColumn(column);
0308             if (curve->yErrorMinusColumnPath() == columnPath)
0309                 curve->setYErrorMinusColumn(column);
0310         }
0311 
0312         if (curve->valuesColumnPath() == columnPath)
0313             curve->setValuesColumn(column);
0314 
0315         curve->setUndoAware(true);
0316     }
0317 
0318     const QVector<Column*>& columns = children<Column>(ChildIndexFlag::Recursive);
0319     for (auto* tempColumn : columns) {
0320         const QStringList& paths = tempColumn->formulaVariableColumnPaths();
0321         for (int i = 0; i < paths.count(); i++) {
0322             if (paths.at(i) == columnPath)
0323                 tempColumn->setformulVariableColumn(i, const_cast<Column*>(static_cast<const Column*>(column)));
0324         }
0325     }
0326 }
0327 
0328 void Project::updateHistogramColumnDependencies(const QVector<Histogram*>& histograms, const AbstractColumn* column) const {
0329     const QString& columnPath = column->path();
0330     for (auto* histogram : histograms) {
0331         if (histogram->dataColumnPath() == columnPath) {
0332             histogram->setUndoAware(false);
0333             histogram->setDataColumn(column);
0334             histogram->setUndoAware(true);
0335         }
0336 
0337         if (histogram->valuesColumnPath() == columnPath) {
0338             histogram->setUndoAware(false);
0339             histogram->setValuesColumn(column);
0340             histogram->setUndoAware(true);
0341         }
0342     }
0343 }
0344 void Project::navigateTo(const QString& path) {
0345     emit requestNavigateTo(path);
0346 }
0347 
0348 bool Project::isLabPlotProject(const QString& fileName) {
0349     return fileName.endsWith(QStringLiteral(".lml"), Qt::CaseInsensitive)
0350             || fileName.endsWith(QStringLiteral(".lml.gz"), Qt::CaseInsensitive)
0351             || fileName.endsWith(QStringLiteral(".lml.bz2"), Qt::CaseInsensitive)
0352             || fileName.endsWith(QStringLiteral(".lml.xz"), Qt::CaseInsensitive);
0353 }
0354 
0355 QString Project::supportedExtensions() {
0356     static const QString extensions = "*.lml *.lml.gz *.lml.bz2 *.lml.xz *.LML *.LML.GZ *.LML.BZ2 *.LML.XZ";
0357     return extensions;
0358 }
0359 
0360 QVector<quintptr> Project::droppedAspects(const QMimeData* mimeData) {
0361     QByteArray data = mimeData->data(QLatin1String("labplot-dnd"));
0362     QDataStream stream(&data, QIODevice::ReadOnly);
0363 
0364     //read the project pointer first
0365     quintptr project = 0;
0366     stream >> project;
0367 
0368     //read the pointers of the dragged aspects
0369     QVector<quintptr> vec;
0370     stream >> vec;
0371 
0372     return vec;
0373 }
0374 
0375 //##############################################################################
0376 //##################  Serialization/Deserialization  ###########################
0377 //##############################################################################
0378 
0379 void Project::save(const QPixmap& thumbnail, QXmlStreamWriter* writer) const {
0380     //set the version and the modification time to the current values
0381     d->version = LVERSION;
0382     d->modificationTime = QDateTime::currentDateTime();
0383 
0384     writer->setAutoFormatting(true);
0385     writer->writeStartDocument();
0386     writer->writeDTD("<!DOCTYPE LabPlotXML>");
0387 
0388     writer->writeStartElement("project");
0389     writer->writeAttribute("version", version());
0390     writer->writeAttribute("fileName", fileName());
0391     writer->writeAttribute("modificationTime", modificationTime().toString("yyyy-dd-MM hh:mm:ss:zzz"));
0392     writer->writeAttribute("author", author());
0393 
0394     QByteArray bArray;
0395     QBuffer buffer(&bArray);
0396     buffer.open(QIODevice::WriteOnly);
0397     QPixmap scaledThumbnail = thumbnail.scaled(512,512, Qt::KeepAspectRatio);
0398     scaledThumbnail.save(&buffer, "JPEG");
0399     QString image = QString::fromLatin1(bArray.toBase64().data());
0400     writer->writeAttribute("thumbnail", image);
0401 
0402     writeBasicAttributes(writer);
0403 
0404     writeCommentElement(writer);
0405 
0406     save(writer);
0407 }
0408 
0409 /**
0410  * \brief Save as XML
0411  */
0412 void Project::save(QXmlStreamWriter* writer) const {
0413     //save all children
0414     for (auto* child : children<AbstractAspect>(ChildIndexFlag::IncludeHidden)) {
0415         writer->writeStartElement("child_aspect");
0416         child->save(writer);
0417         writer->writeEndElement();
0418     }
0419 
0420     //save the state of the views (visible, maximized/minimized/geometry)
0421     //and the state of the project explorer (expanded items, currently selected item)
0422     emit requestSaveState(writer);
0423 
0424     writer->writeEndElement();
0425     writer->writeEndDocument();
0426 }
0427 
0428 bool Project::load(const QString& filename, bool preview) {
0429     QIODevice* file;
0430     // first try gzip compression, because projects can be gzipped and end with .lml
0431     if (filename.endsWith(QLatin1String(".lml"), Qt::CaseInsensitive))
0432         file = new KCompressionDevice(filename,KFilterDev::compressionTypeForMimeType("application/x-gzip"));
0433     else    // opens filename using file ending
0434         file = new KFilterDev(filename);
0435 
0436     if (!file)
0437         file = new QFile(filename);
0438 
0439     if (!file->open(QIODevice::ReadOnly)) {
0440         KMessageBox::error(nullptr, i18n("Sorry. Could not open file for reading."));
0441         return false;
0442     }
0443 
0444     char c;
0445     bool rc = file->getChar(&c);
0446     if (!rc) {
0447         KMessageBox::error(nullptr, i18n("The project file is empty."), i18n("Error opening project"));
0448         file->close();
0449         delete file;
0450         return false;
0451     }
0452     file->seek(0);
0453 
0454     //parse XML
0455     XmlStreamReader reader(file);
0456     setIsLoading(true);
0457     rc = this->load(&reader, preview);
0458     setIsLoading(false);
0459     if (rc == false) {
0460         RESET_CURSOR;
0461         QString msg = reader.errorString();
0462         if (msg.isEmpty())
0463             msg = i18n("Unknown error when opening the project %1.", filename);
0464         KMessageBox::error(nullptr, msg, i18n("Error when opening the project"));
0465         file->close();
0466         delete file;
0467         return false;
0468     }
0469 
0470     if (reader.hasWarnings()) {
0471         qWarning("The following problems occurred when loading the project file:");
0472         const QStringList& warnings = reader.warningStrings();
0473         for (const auto& str : warnings)
0474             qWarning() << qUtf8Printable(str);
0475 
0476 //TODO: show warnings in a kind of "log window" but not in message box
0477 //      KMessageBox::error(this, msg, i18n("Project loading partly failed"));
0478     }
0479 
0480     if (reader.hasMissingCASWarnings()) {
0481         RESET_CURSOR;
0482 
0483         const QString& msg = i18n("The project has content written with %1. "
0484                         "Your installation of LabPlot lacks the support for it.\n\n "
0485                         "You won't be able to see this part of the project. "
0486                         "If you modify and save the project, the CAS content will be lost.\n\n"
0487                         "Do you want to continue?", reader.missingCASWarning());
0488         auto rc = KMessageBox::warningYesNo(nullptr, msg, i18n("Missing Support for CAS"));
0489         if (rc == KMessageBox::ButtonCode::No) {
0490             file->close();
0491             delete file;
0492             return false;
0493         }
0494     }
0495 
0496     file->close();
0497     delete file;
0498 
0499     return true;
0500 }
0501 
0502 /**
0503  * \brief Load from XML
0504  */
0505 bool Project::load(XmlStreamReader* reader, bool preview) {
0506     while (!(reader->isStartDocument() || reader->atEnd()))
0507         reader->readNext();
0508 
0509     if (!(reader->atEnd())) {
0510         if (!reader->skipToNextTag())
0511             return false;
0512 
0513         if (reader->name() == "project") {
0514             QString version = reader->attributes().value("version").toString();
0515             if (version.isEmpty())
0516                 reader->raiseWarning(i18n("Attribute 'version' is missing."));
0517             else
0518                 d->version = version;
0519 
0520             if (!readBasicAttributes(reader)) return false;
0521             if (!readProjectAttributes(reader)) return false;
0522 
0523             while (!reader->atEnd()) {
0524                 reader->readNext();
0525 
0526                 if (reader->isEndElement()) break;
0527 
0528                 if (reader->isStartElement()) {
0529                     if (reader->name() == "comment") {
0530                         if (!readCommentElement(reader))
0531                             return false;
0532                     } else if (reader->name() == "child_aspect") {
0533                         if (!readChildAspectElement(reader, preview))
0534                             return false;
0535                     } else if (reader->name() == "state") {
0536                         //load the state of the views (visible, maximized/minimized/geometry)
0537                         //and the state of the project explorer (expanded items, currently selected item)
0538                         emit requestLoadState(reader);
0539                     } else {
0540                         reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString()));
0541                         if (!reader->skipToEndElement()) return false;
0542                     }
0543                 }
0544             }
0545         } else  // no project element
0546             reader->raiseError(i18n("no project element found"));
0547     } else  // no start document
0548         reader->raiseError(i18n("no valid XML document found"));
0549 
0550     if (!preview) {
0551         //wait until all columns are decoded from base64-encoded data
0552         QThreadPool::globalInstance()->waitForDone();
0553 
0554         //LiveDataSource:
0555         //call finalizeLoad() to replace relative with absolute paths if required
0556         //and to create columns during the initial read
0557         auto sources = children<LiveDataSource>(ChildIndexFlag::Recursive);
0558         for (auto* source : sources) {
0559             if (!source) continue;
0560             source->finalizeLoad();
0561         }
0562 
0563         //everything is read now.
0564         //restore the pointer to the data sets (columns) in xy-curves etc.
0565         auto columns = children<Column>(ChildIndexFlag::Recursive);
0566 
0567         //xy-curves
0568         // cannot be removed by the column observer, because it does not react
0569         // on curve changes
0570         auto curves = children<XYCurve>(ChildIndexFlag::Recursive);
0571         for (auto* curve : curves) {
0572             if (!curve) continue;
0573             curve->suppressRetransform(true);
0574 
0575             auto* equationCurve = dynamic_cast<XYEquationCurve*>(curve);
0576             auto* analysisCurve = dynamic_cast<XYAnalysisCurve*>(curve);
0577             if (equationCurve) {
0578                 //curves defined by a mathematical equations recalculate their own columns on load again.
0579                 if (!preview)
0580                     equationCurve->recalculate();
0581             } else if (analysisCurve) {
0582                 RESTORE_COLUMN_POINTER(analysisCurve, xDataColumn, XDataColumn);
0583                 RESTORE_COLUMN_POINTER(analysisCurve, yDataColumn, YDataColumn);
0584                 RESTORE_COLUMN_POINTER(analysisCurve, y2DataColumn, Y2DataColumn);
0585                 auto* fitCurve = dynamic_cast<XYFitCurve*>(curve);
0586                 if (fitCurve) {
0587                     RESTORE_COLUMN_POINTER(fitCurve, xErrorColumn, XErrorColumn);
0588                     RESTORE_COLUMN_POINTER(fitCurve, yErrorColumn, YErrorColumn);
0589                 }
0590             } else {
0591                 RESTORE_COLUMN_POINTER(curve, xColumn, XColumn);
0592                 RESTORE_COLUMN_POINTER(curve, yColumn, YColumn);
0593                 RESTORE_COLUMN_POINTER(curve, valuesColumn, ValuesColumn);
0594                 RESTORE_COLUMN_POINTER(curve, xErrorPlusColumn, XErrorPlusColumn);
0595                 RESTORE_COLUMN_POINTER(curve, xErrorMinusColumn, XErrorMinusColumn);
0596                 RESTORE_COLUMN_POINTER(curve, yErrorPlusColumn, YErrorPlusColumn);
0597                 RESTORE_COLUMN_POINTER(curve, yErrorMinusColumn, YErrorMinusColumn);
0598             }
0599             if (dynamic_cast<XYAnalysisCurve*>(curve))
0600                 RESTORE_POINTER(dynamic_cast<XYAnalysisCurve*>(curve), dataSourceCurve, DataSourceCurve, XYCurve, curves);
0601 
0602             curve->suppressRetransform(false);
0603         }
0604 
0605         //axes
0606         auto axes = children<Axis>(ChildIndexFlag::Recursive);
0607         for (auto* axis : axes) {
0608             if (!axis) continue;
0609             RESTORE_COLUMN_POINTER(axis, majorTicksColumn, MajorTicksColumn);
0610             RESTORE_COLUMN_POINTER(axis, minorTicksColumn, MinorTicksColumn);
0611         }
0612 
0613         //histograms
0614         auto hists = children<Histogram>(ChildIndexFlag::Recursive);
0615         for (auto* hist : hists) {
0616             if (!hist) continue;
0617             RESTORE_COLUMN_POINTER(hist, dataColumn, DataColumn);
0618             RESTORE_COLUMN_POINTER(hist, valuesColumn, ValuesColumn);
0619         }
0620 
0621         //data picker curves
0622         auto dataPickerCurves = children<DatapickerCurve>(ChildIndexFlag::Recursive);
0623         for (auto* dataPickerCurve : dataPickerCurves) {
0624             if (!dataPickerCurve) continue;
0625             RESTORE_COLUMN_POINTER(dataPickerCurve, posXColumn, PosXColumn);
0626             RESTORE_COLUMN_POINTER(dataPickerCurve, posYColumn, PosYColumn);
0627             RESTORE_COLUMN_POINTER(dataPickerCurve, plusDeltaXColumn, PlusDeltaXColumn);
0628             RESTORE_COLUMN_POINTER(dataPickerCurve, minusDeltaXColumn, MinusDeltaXColumn);
0629             RESTORE_COLUMN_POINTER(dataPickerCurve, plusDeltaYColumn, PlusDeltaYColumn);
0630             RESTORE_COLUMN_POINTER(dataPickerCurve, minusDeltaYColumn, MinusDeltaYColumn);
0631         }
0632 
0633         //if a column was calculated via a formula, restore the pointers to the variable columns defining the formula
0634         for (auto* col : columns) {
0635             if (!col->formulaVariableColumnPaths().isEmpty()) {
0636                 auto& formulaVariableColumns = const_cast<QVector<Column*>&>(col->formulaVariableColumns());
0637                 formulaVariableColumns.resize(col->formulaVariableColumnPaths().length());
0638 
0639                 for (int i = 0; i < col->formulaVariableColumnPaths().length(); i++) {
0640                     auto path = col->formulaVariableColumnPaths()[i];
0641                     for (Column* c : columns) {
0642                         if (!c) continue;
0643                         if (c->path() == path) {
0644                             formulaVariableColumns[i] = c;
0645                             col->finalizeLoad();
0646                             break;
0647                         }
0648                     }
0649                 }
0650             }
0651         }
0652 
0653         //all data was read in spreadsheets:
0654         //call CartesianPlot::retransform() to retransform the plots
0655         for (auto* plot : children<CartesianPlot>(ChildIndexFlag::Recursive)) {
0656             plot->setIsLoading(false);
0657             plot->retransform();
0658         }
0659 
0660         //all data was read in live-data sources:
0661         //call CartesianPlot::dataChanged() to notify affected plots about the new data.
0662         //this needs to be done here since in LiveDataSource::finalizeImport() called above
0663         //where the data is read the column pointers are not restored yes in curves.
0664         QVector<CartesianPlot*> plots;
0665         for (auto* source : sources) {
0666             for (int n = 0; n < source->columnCount(); ++n) {
0667                 Column* column = source->column(n);
0668 
0669                 //determine the plots where the column is consumed
0670                 for (const auto* curve : curves) {
0671                     if (curve->xColumn() == column || curve->yColumn() == column) {
0672                         auto* plot = static_cast<CartesianPlot*>(curve->parentAspect());
0673                         if (plots.indexOf(plot) == -1) {
0674                             plots << plot;
0675                             plot->setSuppressDataChangedSignal(true);
0676                         }
0677                     }
0678                 }
0679 
0680                 column->setChanged();
0681             }
0682         }
0683 
0684         //loop over all affected plots and retransform them
0685         for (auto* plot : plots) {
0686             plot->setSuppressDataChangedSignal(false);
0687             plot->dataChanged();
0688         }
0689     }
0690 
0691     emit loaded();
0692     return !reader->hasError();
0693 }
0694 
0695 bool Project::readProjectAttributes(XmlStreamReader* reader) {
0696     QXmlStreamAttributes attribs = reader->attributes();
0697     QString str = attribs.value(reader->namespaceUri().toString(), "modificationTime").toString();
0698     QDateTime modificationTime = QDateTime::fromString(str, "yyyy-dd-MM hh:mm:ss:zzz");
0699     if (str.isEmpty() || !modificationTime.isValid()) {
0700         reader->raiseWarning(i18n("Invalid project modification time. Using current time."));
0701         d->modificationTime = QDateTime::currentDateTime();
0702     } else
0703         d->modificationTime = modificationTime;
0704 
0705     d->author = attribs.value(reader->namespaceUri().toString(), "author").toString();
0706 
0707     return true;
0708 }