"Fossies" - the Fresh Open Source Software Archive

Member "labplot-2.8.2/src/backend/core/Project.cpp" (24 Feb 2021, 24771 Bytes) of package /linux/privat/labplot-2.8.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Project.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.8.1_vs_2.8.2.

    1 /***************************************************************************
    2     File                 : Project.cpp
    3     Project              : LabPlot
    4     Description          : Represents a LabPlot project.
    5     --------------------------------------------------------------------
    6     Copyright            : (C) 2011-2020 Alexander Semke (alexander.semke@web.de)
    7     Copyright            : (C) 2007-2008 Tilman Benkert (thzs@gmx.net)
    8     Copyright            : (C) 2007 Knut Franke (knut.franke@gmx.de)
    9  ***************************************************************************/
   10 
   11 /***************************************************************************
   12  *                                                                         *
   13  *  This program is free software; you can redistribute it and/or modify   *
   14  *  it under the terms of the GNU General Public License as published by   *
   15  *  the Free Software Foundation; either version 2 of the License, or      *
   16  *  (at your option) any later version.                                    *
   17  *                                                                         *
   18  *  This program is distributed in the hope that it will be useful,        *
   19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
   20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
   21  *  GNU General Public License for more details.                           *
   22  *                                                                         *
   23  *   You should have received a copy of the GNU General Public License     *
   24  *   along with this program; if not, write to the Free Software           *
   25  *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
   26  *   Boston, MA  02110-1301  USA                                           *
   27  *                                                                         *
   28  ***************************************************************************/
   29 #include "backend/core/Project.h"
   30 #include "backend/lib/XmlStreamReader.h"
   31 #include "backend/datasources/LiveDataSource.h"
   32 #include "backend/spreadsheet/Spreadsheet.h"
   33 #include "backend/worksheet/Worksheet.h"
   34 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
   35 #include "backend/worksheet/plots/cartesian/Histogram.h"
   36 #include "backend/worksheet/plots/cartesian/XYEquationCurve.h"
   37 #include "backend/worksheet/plots/cartesian/XYFitCurve.h"
   38 #include "backend/worksheet/plots/cartesian/Axis.h"
   39 #include "backend/datapicker/DatapickerCurve.h"
   40 #ifdef HAVE_MQTT
   41 #include "backend/datasources/MQTTClient.h"
   42 #endif
   43 
   44 #include <QDateTime>
   45 #include <QFile>
   46 #include <QMenu>
   47 #include <QMimeData>
   48 #include <QThreadPool>
   49 #include <QUndoStack>
   50 #include <QBuffer>
   51 
   52 #include <KConfig>
   53 #include <KConfigGroup>
   54 #include <KFilterDev>
   55 #include <KLocalizedString>
   56 #include <KMessageBox>
   57 
   58 /**
   59  * \class Project
   60  * \ingroup core
   61  * \brief Represents a project.
   62  *
   63  * Project represents the root node of all objects created during the runtime of the program.
   64  * Manages also the undo stack.
   65  */
   66 
   67 /**
   68  * \enum Project::MdiWindowVisibility
   69  * \brief MDI subwindow visibility setting
   70  */
   71 /**
   72  * \var Project::folderOnly
   73  * \brief only show MDI windows corresponding to Parts in the current folder
   74  */
   75 /**
   76  * \var Project::foldAndSubfolders
   77  * \brief show MDI windows corresponding to Parts in the current folder and its subfolders
   78  */
   79 /**
   80  * \var Project::allMdiWindows
   81  * \brief show MDI windows for all Parts in the project simultaneously
   82  */
   83 
   84 class Project::Private {
   85 public:
   86     Private(Project* owner) :
   87         version(LVERSION),
   88         author(QString(qgetenv("USER"))),
   89         modificationTime(QDateTime::currentDateTime()),
   90         q(owner) {
   91     }
   92     QString name() const  {
   93         return q->name();
   94     }
   95 
   96     QUndoStack undo_stack;
   97     MdiWindowVisibility mdiWindowVisibility{Project::MdiWindowVisibility::folderOnly};
   98     QString fileName;
   99     QString version;
  100     QString author;
  101     QDateTime modificationTime;
  102     bool changed{false};
  103     bool aspectAddedSignalSuppressed{false};
  104     Project* const q;
  105 };
  106 
  107 Project::Project() : Folder(i18n("Project"), AspectType::Project), d(new Private(this)) {
  108     //load default values for name, comment and author from config
  109     KConfig config;
  110     KConfigGroup group = config.group("Project");
  111 
  112     d->author = group.readEntry("Author", QString());
  113 
  114     //we don't have direct access to the members name and comment
  115     //->temporary disable the undo stack and call the setters
  116     setUndoAware(false);
  117     setIsLoading(true);
  118     setName(group.readEntry("Name", i18n("Project")));
  119     setComment(group.readEntry("Comment", QString()));
  120     setUndoAware(true);
  121     setIsLoading(false);
  122     d->changed = false;
  123 
  124     connect(this, &Project::aspectDescriptionChanged,this, &Project::descriptionChanged);
  125     connect(this, &Project::aspectAdded,this, &Project::aspectAddedSlot);
  126 }
  127 
  128 Project::~Project() {
  129     //if the project is being closed and the live data sources still continue reading the data,
  130     //the dependent objects (columns, etc.), which are already deleted maybe here,  are still being notified about the changes.
  131     //->stop reading the live data sources prior to deleting all objects.
  132     for (auto* lds : children<LiveDataSource>())
  133         lds->pauseReading();
  134 
  135 #ifdef HAVE_MQTT
  136     for (auto* client : children<MQTTClient>())
  137         client->pauseReading();
  138 #endif
  139 
  140     //if the project is being closed, in Worksheet the scene items are being removed and the selection in the view can change.
  141     //don't react on these changes since this can lead crashes (worksheet object is already in the destructor).
  142     //->notify all worksheets about the project being closed.
  143     for (auto* w : children<Worksheet>(ChildIndexFlag::Recursive))
  144         w->setIsClosing();
  145 
  146     d->undo_stack.clear();
  147     delete d;
  148 }
  149 
  150 QUndoStack* Project::undoStack() const {
  151     return &d->undo_stack;
  152 }
  153 
  154 QMenu* Project::createContextMenu() {
  155     QMenu* menu = AbstractAspect::createContextMenu();
  156 
  157     //add close action
  158     menu->addSeparator();
  159     menu->addAction(QIcon::fromTheme(QLatin1String("document-close")), i18n("Close"), this, SIGNAL(closeRequested()));
  160 
  161     //add the actions from MainWin
  162     emit requestProjectContextMenu(menu);
  163 
  164     return menu;
  165 }
  166 
  167 QMenu* Project::createFolderContextMenu(const Folder* folder) {
  168     QMenu* menu = const_cast<Folder*>(folder)->AbstractAspect::createContextMenu();
  169     emit requestFolderContextMenu(folder, menu);
  170     return menu;
  171 }
  172 
  173 void Project::setMdiWindowVisibility(MdiWindowVisibility visibility) {
  174     d->mdiWindowVisibility = visibility;
  175     emit mdiWindowVisibilityChanged();
  176 }
  177 
  178 Project::MdiWindowVisibility Project::mdiWindowVisibility() const {
  179     return d->mdiWindowVisibility;
  180 }
  181 
  182 CLASS_D_ACCESSOR_IMPL(Project, QString, fileName, FileName, fileName)
  183 BASIC_D_ACCESSOR_IMPL(Project, QString, version, Version, version)
  184 CLASS_D_READER_IMPL(Project, QString, author, author)
  185 CLASS_D_ACCESSOR_IMPL(Project, QDateTime, modificationTime, ModificationTime, modificationTime)
  186 
  187 STD_SETTER_CMD_IMPL_S(Project, SetAuthor, QString, author)
  188 void Project::setAuthor(const QString& author) {
  189     if (author != d->author)
  190         exec(new ProjectSetAuthorCmd(d, author, ki18n("%1: set author")));
  191 }
  192 
  193 void Project::setChanged(const bool value) {
  194     if (isLoading())
  195         return;
  196 
  197     d->changed = value;
  198 
  199     if (value)
  200         emit changed();
  201 }
  202 
  203 void Project::setSuppressAspectAddedSignal(bool value) {
  204     d->aspectAddedSignalSuppressed = value;
  205 }
  206 
  207 bool Project::aspectAddedSignalSuppressed() const {
  208     return d->aspectAddedSignalSuppressed;
  209 }
  210 
  211 bool Project::hasChanged() const {
  212     return d->changed;
  213 }
  214 
  215 /*!
  216  * \brief Project::descriptionChanged
  217  * 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
  218  * this is done in this function
  219  * \param aspect
  220  */
  221 void Project::descriptionChanged(const AbstractAspect* aspect) {
  222     if (isLoading())
  223         return;
  224 
  225     //when the name of a column is being changed, it can match again the names being used in the curves, etc.
  226     //and we need to update the dependencies
  227     const auto* column = dynamic_cast<const AbstractColumn*>(aspect);
  228     if (column) {
  229         const auto& curves = children<XYCurve>(ChildIndexFlag::Recursive);
  230         updateCurveColumnDependencies(curves, column);
  231 
  232         const auto& histograms = children<Histogram>(ChildIndexFlag::Recursive);
  233         updateHistogramColumnDependencies(histograms, column);
  234     }
  235 
  236     d->changed = true;
  237     emit changed();
  238 }
  239 
  240 /*!
  241  * \brief Project::aspectAddedSlot
  242  * When adding new columns, these should be connected to the corresponding curves
  243  * \param aspect
  244  */
  245 void Project::aspectAddedSlot(const AbstractAspect* aspect) {
  246     //check whether new columns were added and if yes,
  247     //update the dependencies in the project
  248     QVector<const AbstractColumn*> columns;
  249     const auto* column = dynamic_cast<const AbstractColumn*>(aspect);
  250     if (column)
  251         columns.append(column);
  252     else {
  253         for (auto* child : aspect->children<Column>(ChildIndexFlag::Recursive))
  254             columns.append(static_cast<const AbstractColumn*>(child));
  255     }
  256 
  257     if (columns.isEmpty())
  258         return;
  259 
  260     //if a new column was addded, check whether the column names match the missing
  261     //names in the curves, etc. and update the dependencies
  262     const auto& curves = children<XYCurve>(ChildIndexFlag::Recursive);
  263     for (auto column : columns)
  264         updateCurveColumnDependencies(curves, column);
  265 
  266     const auto& histograms = children<Histogram>(ChildIndexFlag::Recursive);
  267     for (auto column : columns)
  268         updateHistogramColumnDependencies(histograms, column);
  269 }
  270 
  271 void Project::updateCurveColumnDependencies(const QVector<XYCurve*>& curves, const AbstractColumn* column) const {
  272     const QString& columnPath = column->path();
  273 
  274     // setXColumnPath must not be set, because if curve->column matches column, there already exist a
  275     // signal/slot connection between the curve and the column to update this. If they are not same,
  276     // xColumnPath is set in setXColumn. Same for the yColumn.
  277     for (auto* curve : curves) {
  278         curve->setUndoAware(false);
  279         auto* analysisCurve = dynamic_cast<XYAnalysisCurve*>(curve);
  280         if (analysisCurve) {
  281             if (analysisCurve->xDataColumnPath() == columnPath)
  282                 analysisCurve->setXDataColumn(column);
  283             if (analysisCurve->yDataColumnPath() == columnPath)
  284                 analysisCurve->setYDataColumn(column);
  285             if (analysisCurve->y2DataColumnPath() == columnPath)
  286                 analysisCurve->setY2DataColumn(column);
  287 
  288             auto* fitCurve = dynamic_cast<XYFitCurve*>(curve);
  289             if (fitCurve) {
  290                 if (fitCurve->xErrorColumnPath() == columnPath)
  291                     fitCurve->setXErrorColumn(column);
  292                 if (fitCurve->yErrorColumnPath() == columnPath)
  293                     fitCurve->setYErrorColumn(column);
  294             }
  295         } else {
  296             if (curve->xColumnPath() == columnPath)
  297                 curve->setXColumn(column);
  298             if (curve->yColumnPath() == columnPath)
  299                 curve->setYColumn(column);
  300             if (curve->valuesColumnPath() == columnPath)
  301                 curve->setValuesColumn(column);
  302             if (curve->xErrorPlusColumnPath() == columnPath)
  303                 curve->setXErrorPlusColumn(column);
  304             if (curve->xErrorMinusColumnPath() == columnPath)
  305                 curve->setXErrorMinusColumn(column);
  306             if (curve->yErrorPlusColumnPath() == columnPath)
  307                 curve->setYErrorPlusColumn(column);
  308             if (curve->yErrorMinusColumnPath() == columnPath)
  309                 curve->setYErrorMinusColumn(column);
  310         }
  311 
  312         if (curve->valuesColumnPath() == columnPath)
  313             curve->setValuesColumn(column);
  314 
  315         curve->setUndoAware(true);
  316     }
  317 
  318     const QVector<Column*>& columns = children<Column>(ChildIndexFlag::Recursive);
  319     for (auto* tempColumn : columns) {
  320         const QStringList& paths = tempColumn->formulaVariableColumnPaths();
  321         for (int i = 0; i < paths.count(); i++) {
  322             if (paths.at(i) == columnPath)
  323                 tempColumn->setformulVariableColumn(i, const_cast<Column*>(static_cast<const Column*>(column)));
  324         }
  325     }
  326 }
  327 
  328 void Project::updateHistogramColumnDependencies(const QVector<Histogram*>& histograms, const AbstractColumn* column) const {
  329     const QString& columnPath = column->path();
  330     for (auto* histogram : histograms) {
  331         if (histogram->dataColumnPath() == columnPath) {
  332             histogram->setUndoAware(false);
  333             histogram->setDataColumn(column);
  334             histogram->setUndoAware(true);
  335         }
  336 
  337         if (histogram->valuesColumnPath() == columnPath) {
  338             histogram->setUndoAware(false);
  339             histogram->setValuesColumn(column);
  340             histogram->setUndoAware(true);
  341         }
  342     }
  343 }
  344 void Project::navigateTo(const QString& path) {
  345     emit requestNavigateTo(path);
  346 }
  347 
  348 bool Project::isLabPlotProject(const QString& fileName) {
  349     return fileName.endsWith(QStringLiteral(".lml"), Qt::CaseInsensitive)
  350             || fileName.endsWith(QStringLiteral(".lml.gz"), Qt::CaseInsensitive)
  351             || fileName.endsWith(QStringLiteral(".lml.bz2"), Qt::CaseInsensitive)
  352             || fileName.endsWith(QStringLiteral(".lml.xz"), Qt::CaseInsensitive);
  353 }
  354 
  355 QString Project::supportedExtensions() {
  356     static const QString extensions = "*.lml *.lml.gz *.lml.bz2 *.lml.xz *.LML *.LML.GZ *.LML.BZ2 *.LML.XZ";
  357     return extensions;
  358 }
  359 
  360 QVector<quintptr> Project::droppedAspects(const QMimeData* mimeData) {
  361     QByteArray data = mimeData->data(QLatin1String("labplot-dnd"));
  362     QDataStream stream(&data, QIODevice::ReadOnly);
  363 
  364     //read the project pointer first
  365     quintptr project = 0;
  366     stream >> project;
  367 
  368     //read the pointers of the dragged aspects
  369     QVector<quintptr> vec;
  370     stream >> vec;
  371 
  372     return vec;
  373 }
  374 
  375 //##############################################################################
  376 //##################  Serialization/Deserialization  ###########################
  377 //##############################################################################
  378 
  379 void Project::save(const QPixmap& thumbnail, QXmlStreamWriter* writer) const {
  380     //set the version and the modification time to the current values
  381     d->version = LVERSION;
  382     d->modificationTime = QDateTime::currentDateTime();
  383 
  384     writer->setAutoFormatting(true);
  385     writer->writeStartDocument();
  386     writer->writeDTD("<!DOCTYPE LabPlotXML>");
  387 
  388     writer->writeStartElement("project");
  389     writer->writeAttribute("version", version());
  390     writer->writeAttribute("fileName", fileName());
  391     writer->writeAttribute("modificationTime", modificationTime().toString("yyyy-dd-MM hh:mm:ss:zzz"));
  392     writer->writeAttribute("author", author());
  393 
  394     QByteArray bArray;
  395     QBuffer buffer(&bArray);
  396     buffer.open(QIODevice::WriteOnly);
  397     QPixmap scaledThumbnail = thumbnail.scaled(512,512, Qt::KeepAspectRatio);
  398     scaledThumbnail.save(&buffer, "JPEG");
  399     QString image = QString::fromLatin1(bArray.toBase64().data());
  400     writer->writeAttribute("thumbnail", image);
  401 
  402     writeBasicAttributes(writer);
  403 
  404     writeCommentElement(writer);
  405 
  406     save(writer);
  407 }
  408 
  409 /**
  410  * \brief Save as XML
  411  */
  412 void Project::save(QXmlStreamWriter* writer) const {
  413     //save all children
  414     for (auto* child : children<AbstractAspect>(ChildIndexFlag::IncludeHidden)) {
  415         writer->writeStartElement("child_aspect");
  416         child->save(writer);
  417         writer->writeEndElement();
  418     }
  419 
  420     //save the state of the views (visible, maximized/minimized/geometry)
  421     //and the state of the project explorer (expanded items, currently selected item)
  422     emit requestSaveState(writer);
  423 
  424     writer->writeEndElement();
  425     writer->writeEndDocument();
  426 }
  427 
  428 bool Project::load(const QString& filename, bool preview) {
  429     QIODevice* file;
  430     // first try gzip compression, because projects can be gzipped and end with .lml
  431     if (filename.endsWith(QLatin1String(".lml"), Qt::CaseInsensitive))
  432         file = new KCompressionDevice(filename,KFilterDev::compressionTypeForMimeType("application/x-gzip"));
  433     else    // opens filename using file ending
  434         file = new KFilterDev(filename);
  435 
  436     if (!file)
  437         file = new QFile(filename);
  438 
  439     if (!file->open(QIODevice::ReadOnly)) {
  440         KMessageBox::error(nullptr, i18n("Sorry. Could not open file for reading."));
  441         return false;
  442     }
  443 
  444     char c;
  445     bool rc = file->getChar(&c);
  446     if (!rc) {
  447         KMessageBox::error(nullptr, i18n("The project file is empty."), i18n("Error opening project"));
  448         file->close();
  449         delete file;
  450         return false;
  451     }
  452     file->seek(0);
  453 
  454     //parse XML
  455     XmlStreamReader reader(file);
  456     setIsLoading(true);
  457     rc = this->load(&reader, preview);
  458     setIsLoading(false);
  459     if (rc == false) {
  460         RESET_CURSOR;
  461         QString msg = reader.errorString();
  462         if (msg.isEmpty())
  463             msg = i18n("Unknown error when opening the project %1.", filename);
  464         KMessageBox::error(nullptr, msg, i18n("Error when opening the project"));
  465         file->close();
  466         delete file;
  467         return false;
  468     }
  469 
  470     if (reader.hasWarnings()) {
  471         qWarning("The following problems occurred when loading the project file:");
  472         const QStringList& warnings = reader.warningStrings();
  473         for (const auto& str : warnings)
  474             qWarning() << qUtf8Printable(str);
  475 
  476 //TODO: show warnings in a kind of "log window" but not in message box
  477 //      KMessageBox::error(this, msg, i18n("Project loading partly failed"));
  478     }
  479 
  480     if (reader.hasMissingCASWarnings()) {
  481         RESET_CURSOR;
  482 
  483         const QString& msg = i18n("The project has content written with %1. "
  484                         "Your installation of LabPlot lacks the support for it.\n\n "
  485                         "You won't be able to see this part of the project. "
  486                         "If you modify and save the project, the CAS content will be lost.\n\n"
  487                         "Do you want to continue?", reader.missingCASWarning());
  488         auto rc = KMessageBox::warningYesNo(nullptr, msg, i18n("Missing Support for CAS"));
  489         if (rc == KMessageBox::ButtonCode::No) {
  490             file->close();
  491             delete file;
  492             return false;
  493         }
  494     }
  495 
  496     file->close();
  497     delete file;
  498 
  499     return true;
  500 }
  501 
  502 /**
  503  * \brief Load from XML
  504  */
  505 bool Project::load(XmlStreamReader* reader, bool preview) {
  506     while (!(reader->isStartDocument() || reader->atEnd()))
  507         reader->readNext();
  508 
  509     if (!(reader->atEnd())) {
  510         if (!reader->skipToNextTag())
  511             return false;
  512 
  513         if (reader->name() == "project") {
  514             QString version = reader->attributes().value("version").toString();
  515             if (version.isEmpty())
  516                 reader->raiseWarning(i18n("Attribute 'version' is missing."));
  517             else
  518                 d->version = version;
  519 
  520             if (!readBasicAttributes(reader)) return false;
  521             if (!readProjectAttributes(reader)) return false;
  522 
  523             while (!reader->atEnd()) {
  524                 reader->readNext();
  525 
  526                 if (reader->isEndElement()) break;
  527 
  528                 if (reader->isStartElement()) {
  529                     if (reader->name() == "comment") {
  530                         if (!readCommentElement(reader))
  531                             return false;
  532                     } else if (reader->name() == "child_aspect") {
  533                         if (!readChildAspectElement(reader, preview))
  534                             return false;
  535                     } else if (reader->name() == "state") {
  536                         //load the state of the views (visible, maximized/minimized/geometry)
  537                         //and the state of the project explorer (expanded items, currently selected item)
  538                         emit requestLoadState(reader);
  539                     } else {
  540                         reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString()));
  541                         if (!reader->skipToEndElement()) return false;
  542                     }
  543                 }
  544             }
  545         } else  // no project element
  546             reader->raiseError(i18n("no project element found"));
  547     } else  // no start document
  548         reader->raiseError(i18n("no valid XML document found"));
  549 
  550     if (!preview) {
  551         //wait until all columns are decoded from base64-encoded data
  552         QThreadPool::globalInstance()->waitForDone();
  553 
  554         //LiveDataSource:
  555         //call finalizeLoad() to replace relative with absolute paths if required
  556         //and to create columns during the initial read
  557         auto sources = children<LiveDataSource>(ChildIndexFlag::Recursive);
  558         for (auto* source : sources) {
  559             if (!source) continue;
  560             source->finalizeLoad();
  561         }
  562 
  563         //everything is read now.
  564         //restore the pointer to the data sets (columns) in xy-curves etc.
  565         auto columns = children<Column>(ChildIndexFlag::Recursive);
  566 
  567         //xy-curves
  568         // cannot be removed by the column observer, because it does not react
  569         // on curve changes
  570         auto curves = children<XYCurve>(ChildIndexFlag::Recursive);
  571         for (auto* curve : curves) {
  572             if (!curve) continue;
  573             curve->suppressRetransform(true);
  574 
  575             auto* equationCurve = dynamic_cast<XYEquationCurve*>(curve);
  576             auto* analysisCurve = dynamic_cast<XYAnalysisCurve*>(curve);
  577             if (equationCurve) {
  578                 //curves defined by a mathematical equations recalculate their own columns on load again.
  579                 if (!preview)
  580                     equationCurve->recalculate();
  581             } else if (analysisCurve) {
  582                 RESTORE_COLUMN_POINTER(analysisCurve, xDataColumn, XDataColumn);
  583                 RESTORE_COLUMN_POINTER(analysisCurve, yDataColumn, YDataColumn);
  584                 RESTORE_COLUMN_POINTER(analysisCurve, y2DataColumn, Y2DataColumn);
  585                 auto* fitCurve = dynamic_cast<XYFitCurve*>(curve);
  586                 if (fitCurve) {
  587                     RESTORE_COLUMN_POINTER(fitCurve, xErrorColumn, XErrorColumn);
  588                     RESTORE_COLUMN_POINTER(fitCurve, yErrorColumn, YErrorColumn);
  589                 }
  590             } else {
  591                 RESTORE_COLUMN_POINTER(curve, xColumn, XColumn);
  592                 RESTORE_COLUMN_POINTER(curve, yColumn, YColumn);
  593                 RESTORE_COLUMN_POINTER(curve, valuesColumn, ValuesColumn);
  594                 RESTORE_COLUMN_POINTER(curve, xErrorPlusColumn, XErrorPlusColumn);
  595                 RESTORE_COLUMN_POINTER(curve, xErrorMinusColumn, XErrorMinusColumn);
  596                 RESTORE_COLUMN_POINTER(curve, yErrorPlusColumn, YErrorPlusColumn);
  597                 RESTORE_COLUMN_POINTER(curve, yErrorMinusColumn, YErrorMinusColumn);
  598             }
  599             if (dynamic_cast<XYAnalysisCurve*>(curve))
  600                 RESTORE_POINTER(dynamic_cast<XYAnalysisCurve*>(curve), dataSourceCurve, DataSourceCurve, XYCurve, curves);
  601 
  602             curve->suppressRetransform(false);
  603         }
  604 
  605         //axes
  606         auto axes = children<Axis>(ChildIndexFlag::Recursive);
  607         for (auto* axis : axes) {
  608             if (!axis) continue;
  609             RESTORE_COLUMN_POINTER(axis, majorTicksColumn, MajorTicksColumn);
  610             RESTORE_COLUMN_POINTER(axis, minorTicksColumn, MinorTicksColumn);
  611         }
  612 
  613         //histograms
  614         auto hists = children<Histogram>(ChildIndexFlag::Recursive);
  615         for (auto* hist : hists) {
  616             if (!hist) continue;
  617             RESTORE_COLUMN_POINTER(hist, dataColumn, DataColumn);
  618             RESTORE_COLUMN_POINTER(hist, valuesColumn, ValuesColumn);
  619         }
  620 
  621         //data picker curves
  622         auto dataPickerCurves = children<DatapickerCurve>(ChildIndexFlag::Recursive);
  623         for (auto* dataPickerCurve : dataPickerCurves) {
  624             if (!dataPickerCurve) continue;
  625             RESTORE_COLUMN_POINTER(dataPickerCurve, posXColumn, PosXColumn);
  626             RESTORE_COLUMN_POINTER(dataPickerCurve, posYColumn, PosYColumn);
  627             RESTORE_COLUMN_POINTER(dataPickerCurve, plusDeltaXColumn, PlusDeltaXColumn);
  628             RESTORE_COLUMN_POINTER(dataPickerCurve, minusDeltaXColumn, MinusDeltaXColumn);
  629             RESTORE_COLUMN_POINTER(dataPickerCurve, plusDeltaYColumn, PlusDeltaYColumn);
  630             RESTORE_COLUMN_POINTER(dataPickerCurve, minusDeltaYColumn, MinusDeltaYColumn);
  631         }
  632 
  633         //if a column was calculated via a formula, restore the pointers to the variable columns defining the formula
  634         for (auto* col : columns) {
  635             if (!col->formulaVariableColumnPaths().isEmpty()) {
  636                 auto& formulaVariableColumns = const_cast<QVector<Column*>&>(col->formulaVariableColumns());
  637                 formulaVariableColumns.resize(col->formulaVariableColumnPaths().length());
  638 
  639                 for (int i = 0; i < col->formulaVariableColumnPaths().length(); i++) {
  640                     auto path = col->formulaVariableColumnPaths()[i];
  641                     for (Column* c : columns) {
  642                         if (!c) continue;
  643                         if (c->path() == path) {
  644                             formulaVariableColumns[i] = c;
  645                             col->finalizeLoad();
  646                             break;
  647                         }
  648                     }
  649                 }
  650             }
  651         }
  652 
  653         //all data was read in spreadsheets:
  654         //call CartesianPlot::retransform() to retransform the plots
  655         for (auto* plot : children<CartesianPlot>(ChildIndexFlag::Recursive)) {
  656             plot->setIsLoading(false);
  657             plot->retransform();
  658         }
  659 
  660         //all data was read in live-data sources:
  661         //call CartesianPlot::dataChanged() to notify affected plots about the new data.
  662         //this needs to be done here since in LiveDataSource::finalizeImport() called above
  663         //where the data is read the column pointers are not restored yes in curves.
  664         QVector<CartesianPlot*> plots;
  665         for (auto* source : sources) {
  666             for (int n = 0; n < source->columnCount(); ++n) {
  667                 Column* column = source->column(n);
  668 
  669                 //determine the plots where the column is consumed
  670                 for (const auto* curve : curves) {
  671                     if (curve->xColumn() == column || curve->yColumn() == column) {
  672                         auto* plot = static_cast<CartesianPlot*>(curve->parentAspect());
  673                         if (plots.indexOf(plot) == -1) {
  674                             plots << plot;
  675                             plot->setSuppressDataChangedSignal(true);
  676                         }
  677                     }
  678                 }
  679 
  680                 column->setChanged();
  681             }
  682         }
  683 
  684         //loop over all affected plots and retransform them
  685         for (auto* plot : plots) {
  686             plot->setSuppressDataChangedSignal(false);
  687             plot->dataChanged();
  688         }
  689     }
  690 
  691     emit loaded();
  692     return !reader->hasError();
  693 }
  694 
  695 bool Project::readProjectAttributes(XmlStreamReader* reader) {
  696     QXmlStreamAttributes attribs = reader->attributes();
  697     QString str = attribs.value(reader->namespaceUri().toString(), "modificationTime").toString();
  698     QDateTime modificationTime = QDateTime::fromString(str, "yyyy-dd-MM hh:mm:ss:zzz");
  699     if (str.isEmpty() || !modificationTime.isValid()) {
  700         reader->raiseWarning(i18n("Invalid project modification time. Using current time."));
  701         d->modificationTime = QDateTime::currentDateTime();
  702     } else
  703         d->modificationTime = modificationTime;
  704 
  705     d->author = attribs.value(reader->namespaceUri().toString(), "author").toString();
  706 
  707     return true;
  708 }