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 }