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

0001 /*
0002     File                 : Folder.cpp
0003     Project              : LabPlot
0004     Description          : Folder in a project
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2009-2020 Alexander Semke <alexander.semke@web.de>
0007     SPDX-FileCopyrightText: 2007 Tilman Benkert <thzs@gmx.net>
0008     SPDX-FileCopyrightText: 2007 Knut Franke <knut.franke@gmx.de>
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "backend/core/Folder.h"
0013 #include "backend/core/Project.h"
0014 #include "backend/core/column/Column.h"
0015 #ifndef SDK
0016 #include "backend/core/Workbook.h"
0017 #include "backend/datapicker/Datapicker.h"
0018 #include "backend/datasources/LiveDataSource.h"
0019 #include "backend/matrix/Matrix.h"
0020 #include "backend/note/Note.h"
0021 #ifdef HAVE_CANTOR_LIBS
0022 #include "backend/cantorWorksheet/CantorWorksheet.h"
0023 #endif
0024 #ifdef HAVE_MQTT
0025 #include "backend/datasources/MQTTClient.h"
0026 #endif
0027 #endif
0028 
0029 #include "backend/lib/XmlStreamReader.h"
0030 #include "backend/spreadsheet/Spreadsheet.h"
0031 #include "backend/worksheet/Worksheet.h"
0032 
0033 #include <KLocalizedString>
0034 #include <QDropEvent>
0035 #include <QIcon>
0036 #include <QMimeData>
0037 
0038 /**
0039  * \class Folder
0040  * \brief Folder in a project
0041  */
0042 
0043 Folder::Folder(const QString& name, AspectType type)
0044     : AbstractAspect(name, type) {
0045 }
0046 
0047 QIcon Folder::icon() const {
0048     return QIcon::fromTheme(QStringLiteral("folder"));
0049 }
0050 
0051 /**
0052  * \brief Return a new context menu.
0053  *
0054  * The caller takes ownership of the menu.
0055  */
0056 QMenu* Folder::createContextMenu() {
0057     if (project()
0058 #ifdef HAVE_MQTT
0059         && type() != AspectType::MQTTSubscription
0060 #endif
0061     )
0062         return project()->createFolderContextMenu(this);
0063     return nullptr;
0064 }
0065 
0066 bool Folder::isDraggable() const {
0067     if (dynamic_cast<const Project*>(this))
0068         return false;
0069     else
0070         return true;
0071 }
0072 
0073 QVector<AspectType> Folder::pasteTypes() const {
0074     return QVector<AspectType>{AspectType::Folder,
0075                                AspectType::Worksheet,
0076                                AspectType::Workbook,
0077                                AspectType::Spreadsheet,
0078                                AspectType::Matrix,
0079                                AspectType::Datapicker,
0080                                AspectType::LiveDataSource,
0081                                AspectType::Note,
0082                                AspectType::CantorWorksheet};
0083 }
0084 
0085 QVector<AspectType> Folder::dropableOn() const {
0086     return QVector<AspectType>{AspectType::Folder, AspectType::Project};
0087 }
0088 
0089 void Folder::processDropEvent(const QVector<quintptr>& vec) {
0090     // reparent AbstractPart and Folder objects only
0091     AbstractAspect* lastMovedAspect{nullptr};
0092     for (auto a : vec) {
0093         auto* aspect = reinterpret_cast<AbstractAspect*>(a);
0094         auto* part = dynamic_cast<AbstractPart*>(aspect);
0095         if (part) {
0096             part->reparent(this);
0097             lastMovedAspect = part;
0098         } else {
0099             auto* folder = dynamic_cast<Folder*>(aspect);
0100             if (folder && folder != this) {
0101                 folder->reparent(this);
0102                 lastMovedAspect = folder;
0103             }
0104         }
0105     }
0106 
0107     // select the last moved aspect in the project explorer
0108     if (lastMovedAspect)
0109         lastMovedAspect->setSelected(true);
0110 }
0111 
0112 /**
0113  * \brief Save as XML
0114  */
0115 void Folder::save(QXmlStreamWriter* writer) const {
0116     writer->writeStartElement(QStringLiteral("folder"));
0117     writeBasicAttributes(writer);
0118     writeCommentElement(writer);
0119 
0120     const auto& children = this->children<AbstractAspect>(ChildIndexFlag::IncludeHidden);
0121     for (auto* child : children) {
0122         writer->writeStartElement(QLatin1String("child_aspect"));
0123         child->save(writer);
0124         writer->writeEndElement(); // "child_aspect"
0125     }
0126     writer->writeEndElement(); // "folder"
0127 }
0128 
0129 /**
0130  * \brief Load from XML
0131  */
0132 bool Folder::load(XmlStreamReader* reader, bool preview) {
0133     if (!readBasicAttributes(reader))
0134         return false;
0135 
0136     // read child elements
0137     while (!reader->atEnd()) {
0138         reader->readNext();
0139 
0140         if (reader->isEndElement())
0141             break;
0142 
0143         if (reader->isStartElement()) {
0144             if (reader->name() == QLatin1String("comment")) {
0145                 if (!readCommentElement(reader))
0146                     return false;
0147             } else if (reader->name() == QLatin1String("child_aspect")) {
0148                 if (!readChildAspectElement(reader, preview))
0149                     return false;
0150             } else { // unknown element
0151                 reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString()));
0152                 if (!reader->skipToEndElement())
0153                     return false;
0154             }
0155         }
0156     }
0157 
0158     return !reader->hasError();
0159 }
0160 
0161 void Folder::setPathesToLoad(const QStringList& pathes) {
0162     m_pathesToLoad = pathes;
0163 }
0164 
0165 const QStringList& Folder::pathesToLoad() const {
0166     return m_pathesToLoad;
0167 }
0168 
0169 /**
0170  * \brief Read child aspect from XML
0171  */
0172 bool Folder::readChildAspectElement(XmlStreamReader* reader, bool preview) {
0173     if (!reader->skipToNextTag())
0174         return false;
0175 
0176     if (reader->isEndElement() && reader->name() == QLatin1String("child_aspect"))
0177         return true; // empty element tag
0178 
0179     // check whether we need to skip the loading of the current child aspect
0180     if (!m_pathesToLoad.isEmpty()) {
0181         const QString& name = reader->attributes().value(QStringLiteral("name")).toString(); // name of the current child aspect
0182         const QString childPath = path() + QStringLiteral("/") + name; // child's path is not available yet (child not added yet) -> construct it manually
0183 
0184         // skip the current child aspect it is not in the list of aspects to be loaded
0185         if (m_pathesToLoad.indexOf(childPath) == -1) {
0186             // skip to the end of the current element
0187             if (!reader->skipToEndElement())
0188                 return false;
0189 
0190             // skip to the end of the "child_asspect" element
0191             if (!reader->skipToEndElement())
0192                 return false;
0193             return true;
0194         }
0195     }
0196 
0197     QString element_name = reader->name().toString();
0198     if (element_name == QLatin1String("folder")) {
0199         auto* folder = new Folder(QString());
0200 
0201         if (!m_pathesToLoad.isEmpty()) {
0202             // a child folder to be read -> provide the list of aspects to be loaded to the child folder, too.
0203             // since the child folder and all its children are not added yet (path() returns empty string),
0204             // we need to remove the path of the current child folder from the full pathes provided in m_pathesToLoad.
0205             // E.g. we want to import the path "Project/Folder/Spreadsheet" in the following project
0206             //  Project
0207             //         \Spreadsheet
0208             //         \Folder
0209             //                \Spreadsheet
0210             //
0211             // Here, we remove the part "Project/Folder/" and proceed for this child folder with "Spreadsheet" only.
0212             // With this the logic above where it is determined whether to import the child aspect or not works out.
0213 
0214             // manually construct the path of the child folder to be read
0215             const QString& curFolderPath = path() + QStringLiteral("/") + reader->attributes().value(QStringLiteral("name")).toString();
0216 
0217             // remove the path of the current child folder
0218             QStringList pathesToLoadNew;
0219             for (const auto& path : qAsConst(m_pathesToLoad)) {
0220                 if (path.startsWith(curFolderPath))
0221                     pathesToLoadNew << path.right(path.length() - curFolderPath.length());
0222             }
0223 
0224             folder->setPathesToLoad(pathesToLoadNew);
0225         }
0226 
0227         if (!folder->load(reader, preview)) {
0228             delete folder;
0229             return false;
0230         }
0231         addChildFast(folder);
0232     } else if (element_name == QLatin1String("workbook")) {
0233 #ifndef SDK
0234         auto* workbook = new Workbook(QString());
0235         if (!workbook->load(reader, preview)) {
0236             delete workbook;
0237             return false;
0238         }
0239         addChildFast(workbook);
0240 #endif
0241     } else if (element_name == QLatin1String("spreadsheet")) {
0242 #ifndef SDK
0243         auto* spreadsheet = new Spreadsheet(QString(), true);
0244         if (!spreadsheet->load(reader, preview)) {
0245             delete spreadsheet;
0246             return false;
0247         }
0248         addChildFast(spreadsheet);
0249 #endif
0250     } else if (element_name == QLatin1String("matrix")) {
0251 #ifndef SDK
0252         auto* matrix = new Matrix(QString(), true);
0253         if (!matrix->load(reader, preview)) {
0254             delete matrix;
0255             return false;
0256         }
0257         addChildFast(matrix);
0258 #endif
0259     } else if (element_name == QLatin1String("worksheet")) {
0260         auto* worksheet = new Worksheet(QString(), true);
0261         worksheet->setIsLoading(true);
0262         if (!worksheet->load(reader, preview)) {
0263             delete worksheet;
0264             return false;
0265         }
0266         addChildFast(worksheet);
0267         worksheet->setIsLoading(false);
0268     } else if (element_name == QLatin1String("cantorWorksheet")) {
0269 #ifdef HAVE_CANTOR_LIBS
0270 #ifndef SDK
0271         auto* cantorWorksheet = new CantorWorksheet(QLatin1String("null"), true);
0272         if (!cantorWorksheet->load(reader, preview)) {
0273             delete cantorWorksheet;
0274 
0275             // if we only failed to load because of the missing CAS, don't return with false here.
0276             // in this case we continue loading the project and show a warning about missing CAS at the end.
0277             if (!reader->failedCASMissing())
0278                 return false;
0279             else {
0280                 // failed because of the missing CAS. Read until the end of the current
0281                 // element in XML and continue loading the project.
0282                 while (!reader->atEnd()) {
0283                     reader->readNext();
0284                     if (reader->isEndElement() && reader->name() == QLatin1String("cantorWorksheet"))
0285                         break;
0286                 }
0287             }
0288         } else
0289             addChildFast(cantorWorksheet);
0290 #endif
0291 #else
0292         if (!preview) {
0293             while (!reader->atEnd()) {
0294                 reader->readNext();
0295                 if (reader->isEndElement() && reader->name() == QLatin1String("cantorWorksheet"))
0296                     break;
0297 
0298                 if (!reader->isStartElement())
0299                     continue;
0300 
0301                 if (reader->name() == QLatin1String("general")) {
0302                     const QString& backendName = reader->attributes().value(QStringLiteral("backend_name")).toString().trimmed();
0303                     if (!backendName.isEmpty())
0304                         reader->raiseMissingCASWarning(backendName);
0305                 } else
0306                     reader->skipToEndElement();
0307             }
0308         }
0309 #endif
0310 #ifdef HAVE_MQTT
0311 #ifndef SDK
0312     } else if (element_name == QLatin1String("MQTTClient")) {
0313         auto* client = new MQTTClient(QString());
0314         if (!client->load(reader, preview)) {
0315             delete client;
0316             return false;
0317         }
0318         addChildFast(client);
0319 #endif
0320 #endif
0321     } else if (element_name == QLatin1String("liveDataSource")
0322                || element_name == QLatin1String("LiveDataSource")) { // TODO: remove "LiveDataSources" in couple of releases
0323 #ifndef SDK
0324         auto* liveDataSource = new LiveDataSource(QString(), true);
0325         if (!liveDataSource->load(reader, preview)) {
0326             delete liveDataSource;
0327             return false;
0328         }
0329         addChildFast(liveDataSource);
0330 #endif
0331     } else if (element_name == QLatin1String("datapicker")) {
0332 #ifndef SDK
0333         auto* datapicker = new Datapicker(QString(), true);
0334         if (!datapicker->load(reader, preview)) {
0335             delete datapicker;
0336             return false;
0337         }
0338         addChildFast(datapicker);
0339 #endif
0340     } else if (element_name == QLatin1String("note")) {
0341 #ifndef SDK
0342         Note* note = new Note(QString());
0343         if (!note->load(reader, preview)) {
0344             delete note;
0345             return false;
0346         }
0347         addChildFast(note);
0348 #endif
0349     } else {
0350         reader->raiseWarning(i18n("unknown element '%1' found", element_name));
0351         if (!reader->skipToEndElement())
0352             return false;
0353     }
0354 
0355     if (!reader->skipToNextTag())
0356         return false;
0357     return !reader->hasError();
0358 }