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

0001 /***************************************************************************
0002     File                 : CantorWorksheet.cpp
0003     Project              : LabPlot
0004     Description          : Aspect providing a Cantor Worksheets for Multiple backends
0005     --------------------------------------------------------------------
0006     Copyright            : (C) 2015 Garvit Khatri (garvitdelhi@gmail.com)
0007     Copyright            : (C) 2016 by Alexander Semke (alexander.semke@web.de)
0008 
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 "CantorWorksheet.h"
0030 #include "VariableParser.h"
0031 #include "backend/core/column/Column.h"
0032 #include "backend/core/column/ColumnPrivate.h"
0033 #include "backend/core/Project.h"
0034 #include "commonfrontend/cantorWorksheet/CantorWorksheetView.h"
0035 
0036 #include <cantor/cantorlibs_version.h>
0037 #include "3rdparty/cantor/cantor_part.h"
0038 #include <cantor/worksheetaccess.h>
0039 
0040 #ifdef HAVE_NEW_CANTOR_LIBS
0041 #include <cantor/panelpluginhandler.h>
0042 #include <cantor/panelplugin.h>
0043 #else
0044 #include "3rdparty/cantor/panelpluginhandler.h"
0045 #include "3rdparty/cantor/panelplugin.h"
0046 #endif
0047 
0048 #include <QAction>
0049 #include <QFileInfo>
0050 #include <QModelIndex>
0051 
0052 #include <KLocalizedString>
0053 #include <KMessageBox>
0054 #include <KParts/ReadWritePart>
0055 
0056 CantorWorksheet::CantorWorksheet(const QString &name, bool loading)
0057     : AbstractPart(name, AspectType::CantorWorksheet), m_backendName(name) {
0058 
0059     if (!loading)
0060         init();
0061 }
0062 
0063 /*!
0064     initializes Cantor's part and plugins
0065 */
0066 bool CantorWorksheet::init(QByteArray* content) {
0067     KPluginLoader loader(QLatin1String("cantorpart"));
0068     KPluginFactory* factory = loader.factory();
0069 
0070     if (!factory) {
0071         //we can only get to this here if we open a project having Cantor content and Cantor plugins were not found.
0072         //return false here, a proper error message will be created in load() and propagated further.
0073         WARN("Failed to load Cantor plugin:")
0074         WARN("Cantor Part file name: " << STDSTRING(loader.fileName()))
0075         WARN("  " << STDSTRING(loader.errorString()))
0076         return false;
0077     } else {
0078         m_part = factory->create<KParts::ReadWritePart>(this, QVariantList() << m_backendName << QLatin1String("--noprogress"));
0079         if (!m_part) {
0080             DEBUG("Could not create the Cantor Part.")
0081             return false;
0082         }
0083         m_worksheetAccess = m_part->findChild<Cantor::WorksheetAccessInterface*>(Cantor::WorksheetAccessInterface::Name);
0084 
0085         //load worksheet content if available
0086         if (content)
0087             m_worksheetAccess->loadWorksheetFromByteArray(content);
0088 
0089         connect(m_worksheetAccess, SIGNAL(modified()), this, SLOT(modified()));
0090 
0091         //Cantor's session
0092         m_session = m_worksheetAccess->session();
0093         connect(m_session, SIGNAL(statusChanged(Cantor::Session::Status)), this, SIGNAL(statusChanged(Cantor::Session::Status)));
0094 
0095         //variable model
0096         m_variableModel = m_session->variableDataModel();
0097         connect(m_variableModel, &QAbstractItemModel::dataChanged, this, &CantorWorksheet::dataChanged);
0098         connect(m_variableModel, &QAbstractItemModel::rowsInserted, this, &CantorWorksheet::rowsInserted);
0099         connect(m_variableModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &CantorWorksheet::rowsAboutToBeRemoved);
0100         connect(m_variableModel, &QAbstractItemModel::modelReset, this, &CantorWorksheet::modelReset);
0101 
0102         //available plugins
0103 #ifdef HAVE_NEW_CANTOR_LIBS
0104         auto* handler = new Cantor::PanelPluginHandler(this);
0105         handler->loadPlugins();
0106         m_plugins = handler->activePluginsForSession(m_session, Cantor::PanelPluginHandler::PanelStates());
0107         for (auto* plugin : m_plugins)
0108             plugin->connectToShell(m_part);
0109 #else
0110         auto* handler = m_part->findChild<Cantor::PanelPluginHandler*>(QLatin1String("PanelPluginHandler"));
0111         if (!handler) {
0112             KMessageBox::error(nullptr, i18n("No PanelPluginHandle found for the Cantor Part."));
0113             return false;
0114         }
0115         m_plugins = handler->plugins();
0116 #endif
0117     }
0118 
0119     return true;
0120 }
0121 
0122 //SLots
0123 void CantorWorksheet::dataChanged(const QModelIndex& index) {
0124     const QString& name = m_variableModel->data(m_variableModel->index(index.row(), 0)).toString();
0125     Column* col = child<Column>(name);
0126     if (col) {
0127         // Cantor::DefaultVariableModel::DataRole == 257
0128         QVariant dataValue = m_variableModel->data(m_variableModel->index(index.row(), 1), 257);
0129         if (dataValue.isNull())
0130             dataValue = m_variableModel->data(m_variableModel->index(index.row(), 1));
0131         const QString& value = dataValue.toString();
0132         VariableParser parser(m_backendName, value);
0133         if (parser.isParsed())
0134             col->replaceValues(0, parser.values());
0135     }
0136 
0137 }
0138 
0139 void CantorWorksheet::rowsInserted(const QModelIndex& parent, int first, int last) {
0140     Q_UNUSED(parent)
0141     for (int i = first; i <= last; ++i) {
0142         const QString& name = m_variableModel->data(m_variableModel->index(i, 0)).toString();
0143         QVariant dataValue = m_variableModel->data(m_variableModel->index(i, 1), 257);
0144         if (dataValue.isNull())
0145             dataValue = m_variableModel->data(m_variableModel->index(i, 1));
0146         const QString& value = dataValue.toString();
0147         VariableParser parser(m_backendName, value);
0148         if (parser.isParsed()) {
0149             Column* col = child<Column>(name);
0150             if (col) {
0151                 col->replaceValues(0, parser.values());
0152             } else {
0153                 col = new Column(name, parser.values());
0154                 col->setUndoAware(false);
0155                 addChild(col);
0156 
0157                 //TODO: Cantor currently ignores the order of variables in the worksheets
0158                 //and adds new variables at the last position in the model.
0159                 //Fix this in Cantor and switch to insertChildBefore here later.
0160                 //insertChildBefore(col, child<Column>(i));
0161             }
0162         } else {
0163             //the already existing variable doesn't contain any numerical values -> remove it
0164             Column* col = child<Column>(name);
0165             if (col)
0166                 removeChild(col);
0167         }
0168     }
0169 
0170     project()->setChanged(true);
0171 }
0172 
0173 void CantorWorksheet::modified() {
0174     project()->setChanged(true);
0175 }
0176 
0177 void CantorWorksheet::modelReset() {
0178     for (int i = 0; i < childCount<Column>(); ++i)
0179         child<Column>(i)->remove();
0180 }
0181 
0182 void CantorWorksheet::rowsAboutToBeRemoved(const QModelIndex & parent, int first, int last) {
0183     Q_UNUSED(parent);
0184 
0185     for (int i = first; i <= last; ++i) {
0186         const QString& name = m_variableModel->data(m_variableModel->index(first, 0)).toString();
0187         Column* column = child<Column>(name);
0188         if (column)
0189             column->remove();
0190     }
0191 }
0192 
0193 QList<Cantor::PanelPlugin*> CantorWorksheet::getPlugins() {
0194     return m_plugins;
0195 }
0196 
0197 KParts::ReadWritePart* CantorWorksheet::part() {
0198     return m_part;
0199 }
0200 
0201 QIcon CantorWorksheet::icon() const {
0202     if (m_session)
0203         return QIcon::fromTheme(m_session->backend()->icon());
0204     return QIcon();
0205 }
0206 
0207 QWidget* CantorWorksheet::view() const {
0208     if (!m_partView) {
0209         m_view = new CantorWorksheetView(const_cast<CantorWorksheet*>(this));
0210         m_view->setBaseSize(1500, 1500);
0211         m_partView = m_view;
0212         //  connect(m_view, SIGNAL(statusInfo(QString)), this, SIGNAL(statusInfo(QString)));
0213 
0214         //set the current path in the session to the path of the project file
0215         const Project* project = const_cast<CantorWorksheet*>(this)->project();
0216         const QString& fileName = project->fileName();
0217         if (!fileName.isEmpty()) {
0218             QFileInfo fi(fileName);
0219             m_session->setWorksheetPath(fi.filePath());
0220         }
0221     }
0222     return m_partView;
0223 }
0224 
0225 //! Return a new context menu.
0226 /**
0227  * The caller takes ownership of the menu.
0228  */
0229 QMenu* CantorWorksheet::createContextMenu() {
0230     QMenu* menu = AbstractPart::createContextMenu();
0231     Q_ASSERT(menu);
0232     emit requestProjectContextMenu(menu);
0233     return menu;
0234 }
0235 
0236 QString CantorWorksheet::backendName() {
0237     return this->m_backendName;
0238 }
0239 
0240 //TODO
0241 bool CantorWorksheet::exportView() const {
0242     return false;
0243 }
0244 
0245 bool CantorWorksheet::printView() {
0246     m_part->action("file_print")->trigger();
0247     return true;
0248 }
0249 
0250 bool CantorWorksheet::printPreview() const {
0251     m_part->action("file_print_preview")->trigger();
0252     return true;
0253 }
0254 
0255 //##############################################################################
0256 //##################  Serialization/Deserialization  ###########################
0257 //##############################################################################
0258 
0259 //! Save as XML
0260 void CantorWorksheet::save(QXmlStreamWriter* writer) const{
0261     writer->writeStartElement("cantorWorksheet");
0262     writeBasicAttributes(writer);
0263     writeCommentElement(writer);
0264 
0265     //general
0266     writer->writeStartElement( "general" );
0267     writer->writeAttribute( "backend_name", m_backendName);
0268     //TODO: save worksheet settings
0269     writer->writeEndElement();
0270 
0271     //save the content of Cantor's worksheet
0272     QByteArray content = m_worksheetAccess->saveWorksheetToByteArray();
0273     writer->writeStartElement("worksheet");
0274     writer->writeAttribute("content", content.toBase64());
0275     writer->writeEndElement();
0276 
0277     //save columns(variables)
0278     for (auto* col : children<Column>(ChildIndexFlag::IncludeHidden))
0279         col->save(writer);
0280 
0281     writer->writeEndElement(); // close "cantorWorksheet" section
0282 }
0283 
0284 //! Load from XML
0285 bool CantorWorksheet::load(XmlStreamReader* reader, bool preview) {
0286     //reset the status of the reader differentiating between
0287     //"failed because of the missing CAS" and "failed because of the broken XML"
0288     reader->setFailedCASMissing(false);
0289 
0290     if (!readBasicAttributes(reader))
0291         return false;
0292 
0293     KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used");
0294     QXmlStreamAttributes attribs;
0295     bool rc = false;
0296 
0297     while (!reader->atEnd()) {
0298         reader->readNext();
0299         if (reader->isEndElement() && reader->name() == "cantorWorksheet")
0300             break;
0301 
0302         if (!reader->isStartElement())
0303             continue;
0304 
0305         if (reader->name() == "comment") {
0306             if (!readCommentElement(reader))
0307                 return false;
0308         } else if (!preview && reader->name() == "general") {
0309             attribs = reader->attributes();
0310 
0311             m_backendName = attribs.value("backend_name").toString().trimmed();
0312             if (m_backendName.isEmpty())
0313                 reader->raiseWarning(attributeWarning.subs("backend_name").toString());
0314         } else if (!preview && reader->name() == "worksheet") {
0315             attribs = reader->attributes();
0316 
0317             QString str = attribs.value("content").toString().trimmed();
0318             if (str.isEmpty())
0319                 reader->raiseWarning(attributeWarning.subs("content").toString());
0320 
0321             QByteArray content = QByteArray::fromBase64(str.toLatin1());
0322             rc = init(&content);
0323             if (!rc) {
0324                 reader->raiseMissingCASWarning(m_backendName);
0325 
0326                 //failed to load this object because of the missing CAS plugin
0327                 //and not because of the broken project XML. Set this flag to
0328                 //handle this case correctly.
0329                 //TODO: we also can fail in the limit in cases where Cantor's content is broken
0330                 //and not because of the missing CAS plugin. This also needs to be treated accrodingly...
0331                 reader->setFailedCASMissing(true);
0332                 return false;
0333             }
0334         } else if (!preview && reader->name() == "column") {
0335             Column* column = new Column(QString());
0336             column->setUndoAware(false);
0337             if (!column->load(reader, preview)) {
0338                 delete column;
0339                 return false;
0340             }
0341             addChild(column);
0342         } else { // unknown element
0343             reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString()));
0344             if (!reader->skipToEndElement()) return false;
0345         }
0346     }
0347 
0348     return true;
0349 }