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

0001 /*
0002     File                 : Spreadsheet.cpp
0003     Project              : LabPlot
0004     Description          : Aspect providing a spreadsheet table with column logic
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2006-2008 Tilman Benkert <thzs@gmx.net>
0007     SPDX-FileCopyrightText: 2006-2009 Knut Franke <knut.franke@gmx.de>
0008     SPDX-FileCopyrightText: 2012-2023 Alexander Semke <alexander.semke@web.de>
0009     SPDX-FileCopyrightText: 2017-2020 Stefan Gerlach <stefan.gerlach@uni.kn>
0010 
0011     SPDX-License-Identifier: GPL-2.0-or-later
0012 */
0013 #include "Spreadsheet.h"
0014 #include "SpreadsheetModel.h"
0015 #include "SpreadsheetPrivate.h"
0016 #include "StatisticsSpreadsheet.h"
0017 #include "backend/core/AbstractAspect.h"
0018 #include "backend/core/AspectPrivate.h"
0019 #include "backend/core/column/ColumnStringIO.h"
0020 #include "backend/core/datatypes/DateTime2StringFilter.h"
0021 #include "backend/lib/XmlStreamReader.h"
0022 #include "backend/lib/commandtemplates.h"
0023 #include "backend/lib/macros.h"
0024 #include "backend/lib/trace.h"
0025 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
0026 #include "commonfrontend/spreadsheet/SpreadsheetView.h"
0027 
0028 #include "backend/lib/commandtemplates.h"
0029 
0030 #include <KConfig>
0031 #include <KConfigGroup>
0032 #include <KLocalizedString>
0033 
0034 #include <QIcon>
0035 #include <QUndoCommand>
0036 #include <QXmlStreamWriter>
0037 
0038 #include <algorithm>
0039 
0040 /*!
0041   \class Spreadsheet
0042   \brief Aspect providing a spreadsheet table with column logic.
0043 
0044   Spreadsheet is a container object for columns with no data of its own. By definition, it's columns
0045   are all of its children inheriting from class Column. Thus, the basic API is already defined
0046   by AbstractAspect (managing the list of columns, notification of column insertion/removal)
0047   and Column (changing and monitoring state of the actual data).
0048 
0049   Spreadsheet stores a pointer to its primary view of class SpreadsheetView. SpreadsheetView calls the Spreadsheet
0050   API but Spreadsheet only notifies SpreadsheetView by signals without calling its API directly. This ensures a
0051   maximum independence of UI and backend. SpreadsheetView can be easily replaced by a different class.
0052   User interaction is completely handled in SpreadsheetView and translated into
0053   Spreadsheet API calls (e.g., when a user edits a cell this will be handled by the delegate of
0054   SpreadsheetView and Spreadsheet will not know whether a script or a user changed the data.). All actions,
0055   menus etc. for the user interaction are handled SpreadsheetView, e.g., via a context menu.
0056   Selections are also handled by SpreadsheetView. The view itself is created by the first call to view();
0057 
0058   \ingroup backend
0059 */
0060 
0061 Spreadsheet::Spreadsheet(const QString& name, bool loading, AspectType type)
0062     : AbstractDataSource(name, type)
0063     , d_ptr(new SpreadsheetPrivate(this)) {
0064     if (!loading)
0065         init();
0066 }
0067 
0068 Spreadsheet::~Spreadsheet() {
0069     delete m_model;
0070     delete d_ptr;
0071 }
0072 
0073 /*!
0074     initializes the spreadsheet with the default number of columns and rows
0075 */
0076 void Spreadsheet::init() {
0077     KConfig config;
0078     KConfigGroup group = config.group(QLatin1String("Spreadsheet"));
0079 
0080     const int columns = group.readEntry(QLatin1String("ColumnCount"), 2);
0081     const int rows = group.readEntry(QLatin1String("RowCount"), 100);
0082 
0083     for (int i = 0; i < columns; i++) {
0084         Column* new_col = new Column(QString::number(i + 1), AbstractColumn::ColumnMode::Double);
0085         new_col->setPlotDesignation(i == 0 ? AbstractColumn::PlotDesignation::X : AbstractColumn::PlotDesignation::Y);
0086         addChild(new_col);
0087     }
0088     setRowCount(rows);
0089 }
0090 
0091 void Spreadsheet::setModel(SpreadsheetModel* model) {
0092     m_model = model;
0093 }
0094 
0095 SpreadsheetModel* Spreadsheet::model() const {
0096     return m_model;
0097 }
0098 
0099 /*! Constructs a primary view on me.
0100   This method may be called multiple times during the life time of an Aspect, or it might not get
0101   called at all. Aspects must not depend on the existence of a view for their operation.
0102 */
0103 QWidget* Spreadsheet::view() const {
0104 #ifndef SDK
0105     if (!m_partView) {
0106         auto type = this->parentAspect()->type();
0107         bool readOnly = (type == AspectType::Spreadsheet || type == AspectType::DatapickerCurve);
0108         m_view = new SpreadsheetView(const_cast<Spreadsheet*>(this), readOnly);
0109         m_partView = m_view;
0110         connect(this, &Spreadsheet::viewAboutToBeDeleted, [this]() {
0111             m_view = nullptr;
0112         });
0113     }
0114     return m_partView;
0115 #else
0116     return nullptr;
0117 #endif
0118 }
0119 
0120 bool Spreadsheet::exportView() const {
0121 #ifndef SDK
0122     return m_view->exportView();
0123 #else
0124     return true;
0125 #endif
0126 }
0127 
0128 bool Spreadsheet::printView() {
0129 #ifndef SDK
0130     return m_view->printView();
0131 #else
0132     return true;
0133 #endif
0134 }
0135 
0136 bool Spreadsheet::printPreview() const {
0137 #ifndef SDK
0138     return m_view->printPreview();
0139 #else
0140     return true;
0141 #endif
0142 }
0143 
0144 StatisticsSpreadsheet* Spreadsheet::statisticsSpreadsheet() const {
0145     Q_D(const Spreadsheet);
0146     return d->statisticsSpreadsheet;
0147 }
0148 
0149 /*!
0150  * \brief Called when the application settings were changed.
0151  *  adjusts the appearence of the spreadsheet header.
0152  */
0153 void Spreadsheet::updateHorizontalHeader() {
0154 #ifndef SDK
0155     if (m_model) {
0156         const QString& oldHeader = m_model->headerData(0, Qt::Horizontal, Qt::DisplayRole).toString();
0157         m_model->updateHorizontalHeader();
0158         const QString& newHeader = m_model->headerData(0, Qt::Horizontal, Qt::DisplayRole).toString();
0159 
0160         // if the header name of the first column has changed (column mode to be shown, etc.),
0161         // reset the column widths and request the view to adjuste the column sizes to the  content
0162         if (oldHeader != newHeader && m_view) {
0163             const auto& columns = children<Column>();
0164             for (auto col : columns)
0165                 col->setWidth(0);
0166             m_view->resizeHeader();
0167         }
0168     }
0169 #endif
0170 }
0171 
0172 void Spreadsheet::updateLocale() {
0173     for (auto* col : children<Column>())
0174         col->updateLocale();
0175 }
0176 
0177 /*!
0178   Returns the maximum number of rows in the spreadsheet.
0179  */
0180 int Spreadsheet::rowCount() const {
0181     int result = 0;
0182     for (auto* col : children<Column>()) {
0183         const int col_rows = col->rowCount();
0184         if (col_rows > result)
0185             result = col_rows;
0186     }
0187     return result;
0188 }
0189 
0190 class SpreadsheetSetRowsCountCmd : public QUndoCommand {
0191 public:
0192     SpreadsheetSetRowsCountCmd(Spreadsheet* spreadsheet, bool insert, int first, int count, QUndoCommand* parent)
0193         : QUndoCommand(parent)
0194         , m_spreadsheet(spreadsheet)
0195         , m_insert(insert)
0196         , m_first(first)
0197         , m_last(first + count - 1) {
0198         if (insert)
0199             setText(i18np("%1: insert 1 row", "%1: insert %2 rows", spreadsheet->name(), count));
0200         else
0201             setText(i18np("%1: remove 1 row", "%1: remove %2 rows", spreadsheet->name(), count));
0202     }
0203 
0204     virtual void redo() override {
0205         WAIT_CURSOR;
0206         if (m_insert)
0207             Q_EMIT m_spreadsheet->rowsAboutToBeInserted(m_first, m_last);
0208         else
0209             Q_EMIT m_spreadsheet->rowsAboutToBeRemoved(m_first, m_last);
0210 
0211         QUndoCommand::redo();
0212 
0213         if (m_insert)
0214             Q_EMIT m_spreadsheet->rowsInserted(m_spreadsheet->rowCount());
0215         else
0216             Q_EMIT m_spreadsheet->rowsRemoved(m_spreadsheet->rowCount());
0217         RESET_CURSOR;
0218         m_spreadsheet->emitRowCountChanged();
0219     }
0220 
0221     virtual void undo() override {
0222         WAIT_CURSOR;
0223         if (m_insert)
0224             Q_EMIT m_spreadsheet->rowsAboutToBeRemoved(m_first, m_last);
0225         else
0226             Q_EMIT m_spreadsheet->rowsAboutToBeInserted(m_first, m_last);
0227         QUndoCommand::undo();
0228 
0229         if (m_insert)
0230             Q_EMIT m_spreadsheet->rowsRemoved(m_spreadsheet->rowCount());
0231         else
0232             Q_EMIT m_spreadsheet->rowsInserted(m_spreadsheet->rowCount());
0233         RESET_CURSOR;
0234         m_spreadsheet->emitRowCountChanged();
0235     }
0236 
0237 private:
0238     Spreadsheet* m_spreadsheet;
0239     bool m_insert;
0240     int m_first;
0241     int m_last;
0242 };
0243 
0244 void Spreadsheet::removeRows(int first, int count, QUndoCommand* parent) {
0245     if (count < 1 || first < 0 || first + count > rowCount())
0246         return;
0247 
0248     auto* command = new SpreadsheetSetRowsCountCmd(this, false, first, count, parent);
0249 
0250     for (auto* col : children<Column>())
0251         col->removeRows(first, count, command);
0252 
0253     if (!parent)
0254         exec(command);
0255 }
0256 
0257 void Spreadsheet::insertRows(int before, int count, QUndoCommand* parent) {
0258     if (count < 1 || before < 0 || before > rowCount())
0259         return;
0260 
0261     auto* command = new SpreadsheetSetRowsCountCmd(this, true, before, count, parent);
0262 
0263     for (auto* col : children<Column>())
0264         col->insertRows(before, count, command);
0265 
0266     if (!parent)
0267         exec(command);
0268 }
0269 
0270 void Spreadsheet::appendRows(int count) {
0271     insertRows(rowCount(), count);
0272 }
0273 
0274 void Spreadsheet::appendRow() {
0275     insertRows(rowCount(), 1);
0276 }
0277 
0278 /*!
0279  * removes all rows in the spreadsheet if the value in one of the columns is missing/empty.
0280  */
0281 void Spreadsheet::removeEmptyRows() {
0282     const auto& rows = rowsWithMissingValues();
0283     if (rows.isEmpty())
0284         return;
0285 
0286     WAIT_CURSOR;
0287     beginMacro(i18n("%1: remove rows with missing values", name()));
0288 
0289     for (int row = rows.count() - 1; row >= 0; --row)
0290         removeRows(rows.at(row), 1);
0291 
0292     endMacro();
0293     RESET_CURSOR;
0294 }
0295 
0296 /*!
0297  * masks all rows in the spreadsheet if the value in one of the columns is missing/empty.
0298  */
0299 void Spreadsheet::maskEmptyRows() {
0300     const auto& rows = rowsWithMissingValues();
0301     if (rows.isEmpty())
0302         return;
0303 
0304     WAIT_CURSOR;
0305     beginMacro(i18n("%1: mask rows with missing values", name()));
0306 
0307     const auto& columns = children<Column>();
0308     for (int row : rows) {
0309         for (const auto& col : columns)
0310             col->setMasked(row);
0311     }
0312 
0313     endMacro();
0314     RESET_CURSOR;
0315 }
0316 
0317 /*!
0318  * returns the list of all rows having at least one missing/empty value.
0319  */
0320 QVector<int> Spreadsheet::rowsWithMissingValues() const {
0321     QVector<int> rows;
0322     const auto& columns = children<Column>();
0323     for (int row = 0; row < rowCount(); ++row) {
0324         for (const auto& col : columns) {
0325             if (col->asStringColumn()->textAt(row).isEmpty()) {
0326                 rows << row;
0327                 break;
0328             }
0329         }
0330     }
0331 
0332     return rows;
0333 }
0334 
0335 void Spreadsheet::appendColumns(int count) {
0336     insertColumns(columnCount(), count);
0337 }
0338 
0339 void Spreadsheet::appendColumn() {
0340     insertColumns(columnCount(), 1);
0341 }
0342 
0343 void Spreadsheet::prependColumns(int count) {
0344     insertColumns(0, count);
0345 }
0346 
0347 /*!
0348   Sets the number of rows of the spreadsheet to \c new_size
0349 */
0350 void Spreadsheet::setRowCount(int new_size, QUndoCommand* parent) {
0351     int current_size = rowCount();
0352     if (new_size > current_size)
0353         insertRows(current_size, new_size - current_size, parent);
0354     if (new_size < current_size && new_size >= 0)
0355         removeRows(new_size, current_size - new_size, parent);
0356 }
0357 
0358 void Spreadsheet::initConnectionsLinking(const Spreadsheet* sender, const Spreadsheet* receiver) {
0359     QObject::connect(sender, &Spreadsheet::aspectAboutToBeRemoved, receiver, &Spreadsheet::linkedSpreadsheetDeleted);
0360     QObject::connect(sender, &Spreadsheet::rowCountChanged, receiver, &Spreadsheet::linkedSpreadsheetNewRowCount);
0361 }
0362 
0363 class SpreadsheetSetLinkingCmd : public QUndoCommand {
0364 public:
0365     SpreadsheetSetLinkingCmd(Spreadsheet::Private* target,
0366                              const Spreadsheet::Linking& newValue,
0367                              const KLocalizedString& description,
0368                              QUndoCommand* parent = nullptr)
0369         : QUndoCommand(parent)
0370         , m_target(target)
0371         , m_linking(newValue) {
0372         setText(description.subs(m_target->name()).toString());
0373     }
0374 
0375     void execute() {
0376         if (m_target->linking.linkedSpreadsheet)
0377             QObject::disconnect(m_target->linking.linkedSpreadsheet, nullptr, m_target->q, nullptr);
0378 
0379         if (m_linking.linkedSpreadsheet) {
0380             m_linking.linkedSpreadsheetPath = m_linking.linkedSpreadsheet->path();
0381             m_target->q->initConnectionsLinking(m_linking.linkedSpreadsheet, m_target->q);
0382         }
0383 
0384         const Spreadsheet::Linking l = m_target->linking;
0385         m_target->linking = m_linking;
0386         m_linking = l;
0387     }
0388 
0389     virtual void redo() override {
0390         execute();
0391         QUndoCommand::redo();
0392         finalize();
0393     }
0394 
0395     virtual void undo() override {
0396         execute();
0397         QUndoCommand::undo();
0398         finalize();
0399     }
0400 
0401     void finalize() const {
0402         Q_EMIT m_target->q->linkingChanged(m_target->linking.linking);
0403         Q_EMIT m_target->q->linkedSpreadsheetChanged(m_target->linking.linkedSpreadsheet);
0404     }
0405 
0406 private:
0407     Spreadsheet::Private* m_target;
0408     Spreadsheet::Linking m_linking;
0409 };
0410 
0411 BASIC_SHARED_D_READER_IMPL(Spreadsheet, bool, linking, linking.linking)
0412 void Spreadsheet::setLinking(bool linking) {
0413     Q_D(Spreadsheet);
0414     if (linking != d->linking.linking) {
0415         Linking l = d->linking;
0416         l.linking = linking;
0417         auto parent = new SpreadsheetSetLinkingCmd(d, l, ki18n("%1: set linking"));
0418         if (linking && d->linking.linkedSpreadsheet)
0419             setRowCount(d->linking.linkedSpreadsheet->rowCount(), parent);
0420         exec(parent);
0421     }
0422 }
0423 
0424 BASIC_SHARED_D_READER_IMPL(Spreadsheet, const Spreadsheet*, linkedSpreadsheet, linking.linkedSpreadsheet)
0425 void Spreadsheet::setLinkedSpreadsheet(const Spreadsheet* linkedSpreadsheet, bool skipUndo) {
0426     Q_D(Spreadsheet);
0427     if (!d->linking.linking)
0428         return; // Do not allow setting a spreadsheet when linking is disabled
0429 
0430     if (linkedSpreadsheet != d->linking.linkedSpreadsheet) {
0431         if (skipUndo) {
0432             d->linking.linkedSpreadsheet = linkedSpreadsheet;
0433             initConnectionsLinking(linkedSpreadsheet, this);
0434         } else {
0435             Linking l = d->linking;
0436             l.linkedSpreadsheet = linkedSpreadsheet;
0437             auto* parent = new SpreadsheetSetLinkingCmd(d, l, ki18n("%1: set linked spreadsheet"));
0438             if (d->linking.linking && linkedSpreadsheet)
0439                 setRowCount(linkedSpreadsheet->rowCount(), parent);
0440             exec(parent);
0441         }
0442     }
0443 }
0444 
0445 QString Spreadsheet::linkedSpreadsheetPath() const {
0446     Q_D(const Spreadsheet);
0447     return d->linking.spreadsheetPath();
0448 }
0449 
0450 /*!
0451   Returns the column with the number \c index.
0452   Shallow wrapper around \sa AbstractAspect::child() - see there for caveat.
0453 */
0454 Column* Spreadsheet::column(int index) const {
0455     return child<Column>(index);
0456 }
0457 
0458 /*!
0459   Returns the column with the name \c name.
0460 */
0461 Column* Spreadsheet::column(const QString& name) const {
0462     return child<Column>(name);
0463 }
0464 
0465 /*!
0466   Returns the total number of columns in the spreadsheet.
0467 */
0468 int Spreadsheet::columnCount() const {
0469     return childCount<Column>();
0470 }
0471 
0472 /*!
0473   Returns the number of columns matching the given designation.
0474  */
0475 int Spreadsheet::columnCount(AbstractColumn::PlotDesignation pd) const {
0476     int count = 0;
0477     for (auto* col : children<Column>())
0478         if (col->plotDesignation() == pd)
0479             count++;
0480     return count;
0481 }
0482 
0483 class SpreadsheetSetColumnsCountCmd : public QUndoCommand {
0484 public:
0485     SpreadsheetSetColumnsCountCmd(Spreadsheet* spreadsheet, bool insert, int first, int count, QUndoCommand* parent)
0486         : QUndoCommand(parent)
0487         , m_spreadsheet(spreadsheet)
0488         , m_insert(insert)
0489         , m_first(first)
0490         , m_last(first + count - 1) {
0491         if (insert)
0492             setText(i18np("%1: insert 1 column", "%1: insert %2 columns", spreadsheet->name(), count));
0493         else
0494             setText(i18np("%1: remove 1 column", "%1: remove %2 columns", spreadsheet->name(), count));
0495     }
0496 
0497     virtual void redo() override {
0498         WAIT_CURSOR;
0499         if (m_insert)
0500             Q_EMIT m_spreadsheet->aspectsAboutToBeInserted(m_first, m_last);
0501         else
0502             Q_EMIT m_spreadsheet->aspectsAboutToBeRemoved(m_first, m_last);
0503 
0504         QUndoCommand::redo();
0505 
0506         if (m_insert)
0507             Q_EMIT m_spreadsheet->aspectsInserted(m_first, m_last);
0508         else
0509             Q_EMIT m_spreadsheet->aspectsRemoved();
0510         RESET_CURSOR;
0511         m_spreadsheet->emitColumnCountChanged();
0512     }
0513 
0514     virtual void undo() override {
0515         WAIT_CURSOR;
0516         if (m_insert)
0517             Q_EMIT m_spreadsheet->aspectsAboutToBeRemoved(m_first, m_last);
0518         else
0519             Q_EMIT m_spreadsheet->aspectsAboutToBeInserted(m_first, m_last);
0520         QUndoCommand::undo();
0521 
0522         if (m_insert)
0523             Q_EMIT m_spreadsheet->aspectsRemoved();
0524         else
0525             Q_EMIT m_spreadsheet->aspectsInserted(m_first, m_last);
0526         RESET_CURSOR;
0527         m_spreadsheet->emitColumnCountChanged();
0528     }
0529 
0530 private:
0531     Spreadsheet* m_spreadsheet;
0532     bool m_insert;
0533     int m_first;
0534     int m_last;
0535 };
0536 
0537 void Spreadsheet::removeColumns(int first, int count, QUndoCommand* parent) {
0538     if (count < 1 || first < 0 || first + count > columnCount())
0539         return;
0540 
0541     auto* command = new SpreadsheetSetColumnsCountCmd(this, false, first, count, parent);
0542     bool execute = false;
0543     if (!parent) {
0544         execute = true;
0545         parent = command;
0546     }
0547 
0548     const auto& columns = children<Column>();
0549     for (int i = (first + count - 1); i >= first; i--)
0550         columns.at(i)->remove(parent);
0551 
0552     if (execute)
0553         exec(command);
0554 }
0555 
0556 void Spreadsheet::insertColumns(int before, int count, QUndoCommand* parent) {
0557     auto* command = new SpreadsheetSetColumnsCountCmd(this, true, before, count, parent);
0558     bool execute = false;
0559     if (!parent) {
0560         execute = true;
0561         parent = command;
0562     }
0563     const int cols = columnCount();
0564     const int rows = rowCount();
0565     for (int i = 0; i < count; i++) {
0566         auto* new_col = new Column(QString::number(cols + i + 1), AbstractColumn::ColumnMode::Double);
0567         new_col->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
0568         new_col->insertRows(0, rows);
0569         insertChild(new_col, before + i, parent);
0570     }
0571 
0572     if (execute)
0573         exec(command);
0574 }
0575 
0576 /*!
0577   Sets the number of columns to \c new_size
0578 */
0579 void Spreadsheet::setColumnCount(int new_size, QUndoCommand* parent) {
0580     int old_size = columnCount();
0581     if (old_size == new_size || new_size < 0)
0582         return;
0583 
0584     if (new_size < old_size)
0585         removeColumns(new_size, old_size - new_size, parent);
0586     else
0587         insertColumns(old_size, new_size - old_size, parent);
0588 }
0589 
0590 /*!
0591   Clears the whole spreadsheet.
0592 */
0593 void Spreadsheet::clear() {
0594     WAIT_CURSOR;
0595     beginMacro(i18n("%1: clear", name()));
0596     for (auto* col : children<Column>())
0597         col->clear();
0598     endMacro();
0599     RESET_CURSOR;
0600 }
0601 
0602 void Spreadsheet::clear(const QVector<Column*>& columns) {
0603     auto* parent = new LongExecutionCmd(i18n("%1: clear selected columns", name()));
0604 
0605     //  if (formulaModeActive()) {
0606     //      for (auto* col : selectedColumns()) {
0607     //          col->setSuppressDataChangedSignal(true);
0608     //          col->clearFormulas();
0609     //          col->setSuppressDataChangedSignal(false);
0610     //          col->setChanged();
0611     //      }
0612     //  } else {
0613     for (auto* col : columns) {
0614         col->setSuppressDataChangedSignal(true);
0615         col->clear(parent);
0616         col->setSuppressDataChangedSignal(false);
0617         col->setChanged();
0618     }
0619     exec(parent);
0620 }
0621 
0622 /*!
0623   Clears all mask in the spreadsheet.
0624 */
0625 void Spreadsheet::clearMasks() {
0626     WAIT_CURSOR;
0627     beginMacro(i18n("%1: clear all masks", name()));
0628     for (auto* col : children<Column>())
0629         col->clearMasks();
0630     endMacro();
0631     RESET_CURSOR;
0632 }
0633 
0634 /*!
0635   Returns a new context menu. The caller takes ownership of the menu.
0636 */
0637 QMenu* Spreadsheet::createContextMenu() {
0638     QMenu* menu = AbstractPart::createContextMenu();
0639     Q_ASSERT(menu);
0640     if (type() != AspectType::StatisticsSpreadsheet)
0641         Q_EMIT requestProjectContextMenu(menu);
0642     return menu;
0643 }
0644 
0645 void Spreadsheet::fillColumnContextMenu(QMenu* menu, Column* column) {
0646     if (m_view)
0647         m_view->fillColumnContextMenu(menu, column);
0648 }
0649 
0650 void Spreadsheet::moveColumn(int from, int to) {
0651     const auto& columns = children<Column>();
0652     auto* col = columns.at(from);
0653     beginMacro(i18n("%1: move column %2 from position %3 to %4.", name(), col->name(), from + 1, to + 1));
0654     col->remove();
0655     insertChildBefore(col, columns.at(to));
0656     endMacro();
0657 }
0658 
0659 void Spreadsheet::copy(Spreadsheet* other) {
0660     WAIT_CURSOR;
0661     beginMacro(i18n("%1: copy %2", name(), other->name()));
0662 
0663     for (auto* col : children<Column>())
0664         col->remove();
0665     for (auto* src_col : other->children<Column>()) {
0666         Column* new_col = new Column(src_col->name(), src_col->columnMode());
0667         new_col->copy(src_col);
0668         new_col->setPlotDesignation(src_col->plotDesignation());
0669         QVector<Interval<int>> masks = src_col->maskedIntervals();
0670         for (const auto& iv : masks)
0671             new_col->setMasked(iv);
0672         QVector<Interval<int>> formulas = src_col->formulaIntervals();
0673         for (const auto& iv : formulas)
0674             new_col->setFormula(iv, src_col->formula(iv.start()));
0675         new_col->setWidth(src_col->width());
0676         addChild(new_col);
0677     }
0678     setComment(other->comment());
0679 
0680     endMacro();
0681     RESET_CURSOR;
0682 }
0683 
0684 // FIXME: replace index-based API with Column*-based one
0685 /*!
0686   Determines the corresponding X column.
0687 */
0688 int Spreadsheet::colX(int col) {
0689     for (int i = col - 1; i >= 0; i--) {
0690         if (column(i)->plotDesignation() == AbstractColumn::PlotDesignation::X)
0691             return i;
0692     }
0693     int cols = columnCount();
0694     for (int i = col + 1; i < cols; i++) {
0695         if (column(i)->plotDesignation() == AbstractColumn::PlotDesignation::X)
0696             return i;
0697     }
0698     return -1;
0699 }
0700 
0701 /*!
0702   Determines the corresponding Y column.
0703 */
0704 int Spreadsheet::colY(int col) {
0705     int cols = columnCount();
0706 
0707     if (column(col)->plotDesignation() == AbstractColumn::PlotDesignation::XError
0708         || column(col)->plotDesignation() == AbstractColumn::PlotDesignation::YError) {
0709         // look to the left first
0710         for (int i = col - 1; i >= 0; i--) {
0711             if (column(i)->plotDesignation() == AbstractColumn::PlotDesignation::Y)
0712                 return i;
0713         }
0714         for (int i = col + 1; i < cols; i++) {
0715             if (column(i)->plotDesignation() == AbstractColumn::PlotDesignation::Y)
0716                 return i;
0717         }
0718     } else {
0719         // look to the right first
0720         for (int i = col + 1; i < cols; i++) {
0721             if (column(i)->plotDesignation() == AbstractColumn::PlotDesignation::Y)
0722                 return i;
0723         }
0724         for (int i = col - 1; i >= 0; i--) {
0725             if (column(i)->plotDesignation() == AbstractColumn::PlotDesignation::Y)
0726                 return i;
0727         }
0728     }
0729     return -1;
0730 }
0731 
0732 /*! Sorts the given list of column.
0733   If 'leading' is a null pointer, each column is sorted separately.
0734 */
0735 void Spreadsheet::sortColumns(Column* leading, const QVector<Column*>& cols, bool ascending) {
0736     DEBUG(Q_FUNC_INFO << ", ascending = " << ascending)
0737     if (cols.isEmpty())
0738         return;
0739 
0740     // the normal QPair comparison does not work properly with descending sorting
0741     // therefore we use our own compare functions
0742     // TODO: check this. a < b vs. a.first < b.first
0743     class CompareFunctions {
0744     public:
0745         static bool doubleLess(QPair<double, int> a, QPair<double, int> b) {
0746             return a.first < b.first;
0747         }
0748         static bool doubleGreater(QPair<double, int> a, QPair<double, int> b) {
0749             return a.first > b.first;
0750         }
0751         static bool integerLess(QPair<int, int> a, QPair<int, int> b) {
0752             return a.first < b.first;
0753         }
0754         static bool integerGreater(QPair<int, int> a, QPair<int, int> b) {
0755             return a.first > b.first;
0756         }
0757         static bool bigIntLess(QPair<qint64, int> a, QPair<qint64, int> b) {
0758             return a.first < b.first;
0759         }
0760         static bool bigIntGreater(QPair<qint64, int> a, QPair<qint64, int> b) {
0761             return a.first > b.first;
0762         }
0763         static bool QStringLess(const QPair<QString, int>& a, const QPair<QString, int>& b) {
0764             return a < b;
0765         }
0766         static bool QStringGreater(const QPair<QString, int>& a, const QPair<QString, int>& b) {
0767             return a > b;
0768         }
0769         static bool QDateTimeLess(const QPair<QDateTime, int>& a, const QPair<QDateTime, int>& b) {
0770             return a < b;
0771         }
0772         static bool QDateTimeGreater(const QPair<QDateTime, int>& a, const QPair<QDateTime, int>& b) {
0773             return a > b;
0774         }
0775     };
0776 
0777     WAIT_CURSOR;
0778     beginMacro(i18n("%1: sort columns", name()));
0779 
0780     if (!leading) { // sort separately
0781         DEBUG(" sort separately")
0782         for (auto* col : cols) {
0783             int rows = col->rowCount();
0784             std::unique_ptr<Column> tempCol(new Column(QStringLiteral("temp"), col->columnMode()));
0785 
0786             switch (col->columnMode()) {
0787             case AbstractColumn::ColumnMode::Double: {
0788                 QVector<QPair<double, int>> map;
0789 
0790                 for (int i = 0; i < rows; i++)
0791                     if (col->isValid(i))
0792                         map.append(QPair<double, int>(col->valueAt(i), i));
0793                 const int filledRows = map.size();
0794 
0795                 if (ascending)
0796                     std::stable_sort(map.begin(), map.end(), CompareFunctions::doubleLess);
0797                 else
0798                     std::stable_sort(map.begin(), map.end(), CompareFunctions::doubleGreater);
0799 
0800                 // put the values in the right order into tempCol
0801                 for (int i = 0; i < filledRows; i++) {
0802                     int idx = map.at(i).second;
0803                     // too slow: tempCol->copy(col, idx, i, 1);
0804                     tempCol->setFromColumn(i, col, idx);
0805                     tempCol->setMasked(col->isMasked(idx));
0806                 }
0807                 break;
0808             }
0809             case AbstractColumn::ColumnMode::Integer: {
0810                 QVector<QPair<int, int>> map;
0811 
0812                 for (int i = 0; i < rows; i++)
0813                     map.append(QPair<int, int>(col->valueAt(i), i));
0814 
0815                 if (ascending)
0816                     std::stable_sort(map.begin(), map.end(), CompareFunctions::integerLess);
0817                 else
0818                     std::stable_sort(map.begin(), map.end(), CompareFunctions::integerGreater);
0819 
0820                 // put the values in the right order into tempCol
0821                 for (int i = 0; i < rows; i++) {
0822                     int idx = map.at(i).second;
0823                     // too slow: tempCol->copy(col, idx, i, 1);
0824                     tempCol->setFromColumn(i, col, idx);
0825                     tempCol->setMasked(col->isMasked(idx));
0826                 }
0827                 break;
0828             }
0829             case AbstractColumn::ColumnMode::BigInt: {
0830                 QVector<QPair<qint64, int>> map;
0831 
0832                 for (int i = 0; i < rows; i++)
0833                     map.append(QPair<qint64, int>(col->valueAt(i), i));
0834 
0835                 if (ascending)
0836                     std::stable_sort(map.begin(), map.end(), CompareFunctions::bigIntLess);
0837                 else
0838                     std::stable_sort(map.begin(), map.end(), CompareFunctions::bigIntGreater);
0839 
0840                 // put the values in the right order into tempCol
0841                 for (int i = 0; i < rows; i++) {
0842                     int idx = map.at(i).second;
0843                     // too slow: tempCol->copy(col, idx, i, 1);
0844                     tempCol->setFromColumn(i, col, idx);
0845                     tempCol->setMasked(col->isMasked(idx));
0846                 }
0847                 break;
0848             }
0849             case AbstractColumn::ColumnMode::Text: {
0850                 QVector<QPair<QString, int>> map;
0851 
0852                 for (int i = 0; i < rows; i++)
0853                     if (!col->textAt(i).isEmpty())
0854                         map.append(QPair<QString, int>(col->textAt(i), i));
0855                 const int filledRows = map.size();
0856 
0857                 if (ascending)
0858                     std::stable_sort(map.begin(), map.end(), CompareFunctions::QStringLess);
0859                 else
0860                     std::stable_sort(map.begin(), map.end(), CompareFunctions::QStringGreater);
0861 
0862                 // put the values in the right order into tempCol
0863                 for (int i = 0; i < filledRows; i++) {
0864                     int idx = map.at(i).second;
0865                     // too slow: tempCol->copy(col, idx, i, 1);
0866                     tempCol->setFromColumn(i, col, idx);
0867                     tempCol->setMasked(col->isMasked(idx));
0868                 }
0869                 break;
0870             }
0871             case AbstractColumn::ColumnMode::DateTime:
0872             case AbstractColumn::ColumnMode::Month:
0873             case AbstractColumn::ColumnMode::Day: {
0874                 QVector<QPair<QDateTime, int>> map;
0875 
0876                 for (int i = 0; i < rows; i++)
0877                     if (col->isValid(i))
0878                         map.append(QPair<QDateTime, int>(col->dateTimeAt(i), i));
0879                 const int filledRows = map.size();
0880 
0881                 if (ascending)
0882                     std::stable_sort(map.begin(), map.end(), CompareFunctions::QDateTimeLess);
0883                 else
0884                     std::stable_sort(map.begin(), map.end(), CompareFunctions::QDateTimeGreater);
0885 
0886                 // put the values in the right order into tempCol
0887                 for (int i = 0; i < filledRows; i++) {
0888                     int idx = map.at(i).second;
0889                     // too slow: tempCol->copy(col, idx, i, 1);
0890                     tempCol->setFromColumn(i, col, idx);
0891                     tempCol->setMasked(col->isMasked(idx));
0892                 }
0893                 break;
0894             }
0895             }
0896             // copy the sorted column
0897             col->copy(tempCol.get(), 0, 0, rows);
0898         }
0899     } else { // sort with leading column
0900         DEBUG(" sort with leading column")
0901         int rows = leading->rowCount();
0902 
0903         switch (leading->columnMode()) {
0904         case AbstractColumn::ColumnMode::Double: {
0905             QVector<QPair<double, int>> map;
0906             QVector<int> invalidIndex;
0907 
0908             for (int i = 0; i < rows; i++)
0909                 if (leading->isValid(i))
0910                     map.append(QPair<double, int>(leading->valueAt(i), i));
0911                 else
0912                     invalidIndex << i;
0913             const int filledRows = map.size();
0914             const int invalidRows = invalidIndex.size();
0915 
0916             if (ascending)
0917                 std::stable_sort(map.begin(), map.end(), CompareFunctions::doubleLess);
0918             else
0919                 std::stable_sort(map.begin(), map.end(), CompareFunctions::doubleGreater);
0920 
0921             for (auto* col : cols) {
0922                 auto columnMode = col->columnMode();
0923                 std::unique_ptr<Column> tempCol(new Column(QStringLiteral("temp"), columnMode));
0924                 // put the values in correct order into tempCol
0925                 for (int i = 0; i < filledRows; i++) {
0926                     int idx = map.at(i).second;
0927                     // too slow: tempCol->copy(col, idx, i, 1);
0928                     tempCol->setFromColumn(i, col, idx);
0929                     tempCol->setMasked(col->isMasked(idx));
0930                 }
0931 
0932                 // copy the sorted column
0933                 if (col == leading) // update all rows
0934                     col->copy(tempCol.get(), 0, 0, rows);
0935                 else { // do not overwrite unused cols
0936                     std::unique_ptr<Column> tempInvalidCol(new Column(QStringLiteral("temp2"), col->columnMode()));
0937                     for (int i = 0; i < invalidRows; i++) {
0938                         const int idx = invalidIndex.at(i);
0939                         // too slow: tempInvalidCol->copy(col, idx, i, 1);
0940                         tempInvalidCol->setFromColumn(i, col, idx);
0941                         tempInvalidCol->setMasked(col->isMasked(idx));
0942                     }
0943                     col->copy(tempCol.get(), 0, 0, filledRows);
0944                     col->copy(tempInvalidCol.get(), 0, filledRows, invalidRows);
0945                 }
0946             }
0947             break;
0948         }
0949         case AbstractColumn::ColumnMode::Integer: {
0950             // TODO: check if still working when invalid integer entries are supported
0951             QVector<QPair<int, int>> map;
0952 
0953             for (int i = 0; i < rows; i++)
0954                 map.append(QPair<int, int>(leading->valueAt(i), i));
0955 
0956             if (ascending)
0957                 std::stable_sort(map.begin(), map.end(), CompareFunctions::integerLess);
0958             else
0959                 std::stable_sort(map.begin(), map.end(), CompareFunctions::integerGreater);
0960 
0961             for (auto* col : cols) {
0962                 std::unique_ptr<Column> tempCol(new Column(QStringLiteral("temp"), col->columnMode()));
0963                 // put the values in the right order into tempCol
0964                 for (int i = 0; i < rows; i++) {
0965                     int idx = map.at(i).second;
0966                     // too slow: tempCol->copy(col, idx, i, 1);
0967                     tempCol->setFromColumn(i, col, idx);
0968                     tempCol->setMasked(col->isMasked(idx));
0969                 }
0970                 // copy the sorted column
0971                 col->copy(tempCol.get(), 0, 0, rows);
0972             }
0973             break;
0974         }
0975         case AbstractColumn::ColumnMode::BigInt: {
0976             QVector<QPair<qint64, int>> map;
0977 
0978             for (int i = 0; i < rows; i++)
0979                 map.append(QPair<qint64, int>(leading->valueAt(i), i));
0980 
0981             if (ascending)
0982                 std::stable_sort(map.begin(), map.end(), CompareFunctions::bigIntLess);
0983             else
0984                 std::stable_sort(map.begin(), map.end(), CompareFunctions::bigIntGreater);
0985 
0986             for (auto* col : cols) {
0987                 std::unique_ptr<Column> tempCol(new Column(QStringLiteral("temp"), col->columnMode()));
0988                 // put the values in the right order into tempCol
0989                 for (int i = 0; i < rows; i++) {
0990                     int idx = map.at(i).second;
0991                     // too slow: tempCol->copy(col, idx, i, 1);
0992                     tempCol->setFromColumn(i, col, idx);
0993                     tempCol->setMasked(col->isMasked(idx));
0994                 }
0995                 // copy the sorted column
0996                 col->copy(tempCol.get(), 0, 0, rows);
0997             }
0998             break;
0999         }
1000         case AbstractColumn::ColumnMode::Text: {
1001             QVector<QPair<QString, int>> map;
1002             QVector<int> emptyIndex;
1003 
1004             for (int i = 0; i < rows; i++)
1005                 if (!leading->textAt(i).isEmpty())
1006                     map.append(QPair<QString, int>(leading->textAt(i), i));
1007                 else
1008                     emptyIndex << i;
1009             // QDEBUG(" empty indices: " << emptyIndex)
1010             const int filledRows = map.size();
1011             const int emptyRows = emptyIndex.size();
1012 
1013             if (ascending)
1014                 std::stable_sort(map.begin(), map.end(), CompareFunctions::QStringLess);
1015             else
1016                 std::stable_sort(map.begin(), map.end(), CompareFunctions::QStringGreater);
1017 
1018             for (auto* col : cols) {
1019                 std::unique_ptr<Column> tempCol(new Column(QStringLiteral("temp"), col->columnMode()));
1020                 // put the values in the right order into tempCol
1021                 for (int i = 0; i < filledRows; i++) {
1022                     int idx = map.at(i).second;
1023                     // too slow: tempCol->copy(col, idx, i, 1);
1024                     tempCol->setFromColumn(i, col, idx);
1025                     tempCol->setMasked(col->isMasked(idx));
1026                 }
1027 
1028                 // copy the sorted column
1029                 if (col == leading) // update all rows
1030                     col->copy(tempCol.get(), 0, 0, rows);
1031                 else { // do not overwrite unused cols
1032                     std::unique_ptr<Column> tempEmptyCol(new Column(QStringLiteral("temp2"), col->columnMode()));
1033                     for (int i = 0; i < emptyRows; i++) {
1034                         const int idx = emptyIndex.at(i);
1035                         // too slow: tempEmptyCol->copy(col, idx, i, 1);
1036                         tempEmptyCol->setFromColumn(i, col, idx);
1037                         tempEmptyCol->setMasked(col->isMasked(idx));
1038                     }
1039                     col->copy(tempCol.get(), 0, 0, filledRows);
1040                     col->copy(tempEmptyCol.get(), 0, filledRows, emptyRows);
1041                 }
1042             }
1043             break;
1044         }
1045         case AbstractColumn::ColumnMode::DateTime:
1046         case AbstractColumn::ColumnMode::Month:
1047         case AbstractColumn::ColumnMode::Day: {
1048             QVector<QPair<QDateTime, int>> map;
1049             QVector<int> invalidIndex;
1050 
1051             for (int i = 0; i < rows; i++)
1052                 if (leading->isValid(i))
1053                     map.append(QPair<QDateTime, int>(leading->dateTimeAt(i), i));
1054                 else
1055                     invalidIndex << i;
1056             const int filledRows = map.size();
1057             const int invalidRows = invalidIndex.size();
1058 
1059             if (ascending)
1060                 std::stable_sort(map.begin(), map.end(), CompareFunctions::QDateTimeLess);
1061             else
1062                 std::stable_sort(map.begin(), map.end(), CompareFunctions::QDateTimeGreater);
1063 
1064             for (auto* col : cols) {
1065                 std::unique_ptr<Column> tempCol(new Column(QStringLiteral("temp"), col->columnMode()));
1066                 // put the values in the right order into tempCol
1067                 for (int i = 0; i < filledRows; i++) {
1068                     int idx = map.at(i).second;
1069                     // too slow: tempCol->copy(col, idx, i, 1);
1070                     tempCol->setFromColumn(i, col, idx);
1071                     tempCol->setMasked(col->isMasked(idx));
1072                 }
1073                 // copy the sorted column
1074                 if (col == leading) // update all rows
1075                     col->copy(tempCol.get(), 0, 0, rows);
1076                 else { // do not overwrite unused cols
1077                     std::unique_ptr<Column> tempInvalidCol(new Column(QStringLiteral("temp2"), col->columnMode()));
1078                     for (int i = 0; i < invalidRows; i++) {
1079                         const int idx = invalidIndex.at(i);
1080                         // too slow: tempInvalidCol->copy(col, idx, i, 1);
1081                         tempInvalidCol->setFromColumn(i, col, idx);
1082                         tempInvalidCol->setMasked(col->isMasked(idx));
1083                     }
1084                     col->copy(tempCol.get(), 0, 0, filledRows);
1085                     col->copy(tempInvalidCol.get(), 0, filledRows, invalidRows);
1086                 }
1087             }
1088             break;
1089         }
1090         }
1091     }
1092 
1093     endMacro();
1094     RESET_CURSOR;
1095 } // end of sortColumns()
1096 
1097 /*!
1098   Returns an icon to be used for decorating my views.
1099   */
1100 QIcon Spreadsheet::icon() const {
1101     return QIcon::fromTheme(QStringLiteral("labplot-spreadsheet"));
1102 }
1103 
1104 /*!
1105   Returns the text displayed in the given cell.
1106 */
1107 QString Spreadsheet::text(int row, int col) const {
1108     Column* c = column(col);
1109     if (!c)
1110         return {};
1111 
1112     return c->asStringColumn()->textAt(row);
1113 }
1114 
1115 /*!
1116  * This slot is, indirectly, called when a child of \c Spreadsheet (i.e. column) was selected in \c ProjectExplorer.
1117  * Emits the signal \c columnSelected that is handled in \c SpreadsheetView.
1118  */
1119 void Spreadsheet::childSelected(const AbstractAspect* aspect) {
1120     const Column* column = qobject_cast<const Column*>(aspect);
1121     if (column) {
1122         int index = indexOfChild<Column>(column);
1123         Q_EMIT columnSelected(index);
1124     }
1125 }
1126 
1127 /*!
1128  * This slot is, indirectly, called when a child of \c Spreadsheet (i.e. column) was deselected in \c ProjectExplorer.
1129  * Emits the signal \c columnDeselected that is handled in \c SpreadsheetView.
1130  */
1131 void Spreadsheet::childDeselected(const AbstractAspect* aspect) {
1132     const Column* column = qobject_cast<const Column*>(aspect);
1133     if (column) {
1134         int index = indexOfChild<Column>(column);
1135         Q_EMIT columnDeselected(index);
1136     }
1137 }
1138 
1139 void Spreadsheet::linkedSpreadsheetDeleted() {
1140     Q_D(Spreadsheet);
1141     Linking l = d->linking;
1142     l.linkedSpreadsheet = nullptr;
1143     exec(new SpreadsheetSetLinkingCmd(d, l, ki18n("%1: linked spreadsheet removed")));
1144 }
1145 
1146 void Spreadsheet::linkedSpreadsheetNewRowCount(int rowCount) {
1147     setRowCount(rowCount);
1148 }
1149 
1150 /*!
1151  *  Emits the signal to select or to deselect the column number \c index in the project explorer,
1152  *  if \c selected=true or \c selected=false, respectively.
1153  *  The signal is handled in \c AspectTreeModel and forwarded to the tree view in \c ProjectExplorer.
1154  * This function is called in \c SpreadsheetView upon selection changes.
1155  */
1156 void Spreadsheet::setColumnSelectedInView(int index, bool selected) {
1157     if (selected) {
1158         Q_EMIT childAspectSelectedInView(child<Column>(index));
1159 
1160         // deselect the spreadsheet in the project explorer, if a child (column) was selected
1161         // and also all possible parents like folder, workbook, datapicker curve, datapicker
1162         // to prevents unwanted multiple selection in the project explorer
1163         // if one of the parents of the selected column was also selected before.
1164         AbstractAspect* parent = this;
1165         while (parent) {
1166             Q_EMIT childAspectDeselectedInView(parent);
1167             parent = parent->parentAspect();
1168         }
1169     } else
1170         Q_EMIT childAspectDeselectedInView(child<Column>(index));
1171 }
1172 
1173 QVector<AspectType> Spreadsheet::pasteTypes() const {
1174     return QVector<AspectType>{AspectType::Column};
1175 }
1176 
1177 QVector<AspectType> Spreadsheet::dropableOn() const {
1178     auto vec = AbstractPart::dropableOn();
1179     vec << AspectType::Workbook;
1180     return vec;
1181 }
1182 
1183 void Spreadsheet::toggleStatisticsSpreadsheet(bool on) {
1184     Q_D(Spreadsheet);
1185     if (on) {
1186         if (d->statisticsSpreadsheet)
1187             return;
1188 
1189         d->statisticsSpreadsheet = new StatisticsSpreadsheet(this);
1190         addChild(d->statisticsSpreadsheet);
1191     } else {
1192         if (!d->statisticsSpreadsheet)
1193             return;
1194 
1195         setUndoAware(false);
1196         removeChild(d->statisticsSpreadsheet);
1197         setUndoAware(true);
1198         d->statisticsSpreadsheet = nullptr;
1199     }
1200 }
1201 
1202 // ##############################################################################
1203 // ##################  Serialization/Deserialization  ###########################
1204 // ##############################################################################
1205 /*!
1206   Saves as XML.
1207  */
1208 void Spreadsheet::save(QXmlStreamWriter* writer) const {
1209     Q_D(const Spreadsheet);
1210     writer->writeStartElement(QStringLiteral("spreadsheet"));
1211     writeBasicAttributes(writer);
1212     writeCommentElement(writer);
1213 
1214     writer->writeStartElement(QLatin1String("linking"));
1215     writer->writeAttribute(QStringLiteral("enabled"), QString::number(d->linking.linking));
1216     writer->writeAttribute(QStringLiteral("spreadsheet"), d->linking.spreadsheetPath());
1217     writer->writeEndElement();
1218 
1219     // columns
1220     const auto& columns = children<Column>(ChildIndexFlag::IncludeHidden);
1221     for (auto* column : columns)
1222         column->save(writer);
1223 
1224     // statistics spreadsheet, if available
1225     if (d->statisticsSpreadsheet)
1226         d->statisticsSpreadsheet->save(writer);
1227 
1228     writer->writeEndElement(); // "spreadsheet"
1229 }
1230 
1231 /*!
1232   Loads from XML.
1233 */
1234 bool Spreadsheet::load(XmlStreamReader* reader, bool preview) {
1235     Q_D(Spreadsheet);
1236     if (!readBasicAttributes(reader))
1237         return false;
1238 
1239     QString str;
1240     QXmlStreamAttributes attribs;
1241 
1242     // read child elements
1243     while (!reader->atEnd()) {
1244         reader->readNext();
1245 
1246         if (reader->isEndElement() && reader->name() == QLatin1String("spreadsheet"))
1247             break;
1248 
1249         if (reader->isStartElement()) {
1250             if (reader->name() == QLatin1String("comment")) {
1251                 if (!readCommentElement(reader))
1252                     return false;
1253             } else if (reader->name() == QLatin1String("linking")) {
1254                 attribs = reader->attributes();
1255                 str = attribs.value(QStringLiteral("enabled")).toString();
1256                 if (str.isEmpty())
1257                     reader->raiseMissingAttributeWarning(QStringLiteral("enabled"));
1258                 else
1259                     d->linking.linking = static_cast<bool>(str.toInt());
1260 
1261                 str = attribs.value(QStringLiteral("spreadsheet")).toString();
1262                 d->linking.linkedSpreadsheetPath = str;
1263             } else if (reader->name() == QLatin1String("column")) {
1264                 Column* column = new Column(QString());
1265                 column->setIsLoading(true);
1266                 if (!column->load(reader, preview)) {
1267                     delete column;
1268                     setColumnCount(0);
1269                     return false;
1270                 }
1271                 addChildFast(column);
1272             } else if (reader->name() == QLatin1String("statisticsSpreadsheet")) {
1273                 d->statisticsSpreadsheet = new StatisticsSpreadsheet(this, true);
1274                 if (!d->statisticsSpreadsheet->load(reader, preview)) {
1275                     delete d->statisticsSpreadsheet;
1276                     d->statisticsSpreadsheet = nullptr;
1277                 } else
1278                     addChildFast(d->statisticsSpreadsheet);
1279             } else { // unknown element
1280                 reader->raiseUnknownElementWarning();
1281                 if (!reader->skipToEndElement())
1282                     return false;
1283             }
1284         }
1285     }
1286 
1287     return !reader->hasError();
1288 }
1289 
1290 // ##############################################################################
1291 // ########################  Data Import  #######################################
1292 // ##############################################################################
1293 int Spreadsheet::prepareImport(std::vector<void*>& dataContainer,
1294                                AbstractFileFilter::ImportMode importMode,
1295                                int actualRows,
1296                                int actualCols,
1297                                QStringList colNameList,
1298                                QVector<AbstractColumn::ColumnMode> columnMode,
1299                                bool initializeContainer) {
1300     PERFTRACE(QLatin1String(Q_FUNC_INFO));
1301     DEBUG(Q_FUNC_INFO << ", resize spreadsheet to rows = " << actualRows << " and cols = " << actualCols)
1302     QDEBUG(Q_FUNC_INFO << ", column name list = " << colNameList)
1303     int columnOffset = 0;
1304     setUndoAware(false);
1305     if (m_model != nullptr)
1306         m_model->suppressSignals(true);
1307 
1308     // make the available columns undo unaware before we resize and rename them below,
1309     // the same will be done for new columns in this->resize().
1310     {
1311         const auto& columns = children<Column>();
1312         for (auto* column : qAsConst(columns))
1313             column->setUndoAware(false);
1314     }
1315 
1316     columnOffset = this->resize(importMode, colNameList, actualCols);
1317     if (initializeContainer)
1318         dataContainer.resize(actualCols);
1319     const auto& columns = children<Column>(); // Get new children because of the resize it might be different
1320 
1321     // resize the spreadsheet
1322     if (importMode == AbstractFileFilter::ImportMode::Replace) {
1323         clear();
1324         setRowCount(actualRows);
1325     } else {
1326         if (rowCount() < actualRows)
1327             setRowCount(actualRows);
1328     }
1329 
1330     if (columnMode.size() < actualCols) {
1331         QDEBUG(Q_FUNC_INFO << ", columnMode[] size " << columnMode.size() << " is too small, should be " << actualCols << "! Giving up.");
1332         return -1;
1333     }
1334 
1335     for (int n = 0; n < actualCols; n++) {
1336         // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp)
1337         auto* column = columns.at(columnOffset + n);
1338         DEBUG(" column " << n << " columnMode = " << ENUM_TO_STRING(AbstractColumn, ColumnMode, columnMode[n]));
1339         column->setColumnModeFast(columnMode[n]);
1340 
1341         // in most cases the first imported column is meant to be used as x-data.
1342         // Other columns provide mostly y-data or errors.
1343         // TODO: this has to be configurable for the user in the import widget,
1344         // it should be possible to specify x-error plot designation, etc.
1345         if (n == 0 && importMode == AbstractFileFilter::ImportMode::Replace)
1346             column->setPlotDesignation(AbstractColumn::PlotDesignation::X);
1347         else
1348             column->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
1349 
1350         if (initializeContainer) {
1351             switch (columnMode[n]) {
1352             case AbstractColumn::ColumnMode::Double: {
1353                 auto* vector = static_cast<QVector<double>*>(column->data());
1354                 dataContainer[n] = static_cast<void*>(vector);
1355                 break;
1356             }
1357             case AbstractColumn::ColumnMode::Integer: {
1358                 auto* vector = static_cast<QVector<int>*>(column->data());
1359                 dataContainer[n] = static_cast<void*>(vector);
1360                 break;
1361             }
1362             case AbstractColumn::ColumnMode::BigInt: {
1363                 auto* vector = static_cast<QVector<qint64>*>(column->data());
1364                 dataContainer[n] = static_cast<void*>(vector);
1365                 break;
1366             }
1367             case AbstractColumn::ColumnMode::Text: {
1368                 auto* vector = static_cast<QVector<QString>*>(column->data());
1369                 dataContainer[n] = static_cast<void*>(vector);
1370                 break;
1371             }
1372             case AbstractColumn::ColumnMode::Month:
1373             case AbstractColumn::ColumnMode::Day:
1374             case AbstractColumn::ColumnMode::DateTime: {
1375                 auto* vector = static_cast<QVector<QDateTime>*>(column->data());
1376                 dataContainer[n] = static_cast<void*>(vector);
1377                 break;
1378             }
1379             }
1380         } else {
1381             column->setData(dataContainer[n]);
1382         }
1383     }
1384     //  QDEBUG("dataPointers =" << dataPointers);
1385 
1386     // DEBUG(Q_FUNC_INFO << ", DONE");
1387 
1388     return columnOffset;
1389 }
1390 
1391 /*!
1392     resize data source to cols columns
1393     returns column offset depending on import mode
1394 */
1395 int Spreadsheet::resize(AbstractFileFilter::ImportMode mode, QStringList names, int cols) {
1396     //  PERFTRACE(Q_FUNC_INFO);
1397     DEBUG(Q_FUNC_INFO << ", mode = " << ENUM_TO_STRING(AbstractFileFilter, ImportMode, mode) << ", cols = " << cols)
1398     // QDEBUG(" column name list = " << colNameList)
1399 
1400     Q_EMIT aboutToResize(); // call this to disable the retransforms in worksheet elements in Project
1401 
1402     // make sure the column names provided by the user don't have any duplicates
1403     QStringList uniqueNames;
1404     if (names.count() > 1) {
1405         uniqueNames << names.at(0);
1406         for (int i = 1; i < names.count(); ++i)
1407             uniqueNames << AbstractAspect::uniqueNameFor(names.at(i), uniqueNames);
1408     } else
1409         uniqueNames = names;
1410 
1411     // if the number of provided column names is smaller than the number of columns to be created,
1412     // create standard names
1413     for (int k = uniqueNames.size(); k < cols; k++)
1414         uniqueNames.append(QStringLiteral("Column ") + QString::number(k + 1));
1415 
1416     int columnOffset = 0; // indexes the "start column" in the spreadsheet. Starting from this column the data will be imported.
1417 
1418     Column* newColumn = nullptr;
1419     int rows = rowCount();
1420     if (mode == AbstractFileFilter::ImportMode::Append) {
1421         columnOffset = childCount<Column>();
1422         for (int n = 0; n < cols; n++) {
1423             newColumn = new Column(uniqueNames.at(n), AbstractColumn::ColumnMode::Double);
1424             newColumn->resizeTo(rows);
1425             newColumn->setUndoAware(false);
1426             newColumn->resizeTo(rows);
1427             addChild(newColumn);
1428         }
1429     } else if (mode == AbstractFileFilter::ImportMode::Prepend) {
1430         Column* firstColumn = child<Column>(0);
1431         for (int n = 0; n < cols; n++) {
1432             newColumn = new Column(uniqueNames.at(n), AbstractColumn::ColumnMode::Double);
1433             newColumn->resizeTo(rows);
1434             newColumn->setUndoAware(false);
1435             newColumn->resizeTo(rows);
1436             insertChildBefore(newColumn, firstColumn);
1437         }
1438     } else if (mode == AbstractFileFilter::ImportMode::Replace) {
1439         // replace completely the previous content of the data source with the content to be imported.
1440         int columnsCount = childCount<Column>();
1441 
1442         if (columnsCount > cols) {
1443             // there are more columns in the data source than required -> remove the superfluous columns
1444             for (int i = 0; i < columnsCount - cols; i++)
1445                 removeChild(child<Column>(0));
1446         } else {
1447             // create additional columns if needed
1448             if (cols - columnsCount > 30)
1449                 Q_EMIT manyAspectsAboutToBeInserted();
1450             Q_EMIT aspectsAboutToBeInserted(columnsCount, cols - 1);
1451             for (int i = columnsCount; i < cols; i++) {
1452                 newColumn = new Column(uniqueNames.at(i), AbstractColumn::ColumnMode::Double);
1453                 newColumn->resizeTo(rows);
1454                 newColumn->setUndoAware(false);
1455                 newColumn->resizeTo(rows);
1456                 addChildFast(newColumn); // in the replace mode, we can skip checking the uniqueness of the names and use the "fast" method
1457             }
1458             Q_EMIT aspectsInserted(columnsCount, cols - 1);
1459         }
1460 
1461         // 1. if the column name has changed, call Column::reset() to disconnect all dependent objects from the dataChanged signal
1462         // 2. suppress the dataChanged signal for all columns (will be restored later in finalizeImport())
1463         // 3. rename the columns that were already available
1464         // 4. column->aspectDescriptionChanged() to trigger the update of the dependencies on column in Project.
1465         const auto& columns = children<Column>();
1466         int index = 0;
1467         for (auto* column : columns) {
1468             column->setSuppressDataChangedSignal(true);
1469             const auto& newName = uniqueNames.at(index);
1470             if (column->name() != newName) {
1471                 column->reset();
1472                 column->setName(newName, AbstractAspect::NameHandling::UniqueNotRequired);
1473                 column->aspectDescriptionChanged(column);
1474             }
1475             ++index;
1476         }
1477     }
1478 
1479     Q_EMIT resizeFinished(); // call this to re-enable the retransforms in worksheet elements in Project
1480 
1481     return columnOffset;
1482 }
1483 
1484 void Spreadsheet::finalizeImport(size_t columnOffset,
1485                                  size_t startColumn,
1486                                  size_t endColumn,
1487                                  const QString& dateTimeFormat,
1488                                  AbstractFileFilter::ImportMode importMode) {
1489     PERFTRACE(QLatin1String(Q_FUNC_INFO));
1490     // DEBUG(Q_FUNC_INFO << ", start/end col = " << startColumn << " / " << endColumn);
1491 
1492     // determine the dependent plots
1493     QVector<CartesianPlot*> plots;
1494     if (importMode == AbstractFileFilter::ImportMode::Replace) {
1495         for (size_t n = startColumn; n <= endColumn; n++) {
1496             auto* column = this->column((int)(columnOffset + n - startColumn));
1497             if (column)
1498                 column->addUsedInPlots(plots);
1499         }
1500 
1501         // suppress retransform in the dependent plots
1502         for (auto* plot : plots)
1503             plot->setSuppressRetransform(true);
1504     }
1505 
1506     // set the comments for each of the columns if datasource is a spreadsheet
1507     const int rows = rowCount();
1508     for (size_t col = startColumn; col <= endColumn; col++) {
1509         // DEBUG(Q_FUNC_INFO << ", column " << columnOffset + col - startColumn);
1510         Column* column = this->column((int)(columnOffset + col - startColumn));
1511         DEBUG(Q_FUNC_INFO << ", type " << ENUM_TO_STRING(AbstractColumn, ColumnMode, column->columnMode()))
1512 
1513         QString comment;
1514         switch (column->columnMode()) {
1515         case AbstractColumn::ColumnMode::Double:
1516             comment = i18np("double precision data, %1 element", "numerical data, %1 elements", rows);
1517             break;
1518         case AbstractColumn::ColumnMode::Integer:
1519             comment = i18np("integer data, %1 element", "integer data, %1 elements", rows);
1520             break;
1521         case AbstractColumn::ColumnMode::BigInt:
1522             comment = i18np("big integer data, %1 element", "big integer data, %1 elements", rows);
1523             break;
1524         case AbstractColumn::ColumnMode::Text:
1525             comment = i18np("text data, %1 element", "text data, %1 elements", rows);
1526             break;
1527         case AbstractColumn::ColumnMode::Month:
1528             comment = i18np("month data, %1 element", "month data, %1 elements", rows);
1529             break;
1530         case AbstractColumn::ColumnMode::Day:
1531             comment = i18np("day data, %1 element", "day data, %1 elements", rows);
1532             break;
1533         case AbstractColumn::ColumnMode::DateTime:
1534             comment = i18np("date and time data, %1 element", "date and time data, %1 elements", rows);
1535             // set same datetime format in column
1536             auto* filter = static_cast<DateTime2StringFilter*>(column->outputFilter());
1537             filter->setFormat(dateTimeFormat);
1538         }
1539         column->setComment(comment);
1540 
1541         if (importMode == AbstractFileFilter::ImportMode::Replace) {
1542             column->setSuppressDataChangedSignal(false);
1543             column->setChanged();
1544         }
1545     }
1546 
1547     if (importMode == AbstractFileFilter::ImportMode::Replace) {
1548         // retransform the dependent plots
1549         for (auto* plot : plots) {
1550             plot->setSuppressRetransform(false);
1551             plot->dataChanged(-1, -1); // TODO: check if all ranges must be updated
1552         }
1553     }
1554 
1555     // make the spreadsheet and all its children undo aware again
1556     setUndoAware(true);
1557     for (int i = 0; i < childCount<Column>(); i++)
1558         child<Column>(i)->setUndoAware(true);
1559 
1560     if (m_model)
1561         m_model->suppressSignals(false);
1562 
1563 #ifndef SDK
1564     if (m_partView && m_view)
1565         m_view->resizeHeader();
1566 #endif
1567 
1568     // row count most probably changed after the import, notify the dock widget.
1569     // no need to notify about the column count change, this is already done by add/removeChild signals
1570     Q_EMIT rowCountChanged(rowCount());
1571 
1572     // DEBUG(Q_FUNC_INFO << " DONE");
1573 }
1574 
1575 // ##############################################################################
1576 // ######################### Private implementation #############################
1577 // ##############################################################################
1578 SpreadsheetPrivate::SpreadsheetPrivate(Spreadsheet* owner)
1579     : q(owner) {
1580 }
1581 
1582 QString SpreadsheetPrivate::name() const {
1583     return q->name();
1584 }