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 }