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 }