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 }