File indexing completed on 2024-05-12 03:47:55
0001 /* 0002 File : SpreadsheetModel.cpp 0003 Project : LabPlot 0004 Description : Model for the access to a Spreadsheet 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2007 Tilman Benkert <thzs@gmx.net> 0007 SPDX-FileCopyrightText: 2009 Knut Franke <knut.franke@gmx.de> 0008 SPDX-FileCopyrightText: 2013-2022 Alexander Semke <alexander.semke@web.de> 0009 SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 0012 #include "backend/spreadsheet/SpreadsheetModel.h" 0013 #include "backend/core/Settings.h" 0014 #include "backend/core/datatypes/Double2StringFilter.h" 0015 #include "backend/lib/macros.h" 0016 #include "backend/lib/trace.h" 0017 #include "backend/spreadsheet/Spreadsheet.h" 0018 0019 #include <KConfigGroup> 0020 #include <KLocalizedString> 0021 0022 #include <QBrush> 0023 #include <QIcon> 0024 #include <QPalette> 0025 0026 /*! 0027 \class SpreadsheetModel 0028 \brief Model for the access to a Spreadsheet 0029 0030 This is a model in the sense of Qt4 model/view framework which is used 0031 to access a Spreadsheet object from any of Qt4s view classes, typically a QTableView. 0032 Its main purposes are translating Spreadsheet signals into QAbstractItemModel signals 0033 and translating calls to the QAbstractItemModel read/write API into calls 0034 in the public API of Spreadsheet. In many cases a pointer to the addressed column 0035 is obtained by calling Spreadsheet::column() and the manipulation is done using the 0036 public API of column. 0037 0038 \ingroup backend 0039 */ 0040 SpreadsheetModel::SpreadsheetModel(Spreadsheet* spreadsheet) 0041 : QAbstractItemModel(nullptr) 0042 , m_spreadsheet(spreadsheet) 0043 , m_rowCount(spreadsheet->rowCount()) 0044 , m_verticalHeaderCount(spreadsheet->rowCount()) 0045 , m_columnCount(spreadsheet->columnCount()) { 0046 updateVerticalHeader(); 0047 updateHorizontalHeader(false); 0048 connect(m_spreadsheet, &Spreadsheet::aspectDescriptionChanged, this, &SpreadsheetModel::handleDescriptionChange); 0049 0050 // Used when single columns get deleted or added 0051 connect(m_spreadsheet, 0052 QOverload<const AbstractAspect*, int, const AbstractAspect*>::of(&Spreadsheet::childAspectAboutToBeAdded), 0053 this, 0054 &SpreadsheetModel::handleAspectAboutToBeAdded); 0055 connect(m_spreadsheet, &Spreadsheet::childAspectAdded, this, &SpreadsheetModel::handleAspectAdded); 0056 connect(m_spreadsheet, &Spreadsheet::childAspectAboutToBeRemoved, this, &SpreadsheetModel::handleAspectAboutToBeRemoved); 0057 connect(m_spreadsheet, &Spreadsheet::childAspectRemoved, this, &SpreadsheetModel::handleAspectRemoved); 0058 0059 // Used when changing the column count 0060 connect(m_spreadsheet, &Spreadsheet::aspectsAboutToBeInserted, this, &SpreadsheetModel::handleAspectsAboutToBeInserted); 0061 connect(m_spreadsheet, &Spreadsheet::aspectsAboutToBeRemoved, this, &SpreadsheetModel::handleAspectsAboutToBeRemoved); 0062 connect(m_spreadsheet, &Spreadsheet::aspectsInserted, this, &SpreadsheetModel::handleAspectsInserted); 0063 connect(m_spreadsheet, &Spreadsheet::aspectsRemoved, this, &SpreadsheetModel::handleAspectsRemoved); 0064 0065 connect(m_spreadsheet, &Spreadsheet::rowsAboutToBeInserted, this, &SpreadsheetModel::handleRowsAboutToBeInserted); 0066 connect(m_spreadsheet, &Spreadsheet::rowsAboutToBeRemoved, this, &SpreadsheetModel::handleRowsAboutToBeRemoved); 0067 connect(m_spreadsheet, &Spreadsheet::rowsInserted, this, &SpreadsheetModel::handleRowsInserted); 0068 connect(m_spreadsheet, &Spreadsheet::rowsRemoved, this, &SpreadsheetModel::handleRowsRemoved); 0069 0070 m_suppressSignals = true; 0071 handleAspectsAboutToBeInserted(0, spreadsheet->columnCount() - 1); 0072 handleAspectsInserted(0, spreadsheet->columnCount() - 1); // make connections 0073 m_suppressSignals = false; 0074 0075 m_spreadsheet->setModel(this); 0076 } 0077 0078 void SpreadsheetModel::suppressSignals(bool value) { 0079 m_suppressSignals = value; 0080 0081 // update the headers after all the data was added to the model 0082 // and we start listening to signals again 0083 if (m_suppressSignals) 0084 beginResetModel(); 0085 else { 0086 m_rowCount = m_spreadsheet->rowCount(); 0087 m_columnCount = m_spreadsheet->columnCount(); 0088 updateHorizontalHeader(false); 0089 endResetModel(); 0090 } 0091 } 0092 0093 Qt::ItemFlags SpreadsheetModel::flags(const QModelIndex& index) const { 0094 if (index.isValid()) 0095 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; 0096 else 0097 return Qt::ItemIsEnabled; 0098 } 0099 0100 void SpreadsheetModel::setSearchText(const QString& text) { 0101 m_searchText = text; 0102 } 0103 0104 QModelIndex SpreadsheetModel::index(const QString& text) const { 0105 const int colCount = m_spreadsheet->columnCount(); 0106 const int rowCount = m_spreadsheet->rowCount(); 0107 for (int col = 0; col < colCount; ++col) { 0108 auto* column = m_spreadsheet->column(col)->asStringColumn(); 0109 for (int row = 0; row < rowCount; ++row) { 0110 if (column->textAt(row).indexOf(text) != -1) 0111 return createIndex(row, col); 0112 } 0113 } 0114 0115 return createIndex(-1, -1); 0116 } 0117 0118 QVariant SpreadsheetModel::data(const QModelIndex& index, int role) const { 0119 if (!index.isValid()) 0120 return {}; 0121 0122 const int row = index.row(); 0123 const int col = index.column(); 0124 const Column* col_ptr = m_spreadsheet->column(col); 0125 0126 if (!col_ptr) 0127 return {}; 0128 0129 switch (role) { 0130 case Qt::ToolTipRole: 0131 if (col_ptr->isValid(row)) { 0132 if (col_ptr->isMasked(row)) 0133 return {i18n("%1, masked (ignored in all operations)", col_ptr->asStringColumn()->textAt(row))}; 0134 else 0135 return {col_ptr->asStringColumn()->textAt(row)}; 0136 } else { 0137 if (col_ptr->isMasked(row)) 0138 return {i18n("invalid cell, masked (ignored in all operations)")}; 0139 else 0140 return {i18n("invalid cell (ignored in all operations)")}; 0141 } 0142 case Qt::EditRole: 0143 if (col_ptr->columnMode() == AbstractColumn::ColumnMode::Double) { 0144 double value = col_ptr->valueAt(row); 0145 if (std::isnan(value)) 0146 return {QStringLiteral("-")}; 0147 else if (std::isinf(value)) 0148 return {QStringLiteral("inf")}; 0149 else 0150 return {col_ptr->asStringColumn()->textAt(row)}; 0151 } 0152 0153 if (col_ptr->isValid(row)) 0154 return {col_ptr->asStringColumn()->textAt(row)}; 0155 0156 // m_formula_mode is not used at the moment 0157 // if (m_formula_mode) 0158 // return QVariant(col_ptr->formula(row)); 0159 0160 break; 0161 case Qt::DisplayRole: 0162 if (col_ptr->columnMode() == AbstractColumn::ColumnMode::Double) { 0163 double value = col_ptr->valueAt(row); 0164 if (std::isnan(value)) 0165 return {QStringLiteral("-")}; 0166 else if (std::isinf(value)) 0167 return {UTF8_QSTRING("∞")}; 0168 else 0169 return {col_ptr->asStringColumn()->textAt(row)}; 0170 } 0171 0172 if (!col_ptr->isValid(row)) 0173 return {QStringLiteral("-")}; 0174 0175 // m_formula_mode is not used at the moment 0176 // if (m_formula_mode) 0177 // return QVariant(col_ptr->formula(row)); 0178 0179 return {col_ptr->asStringColumn()->textAt(row)}; 0180 case Qt::ForegroundRole: 0181 if (!col_ptr->isValid(row)) 0182 return {QBrush(Qt::red)}; 0183 return color(col_ptr, row, AbstractColumn::Formatting::Foreground); 0184 case Qt::BackgroundRole: 0185 if (m_searchText.isEmpty()) 0186 return color(col_ptr, row, AbstractColumn::Formatting::Background); 0187 else { 0188 if (col_ptr->asStringColumn()->textAt(row).indexOf(m_searchText) == -1) 0189 return color(col_ptr, row, AbstractColumn::Formatting::Background); 0190 else 0191 return {QApplication::palette().color(QPalette::Highlight)}; 0192 } 0193 case static_cast<int>(CustomDataRole::MaskingRole): 0194 return {col_ptr->isMasked(row)}; 0195 case static_cast<int>(CustomDataRole::FormulaRole): 0196 return {col_ptr->formula(row)}; 0197 case Qt::DecorationRole: 0198 return color(col_ptr, row, AbstractColumn::Formatting::Icon); 0199 // if (m_formula_mode) 0200 // return QIcon(QPixmap(":/equals.png")); //TODO 0201 } 0202 0203 return {}; 0204 } 0205 0206 QVariant SpreadsheetModel::headerData(int section, Qt::Orientation orientation, int role) const { 0207 if ((orientation == Qt::Horizontal && section > m_columnCount - 1) || (orientation == Qt::Vertical && section > m_rowCount - 1)) 0208 return {}; 0209 0210 switch (orientation) { 0211 case Qt::Horizontal: 0212 switch (role) { 0213 case Qt::DisplayRole: 0214 case Qt::ToolTipRole: 0215 case Qt::EditRole: 0216 return m_horizontal_header_data.at(section); 0217 case Qt::DecorationRole: 0218 return m_spreadsheet->child<Column>(section)->icon(); 0219 case static_cast<int>(CustomDataRole::CommentRole): 0220 return m_spreadsheet->child<Column>(section)->comment(); 0221 } 0222 break; 0223 case Qt::Vertical: 0224 switch (role) { 0225 case Qt::DisplayRole: 0226 case Qt::ToolTipRole: 0227 return section + 1; 0228 } 0229 } 0230 0231 return {}; 0232 } 0233 0234 int SpreadsheetModel::rowCount(const QModelIndex& /*parent*/) const { 0235 return m_rowCount; 0236 } 0237 0238 int SpreadsheetModel::columnCount(const QModelIndex& /*parent*/) const { 0239 return m_columnCount; 0240 } 0241 0242 bool SpreadsheetModel::setData(const QModelIndex& index, const QVariant& value, int role) { 0243 if (!index.isValid()) 0244 return false; 0245 0246 int row = index.row(); 0247 auto* column = m_spreadsheet->column(index.column()); 0248 0249 // DEBUG("SpreadsheetModel::setData() value = " << STDSTRING(value.toString())) 0250 0251 // don't do anything if no new value was provided 0252 if (column->columnMode() == AbstractColumn::ColumnMode::Double) { 0253 bool ok; 0254 double new_value = QLocale().toDouble(value.toString(), &ok); 0255 if (ok) { 0256 if (column->valueAt(row) == new_value) 0257 return false; 0258 } else { 0259 // an empty (non-numeric value) was provided 0260 if (std::isnan(column->valueAt(row))) 0261 return false; 0262 } 0263 } else { 0264 if (column->asStringColumn()->textAt(row) == value.toString()) 0265 return false; 0266 } 0267 0268 switch (role) { 0269 case Qt::EditRole: 0270 // remark: the validity of the cell is determined by the input filter 0271 if (m_formula_mode) 0272 column->setFormula(row, value.toString()); 0273 else 0274 column->asStringColumn()->setTextAt(row, value.toString()); 0275 return true; 0276 case static_cast<int>(CustomDataRole::MaskingRole): 0277 m_spreadsheet->column(index.column())->setMasked(row, value.toBool()); 0278 return true; 0279 case static_cast<int>(CustomDataRole::FormulaRole): 0280 m_spreadsheet->column(index.column())->setFormula(row, value.toString()); 0281 return true; 0282 } 0283 0284 return false; 0285 } 0286 0287 QModelIndex SpreadsheetModel::index(int row, int column, const QModelIndex& /*parent*/) const { 0288 return createIndex(row, column); 0289 } 0290 0291 QModelIndex SpreadsheetModel::parent(const QModelIndex& /*child*/) const { 0292 return QModelIndex{}; 0293 } 0294 0295 bool SpreadsheetModel::hasChildren(const QModelIndex& /*parent*/) const { 0296 return false; 0297 } 0298 0299 void SpreadsheetModel::handleAspectsAboutToBeInserted(int first, int last) { 0300 if (m_suppressSignals) 0301 return; 0302 m_spreadsheetColumnCountChanging = true; 0303 beginInsertColumns(QModelIndex(), first, last); 0304 } 0305 0306 void SpreadsheetModel::handleAspectAboutToBeAdded(const AbstractAspect* parent, int index, const AbstractAspect* aspect) { 0307 if (m_spreadsheetColumnCountChanging || m_suppressSignals) 0308 return; 0309 const Column* col = dynamic_cast<const Column*>(aspect); 0310 if (!col || parent != m_spreadsheet) 0311 return; 0312 beginInsertColumns(QModelIndex(), index, index); 0313 } 0314 0315 void SpreadsheetModel::handleAspectsInserted(int first, int last) { 0316 const auto& children = m_spreadsheet->children<Column>(); 0317 if (first < 0 || first >= children.count() || last >= children.count() || first > last) 0318 return; 0319 0320 for (int i = first; i <= last; i++) { 0321 const auto* col = children.at(i); 0322 connect(col, &Column::plotDesignationChanged, this, &SpreadsheetModel::handlePlotDesignationChange); 0323 connect(col, &Column::modeChanged, this, &SpreadsheetModel::handleDataChange); 0324 connect(col, &Column::dataChanged, this, &SpreadsheetModel::handleDataChange); 0325 connect(col, &Column::formatChanged, this, &SpreadsheetModel::handleDataChange); 0326 connect(col, &Column::modeChanged, this, &SpreadsheetModel::handleModeChange); 0327 connect(col, &Column::maskingChanged, this, &SpreadsheetModel::handleDataChange); 0328 connect(col, &Column::formulaChanged, this, &SpreadsheetModel::handlePlotDesignationChange); // we can re-use the same slot to update the header here 0329 connect(col->outputFilter(), &AbstractSimpleFilter::digitsChanged, this, &SpreadsheetModel::handleDigitsChange); 0330 } 0331 0332 handleAspectCountChanged(); 0333 if (!m_suppressSignals) 0334 endInsertColumns(); 0335 m_spreadsheetColumnCountChanging = false; 0336 } 0337 0338 void SpreadsheetModel::handleAspectAdded(const AbstractAspect* aspect) { 0339 // PERFTRACE(Q_FUNC_INFO); 0340 if (m_spreadsheetColumnCountChanging) 0341 return; 0342 const Column* col = dynamic_cast<const Column*>(aspect); 0343 if (!col || aspect->parentAspect() != m_spreadsheet) 0344 return; 0345 int index = m_spreadsheet->indexOfChild<Column>(aspect); 0346 handleAspectsInserted(index, index); 0347 } 0348 0349 void SpreadsheetModel::handleAspectsAboutToBeRemoved(int first, int last) { 0350 if (m_suppressSignals) 0351 return; 0352 0353 const auto& children = m_spreadsheet->children<Column>(); 0354 if (first < 0 || first >= children.count() || last >= children.count() || first > last) 0355 return; 0356 0357 m_spreadsheetColumnCountChanging = true; 0358 0359 beginRemoveColumns(QModelIndex(), first, last); 0360 for (int i = first; i <= last; i++) 0361 disconnect(children.at(i), nullptr, this, nullptr); 0362 } 0363 0364 void SpreadsheetModel::handleAspectAboutToBeRemoved(const AbstractAspect* aspect) { 0365 if (m_suppressSignals || m_spreadsheetColumnCountChanging) 0366 return; 0367 0368 const Column* col = dynamic_cast<const Column*>(aspect); 0369 if (!col || aspect->parentAspect() != m_spreadsheet) 0370 return; 0371 0372 const int index = m_spreadsheet->indexOfChild<AbstractAspect>(aspect); 0373 beginRemoveColumns(QModelIndex(), index, index); 0374 disconnect(col, nullptr, this, nullptr); 0375 } 0376 0377 void SpreadsheetModel::handleAspectsRemoved() { 0378 if (m_suppressSignals) 0379 return; 0380 handleAspectCountChanged(); 0381 endRemoveColumns(); 0382 m_spreadsheetColumnCountChanging = false; 0383 } 0384 0385 void SpreadsheetModel::handleAspectRemoved(const AbstractAspect* parent, const AbstractAspect* /*before*/, const AbstractAspect* child) { 0386 // same conditions as in handleAspectAboutToBeRemoved() 0387 if (m_spreadsheetColumnCountChanging || child->type() != AspectType::Column || parent != m_spreadsheet) 0388 return; 0389 0390 handleAspectsRemoved(); 0391 } 0392 0393 void SpreadsheetModel::handleAspectCountChanged() { 0394 if (m_suppressSignals) 0395 return; 0396 0397 m_columnCount = m_spreadsheet->columnCount(); 0398 updateHorizontalHeader(false); 0399 } 0400 0401 void SpreadsheetModel::handleDescriptionChange(const AbstractAspect* aspect) { 0402 if (m_suppressSignals) 0403 return; 0404 0405 const Column* col = dynamic_cast<const Column*>(aspect); 0406 if (!col || aspect->parentAspect() != m_spreadsheet) 0407 return; 0408 0409 if (!m_suppressSignals) { 0410 updateHorizontalHeader(false); 0411 int index = m_spreadsheet->indexOfChild<Column>(col); 0412 Q_EMIT headerDataChanged(Qt::Horizontal, index, index); 0413 } 0414 } 0415 0416 void SpreadsheetModel::handleModeChange(const AbstractColumn* col) { 0417 if (m_suppressSignals) 0418 return; 0419 0420 updateHorizontalHeader(false); 0421 int index = m_spreadsheet->indexOfChild<Column>(col); 0422 Q_EMIT headerDataChanged(Qt::Horizontal, index, index); 0423 handleDataChange(col); 0424 0425 // output filter was changed after the mode change, update the signal-slot connection 0426 disconnect(nullptr, SIGNAL(digitsChanged()), this, SLOT(handledigitsChange())); 0427 connect(static_cast<const Column*>(col)->outputFilter(), &AbstractSimpleFilter::digitsChanged, this, &SpreadsheetModel::handleDigitsChange); 0428 } 0429 0430 void SpreadsheetModel::handleDigitsChange() { 0431 if (m_suppressSignals) 0432 return; 0433 0434 const auto* filter = dynamic_cast<const Double2StringFilter*>(QObject::sender()); 0435 if (!filter) 0436 return; 0437 0438 const AbstractColumn* col = filter->output(0); 0439 handleDataChange(col); 0440 } 0441 0442 void SpreadsheetModel::handlePlotDesignationChange(const AbstractColumn* col) { 0443 if (m_suppressSignals) 0444 return; 0445 0446 updateHorizontalHeader(false); 0447 int index = m_spreadsheet->indexOfChild<Column>(col); 0448 Q_EMIT headerDataChanged(Qt::Horizontal, index, m_columnCount - 1); 0449 } 0450 0451 void SpreadsheetModel::handleDataChange(const AbstractColumn* col) { 0452 if (m_suppressSignals) 0453 return; 0454 0455 int i = m_spreadsheet->indexOfChild<Column>(col); 0456 Q_EMIT dataChanged(index(0, i), index(m_rowCount - 1, i)); 0457 } 0458 0459 void SpreadsheetModel::handleRowsAboutToBeInserted(int before, int last) { 0460 if (m_suppressSignals) 0461 return; 0462 beginInsertRows(QModelIndex(), before, last); 0463 } 0464 0465 void SpreadsheetModel::handleRowsAboutToBeRemoved(int first, int last) { 0466 if (m_suppressSignals) 0467 return; 0468 beginRemoveRows(QModelIndex(), first, last); 0469 } 0470 0471 void SpreadsheetModel::handleRowsInserted(int newRowCount) { 0472 handleRowCountChanged(newRowCount); 0473 if (m_suppressSignals) 0474 return; 0475 endInsertRows(); 0476 } 0477 0478 void SpreadsheetModel::handleRowsRemoved(int newRowCount) { 0479 handleRowCountChanged(newRowCount); 0480 if (m_suppressSignals) 0481 return; 0482 endRemoveRows(); 0483 } 0484 0485 void SpreadsheetModel::handleRowCountChanged(int newRowCount) { 0486 if (m_suppressSignals) 0487 return; 0488 m_rowCount = newRowCount; 0489 updateVerticalHeader(); 0490 } 0491 0492 void SpreadsheetModel::updateVerticalHeader() { 0493 m_verticalHeaderCount = m_rowCount; 0494 } 0495 0496 void SpreadsheetModel::updateHorizontalHeader(bool sendSignal) { 0497 int column_count = m_spreadsheet->childCount<Column>(); 0498 0499 while (m_horizontal_header_data.size() < column_count) 0500 m_horizontal_header_data << QString(); 0501 0502 while (m_horizontal_header_data.size() > column_count) 0503 m_horizontal_header_data.removeLast(); 0504 0505 KConfigGroup group = Settings::group(QStringLiteral("Settings_Spreadsheet")); 0506 bool showColumnType = group.readEntry(QLatin1String("ShowColumnType"), true); 0507 bool showPlotDesignation = group.readEntry(QLatin1String("ShowPlotDesignation"), true); 0508 0509 for (int i = 0; i < column_count; i++) { 0510 Column* col = m_spreadsheet->child<Column>(i); 0511 QString header; 0512 if (!col->formula().isEmpty() && col->formulaAutoUpdate()) 0513 header += QLatin1String("*"); 0514 header += col->name(); 0515 0516 if (showColumnType) 0517 header += QLatin1String(" {") + col->columnModeString() + QLatin1Char('}'); 0518 0519 if (showPlotDesignation) { 0520 if (col->plotDesignation() != AbstractColumn::PlotDesignation::NoDesignation) 0521 header += QLatin1String(" ") + col->plotDesignationString(); 0522 } 0523 0524 m_horizontal_header_data.replace(i, header); 0525 } 0526 0527 if (sendSignal) 0528 Q_EMIT headerDataChanged(Qt::Horizontal, 0, column_count - 1); 0529 } 0530 0531 Column* SpreadsheetModel::column(int index) { 0532 return m_spreadsheet->column(index); 0533 } 0534 0535 void SpreadsheetModel::activateFormulaMode(bool on) { 0536 if (m_formula_mode == on) 0537 return; 0538 0539 m_formula_mode = on; 0540 if (m_rowCount > 0 && m_columnCount > 0) 0541 Q_EMIT dataChanged(index(0, 0), index(m_rowCount - 1, m_columnCount - 1)); 0542 } 0543 0544 bool SpreadsheetModel::formulaModeActive() const { 0545 return m_formula_mode; 0546 } 0547 0548 QVariant SpreadsheetModel::color(const AbstractColumn* column, int row, AbstractColumn::Formatting type) const { 0549 if ((!column->isNumeric() && column->columnMode() != AbstractColumn::ColumnMode::Text) || !column->isValid(row) || !column->hasHeatmapFormat()) 0550 return {}; 0551 0552 const auto& format = column->heatmapFormat(); 0553 if (format.type != type || format.colors.isEmpty()) 0554 return {}; 0555 0556 int index = 0; 0557 if (column->isNumeric()) { 0558 double value = column->valueAt(row); 0559 double range = (format.max - format.min) / format.colors.count(); 0560 0561 if (value > format.max) 0562 index = format.colors.count() - 1; 0563 else { 0564 for (int i = 0; i < format.colors.count(); ++i) { 0565 if (value <= format.min + (i + 1) * range) { 0566 index = i; 0567 break; 0568 } 0569 } 0570 } 0571 } else { 0572 index = column->dictionaryIndex(row); 0573 } 0574 0575 if (index < format.colors.count()) 0576 return {QColor(format.colors.at(index))}; 0577 else 0578 return {QColor(format.colors.constLast())}; 0579 }