File indexing completed on 2024-04-28 16:21:21
0001 /* This file is part of the KDE project 0002 Copyright 2010 Marijn Kruisselbrink <mkruisselbrink@kde.org> 0003 Copyright 2006-2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net> 0004 Copyright 2004 Tomas Mecir <mecirt@gmail.com> 0005 0006 This library is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU Library General Public 0008 License as published by the Free Software Foundation; either 0009 version 2 of the License, or (at your option) any later version. 0010 0011 This library is distributed in the hope that it will be useful, 0012 but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0014 Library General Public License for more details. 0015 0016 You should have received a copy of the GNU Library General Public License 0017 along with this library; see the file COPYING.LIB. If not, write to 0018 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0019 Boston, MA 02110-1301, USA. 0020 */ 0021 0022 // Local 0023 #include "DependencyManager.h" 0024 #include "DependencyManager_p.h" 0025 0026 #include "Cell.h" 0027 #include "CellStorage.h" 0028 #include "Formula.h" 0029 #include "FormulaStorage.h" 0030 #include "Map.h" 0031 #include "NamedAreaManager.h" 0032 #include "Region.h" 0033 #include "RTree.h" 0034 #include "Sheet.h" 0035 #include "Value.h" 0036 #include "DocBase.h" 0037 #include "ElapsedTime_p.h" 0038 0039 #include <QHash> 0040 #include <QList> 0041 0042 #include <KoUpdater.h> 0043 0044 using namespace Calligra::Sheets; 0045 0046 // This is currently not called - but it's really convenient to call it from 0047 // gdb or from debug output to check that everything is set up ok. 0048 void DependencyManager::Private::dump() const 0049 { 0050 QMap<Cell, Region>::ConstIterator mend(providers.end()); 0051 for (QMap<Cell, Region>::ConstIterator mit(providers.begin()); mit != mend; ++mit) { 0052 Cell cell = mit.key(); 0053 0054 QStringList debugStr; 0055 Region::ConstIterator rend((*mit).constEnd()); 0056 for (Region::ConstIterator rit((*mit).constBegin()); rit != rend; ++rit) { 0057 debugStr << (*rit)->name(); 0058 } 0059 0060 debugSheetsFormula << cell.name() << " consumes values of:" << debugStr.join(","); 0061 } 0062 0063 foreach(Sheet* sheet, consumers.keys()) { 0064 const QList< QPair<QRectF, Cell> > pairs = consumers[sheet]->intersectingPairs(QRect(1, 1, KS_colMax, KS_rowMax)).values(); 0065 QHash<QString, QString> table; 0066 for (int i = 0; i < pairs.count(); ++i) { 0067 Region tmpRange(pairs[i].first.toRect(), sheet); 0068 table.insertMulti(tmpRange.name(), pairs[i].second.name()); 0069 } 0070 foreach(const QString &uniqueKey, table.uniqueKeys()) { 0071 QStringList debugStr(table.values(uniqueKey)); 0072 debugSheetsFormula << uniqueKey << " provides values for:" << debugStr.join(","); 0073 } 0074 } 0075 0076 foreach(const Cell &cell, depths.keys()) { 0077 QString cellName = cell.name(); 0078 while (cellName.count() < 4) cellName.prepend(' '); 0079 debugSheetsFormula << "depth(" << cellName << " ) =" << depths[cell]; 0080 } 0081 } 0082 0083 DependencyManager::DependencyManager(const Map* map) 0084 : d(new Private) 0085 { 0086 d->map = map; 0087 } 0088 0089 DependencyManager::~DependencyManager() 0090 { 0091 qDeleteAll(d->consumers); 0092 delete d; 0093 } 0094 0095 void DependencyManager::reset() 0096 { 0097 d->reset(); 0098 } 0099 0100 void DependencyManager::regionChanged(const Region& region) 0101 { 0102 if (region.isEmpty()) 0103 return; 0104 debugSheetsFormula << "DependencyManager::regionChanged" << region.name(); 0105 Region::ConstIterator end(region.constEnd()); 0106 for (Region::ConstIterator it(region.constBegin()); it != end; ++it) { 0107 const QRect range = (*it)->rect(); 0108 const Sheet* sheet = (*it)->sheet(); 0109 0110 for (int col = range.left(); col <= range.right(); ++col) { 0111 for (int row = range.top(); row <= range.bottom(); ++row) { 0112 Cell cell(sheet, col, row); 0113 const Formula formula = cell.formula(); 0114 0115 // remove it and all its consumers from the reference depth list 0116 d->removeDepths(cell); 0117 0118 // cell without a formula? remove it 0119 if (formula.expression().isEmpty()) { 0120 d->removeDependencies(cell); 0121 continue; 0122 } 0123 0124 d->generateDependencies(cell, formula); 0125 } 0126 } 0127 } 0128 { 0129 ElapsedTime et("Computing reference depths", ElapsedTime::PrintOnlyTime); 0130 d->generateDepths(region); 0131 } 0132 // d->dump(); 0133 } 0134 0135 void DependencyManager::namedAreaModified(const QString &name) 0136 { 0137 d->namedAreaModified(name); 0138 } 0139 0140 void DependencyManager::addSheet(Sheet *sheet) 0141 { 0142 // Manages also the revival of a deleted sheet. 0143 Q_UNUSED(sheet); 0144 #if 0 // TODO Stefan: Enable, if dependencies should not be tracked all the time. 0145 ElapsedTime et("Generating dependencies", ElapsedTime::PrintOnlyTime); 0146 0147 // Clear orphaned dependencies (i.e. cells formerly containing formulas) 0148 // FIXME Stefan: Iterate only over the consumers in sheet. Needs adjustment 0149 // of the way the providers are stored. Now: only by cell. 0150 // Future: QHash<Sheet*, QHash<Cell, Region> > 0151 const QList<Cell> consumers = d->providers.keys(); 0152 foreach(const Cell& cell, consumers) { 0153 if (cell.sheet() == sheet) { 0154 // Those cells may had got providing regions. Clear them first. 0155 // TODO 0156 0157 // Clear this cell as provider. 0158 d->providers.remove(cell); 0159 } 0160 } 0161 0162 Cell cell; 0163 for (int c = 0; c < sheet->formulaStorage()->count(); ++c) { 0164 cell = Cell(sheet, sheet->formulaStorage()->col(c), sheet->formulaStorage()->row(c)); 0165 0166 d->generateDependencies(cell, sheet->formulaStorage()->data(c)); 0167 if (!d->depths.contains(cell)) { 0168 int depth = d->computeDepth(cell); 0169 d->depths.insert(cell , depth); 0170 } 0171 } 0172 #endif 0173 } 0174 0175 void DependencyManager::removeSheet(Sheet *sheet) 0176 { 0177 Q_UNUSED(sheet); 0178 // TODO Stefan: Implement, if dependencies should not be tracked all the time. 0179 } 0180 0181 void DependencyManager::updateAllDependencies(const Map* map, KoUpdater *updater) 0182 { 0183 ElapsedTime et("Generating dependencies", ElapsedTime::PrintOnlyTime); 0184 0185 // clear everything 0186 d->providers.clear(); 0187 qDeleteAll(d->consumers); 0188 d->consumers.clear(); 0189 d->namedAreaConsumers.clear(); 0190 d->depths.clear(); 0191 0192 int cellsCount = 9; 0193 0194 if (updater) { 0195 updater->setProgress(0); 0196 0197 foreach(const Sheet* sheet, map->sheetList()) 0198 cellsCount += sheet->formulaStorage()->count(); 0199 } 0200 0201 Cell cell; 0202 int cellCurrent = 0; 0203 foreach(const Sheet* sheet, map->sheetList()) { 0204 for (int c = 0; c < sheet->formulaStorage()->count(); ++c, ++cellCurrent) { 0205 cell = Cell(sheet, sheet->formulaStorage()->col(c), sheet->formulaStorage()->row(c)); 0206 0207 d->generateDependencies(cell, sheet->formulaStorage()->data(c)); 0208 if (!sheet->formulaStorage()->data(c).isValid()) 0209 cell.setValue(Value::errorPARSE()); 0210 0211 if (updater) 0212 updater->setProgress(int(qreal(cellCurrent) / qreal(cellsCount) * 50.)); 0213 } 0214 } 0215 cellCurrent = 0; 0216 foreach(const Sheet* sheet, map->sheetList()) { 0217 for (int c = 0; c < sheet->formulaStorage()->count(); ++c, ++cellCurrent) { 0218 cell = Cell(sheet, sheet->formulaStorage()->col(c), sheet->formulaStorage()->row(c)); 0219 0220 if (!d->depths.contains(cell)) { 0221 int depth = d->computeDepth(cell); 0222 d->depths.insert(cell , depth); 0223 } 0224 0225 if (updater) 0226 updater->setProgress(50 + int(qreal(cellCurrent) / qreal(cellsCount) * 50.)); 0227 } 0228 } 0229 0230 if (updater) 0231 updater->setProgress(100); 0232 } 0233 0234 QMap<Cell, int> DependencyManager::depths() const 0235 { 0236 return d->depths; 0237 } 0238 0239 Calligra::Sheets::Region DependencyManager::consumingRegion(const Cell& cell) const 0240 { 0241 return d->consumingRegion(cell); 0242 } 0243 0244 Calligra::Sheets::Region DependencyManager::reduceToProvidingRegion(const Region& region) const 0245 { 0246 Region providingRegion; 0247 QList< QPair<QRectF, Cell> > pairs; 0248 Region::ConstIterator end(region.constEnd()); 0249 for (Region::ConstIterator it(region.constBegin()); it != end; ++it) { 0250 Sheet* const sheet = (*it)->sheet(); 0251 QHash<Sheet*, RTree<Cell>*>::ConstIterator cit = d->consumers.constFind(sheet); 0252 if (cit == d->consumers.constEnd()) 0253 continue; 0254 0255 pairs = cit.value()->intersectingPairs((*it)->rect()).values(); 0256 for (int i = 0; i < pairs.count(); ++i) 0257 providingRegion.add(pairs[i].first.toRect() & (*it)->rect(), sheet); 0258 } 0259 return providingRegion; 0260 } 0261 0262 void DependencyManager::regionMoved(const Region& movedRegion, const Cell& destination) 0263 { 0264 Region::Point locationOffset(destination.cellPosition() - movedRegion.boundingRect().topLeft()); 0265 0266 Region::ConstIterator end(movedRegion.constEnd()); 0267 for (Region::ConstIterator it(movedRegion.constBegin()); it != end; ++it) { 0268 Sheet* const sheet = (*it)->sheet(); 0269 locationOffset.setSheet((sheet == destination.sheet()) ? 0 : destination.sheet()); 0270 0271 QHash<Sheet*, RTree<Cell>*>::ConstIterator cit = d->consumers.constFind(sheet); 0272 if (cit == d->consumers.constEnd()) 0273 continue; 0274 0275 QList<Cell> dependentLocations = cit.value()->intersects((*it)->rect()); 0276 foreach(const Cell &c, dependentLocations) { 0277 updateFormula(c, (*it), locationOffset); 0278 } 0279 } 0280 } 0281 0282 void DependencyManager::updateFormula(const Cell& cell, const Region::Element* oldLocation, const Region::Point& offset) 0283 { 0284 // Not a formula -> no dependencies 0285 if (!cell.isFormula()) 0286 return; 0287 0288 const Formula formula = cell.formula(); 0289 0290 // Broken formula -> meaningless dependencies 0291 if (!formula.isValid()) 0292 return; 0293 0294 Tokens tokens = formula.tokens(); 0295 0296 //return empty list if the tokens aren't valid 0297 if (!tokens.valid()) 0298 return; 0299 0300 QString expression('='); 0301 Sheet* sheet = cell.sheet(); 0302 foreach(const Token &token, tokens) { 0303 //parse each cell/range and put it to our expression 0304 if (token.type() == Token::Cell || token.type() == Token::Range) { 0305 // FIXME Stefan: Special handling for named areas 0306 const Region region(token.text(), sheet->map(), sheet); 0307 //debugSheetsFormula << region.name(); 0308 0309 // the offset contains a sheet, only if it was an intersheet move. 0310 if ((oldLocation->sheet() == region.firstSheet()) && 0311 (oldLocation->rect().contains(region.firstRange()))) { 0312 const Region yetAnotherRegion(region.firstRange().translated(offset.pos()), 0313 offset.sheet() ? offset.sheet() : sheet); 0314 expression.append(yetAnotherRegion.name(sheet)); 0315 } else { 0316 expression.append(token.text()); 0317 } 0318 } else { 0319 expression.append(token.text()); 0320 } 0321 } 0322 Cell(cell).parseUserInput(expression); 0323 } 0324 0325 void DependencyManager::Private::reset() 0326 { 0327 providers.clear(); 0328 consumers.clear(); 0329 } 0330 0331 Calligra::Sheets::Region DependencyManager::Private::consumingRegion(const Cell& cell) const 0332 { 0333 QHash<Sheet*, RTree<Cell>*>::ConstIterator cit = consumers.constFind(cell.sheet()); 0334 if (cit == consumers.constEnd()) { 0335 //debugSheetsFormula << "No consumer tree found for the cell's sheet."; 0336 return Region(); 0337 } 0338 0339 const QList<Cell> consumers = cit.value()->contains(cell.cellPosition()); 0340 0341 Region region; 0342 foreach(const Cell& c, consumers) 0343 region.add(c.cellPosition(), c.sheet()); 0344 return region; 0345 } 0346 0347 void DependencyManager::Private::namedAreaModified(const QString& name) 0348 { 0349 // since area names are something like aliases, modifying an area name 0350 // basically means that all cells referencing this area should be treated 0351 // as modified - that will retrieve updated area ranges and also update 0352 // everything as necessary ... 0353 QHash<QString, QList<Cell> >::ConstIterator it = namedAreaConsumers.constFind(name); 0354 if (it == namedAreaConsumers.constEnd()) 0355 return; 0356 0357 Region region; 0358 const QList<Cell> namedAreaConsumersList = it.value(); 0359 foreach(const Cell &c, namedAreaConsumersList) { 0360 generateDependencies(c, c.formula()); 0361 region.add(c.cellPosition(), c.sheet()); 0362 } 0363 generateDepths(region); 0364 } 0365 0366 void DependencyManager::Private::removeDependencies(const Cell& cell) 0367 { 0368 // look if the cell has any providers 0369 QMap<Cell, Region>::ConstIterator pit = providers.constFind(cell); 0370 if (pit == providers.constEnd()) 0371 return; //it doesn't - nothing more to do 0372 0373 // first this cell is no longer a provider for all consumers 0374 Region region = pit.value(); 0375 Region::ConstIterator end(region.constEnd()); 0376 for (Region::ConstIterator it(region.constBegin()); it != end; ++it) { 0377 QHash<Sheet*, RTree<Cell>*>::ConstIterator cit = consumers.constFind((*it)->sheet()); 0378 if (cit != consumers.constEnd()) { 0379 cit.value()->remove((*it)->rect(), cell); 0380 } 0381 } 0382 0383 // remove information about named area dependencies 0384 QHash<QString, QList<Cell> >::Iterator nit(namedAreaConsumers.begin()); 0385 while (nit != namedAreaConsumers.end()) { 0386 nit.value().removeAll(cell); 0387 if (nit.value().isEmpty()) 0388 nit = namedAreaConsumers.erase(nit); 0389 else 0390 ++nit; 0391 } 0392 0393 // clear the circular dependency flags 0394 removeCircularDependencyFlags(providers.value(cell), Backward); 0395 removeCircularDependencyFlags(consumingRegion(cell), Forward); 0396 0397 // finally, remove the providers for this cell 0398 providers.remove(cell); 0399 } 0400 0401 void DependencyManager::Private::removeDepths(const Cell& cell) 0402 { 0403 QMap<Cell, int>::Iterator dit = depths.find(cell); 0404 if (dit == depths.end()) 0405 return; 0406 QHash<Sheet*, RTree<Cell>*>::ConstIterator cit = consumers.constFind(cell.sheet()); 0407 if (cit == consumers.constEnd()) 0408 return; 0409 depths.erase(dit); 0410 const QList<Cell> consumers = cit.value()->contains(cell.cellPosition()); 0411 foreach(const Cell &c, consumers) 0412 removeDepths(c); 0413 } 0414 0415 void DependencyManager::Private::generateDependencies(const Cell& cell, const Formula& formula) 0416 { 0417 //get rid of old dependencies first 0418 removeDependencies(cell); 0419 0420 //new dependencies only need to be generated if the cell contains a formula 0421 // if (cell.isNull()) 0422 // return; 0423 // if (!cell.isFormula()) 0424 // return; 0425 0426 //now we need to generate the providing region 0427 computeDependencies(cell, formula); 0428 } 0429 0430 void DependencyManager::Private::generateDepths(const Region& region) 0431 { 0432 QSet<Cell> computedDepths; 0433 0434 Region::ConstIterator end(region.constEnd()); 0435 for (Region::ConstIterator it(region.constBegin()); it != end; ++it) { 0436 const QRect range = (*it)->rect(); 0437 const Sheet* sheet = (*it)->sheet(); 0438 const CellStorage *cells = sheet->cellStorage(); 0439 0440 int bottom = range.bottom(); 0441 if (bottom > cells->rows()) bottom = cells->rows(); 0442 int right = range.right(); 0443 if (right > cells->columns()) right = cells->columns(); 0444 0445 for (int row = range.top(); row <= bottom; ++row) { 0446 for (int col = range.left(); col <= right; ++col) { 0447 Cell cell(sheet, col, row); 0448 generateDepths(cell, computedDepths); 0449 } 0450 } 0451 } 0452 } 0453 0454 void DependencyManager::Private::generateDepths(Cell cell, QSet<Cell>& computedDepths) 0455 { 0456 static QSet<Cell> processedCells; 0457 0458 //prevent infinite recursion (circular dependencies) 0459 if (processedCells.contains(cell) || cell.value() == Value::errorCIRCLE()) { 0460 debugSheetsFormula << "Circular dependency at" << cell.fullName(); 0461 cell.setValue(Value::errorCIRCLE()); 0462 depths.insert(cell, 0); 0463 return; 0464 } 0465 if (computedDepths.contains(cell)) { 0466 return; 0467 } 0468 0469 // set the compute reference depth flag 0470 processedCells.insert(cell); 0471 0472 int depth = computeDepth(cell); 0473 depths.insert(cell, depth); 0474 0475 computedDepths.insert(cell); 0476 0477 // Recursion. We need the whole dependency tree of the changed region. 0478 // An infinite loop is prevented by the check above. 0479 QHash<Sheet*, RTree<Cell>*>::ConstIterator cit = consumers.constFind(cell.sheet()); 0480 if (cit == consumers.constEnd()) { 0481 processedCells.remove(cell); 0482 return; 0483 } 0484 const QList<Cell> consumers = cit.value()->contains(cell.cellPosition()); 0485 foreach (const Cell &c, consumers) { 0486 generateDepths(c, computedDepths); 0487 } 0488 0489 // clear the compute reference depth flag 0490 processedCells.remove(cell); 0491 } 0492 0493 int DependencyManager::Private::computeDepth(Cell cell) const 0494 { 0495 // a set of cell, which depth is currently calculated 0496 static QSet<Cell> processedCells; 0497 0498 //prevent infinite recursion (circular dependencies) 0499 if (processedCells.contains(cell) || cell.value() == Value::errorCIRCLE()) { 0500 debugSheetsFormula << "Circular dependency at" << cell.fullName(); 0501 cell.setValue(Value::errorCIRCLE()); 0502 return 0; 0503 } 0504 0505 // set the compute reference depth flag 0506 processedCells.insert(cell); 0507 0508 int depth = 0; 0509 0510 const Region region = providers.value(cell); 0511 0512 Region::ConstIterator end(region.constEnd()); 0513 for (Region::ConstIterator it(region.constBegin()); it != end; ++it) { 0514 const QRect range = (*it)->rect(); 0515 Sheet* sheet = (*it)->sheet(); 0516 const int right = range.right(); 0517 const int bottom = range.bottom(); 0518 for (int col = range.left(); col <= right; ++col) { 0519 for (int row = range.top(); row <= bottom; ++row) { 0520 Cell referencedCell = Cell(sheet, col, row); 0521 if (!providers.contains(referencedCell)) { 0522 // no further references 0523 // depth is one at least 0524 depth = qMax(depth, 1); 0525 continue; 0526 } 0527 0528 QMap<Cell, int>::ConstIterator it = depths.constFind(referencedCell); 0529 if (it != depths.constEnd()) { 0530 // the referenced cell depth was already computed 0531 depth = qMax(it.value() + 1, depth); 0532 continue; 0533 } 0534 0535 // compute the depth of the referenced cell, add one and 0536 // take it as new depth, if it's greater than the current one 0537 depth = qMax(computeDepth(referencedCell) + 1, depth); 0538 } 0539 } 0540 } 0541 0542 //clear the computing reference depth flag 0543 processedCells.remove(cell); 0544 0545 return depth; 0546 } 0547 0548 void DependencyManager::Private::computeDependencies(const Cell& cell, const Formula& formula) 0549 { 0550 // Broken formula -> meaningless dependencies 0551 if (!formula.isValid()) 0552 return; 0553 0554 const Tokens tokens = formula.tokens(); 0555 0556 //return empty list if the tokens aren't valid 0557 if (!tokens.valid()) 0558 return; 0559 0560 Sheet* sheet = cell.sheet(); 0561 int inAreasCall = 0; 0562 Region providingRegion; 0563 for (int i = 0; i < tokens.count(); i++) { 0564 const Token &token = tokens[i]; 0565 0566 if (inAreasCall) { 0567 if (token.isOperator() && token.asOperator() == Token::LeftPar) 0568 inAreasCall++; 0569 else if (token.isOperator() && token.asOperator() == Token::RightPar) 0570 inAreasCall--; 0571 } else { 0572 if (i > 0 && token.isOperator() && token.asOperator() == Token::LeftPar && tokens[i-1].isIdentifier() && QString::compare(tokens[i-1].text(), "AREAS", Qt::CaseInsensitive) == 0) 0573 inAreasCall = 1; 0574 0575 //parse each cell/range and put it to our Region 0576 if (token.type() == Token::Cell || token.type() == Token::Range) { 0577 // check for named area 0578 const bool isNamedArea = sheet->map()->namedAreaManager()->contains(token.text()); 0579 if (isNamedArea) { 0580 // add cell as consumer of the named area 0581 namedAreaConsumers[token.text()].append(cell); 0582 } 0583 0584 // check if valid cell/range 0585 Region region(token.text(), sheet->map(), sheet); 0586 if (region.isValid()) { 0587 if (isNamedArea) { 0588 if ((i > 0 && tokens[i-1].isOperator()) || (i < tokens.count()-1 && tokens[i+1].isOperator())) { 0589 // TODO: this check is not quite correct, to really properly determine if the entire range is referenced 0590 // or just a single cell we would need to actually have the compile formula, not just the tokenized one 0591 // basically this is the same logic as Formula::Private::valueOrElement 0592 0593 Region realRegion = region.intersectedWithRow(cell.row()); 0594 if (!realRegion.isEmpty()) { 0595 region = realRegion; 0596 } 0597 } 0598 } 0599 // add it to the providers 0600 providingRegion.add(region); 0601 0602 Sheet* sheet = region.firstSheet(); 0603 0604 // create consumer tree, if not existing yet 0605 QHash<Sheet*, RTree<Cell>*>::iterator it = consumers.find(sheet); 0606 if (it == consumers.end()) { 0607 it = consumers.insert(sheet, new RTree<Cell>()); 0608 } 0609 // add cell as consumer of the range 0610 it.value()->insert(region.firstRange(), cell); 0611 } 0612 } 0613 } 0614 } 0615 0616 // store the providing region 0617 // NOTE Stefan: Also store cells without dependencies to avoid an 0618 // iteration over all cells in a map/sheet on recalculation. 0619 // empty region will be created automatically, if necessary 0620 providers[cell].add(providingRegion); 0621 } 0622 0623 void DependencyManager::Private::removeCircularDependencyFlags(const Region& region, Direction direction) 0624 { 0625 // a set of cells, which circular dependency flag is currently removed 0626 static QSet<Cell> processedCells; 0627 0628 Region::ConstIterator end(region.constEnd()); 0629 for (Region::ConstIterator it(region.constBegin()); it != end; ++it) { 0630 const QRect range = (*it)->rect(); 0631 const Sheet* sheet = (*it)->sheet(); 0632 for (int col = range.left(); col <= range.right(); ++col) { 0633 for (int row = range.top(); row <= range.bottom(); ++row) { 0634 Cell cell(sheet, col, row); 0635 0636 if (processedCells.contains(cell)) 0637 continue; 0638 processedCells.insert(cell); 0639 0640 if (cell.value() == Value::errorCIRCLE()) 0641 cell.setValue(Value::empty()); 0642 0643 if (direction == Backward) 0644 removeCircularDependencyFlags(providers.value(cell), Backward); 0645 else // Forward 0646 removeCircularDependencyFlags(consumingRegion(cell), Forward); 0647 0648 processedCells.remove(cell); 0649 } 0650 } 0651 } 0652 }