You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koffice/kspread/dependencies.cc

683 lines
20 KiB

/* This file is part of the KDE project
Copyright 2004 Tomas Mecir <mecirt@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "dependencies.h"
#include "formula.h"
#include "kspread_cell.h"
#include "kspread_sheet.h"
#include <tqmap.h>
//rows x cols in one cell-chunk; bigger values lead to slower updating
//of range-dependencies, lower values will increase memory usage
#define CELLCHUNK_ROWS 128
#define CELLCHUNK_COLS 16
namespace KSpread {
/** d-pointer of DependencyManager */
class DependencyList {
public:
DependencyList (Sheet *s);
~DependencyList () { reset (); };
/** clear internal structures */
void reset ();
/** handle the fact that a cell has been changed */
void cellChanged (const Point &cell);
/** generate list of dependencies of a cell */
void generateDependencies (const Point &cell);
/** generate list of dependencies of a range */
void generateDependencies (const Range &range);
/** generate list of dependencies of a range list */
void generateDependencies (const RangeList &rangeList);
/** update cells dependending on a given cell */
void processDependencies (const Point &cell);
/** update cells dependending on a cell in a given range */
void processDependencies (const Range &range);
/** update cells dependending on a given range-list */
void processDependencies (const RangeList &rangeList);
/** get dependencies of a cell */
RangeList getDependencies (const Point &cell);
/** get cells depending on this cell, either through normal or range dependency */
TQValueList<Point> getDependants (const Point &cell);
void areaModified (const TQString &name);
protected:
/** update structures: cell 1 depends on cell 2 */
void addDependency (const Point &cell1, const Point &cell2);
/** update structures: cell depends on a range */
void addRangeDependency (const RangeDependency &rd);
/** remove all dependencies of a cell */
void removeDependencies (const Point &cell);
/** update all cells depending on a range containing this cell */
void processRangeDependencies (const Point &cell);
/** update all cells depending on a range intersecting with this range */
void processRangeDependencies (const Range &range);
/** update one cell due to changed dependencies */
void updateCell (const Point &cell) const;
/** return a leading cell for a given cell (used to store range
dependencies effectively) */
Point leadingCell (const Point &cell) const;
/** list of leading cells of all cell chunks that this range belongs to */
TQValueList<Point> leadingCells (const Range &range) const;
/** retrieve a list of cells that a given cell depends on */
RangeList computeDependencies (const Point &cell) const;
TQValueList<Point> getCellDeps(const Point& cell) const {
CellDepsMap::const_iterator it = cellDeps.tqfind( cell );
return it == cellDeps.end() ? TQValueList<Point>() : *it;
}
/** debug */
void dump();
/** Sheet whose dependencies are managed by this instance */
Sheet *sheet;
/** dependencies of each cell */
TQMap<Point, RangeList> dependencies;
/** list of cells (but NOT ranges) that depend on a cell */
typedef TQMap<Point, TQValueList<Point> > CellDepsMap;
CellDepsMap cellDeps;
/** all range dependencies splitted into cell-chunks (TODO: describe) */
TQMap<Point, TQValueList<RangeDependency> > rangeDeps;
/** list of cells referencing a given named area */
TQMap<TQString, TQMap<Point, bool> > areaDeps;
};
} // namespace KSpread
using namespace KSpread;
// This is currently not called - but it's really convenient to call it from
// gdb or from debug output to check that everything is set up ok.
void DependencyList::dump()
{
TQMap<Point, RangeList>::const_iterator it = dependencies.begin();
for ( ; it != dependencies.end(); ++it ) {
Point p = it.key();
kdDebug() << "Cell " << p.sheetName() << " " << p.pos()
<< " depends on :" << endl;
RangeList rl = (*it);
TQValueList<Point>::const_iterator itp = rl.cells.begin();
for ( ; itp != rl.cells.end(); ++itp )
kdDebug() << " cell " << (*itp).pos() << endl;
TQValueList<Range>::const_iterator itr = rl.ranges.begin();
for ( ; itr != rl.ranges.end(); ++itr )
kdDebug() << " range " << (*itr).toString() << endl;
}
CellDepsMap::const_iterator cit = cellDeps.begin();
for ( ; cit != cellDeps.end(); ++cit )
{
Point p = cit.key();
kdDebug() << "The cells that depend on " << p.sheetName() << " " << p.pos()
<< " are :" << endl;
TQValueList<Point>::const_iterator itp = (*cit).begin();
for ( ; itp != (*cit).end(); ++itp )
kdDebug() << " cell " << (*itp).pos() << endl;
}
}
DependencyManager::DependencyManager (Sheet *s)
{
deps = new DependencyList (s);
}
DependencyManager::~DependencyManager ()
{
delete deps;
deps = 0;
}
void DependencyManager::reset ()
{
deps->reset();
}
void DependencyManager::cellChanged (const Point &cell)
{
deps->cellChanged (cell);
}
void DependencyManager::rangeChanged (const Range &range)
{
deps->generateDependencies (range);
deps->processDependencies (range);
}
void DependencyManager::rangeListChanged (const RangeList &rangeList)
{
deps->generateDependencies (rangeList);
deps->processDependencies (rangeList);
}
void DependencyManager::areaModified (const TQString &name)
{
deps->areaModified (name);
}
RangeList DependencyManager::getDependencies (const Point &cell)
{
return deps->getDependencies (cell);
}
TQValueList<Point> DependencyManager::getDependants (const Point &cell)
{
return deps->getDependants (cell);
}
DependencyList::DependencyList (Sheet *s)
: sheet (s)
{
}
void DependencyList::reset ()
{
dependencies.clear();
cellDeps.clear();
rangeDeps.clear();
}
void DependencyList::cellChanged (const Point &cell)
{
Cell *c = cell.cell();
// empty or default cell? do nothing
if( c->isDefault() )
return;
//if the cell contains the circle error, we mustn't do anything
if (c->testFlag (Cell::Flag_CircularCalculation))
return;
//don't re-generate dependencies if we're updating dependencies
if ( !(c->testFlag (Cell::Flag_Progress)))
generateDependencies (cell);
processDependencies (cell);
}
RangeList DependencyList::getDependencies (const Point &cell)
{
RangeList rl;
//look if the cell has any dependencies
if (!dependencies.tqcontains (cell))
return rl; //it doesn't - return an empty list
//the cell does have dependencies - return them!
return dependencies[cell];
}
TQValueList<Point> DependencyList::getDependants (const Point &cell)
{
//cell dependencies go first
TQValueList<Point> list = getCellDeps( cell );
//next, append range dependencies
Point leading = leadingCell (cell);
TQValueList<RangeDependency>::iterator it;
if (!rangeDeps.count (leading))
return list; //no range dependencies in this cell chunk -> nothing more to do
for (it = rangeDeps[leading].begin();
it != rangeDeps[leading].end(); ++it)
{
//process all range dependencies, and for each range including the questioned cell,
//add the depending cell to the list
if ((*it).range.tqcontains (cell))
{
Point c;
c.setRow ((*it).cellrow);
c.setColumn ((*it).cellcolumn);
c.setSheet ( (*it).cellsheet );
list.push_back (c);
}
}
return list;
}
void DependencyList::areaModified (const TQString &name)
{
// since area names are something like aliases, modifying an area name
// basically means that all cells referencing this area should be treated
// as modified - that will retrieve updated area ranges and also update
// everything as necessary ...
if (!areaDeps.tqcontains (name))
return;
TQMap<Point, bool>::iterator it;
for (it = areaDeps[name].begin(); it != areaDeps[name].end(); ++it)
{
Cell *c = it.key().cell();
// this forces the cell to regenerate everything - new range dependencies
// and so on
c->setValue (c->value ());
}
}
void DependencyList::addDependency (const Point &cell1,
const Point &cell2)
{
//cell2 can be in another sheet (inter-sheet dependency)
Sheet *sh = cell2.sheet();
if (!sh)
sh = sheet;
dependencies[cell1].cells.push_back (cell2);
sh->dependencies()->deps->cellDeps[cell2].push_back (cell1);
}
void DependencyList::addRangeDependency (const RangeDependency &rd)
{
//target range can be in another sheet (inter-sheet dependency)
Sheet *sh = rd.range.sheet();
if (!sh)
sh = sheet;
Point cell;
cell.setSheet (rd.cellsheet);
cell.setRow (rd.cellrow);
cell.setColumn (rd.cellcolumn);
dependencies[cell].ranges.push_back (rd.range);
TQValueList<Point> leadings = leadingCells (rd.range);
TQValueList<Point>::iterator it;
for (it = leadings.begin(); it != leadings.end(); ++it)
sh->dependencies()->deps->rangeDeps[*it].push_back (rd);
// the target range could be a named area ...
if (!rd.range.namedArea().isNull())
areaDeps[rd.range.namedArea()][cell] = true;
}
void DependencyList::removeDependencies (const Point &cell)
{
//look if the cell has any dependencies
if (!dependencies.tqcontains (cell))
return; //it doesn't - nothing more to do
//first we remove cell-dependencies
TQValueList<Point> cells = dependencies[cell].cells;
TQValueList<Point>::iterator it1;
for (it1 = cells.begin(); it1 != cells.end(); ++it1)
{
//get sheet-pointer - needed to handle inter-sheet dependencies correctly
Sheet *sh = (*it1).sheet();
if (!sh)
sh = sheet;
if (!sh->dependencies()->deps->cellDeps.tqcontains (*it1))
continue; //this should never happen
//we no longer depend on this cell
TQValueList<Point>::iterator cit;
cit = sh->dependencies()->deps->cellDeps[*it1].tqfind (cell);
if (cit != sh->dependencies()->deps->cellDeps[*it1].end())
sh->dependencies()->deps->cellDeps[*it1].erase (cit);
}
//then range-dependencies are removed
TQValueList<Range> ranges = dependencies[cell].ranges;
TQValueList<Range>::iterator it2;
TQValueList<Point> leads;
for (it2 = ranges.begin(); it2 != ranges.end(); ++it2)
{
//we construct a list of cell-chunks containing a range and merge it
//with lists generated from all previous ranges (duplicates are removed)
TQValueList<Point> leadings = leadingCells (*it2);
for (it1 = leadings.begin(); it1 != leadings.end(); ++it1)
if (!leads.tqcontains (*it1))
leads.push_back (*it1);
}
for (it1 = leads.begin(); it1 != leads.end(); ++it1)
{
//get sheet-pointer - needed to handle inter-sheet dependencies correctly
Sheet *sh = (*it1).sheet();
if (!sh)
sh = sheet;
if (sh->dependencies()->deps->rangeDeps.tqcontains (*it1))
{
TQValueList<RangeDependency>::iterator it3;
it3 = sh->dependencies()->deps->rangeDeps[*it1].begin();
//erase all range dependencies of this cell in this cell-chunk
while (it3 != sh->dependencies()->deps->rangeDeps[*it1].end())
if (((*it3).cellrow == cell.row()) &&
((*it3).cellcolumn == cell.column()))
it3 = sh->dependencies()->deps->rangeDeps[*it1].erase (it3);
else
++it3;
//erase the list if we no longer need it
if (sh->dependencies()->deps->rangeDeps[*it1].empty())
sh->dependencies()->deps->rangeDeps.erase (*it1);
}
}
// remove information about named area dependencies
TQMap<TQString, TQMap<Point, bool> >::iterator itr;
for (itr = areaDeps.begin(); itr != areaDeps.end(); ++itr) {
if (itr.data().tqcontains (cell))
itr.data().remove (cell);
}
// finally, remove the entry about this cell
dependencies[cell].cells.clear();
dependencies[cell].ranges.clear();
dependencies.erase (cell);
}
void DependencyList::generateDependencies (const Point &cell)
{
//get rid of old dependencies first
removeDependencies (cell);
//new dependencies only need to be generated if the cell contains a formula
Cell *c = sheet->cellAt (cell.column(), cell.row());
if( c->isDefault() )
return;
if (!c->isFormula())
return;
//now we need to generate dependencies
RangeList rl = computeDependencies (cell);
//now that we have the new dependencies, we put them into our data structures
//and we're done
TQValueList<Point>::iterator it1;
TQValueList<Range>::iterator it2;
for (it1 = rl.cells.begin(); it1 != rl.cells.end(); ++it1)
addDependency (cell, *it1);
for (it2 = rl.ranges.begin(); it2 != rl.ranges.end(); ++it2)
{
RangeDependency rd;
rd.cellrow = cell.row();
rd.cellcolumn = cell.column();
rd.cellsheet = sheet;
rd.range = *it2;
if (rd.range.sheet() == 0)
rd.range.setSheet(sheet);
addRangeDependency (rd);
}
}
void DependencyList::generateDependencies (const Range &range)
{
for (int row = range.startRow(); row <= range.endRow(); row++)
for (int col = range.startCol(); col <= range.endCol(); col++)
{
Point c;
c.setRow (row);
c.setColumn (col);
c.setSheet(sheet);
generateDependencies (c);
}
}
void DependencyList::generateDependencies (const RangeList &rangeList)
{
TQValueList<Point>::const_iterator it1;
TQValueList<Range>::const_iterator it2;
for (it1 = rangeList.cells.begin(); it1 != rangeList.cells.end(); ++it1)
generateDependencies (*it1);
for (it2 = rangeList.ranges.begin(); it2 != rangeList.ranges.end(); ++it2)
generateDependencies (*it2);
}
void DependencyList::processDependencies (const Point &cell)
{
const TQValueList<Point> d = getCellDeps(cell);
TQValueList<Point>::const_iterator it = d.begin();
const TQValueList<Point>::const_iterator end = d.end();
for (; it != end; ++it)
updateCell (*it);
processRangeDependencies (cell);
}
void DependencyList::processRangeDependencies (const Point &cell)
{
Point leading = leadingCell (cell);
if (!rangeDeps.count (leading))
return; //no range dependencies in this cell chunk
const TQValueList<RangeDependency> rd = rangeDeps[leading];
TQValueList<RangeDependency>::const_iterator it;
for (it = rd.begin(); it != rd.end(); ++it)
{
//process all range dependencies, and for each range including the modified cell,
//recalc the depending cell
if ((*it).range.tqcontains (cell))
{
Point c;
c.setRow ((*it).cellrow);
c.setColumn ((*it).cellcolumn);
c.setSheet ( (*it).cellsheet );
updateCell (c);
}
}
}
void DependencyList::processDependencies (const Range &range)
{
//each cell's dependencies need to be updated - that cannot be helped - having a range
//only helps with range dependencies
for (int row = range.startRow(); row <= range.endRow(); row++)
for (int col = range.startCol(); col <= range.endCol(); col++)
{
Point c;
c.setRow (row);
c.setColumn (col);
c.setSheet( sheet );
const TQValueList<Point> d = getCellDeps(c);
TQValueList<Point>::const_iterator it = d.begin();
const TQValueList<Point>::const_iterator end = d.end();
for (; it != end; ++it)
updateCell (*it);
}
processRangeDependencies (range);
}
void DependencyList::processRangeDependencies (const Range &range)
{
//TODO: some optimization, so that we don't recompute cells depending of huge
//ranges more than once (now we recompute them once per cell-chunk used by their dependency)
//This will probably happen as a part of splitting this into dep manager
//and recalc manager
TQValueList<Point> leadings = leadingCells (range);
TQValueList<Point>::iterator it;
for (it = leadings.begin(); it != leadings.end(); ++it)
{
if (!rangeDeps.count (*it))
continue; //no range dependencies in this cell chunk
TQValueList<RangeDependency>::iterator it2;
for (it2 = rangeDeps[*it].begin(); it2 != rangeDeps[*it].end(); ++it2)
{
//process all range dependencies, and for each range intersecting with our range,
//recalc the depending cell
if ((*it2).range.intersects (range))
{
Point c;
c.setRow ((*it2).range.startRow());
c.setColumn ((*it2).range.startCol());
c.setSheet(sheet);
updateCell (c);
}
}
}
}
void DependencyList::processDependencies (const RangeList &rangeList)
{
TQValueList<Point>::const_iterator it1;
TQValueList<Range>::const_iterator it2;
for (it1 = rangeList.cells.begin(); it1 != rangeList.cells.end(); ++it1)
processDependencies (*it1);
for (it2 = rangeList.ranges.begin(); it2 != rangeList.ranges.end(); ++it2)
processDependencies (*it2);
}
void DependencyList::updateCell (const Point &cell) const
{
Cell *c = cell.cell();
//prevent infinite recursion (circular dependencies)
if (c->testFlag (Cell::Flag_Progress) ||
c->testFlag (Cell::Flag_CircularCalculation))
{
kdError(36001) << "ERROR: Circle, cell " << c->fullName() <<
", in dep.manager for sheet " << sheet->name() << endl;
Value v;
// don't set anything if the cell already has all these things set
// this prevents endless loop for inter-sheet curcular dependencies,
// where the usual mechanisms fail doe to having multiple dependency
// managers ...
if (!c->testFlag (Cell::Flag_CircularCalculation))
{
c->setFlag(Cell::Flag_CircularCalculation);
v.setError ( "####" );
c->setValue (v);
}
//clear the computing-dependencies flag
c->clearFlag (Cell::Flag_Progress);
return;
}
//set the computing-dependencies flag
c->setFlag (Cell::Flag_Progress);
//mark the cell as calc-dirty
c->setCalcDirtyFlag();
//recalculate the cell
c->calc (false);
//clear the computing-dependencies flag
c->clearFlag (Cell::Flag_Progress);
}
Point DependencyList::leadingCell (const Point &cell) const
{
Point c;
c.setRow (cell.row() - cell.row() % CELLCHUNK_ROWS + 1);
c.setColumn (cell.column() - cell.column() % CELLCHUNK_COLS + 1);
c.setSheet(cell.sheet());
return c;
}
TQValueList<Point> DependencyList::leadingCells (const Range &range) const
{
TQValueList<Point> cells;
Point cell1, cell2, cell;
cell1.setRow (range.startRow());
cell1.setColumn (range.startCol());
cell2.setRow (range.endRow());
cell2.setColumn (range.endCol());
cell1.setSheet(range.sheet());
cell2.setSheet(range.sheet());
cell1 = leadingCell (cell1);
cell2 = leadingCell (cell2);
for (int row = cell1.row(); row <= cell2.row(); row += CELLCHUNK_ROWS)
for (int col = cell1.column(); col <= cell2.column();
col += CELLCHUNK_COLS)
{
cell.setRow (row);
cell.setColumn (col);
cell.setSheet(range.sheet());
cells.push_back (cell);
}
return cells;
}
RangeList DependencyList::computeDependencies (const Point &cell) const
{
Cell *c = cell.cell();
// Not a formula -> no dependencies
if (!c->isFormula())
return RangeList();
// Broken formula -> meaningless dependencies
// (tries to avoid c->formula() being null)
if (c->hasError())
return RangeList();
Formula* f = c->formula();
Q_ASSERT(f);
if (f==NULL)
{
kdDebug() << "Cell at row " << cell.row() << ", col " << cell.column() << " marked as formula, but formula is NULL" << endl;
return RangeList();
}
Tokens tokens = f->tokens();
//return empty list if the tokens aren't valid
if (!tokens.valid())
return RangeList();
RangeList rl;
for( unsigned i = 0; i < tokens.count(); i++ )
{
Token token = tokens[i];
Token::Type tokenType = token.type();
//parse each cell/range and put it to our RangeList
if (tokenType == Token::Cell)
{
TQString text = token.text();
Point cell (text, sheet->workbook(), sheet);
if (cell.isValid())
rl.cells.push_back (cell);
}
else if (tokenType == Token::Range)
{
TQString text = token.text();
Range range (text, sheet->workbook(), sheet);
if (range.isValid())
rl.ranges.push_back (range);
}
}
return rl;
}