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 }