File indexing completed on 2024-04-28 16:21:26

0001 /* This file is part of the KDE project
0002    Copyright 2006-2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
0003    Copyright 2004 Tomas Mecir <mecirt@gmail.com>
0004 
0005    This library is free software; you can redistribute it and/or
0006    modify it under the terms of the GNU Library General Public
0007    License as published by the Free Software Foundation; only
0008    version 2 of the License.
0009 
0010    This library is distributed in the hope that it will be useful,
0011    but WITHOUT ANY WARRANTY; without even the implied warranty of
0012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013    Library General Public License for more details.
0014 
0015    You should have received a copy of the GNU Library General Public License
0016    along with this library; see the file COPYING.LIB.  If not, write to
0017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018    Boston, MA 02110-1301, USA.
0019 */
0020 
0021 // Local
0022 #include "RecalcManager.h"
0023 
0024 #include "Cell.h"
0025 #include "CellStorage.h"
0026 #include "DependencyManager.h"
0027 #include "Formula.h"
0028 #include "FormulaStorage.h"
0029 #include "Map.h"
0030 #include "Sheet.h"
0031 #include "Region.h"
0032 #include "Value.h"
0033 #include "ValueFormatter.h"
0034 #include "DocBase.h"
0035 #include "ElapsedTime_p.h"
0036 
0037 #include <KoUpdater.h>
0038 
0039 #include <QHash>
0040 #include <QMap>
0041 
0042 using namespace Calligra::Sheets;
0043 
0044 class Q_DECL_HIDDEN RecalcManager::Private
0045 {
0046 public:
0047     /**
0048      * Finds all cells in region and their dependents, that need recalculation.
0049      *
0050      * \see RecalcManager::regionChanged
0051      */
0052     void cellsToCalculate(const Region& region);
0053 
0054     /**
0055      * Finds all cells in \p sheet , that have got a formula and hence need
0056      * recalculation.
0057      * If \p sheet is zero, all cells in the Map a returned.
0058      *
0059      * \see RecalcManager::recalcMap
0060      * \see RecalcManager::recalcSheet
0061      */
0062     void cellsToCalculate(Sheet* sheet = 0);
0063 
0064     /**
0065      * Helper function for cellsToCalculate(const Region&) and cellsToCalculate(Sheet*).
0066      */
0067     void cellsToCalculate(const Region& region, QSet<Cell>& cells) const;
0068 
0069     /*
0070      * Stores cells ordered by its reference depth.
0071      * Depth means the maximum depth of all cells this cell depends on plus one,
0072      * while a cell which has a formula without cell references has a depth
0073      * of zero.
0074      *
0075      * Examples:
0076      * \li A1: '=1.0'
0077      * \li A2: '=A1+A1'
0078      * \li A3: '=A1+A1+A2'
0079      *
0080      * \li depth(A1) = 0
0081      * \li depth(A2) = 1
0082      * \li depth(A3) = 2
0083      */
0084     QMap<int, Cell> cells;
0085     const Map* map;
0086     bool active;
0087 };
0088 
0089 void RecalcManager::Private::cellsToCalculate(const Region& region)
0090 {
0091     if (region.isEmpty())
0092         return;
0093 
0094     // retrieve the cell depths
0095     QMap<Cell, int> depths = map->dependencyManager()->depths();
0096 
0097     // create the cell map ordered by depth
0098     QSet<Cell> cells;
0099     cellsToCalculate(region, cells);
0100     const QSet<Cell>::ConstIterator end(cells.end());
0101     for (QSet<Cell>::ConstIterator it(cells.begin()); it != end; ++it) {
0102         if ((*it).sheet()->isAutoCalculationEnabled())
0103             this->cells.insertMulti(depths[*it], *it);
0104     }
0105 }
0106 
0107 void RecalcManager::Private::cellsToCalculate(Sheet* sheet)
0108 {
0109     // retrieve the cell depths
0110     QMap<Cell, int> depths = map->dependencyManager()->depths();
0111 
0112     // NOTE Stefan: It's necessary, that the cells are filled in row-wise;
0113     //              beginning with the top left; ending with the bottom right.
0114     //              This ensures, that the value storage is processed the same
0115     //              way, which boosts performance (using PointStorage) for an
0116     //              empty storage (on loading). For an already filled value
0117     //              storage, the speed gain is not that sensible.
0118     Cell cell;
0119     if (!sheet) { // map recalculation
0120         for (int s = 0; s < map->count(); ++s) {
0121             sheet = map->sheet(s);
0122             for (int c = 0; c < sheet->formulaStorage()->count(); ++c) {
0123                 cell = Cell(sheet, sheet->formulaStorage()->col(c), sheet->formulaStorage()->row(c));
0124                 cells.insertMulti(depths[cell], cell);
0125             }
0126         }
0127     } else { // sheet recalculation
0128         for (int c = 0; c < sheet->formulaStorage()->count(); ++c) {
0129             cell = Cell(sheet, sheet->formulaStorage()->col(c), sheet->formulaStorage()->row(c));
0130             cells.insertMulti(depths[cell], cell);
0131         }
0132     }
0133 }
0134 
0135 void RecalcManager::Private::cellsToCalculate(const Region& region, QSet<Cell>& cells) const
0136 {
0137     Region::ConstIterator end(region.constEnd());
0138     for (Region::ConstIterator it(region.constBegin()); it != end; ++it) {
0139         const QRect range = (*it)->rect();
0140         const Sheet* sheet = (*it)->sheet();
0141         for (int col = range.left(); col <= range.right(); ++col) {
0142             for (int row = range.top(); row <= range.bottom(); ++row) {
0143                 Cell cell(sheet, col, row);
0144                 // Even empty cells may act as value
0145                 // providers and need to be processed.
0146 
0147                 // check for already processed cells
0148                 if (cells.contains(cell))
0149                     continue;
0150 
0151                 // add it to the list
0152                 if (cell.isFormula())
0153                     cells.insert(cell);
0154 
0155                 // add its consumers to the list
0156                 cellsToCalculate(map->dependencyManager()->consumingRegion(cell), cells);
0157             }
0158         }
0159     }
0160 }
0161 
0162 RecalcManager::RecalcManager(Map *const map)
0163         : QObject(map)
0164         , d(new Private)
0165 {
0166     d->map  = map;
0167     d->active = false;
0168 }
0169 
0170 RecalcManager::~RecalcManager()
0171 {
0172     delete d;
0173 }
0174 
0175 void RecalcManager::regionChanged(const Region& region)
0176 {
0177     if (d->active || region.isEmpty())
0178         return;
0179     d->active = true;
0180     debugSheetsFormula << "RecalcManager::regionChanged" << region.name();
0181     ElapsedTime et("Overall region recalculation", ElapsedTime::PrintOnlyTime);
0182     d->cellsToCalculate(region);
0183     recalc();
0184     d->active = false;
0185 }
0186 
0187 void RecalcManager::recalcSheet(Sheet* const sheet)
0188 {
0189     if (d->active)
0190         return;
0191     d->active = true;
0192     ElapsedTime et("Overall sheet recalculation", ElapsedTime::PrintOnlyTime);
0193     d->cellsToCalculate(sheet);
0194     recalc();
0195     d->active = false;
0196 }
0197 
0198 void RecalcManager::recalcMap(KoUpdater *updater)
0199 {
0200     if (d->active)
0201         return;
0202     d->active = true;
0203     ElapsedTime et("Overall map recalculation", ElapsedTime::PrintOnlyTime);
0204     d->cellsToCalculate();
0205     recalc(updater);
0206     d->active = false;
0207 }
0208 
0209 bool RecalcManager::isActive() const
0210 {
0211     return d->active;
0212 }
0213 
0214 void RecalcManager::addSheet(Sheet *sheet)
0215 {
0216     // Manages also the revival of a deleted sheet.
0217     Q_UNUSED(sheet);
0218 
0219     // sebsauer: not recalc every time on loading - bug 284325
0220     if (!d->map->isLoading()) {
0221         recalcMap(); // FIXME Stefan: Implement a more elegant solution.
0222     }
0223 }
0224 
0225 void RecalcManager::removeSheet(Sheet *sheet)
0226 {
0227     Q_UNUSED(sheet);
0228     recalcMap(); // FIXME Stefan: Implement a more elegant solution.
0229 }
0230 
0231 void RecalcManager::recalc(KoUpdater *updater)
0232 {
0233     debugSheetsFormula << "Recalculating" << d->cells.count() << " cell(s)..";
0234     ElapsedTime et("Recalculating cells", ElapsedTime::PrintOnlyTime);
0235 
0236     if (updater)
0237         updater->setProgress(0);
0238 
0239     const QList<Cell> cells = d->cells.values();
0240     const int cellsCount = cells.count();
0241     for (int c = 0; c < cellsCount; ++c) {
0242         // only recalculate, if no circular dependency occurred
0243         if (cells.value(c).value() == Value::errorCIRCLE())
0244             continue;
0245         // Check for valid formula; parses the expression, if not done already.
0246         if (!cells.value(c).formula().isValid())
0247             continue;
0248 
0249         const Sheet* sheet = cells.value(c).sheet();
0250 
0251         // evaluate the formula and set the result
0252         Value result = cells.value(c).formula().eval();
0253         if (result.isArray() && (result.columns() > 1 || result.rows() > 1)) {
0254             const QRect rect = cells.value(c).lockedCells();
0255             // unlock
0256             sheet->cellStorage()->unlockCells(rect.left(), rect.top());
0257             for (int row = rect.top(); row <= rect.bottom(); ++row) {
0258                 for (int col = rect.left(); col <= rect.right(); ++col) {
0259                     Cell(sheet, col, row).setValue(result.element(col - rect.left(), row - rect.top()));
0260                 }
0261             }
0262             // relock
0263             sheet->cellStorage()->lockCells(rect);
0264         } else {
0265             Cell(cells.value(c)).setValue(result);
0266         }
0267         if (updater)
0268             updater->setProgress(int(qreal(c) / qreal(cellsCount) * 100.));
0269     }
0270 
0271     if (updater)
0272         updater->setProgress(100);
0273 
0274 //     dump();
0275     d->cells.clear();
0276 }
0277 
0278 void RecalcManager::dump() const
0279 {
0280     QMap<int, Cell>::ConstIterator end(d->cells.constEnd());
0281     for (QMap<int, Cell>::ConstIterator it(d->cells.constBegin()); it != end; ++it) {
0282         Cell cell = it.value();
0283         QString cellName = cell.name();
0284         while (cellName.count() < 4) cellName.prepend(' ');
0285         debugSheetsFormula << "depth(" << cellName << " ) =" << it.key();
0286     }
0287 }