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

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