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 }