File indexing completed on 2024-05-12 15:27:12
0001 /*************************************************************************** 0002 File : SpreadsheetModel.cpp 0003 Project : LabPlot 0004 Description : Model for the access to a Spreadsheet 0005 -------------------------------------------------------------------- 0006 Copyright : (C) 2007 Tilman Benkert (thzs@gmx.net) 0007 Copyright : (C) 2009 Knut Franke (knut.franke@gmx.de) 0008 Copyright : (C) 2013-2017 Alexander Semke (alexander.semke@web.de) 0009 0010 ***************************************************************************/ 0011 0012 /*************************************************************************** 0013 * * 0014 * This program is free software; you can redistribute it and/or modify * 0015 * it under the terms of the GNU General Public License as published by * 0016 * the Free Software Foundation; either version 2 of the License, or * 0017 * (at your option) any later version. * 0018 * * 0019 * This program is distributed in the hope that it will be useful, * 0020 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0021 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0022 * GNU General Public License for more details. * 0023 * * 0024 * You should have received a copy of the GNU General Public License * 0025 * along with this program; if not, write to the Free Software * 0026 * Foundation, Inc., 51 Franklin Street, Fifth Floor, * 0027 * Boston, MA 02110-1301 USA * 0028 * * 0029 ***************************************************************************/ 0030 0031 #include "backend/spreadsheet/Spreadsheet.h" 0032 #include "backend/spreadsheet/SpreadsheetModel.h" 0033 #include "backend/core/datatypes/Double2StringFilter.h" 0034 0035 #include <QBrush> 0036 #include <QIcon> 0037 0038 #include <KLocalizedString> 0039 0040 /*! 0041 \class SpreadsheetModel 0042 \brief Model for the access to a Spreadsheet 0043 0044 This is a model in the sense of Qt4 model/view framework which is used 0045 to access a Spreadsheet object from any of Qt4s view classes, typically a QTableView. 0046 Its main purposes are translating Spreadsheet signals into QAbstractItemModel signals 0047 and translating calls to the QAbstractItemModel read/write API into calls 0048 in the public API of Spreadsheet. In many cases a pointer to the addressed column 0049 is obtained by calling Spreadsheet::column() and the manipulation is done using the 0050 public API of column. 0051 0052 \ingroup backend 0053 */ 0054 SpreadsheetModel::SpreadsheetModel(Spreadsheet* spreadsheet) : QAbstractItemModel(nullptr), 0055 m_spreadsheet(spreadsheet), 0056 m_rowCount(spreadsheet->rowCount()), 0057 m_columnCount(spreadsheet->columnCount()) { 0058 0059 updateVerticalHeader(); 0060 updateHorizontalHeader(); 0061 0062 connect(m_spreadsheet, &Spreadsheet::aspectAdded, this, &SpreadsheetModel::handleAspectAdded); 0063 connect(m_spreadsheet, &Spreadsheet::aspectAboutToBeRemoved, this, &SpreadsheetModel::handleAspectAboutToBeRemoved); 0064 connect(m_spreadsheet, &Spreadsheet::aspectRemoved, this, &SpreadsheetModel::handleAspectRemoved); 0065 connect(m_spreadsheet, &Spreadsheet::aspectDescriptionChanged, this, &SpreadsheetModel::handleDescriptionChange); 0066 0067 for (int i = 0; i < spreadsheet->columnCount(); ++i) { 0068 beginInsertColumns(QModelIndex(), i, i); 0069 handleAspectAdded(spreadsheet->column(i)); 0070 } 0071 0072 m_spreadsheet->setModel(this); 0073 } 0074 0075 void SpreadsheetModel::suppressSignals(bool value) { 0076 m_suppressSignals = value; 0077 0078 //update the headers after all the data was added to the model 0079 //and we start listening to signals again 0080 if (!m_suppressSignals) { 0081 m_rowCount = m_spreadsheet->rowCount(); 0082 m_columnCount = m_spreadsheet->columnCount(); 0083 m_spreadsheet->emitColumnCountChanged(); 0084 updateVerticalHeader(); 0085 updateHorizontalHeader(); 0086 beginResetModel(); 0087 endResetModel(); 0088 } 0089 } 0090 0091 Qt::ItemFlags SpreadsheetModel::flags(const QModelIndex& index) const { 0092 if (index.isValid()) 0093 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; 0094 else 0095 return Qt::ItemIsEnabled; 0096 } 0097 0098 QVariant SpreadsheetModel::data(const QModelIndex& index, int role) const { 0099 if ( !index.isValid() ) 0100 return QVariant(); 0101 0102 const int row = index.row(); 0103 const int col = index.column(); 0104 const Column* col_ptr = m_spreadsheet->column(col); 0105 0106 if (!col_ptr) 0107 return QVariant(); 0108 0109 switch (role) { 0110 case Qt::ToolTipRole: 0111 if (col_ptr->isValid(row)) { 0112 if (col_ptr->isMasked(row)) 0113 return QVariant(i18n("%1, masked (ignored in all operations)", col_ptr->asStringColumn()->textAt(row))); 0114 else 0115 return QVariant(col_ptr->asStringColumn()->textAt(row)); 0116 } else { 0117 if (col_ptr->isMasked(row)) 0118 return QVariant(i18n("invalid cell, masked (ignored in all operations)")); 0119 else 0120 return QVariant(i18n("invalid cell (ignored in all operations)")); 0121 } 0122 case Qt::EditRole: 0123 if (col_ptr->columnMode() == AbstractColumn::ColumnMode::Numeric) { 0124 double value = col_ptr->valueAt(row); 0125 if (std::isnan(value)) 0126 return QVariant("-"); 0127 else if (std::isinf(value)) 0128 return QVariant(QLatin1String("inf")); 0129 else 0130 return QVariant(col_ptr->asStringColumn()->textAt(row)); 0131 } 0132 0133 if (col_ptr->isValid(row)) 0134 return QVariant(col_ptr->asStringColumn()->textAt(row)); 0135 0136 //m_formula_mode is not used at the moment 0137 //if (m_formula_mode) 0138 // return QVariant(col_ptr->formula(row)); 0139 0140 break; 0141 case Qt::DisplayRole: 0142 if (col_ptr->columnMode() == AbstractColumn::ColumnMode::Numeric) { 0143 double value = col_ptr->valueAt(row); 0144 if (std::isnan(value)) 0145 return QVariant("-"); 0146 else if (std::isinf(value)) 0147 return QVariant(UTF8_QSTRING("∞")); 0148 else 0149 return QVariant(col_ptr->asStringColumn()->textAt(row)); 0150 } 0151 0152 if (!col_ptr->isValid(row)) 0153 return QVariant("-"); 0154 0155 //m_formula_mode is not used at the moment 0156 //if (m_formula_mode) 0157 // return QVariant(col_ptr->formula(row)); 0158 0159 return QVariant(col_ptr->asStringColumn()->textAt(row)); 0160 case Qt::ForegroundRole: 0161 if (!col_ptr->isValid(row)) 0162 return QVariant(QBrush(Qt::red)); 0163 break; 0164 case static_cast<int>(CustomDataRole::MaskingRole): 0165 return QVariant(col_ptr->isMasked(row)); 0166 case static_cast<int>(CustomDataRole::FormulaRole): 0167 return QVariant(col_ptr->formula(row)); 0168 // case Qt::DecorationRole: 0169 // if (m_formula_mode) 0170 // return QIcon(QPixmap(":/equals.png")); //TODO 0171 } 0172 0173 return QVariant(); 0174 } 0175 0176 QVariant SpreadsheetModel::headerData(int section, Qt::Orientation orientation, int role) const { 0177 if ( (orientation == Qt::Horizontal && section > m_columnCount-1) 0178 || (orientation == Qt::Vertical && section > m_rowCount-1) ) 0179 return QVariant(); 0180 0181 switch (orientation) { 0182 case Qt::Horizontal: 0183 switch (role) { 0184 case Qt::DisplayRole: 0185 case Qt::ToolTipRole: 0186 case Qt::EditRole: 0187 return m_horizontal_header_data.at(section); 0188 case Qt::DecorationRole: 0189 return m_spreadsheet->child<Column>(section)->icon(); 0190 case static_cast<int>(CustomDataRole::CommentRole): 0191 return m_spreadsheet->child<Column>(section)->comment(); 0192 } 0193 break; 0194 case Qt::Vertical: 0195 switch (role) { 0196 case Qt::DisplayRole: 0197 case Qt::ToolTipRole: 0198 return m_vertical_header_data.at(section); 0199 } 0200 } 0201 0202 return QVariant(); 0203 } 0204 0205 int SpreadsheetModel::rowCount(const QModelIndex& parent) const { 0206 Q_UNUSED(parent) 0207 return m_rowCount; 0208 } 0209 0210 int SpreadsheetModel::columnCount(const QModelIndex& parent) const { 0211 Q_UNUSED(parent) 0212 return m_columnCount; 0213 } 0214 0215 bool SpreadsheetModel::setData(const QModelIndex& index, const QVariant& value, int role) { 0216 if (!index.isValid()) 0217 return false; 0218 0219 int row = index.row(); 0220 Column* column = m_spreadsheet->column(index.column()); 0221 0222 SET_NUMBER_LOCALE 0223 //DEBUG("SpreadsheetModel::setData() value = " << STDSTRING(value.toString())) 0224 0225 //don't do anything if no new value was provided 0226 if (column->columnMode() == AbstractColumn::ColumnMode::Numeric) { 0227 bool ok; 0228 double new_value = numberLocale.toDouble(value.toString(), &ok); 0229 if (ok) { 0230 if (column->valueAt(row) == new_value) 0231 return false; 0232 } else { 0233 //an empty (non-numeric value) was provided 0234 if (std::isnan(column->valueAt(row))) 0235 return false; 0236 } 0237 } else { 0238 if (column->asStringColumn()->textAt(row) == value.toString()) 0239 return false; 0240 } 0241 0242 switch (role) { 0243 case Qt::EditRole: 0244 // remark: the validity of the cell is determined by the input filter 0245 if (m_formula_mode) 0246 column->setFormula(row, value.toString()); 0247 else 0248 column->asStringColumn()->setTextAt(row, value.toString()); 0249 return true; 0250 case static_cast<int>(CustomDataRole::MaskingRole): 0251 m_spreadsheet->column(index.column())->setMasked(row, value.toBool()); 0252 return true; 0253 case static_cast<int>(CustomDataRole::FormulaRole): 0254 m_spreadsheet->column(index.column())->setFormula(row, value.toString()); 0255 return true; 0256 } 0257 0258 return false; 0259 } 0260 0261 QModelIndex SpreadsheetModel::index(int row, int column, const QModelIndex& parent) const { 0262 Q_UNUSED(parent) 0263 return createIndex(row, column); 0264 } 0265 0266 QModelIndex SpreadsheetModel::parent(const QModelIndex& child) const { 0267 Q_UNUSED(child) 0268 return QModelIndex{}; 0269 } 0270 0271 bool SpreadsheetModel::hasChildren(const QModelIndex& parent) const { 0272 Q_UNUSED(parent) 0273 return false; 0274 } 0275 0276 void SpreadsheetModel::handleAspectAdded(const AbstractAspect* aspect) { 0277 const Column* col = dynamic_cast<const Column*>(aspect); 0278 if (!col || aspect->parentAspect() != m_spreadsheet) 0279 return; 0280 0281 connect(col, &Column::plotDesignationChanged, this, &SpreadsheetModel::handlePlotDesignationChange); 0282 connect(col, &Column::modeChanged, this, &SpreadsheetModel::handleDataChange); 0283 connect(col, &Column::dataChanged, this, &SpreadsheetModel::handleDataChange); 0284 connect(col, &Column::formatChanged, this, &SpreadsheetModel::handleDataChange); 0285 connect(col, &Column::modeChanged, this, &SpreadsheetModel::handleModeChange); 0286 connect(col, &Column::rowsInserted, this, &SpreadsheetModel::handleRowsInserted); 0287 connect(col, &Column::rowsRemoved, this, &SpreadsheetModel::handleRowsRemoved); 0288 connect(col, &Column::maskingChanged, this, &SpreadsheetModel::handleDataChange); 0289 connect(col->outputFilter(), &AbstractSimpleFilter::digitsChanged, this, &SpreadsheetModel::handleDigitsChange); 0290 0291 if (!m_suppressSignals) { 0292 beginResetModel(); 0293 updateVerticalHeader(); 0294 updateHorizontalHeader(); 0295 endResetModel(); 0296 0297 int index = m_spreadsheet->indexOfChild<AbstractAspect>(aspect); 0298 m_columnCount = m_spreadsheet->columnCount(); 0299 m_spreadsheet->emitColumnCountChanged(); 0300 emit headerDataChanged(Qt::Horizontal, index, m_columnCount - 1); 0301 } 0302 } 0303 0304 void SpreadsheetModel::handleAspectAboutToBeRemoved(const AbstractAspect* aspect) { 0305 if (m_suppressSignals) 0306 return; 0307 0308 const Column* col = dynamic_cast<const Column*>(aspect); 0309 if (!col || aspect->parentAspect() != m_spreadsheet) 0310 return; 0311 0312 beginResetModel(); 0313 disconnect(col, nullptr, this, nullptr); 0314 } 0315 0316 void SpreadsheetModel::handleAspectRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) { 0317 Q_UNUSED(before) 0318 const Column* col = dynamic_cast<const Column*>(child); 0319 if (!col || parent != m_spreadsheet) 0320 return; 0321 0322 updateVerticalHeader(); 0323 updateHorizontalHeader(); 0324 0325 m_columnCount = m_spreadsheet->columnCount(); 0326 m_spreadsheet->emitColumnCountChanged(); 0327 0328 endResetModel(); 0329 } 0330 0331 void SpreadsheetModel::handleDescriptionChange(const AbstractAspect* aspect) { 0332 if (m_suppressSignals) 0333 return; 0334 0335 const Column* col = dynamic_cast<const Column*>(aspect); 0336 if (!col || aspect->parentAspect() != m_spreadsheet) 0337 return; 0338 0339 if (!m_suppressSignals) { 0340 updateHorizontalHeader(); 0341 int index = m_spreadsheet->indexOfChild<Column>(col); 0342 emit headerDataChanged(Qt::Horizontal, index, index); 0343 } 0344 } 0345 0346 void SpreadsheetModel::handleModeChange(const AbstractColumn* col) { 0347 if (m_suppressSignals) 0348 return; 0349 0350 updateHorizontalHeader(); 0351 int index = m_spreadsheet->indexOfChild<Column>(col); 0352 emit headerDataChanged(Qt::Horizontal, index, index); 0353 handleDataChange(col); 0354 0355 //output filter was changed after the mode change, update the signal-slot connection 0356 disconnect(nullptr, SIGNAL(digitsChanged()), this, SLOT(handledigitsChange())); 0357 connect(static_cast<const Column*>(col)->outputFilter(), &AbstractSimpleFilter::digitsChanged, this, &SpreadsheetModel::handleDigitsChange); 0358 } 0359 0360 void SpreadsheetModel::handleDigitsChange() { 0361 if (m_suppressSignals) 0362 return; 0363 0364 const auto* filter = dynamic_cast<const Double2StringFilter*>(QObject::sender()); 0365 if (!filter) 0366 return; 0367 0368 const AbstractColumn* col = filter->output(0); 0369 handleDataChange(col); 0370 } 0371 0372 void SpreadsheetModel::handlePlotDesignationChange(const AbstractColumn* col) { 0373 if (m_suppressSignals) 0374 return; 0375 0376 updateHorizontalHeader(); 0377 int index = m_spreadsheet->indexOfChild<Column>(col); 0378 emit headerDataChanged(Qt::Horizontal, index, m_columnCount-1); 0379 } 0380 0381 void SpreadsheetModel::handleDataChange(const AbstractColumn* col) { 0382 if (m_suppressSignals) 0383 return; 0384 0385 int i = m_spreadsheet->indexOfChild<Column>(col); 0386 emit dataChanged(index(0, i), index(m_rowCount-1, i)); 0387 } 0388 0389 void SpreadsheetModel::handleRowsInserted(const AbstractColumn* col, int before, int count) { 0390 if (m_suppressSignals) 0391 return; 0392 0393 Q_UNUSED(before) Q_UNUSED(count) 0394 int i = m_spreadsheet->indexOfChild<Column>(col); 0395 m_rowCount = col->rowCount(); 0396 emit dataChanged(index(0, i), index(m_rowCount-1, i)); 0397 updateVerticalHeader(); 0398 m_spreadsheet->emitRowCountChanged(); 0399 } 0400 0401 void SpreadsheetModel::handleRowsRemoved(const AbstractColumn* col, int first, int count) { 0402 if (m_suppressSignals) 0403 return; 0404 0405 Q_UNUSED(first) Q_UNUSED(count) 0406 int i = m_spreadsheet->indexOfChild<Column>(col); 0407 m_rowCount = col->rowCount(); 0408 emit dataChanged(index(0, i), index(m_rowCount-1, i)); 0409 updateVerticalHeader(); 0410 m_spreadsheet->emitRowCountChanged(); 0411 } 0412 0413 void SpreadsheetModel::updateVerticalHeader() { 0414 int old_rows = m_vertical_header_data.size(); 0415 int new_rows = m_rowCount; 0416 0417 if (new_rows > old_rows) { 0418 beginInsertRows(QModelIndex(), old_rows, new_rows-1); 0419 0420 for (int i = old_rows+1; i <= new_rows; i++) 0421 m_vertical_header_data << i; 0422 0423 endInsertRows(); 0424 } else if (new_rows < old_rows) { 0425 beginRemoveRows(QModelIndex(), new_rows, old_rows-1); 0426 0427 while (m_vertical_header_data.size() > new_rows) 0428 m_vertical_header_data.removeLast(); 0429 0430 endRemoveRows(); 0431 } 0432 } 0433 0434 void SpreadsheetModel::updateHorizontalHeader() { 0435 int column_count = m_spreadsheet->childCount<Column>(); 0436 0437 while (m_horizontal_header_data.size() < column_count) 0438 m_horizontal_header_data << QString(); 0439 0440 while (m_horizontal_header_data.size() > column_count) 0441 m_horizontal_header_data.removeLast(); 0442 0443 KConfigGroup group = KSharedConfig::openConfig()->group("Settings_Spreadsheet"); 0444 bool showColumnType = group.readEntry(QLatin1String("ShowColumnType"), true); 0445 bool showPlotDesignation = group.readEntry(QLatin1String("ShowPlotDesignation"), true); 0446 0447 for (int i = 0; i < column_count; i++) { 0448 Column* col = m_spreadsheet->child<Column>(i); 0449 QString header = col->name(); 0450 0451 if (showColumnType) { 0452 switch (col->columnMode()) { 0453 case AbstractColumn::ColumnMode::Numeric: 0454 header += QLatin1String(" {") + i18n("Numeric") + QLatin1Char('}'); 0455 break; 0456 case AbstractColumn::ColumnMode::Integer: 0457 header += QLatin1String(" {") + i18n("Integer") + QLatin1Char('}'); 0458 break; 0459 case AbstractColumn::ColumnMode::BigInt: 0460 header += QLatin1String(" {") + i18n("Big Integer") + QLatin1Char('}'); 0461 break; 0462 case AbstractColumn::ColumnMode::Text: 0463 header += QLatin1String(" {") + i18n("Text") + QLatin1Char('}'); 0464 break; 0465 case AbstractColumn::ColumnMode::Month: 0466 header += QLatin1String(" {") + i18n("Month Names") + QLatin1Char('}'); 0467 break; 0468 case AbstractColumn::ColumnMode::Day: 0469 header += QLatin1String(" {") + i18n("Day Names") + QLatin1Char('}'); 0470 break; 0471 case AbstractColumn::ColumnMode::DateTime: 0472 header += QLatin1String(" {") + i18n("Date and Time") + QLatin1Char('}'); 0473 break; 0474 } 0475 } 0476 0477 if (showPlotDesignation) { 0478 switch (col->plotDesignation()) { 0479 case AbstractColumn::PlotDesignation::NoDesignation: 0480 break; 0481 case AbstractColumn::PlotDesignation::X: 0482 header += QLatin1String(" [X]"); 0483 break; 0484 case AbstractColumn::PlotDesignation::Y: 0485 header += QLatin1String(" [Y]"); 0486 break; 0487 case AbstractColumn::PlotDesignation::Z: 0488 header += QLatin1String(" [Z]"); 0489 break; 0490 case AbstractColumn::PlotDesignation::XError: 0491 header += QLatin1String(" [") + i18n("X-error") + QLatin1Char(']'); 0492 break; 0493 case AbstractColumn::PlotDesignation::XErrorPlus: 0494 header += QLatin1String(" [") + i18n("X-error +") + QLatin1Char(']'); 0495 break; 0496 case AbstractColumn::PlotDesignation::XErrorMinus: 0497 header += QLatin1String(" [") + i18n("X-error -") + QLatin1Char(']'); 0498 break; 0499 case AbstractColumn::PlotDesignation::YError: 0500 header += QLatin1String(" [") + i18n("Y-error") + QLatin1Char(']'); 0501 break; 0502 case AbstractColumn::PlotDesignation::YErrorPlus: 0503 header += QLatin1String(" [") + i18n("Y-error +") + QLatin1Char(']'); 0504 break; 0505 case AbstractColumn::PlotDesignation::YErrorMinus: 0506 header += QLatin1String(" [") + i18n("Y-error -") + QLatin1Char(']'); 0507 break; 0508 } 0509 } 0510 m_horizontal_header_data.replace(i, header); 0511 } 0512 } 0513 0514 Column* SpreadsheetModel::column(int index) { 0515 return m_spreadsheet->column(index); 0516 } 0517 0518 void SpreadsheetModel::activateFormulaMode(bool on) { 0519 if (m_formula_mode == on) return; 0520 0521 m_formula_mode = on; 0522 if (m_rowCount > 0 && m_columnCount > 0) 0523 emit dataChanged(index(0,0), index(m_rowCount - 1, m_columnCount - 1)); 0524 } 0525 0526 bool SpreadsheetModel::formulaModeActive() const { 0527 return m_formula_mode; 0528 }