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 }