kspread

dependencies.cpp
1/* This file is part of the KDE project
2 Copyright 2004 Tomas Mecir <mecirt@gmail.com>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18*/
19
20
21#include "dependencies.h"
22
23#include "formula.h"
24
25#include "kspread_cell.h"
26#include "kspread_sheet.h"
27
28#include <tqmap.h>
29
30//rows x cols in one cell-chunk; bigger values lead to slower updating
31//of range-dependencies, lower values will increase memory usage
32#define CELLCHUNK_ROWS 128
33#define CELLCHUNK_COLS 16
34
35namespace KSpread {
36
37
39class DependencyList {
40 public:
41 DependencyList (Sheet *s);
42 ~DependencyList () { reset (); };
44 void reset ();
45
47 void cellChanged (const Point &cell);
48
50 void generateDependencies (const Point &cell);
52 void generateDependencies (const Range &range);
54 void generateDependencies (const RangeList &rangeList);
55
57 void processDependencies (const Point &cell);
59 void processDependencies (const Range &range);
61 void processDependencies (const RangeList &rangeList);
62
64 RangeList getDependencies (const Point &cell);
66 TQValueList<Point> getDependants (const Point &cell);
67
68 void areaModified (const TQString &name);
69 protected:
71 void addDependency (const Point &cell1, const Point &cell2);
73 void addRangeDependency (const RangeDependency &rd);
75 void removeDependencies (const Point &cell);
76
78 void processRangeDependencies (const Point &cell);
79
81 void processRangeDependencies (const Range &range);
82
84 void updateCell (const Point &cell) const;
85
88 Point leadingCell (const Point &cell) const;
90 TQValueList<Point> leadingCells (const Range &range) const;
92 RangeList computeDependencies (const Point &cell) const;
93
94 TQValueList<Point> getCellDeps(const Point& cell) const {
95 CellDepsMap::const_iterator it = cellDeps.find( cell );
96 return it == cellDeps.end() ? TQValueList<Point>() : *it;
97 }
98
100 void dump();
101
103 Sheet *sheet;
104
106 TQMap<Point, RangeList> dependencies;
108 typedef TQMap<Point, TQValueList<Point> > CellDepsMap;
109 CellDepsMap cellDeps;
111 TQMap<Point, TQValueList<RangeDependency> > rangeDeps;
113 TQMap<TQString, TQMap<Point, bool> > areaDeps;
114};
115
116} // namespace KSpread
117
118using namespace KSpread;
119
120// This is currently not called - but it's really convenient to call it from
121// gdb or from debug output to check that everything is set up ok.
122void DependencyList::dump()
123{
124 TQMap<Point, RangeList>::const_iterator it = dependencies.begin();
125 for ( ; it != dependencies.end(); ++it ) {
126 Point p = it.key();
127 kdDebug() << "Cell " << p.sheetName() << " " << p.pos()
128 << " depends on :" << endl;
129 RangeList rl = (*it);
130 TQValueList<Point>::const_iterator itp = rl.cells.begin();
131 for ( ; itp != rl.cells.end(); ++itp )
132 kdDebug() << " cell " << (*itp).pos() << endl;
133 TQValueList<Range>::const_iterator itr = rl.ranges.begin();
134 for ( ; itr != rl.ranges.end(); ++itr )
135 kdDebug() << " range " << (*itr).toString() << endl;
136 }
137
138 CellDepsMap::const_iterator cit = cellDeps.begin();
139 for ( ; cit != cellDeps.end(); ++cit )
140 {
141 Point p = cit.key();
142 kdDebug() << "The cells that depend on " << p.sheetName() << " " << p.pos()
143 << " are :" << endl;
144 TQValueList<Point>::const_iterator itp = (*cit).begin();
145 for ( ; itp != (*cit).end(); ++itp )
146 kdDebug() << " cell " << (*itp).pos() << endl;
147 }
148
149}
150
152{
153 deps = new DependencyList (s);
154}
155
157{
158 delete deps;
159 deps = 0;
160}
161
163{
164 deps->reset();
165}
166
168{
169 deps->cellChanged (cell);
170}
171
173{
174 deps->generateDependencies (range);
175 deps->processDependencies (range);
176}
177
179{
180 deps->generateDependencies (rangeList);
181 deps->processDependencies (rangeList);
182}
183
184void DependencyManager::areaModified (const TQString &name)
185{
186 deps->areaModified (name);
187}
188
190{
191 return deps->getDependencies (cell);
192}
193
194TQValueList<Point> DependencyManager::getDependants (const Point &cell)
195{
196 return deps->getDependants (cell);
197}
198
199DependencyList::DependencyList (Sheet *s)
200 : sheet (s)
201{
202}
203
204void DependencyList::reset ()
205{
206 dependencies.clear();
207 cellDeps.clear();
208 rangeDeps.clear();
209}
210
211void DependencyList::cellChanged (const Point &cell)
212{
213 Cell *c = cell.cell();
214
215 // empty or default cell? do nothing
216 if( c->isDefault() )
217 return;
218
219 //if the cell contains the circle error, we mustn't do anything
220 if (c->testFlag (Cell::Flag_CircularCalculation))
221 return;
222
223 //don't re-generate dependencies if we're updating dependencies
224 if ( !(c->testFlag (Cell::Flag_Progress)))
225 generateDependencies (cell);
226
227 processDependencies (cell);
228}
229
230RangeList DependencyList::getDependencies (const Point &cell)
231{
232 RangeList rl;
233 //look if the cell has any dependencies
234 if (!dependencies.contains (cell))
235 return rl; //it doesn't - return an empty list
236
237 //the cell does have dependencies - return them!
238 return dependencies[cell];
239}
240
241TQValueList<Point> DependencyList::getDependants (const Point &cell)
242{
243 //cell dependencies go first
244 TQValueList<Point> list = getCellDeps( cell );
245 //next, append range dependencies
246 Point leading = leadingCell (cell);
247 TQValueList<RangeDependency>::iterator it;
248 if (!rangeDeps.count (leading))
249 return list; //no range dependencies in this cell chunk -> nothing more to do
250
251 for (it = rangeDeps[leading].begin();
252 it != rangeDeps[leading].end(); ++it)
253 {
254 //process all range dependencies, and for each range including the questioned cell,
255 //add the depending cell to the list
256 if ((*it).range.contains (cell))
257 {
258 Point c;
259 c.setRow ((*it).cellrow);
260 c.setColumn ((*it).cellcolumn);
261 c.setSheet ( (*it).cellsheet );
262 list.push_back (c);
263 }
264 }
265
266 return list;
267}
268
269void DependencyList::areaModified (const TQString &name)
270{
271 // since area names are something like aliases, modifying an area name
272 // basically means that all cells referencing this area should be treated
273 // as modified - that will retrieve updated area ranges and also update
274 // everything as necessary ...
275 if (!areaDeps.contains (name))
276 return;
277
278 TQMap<Point, bool>::iterator it;
279 for (it = areaDeps[name].begin(); it != areaDeps[name].end(); ++it)
280 {
281 Cell *c = it.key().cell();
282 // this forces the cell to regenerate everything - new range dependencies
283 // and so on
284 c->setValue (c->value ());
285 }
286}
287
288void DependencyList::addDependency (const Point &cell1,
289 const Point &cell2)
290{
291 //cell2 can be in another sheet (inter-sheet dependency)
292 Sheet *sh = cell2.sheet();
293 if (!sh)
294 sh = sheet;
295
296 dependencies[cell1].cells.push_back (cell2);
297 sh->dependencies()->deps->cellDeps[cell2].push_back (cell1);
298}
299
300void DependencyList::addRangeDependency (const RangeDependency &rd)
301{
302 //target range can be in another sheet (inter-sheet dependency)
303 Sheet *sh = rd.range.sheet();
304 if (!sh)
305 sh = sheet;
306
307 Point cell;
308 cell.setSheet (rd.cellsheet);
309 cell.setRow (rd.cellrow);
310 cell.setColumn (rd.cellcolumn);
311 dependencies[cell].ranges.push_back (rd.range);
312
313 TQValueList<Point> leadings = leadingCells (rd.range);
314 TQValueList<Point>::iterator it;
315 for (it = leadings.begin(); it != leadings.end(); ++it)
316 sh->dependencies()->deps->rangeDeps[*it].push_back (rd);
317
318 // the target range could be a named area ...
319 if (!rd.range.namedArea().isNull())
320 areaDeps[rd.range.namedArea()][cell] = true;
321}
322
323void DependencyList::removeDependencies (const Point &cell)
324{
325 //look if the cell has any dependencies
326 if (!dependencies.contains (cell))
327 return; //it doesn't - nothing more to do
328
329 //first we remove cell-dependencies
330 TQValueList<Point> cells = dependencies[cell].cells;
331 TQValueList<Point>::iterator it1;
332 for (it1 = cells.begin(); it1 != cells.end(); ++it1)
333 {
334 //get sheet-pointer - needed to handle inter-sheet dependencies correctly
335 Sheet *sh = (*it1).sheet();
336 if (!sh)
337 sh = sheet;
338
339 if (!sh->dependencies()->deps->cellDeps.contains (*it1))
340 continue; //this should never happen
341
342 //we no longer depend on this cell
343 TQValueList<Point>::iterator cit;
344 cit = sh->dependencies()->deps->cellDeps[*it1].find (cell);
345 if (cit != sh->dependencies()->deps->cellDeps[*it1].end())
346 sh->dependencies()->deps->cellDeps[*it1].erase (cit);
347 }
348
349 //then range-dependencies are removed
350 TQValueList<Range> ranges = dependencies[cell].ranges;
351 TQValueList<Range>::iterator it2;
352 TQValueList<Point> leads;
353 for (it2 = ranges.begin(); it2 != ranges.end(); ++it2)
354 {
355 //we construct a list of cell-chunks containing a range and merge it
356 //with lists generated from all previous ranges (duplicates are removed)
357 TQValueList<Point> leadings = leadingCells (*it2);
358 for (it1 = leadings.begin(); it1 != leadings.end(); ++it1)
359 if (!leads.contains (*it1))
360 leads.push_back (*it1);
361 }
362 for (it1 = leads.begin(); it1 != leads.end(); ++it1)
363 {
364 //get sheet-pointer - needed to handle inter-sheet dependencies correctly
365 Sheet *sh = (*it1).sheet();
366 if (!sh)
367 sh = sheet;
368
369 if (sh->dependencies()->deps->rangeDeps.contains (*it1))
370 {
371 TQValueList<RangeDependency>::iterator it3;
372 it3 = sh->dependencies()->deps->rangeDeps[*it1].begin();
373 //erase all range dependencies of this cell in this cell-chunk
374 while (it3 != sh->dependencies()->deps->rangeDeps[*it1].end())
375 if (((*it3).cellrow == cell.row()) &&
376 ((*it3).cellcolumn == cell.column()))
377 it3 = sh->dependencies()->deps->rangeDeps[*it1].erase (it3);
378 else
379 ++it3;
380 //erase the list if we no longer need it
381 if (sh->dependencies()->deps->rangeDeps[*it1].empty())
382 sh->dependencies()->deps->rangeDeps.erase (*it1);
383 }
384 }
385
386 // remove information about named area dependencies
387 TQMap<TQString, TQMap<Point, bool> >::iterator itr;
388 for (itr = areaDeps.begin(); itr != areaDeps.end(); ++itr) {
389 if (itr.data().contains (cell))
390 itr.data().remove (cell);
391 }
392
393 // finally, remove the entry about this cell
394 dependencies[cell].cells.clear();
395 dependencies[cell].ranges.clear();
396 dependencies.erase (cell);
397}
398
399void DependencyList::generateDependencies (const Point &cell)
400{
401 //get rid of old dependencies first
402 removeDependencies (cell);
403
404 //new dependencies only need to be generated if the cell contains a formula
405 Cell *c = sheet->cellAt (cell.column(), cell.row());
406 if( c->isDefault() )
407 return;
408 if (!c->isFormula())
409 return;
410
411 //now we need to generate dependencies
412 RangeList rl = computeDependencies (cell);
413
414 //now that we have the new dependencies, we put them into our data structures
415 //and we're done
416 TQValueList<Point>::iterator it1;
417 TQValueList<Range>::iterator it2;
418
419 for (it1 = rl.cells.begin(); it1 != rl.cells.end(); ++it1)
420 addDependency (cell, *it1);
421 for (it2 = rl.ranges.begin(); it2 != rl.ranges.end(); ++it2)
422 {
423 RangeDependency rd;
424 rd.cellrow = cell.row();
425 rd.cellcolumn = cell.column();
426 rd.cellsheet = sheet;
427 rd.range = *it2;
428
429 if (rd.range.sheet() == 0)
430 rd.range.setSheet(sheet);
431
432 addRangeDependency (rd);
433 }
434}
435
436void DependencyList::generateDependencies (const Range &range)
437{
438 for (int row = range.startRow(); row <= range.endRow(); row++)
439 for (int col = range.startCol(); col <= range.endCol(); col++)
440 {
441 Point c;
442 c.setRow (row);
443 c.setColumn (col);
444 c.setSheet(sheet);
445 generateDependencies (c);
446 }
447}
448
449void DependencyList::generateDependencies (const RangeList &rangeList)
450{
451 TQValueList<Point>::const_iterator it1;
452 TQValueList<Range>::const_iterator it2;
453
454 for (it1 = rangeList.cells.begin(); it1 != rangeList.cells.end(); ++it1)
455 generateDependencies (*it1);
456 for (it2 = rangeList.ranges.begin(); it2 != rangeList.ranges.end(); ++it2)
457 generateDependencies (*it2);
458}
459
460void DependencyList::processDependencies (const Point &cell)
461{
462 const TQValueList<Point> d = getCellDeps(cell);
463 TQValueList<Point>::const_iterator it = d.begin();
464 const TQValueList<Point>::const_iterator end = d.end();
465 for (; it != end; ++it)
466 updateCell (*it);
467
468 processRangeDependencies (cell);
469}
470
471void DependencyList::processRangeDependencies (const Point &cell)
472{
473 Point leading = leadingCell (cell);
474 if (!rangeDeps.count (leading))
475 return; //no range dependencies in this cell chunk
476
477 const TQValueList<RangeDependency> rd = rangeDeps[leading];
478
479 TQValueList<RangeDependency>::const_iterator it;
480 for (it = rd.begin(); it != rd.end(); ++it)
481 {
482 //process all range dependencies, and for each range including the modified cell,
483 //recalc the depending cell
484 if ((*it).range.contains (cell))
485 {
486 Point c;
487 c.setRow ((*it).cellrow);
488 c.setColumn ((*it).cellcolumn);
489 c.setSheet ( (*it).cellsheet );
490 updateCell (c);
491 }
492 }
493}
494
495void DependencyList::processDependencies (const Range &range)
496{
497 //each cell's dependencies need to be updated - that cannot be helped - having a range
498 //only helps with range dependencies
499 for (int row = range.startRow(); row <= range.endRow(); row++)
500 for (int col = range.startCol(); col <= range.endCol(); col++)
501 {
502 Point c;
503 c.setRow (row);
504 c.setColumn (col);
505 c.setSheet( sheet );
506
507 const TQValueList<Point> d = getCellDeps(c);
508 TQValueList<Point>::const_iterator it = d.begin();
509 const TQValueList<Point>::const_iterator end = d.end();
510 for (; it != end; ++it)
511 updateCell (*it);
512 }
513
514 processRangeDependencies (range);
515}
516
517void DependencyList::processRangeDependencies (const Range &range)
518{
519 //TODO: some optimization, so that we don't recompute cells depending of huge
520 //ranges more than once (now we recompute them once per cell-chunk used by their dependency)
521 //This will probably happen as a part of splitting this into dep manager
522 //and recalc manager
523
524 TQValueList<Point> leadings = leadingCells (range);
525 TQValueList<Point>::iterator it;
526 for (it = leadings.begin(); it != leadings.end(); ++it)
527 {
528 if (!rangeDeps.count (*it))
529 continue; //no range dependencies in this cell chunk
530 TQValueList<RangeDependency>::iterator it2;
531 for (it2 = rangeDeps[*it].begin(); it2 != rangeDeps[*it].end(); ++it2)
532 {
533 //process all range dependencies, and for each range intersecting with our range,
534 //recalc the depending cell
535 if ((*it2).range.intersects (range))
536 {
537 Point c;
538 c.setRow ((*it2).range.startRow());
539 c.setColumn ((*it2).range.startCol());
540 c.setSheet(sheet);
541 updateCell (c);
542 }
543 }
544 }
545}
546
547void DependencyList::processDependencies (const RangeList &rangeList)
548{
549 TQValueList<Point>::const_iterator it1;
550 TQValueList<Range>::const_iterator it2;
551
552 for (it1 = rangeList.cells.begin(); it1 != rangeList.cells.end(); ++it1)
553 processDependencies (*it1);
554 for (it2 = rangeList.ranges.begin(); it2 != rangeList.ranges.end(); ++it2)
555 processDependencies (*it2);
556}
557
558void DependencyList::updateCell (const Point &cell) const
559{
560 Cell *c = cell.cell();
561 //prevent infinite recursion (circular dependencies)
562 if (c->testFlag (Cell::Flag_Progress) ||
563 c->testFlag (Cell::Flag_CircularCalculation))
564 {
565 kdError(36001) << "ERROR: Circle, cell " << c->fullName() <<
566 ", in dep.manager for sheet " << sheet->name() << endl;
567 Value v;
568 // don't set anything if the cell already has all these things set
569 // this prevents endless loop for inter-sheet curcular dependencies,
570 // where the usual mechanisms fail doe to having multiple dependency
571 // managers ...
572 if (!c->testFlag (Cell::Flag_CircularCalculation))
573 {
574 c->setFlag(Cell::Flag_CircularCalculation);
575 v.setError ( "####" );
576 c->setValue (v);
577 }
578 //clear the computing-dependencies flag
579 c->clearFlag (Cell::Flag_Progress);
580 return;
581 }
582 //set the computing-dependencies flag
583 c->setFlag (Cell::Flag_Progress);
584
585 //mark the cell as calc-dirty
586 c->setCalcDirtyFlag();
587
588 //recalculate the cell
589 c->calc (false);
590
591 //clear the computing-dependencies flag
592 c->clearFlag (Cell::Flag_Progress);
593}
594
595Point DependencyList::leadingCell (const Point &cell) const
596{
597 Point c;
598 c.setRow (cell.row() - cell.row() % CELLCHUNK_ROWS + 1);
599 c.setColumn (cell.column() - cell.column() % CELLCHUNK_COLS + 1);
600 c.setSheet(cell.sheet());
601 return c;
602}
603
604TQValueList<Point> DependencyList::leadingCells (const Range &range) const
605{
606 TQValueList<Point> cells;
607 Point cell1, cell2, cell;
608
609 cell1.setRow (range.startRow());
610 cell1.setColumn (range.startCol());
611 cell2.setRow (range.endRow());
612 cell2.setColumn (range.endCol());
613 cell1.setSheet(range.sheet());
614 cell2.setSheet(range.sheet());
615
616 cell1 = leadingCell (cell1);
617 cell2 = leadingCell (cell2);
618 for (int row = cell1.row(); row <= cell2.row(); row += CELLCHUNK_ROWS)
619 for (int col = cell1.column(); col <= cell2.column();
620 col += CELLCHUNK_COLS)
621 {
622 cell.setRow (row);
623 cell.setColumn (col);
624 cell.setSheet(range.sheet());
625 cells.push_back (cell);
626 }
627 return cells;
628}
629
630RangeList DependencyList::computeDependencies (const Point &cell) const
631{
632 Cell *c = cell.cell();
633
634 // Not a formula -> no dependencies
635 if (!c->isFormula())
636 return RangeList();
637
638 // Broken formula -> meaningless dependencies
639 // (tries to avoid c->formula() being null)
640 if (c->hasError())
641 return RangeList();
642
643 Formula* f = c->formula();
644 Q_ASSERT(f);
645 if (f==NULL)
646 {
647 kdDebug() << "Cell at row " << cell.row() << ", col " << cell.column() << " marked as formula, but formula is NULL" << endl;
648 return RangeList();
649 }
650
651 Tokens tokens = f->tokens();
652
653 //return empty list if the tokens aren't valid
654 if (!tokens.valid())
655 return RangeList();
656
657 RangeList rl;
658 for( unsigned i = 0; i < tokens.count(); i++ )
659 {
660 Token token = tokens[i];
661 Token::Type tokenType = token.type();
662
663 //parse each cell/range and put it to our RangeList
664 if (tokenType == Token::Cell)
665 {
666 TQString text = token.text();
667 Point cell (text, sheet->workbook(), sheet);
668 if (cell.isValid())
669 rl.cells.push_back (cell);
670 }
671 else if (tokenType == Token::Range)
672 {
673 TQString text = token.text();
674 Range range (text, sheet->workbook(), sheet);
675 if (range.isValid())
676 rl.ranges.push_back (range);
677 }
678 }
679
680 return rl;
681}
682
bool calc(bool delay=true)
Starts calculating.
int row() const
Returns the cell's row.
TQString fullName() const
Returns the full name of the cell, i.e.
const Value value() const
Returns the value that this cell holds.
Sheet * sheet() const
Returns the worksheet which owns this cell.
void setValue(const Value &value)
Sets the value for this cell.
int column() const
Returns the cell's column.
bool isFormula() const
Returns true if this cell holds a formula.
void setCalcDirtyFlag()
Set the calcDirtyFlag.
bool hasError() const
bool isDefault() const
Returns true if this is a default cell (with row and column equal to zero).
void cellChanged(const Point &cell)
handle the fact that cell's contents have changed
void areaModified(const TQString &name)
a named area was somehow modified
DependencyList * deps
local d-pointer
TQValueList< Point > getDependants(const Point &cell)
get cells depending on this cell, either through normal or range dependency
void rangeListChanged(const RangeList &rangeList)
handle the fact that a range list has been changed
RangeList getDependencies(const Point &cell)
get dependencies of a cell
void reset()
clear all data
void rangeChanged(const Range &range)
handle the fact that a range has been changed
DependencyManager(Sheet *s)
constructor
Tokens tokens() const
Returns list of tokens associated with this formula.
Definition formula.cpp:456
Represents the position of a single cell in a workbook.
void setSheet(Sheet *sheet)
Sets the sheet which this point lies on.
Represents a region within a workbook.
bool isValid() const
Returns true if this Range represents a valid region of a spreadsheet.
void setSheet(Sheet *sheet)
Sets the Sheet object associated with this range.
TQString namedArea() const
Returns the named area represented by this range or an empty string otherwise.
TQString text() const
Returns text associated with the token.
Definition formula.h:103
Type type() const
Returns type of the token.
Definition formula.h:94
Type
token types
Definition formula.h:44
@ Cell
$A$1, F4, Sheet2!B5, 'Sales Forecast'!Sum
Definition formula.h:51
@ Range
C1:C100.
Definition formula.h:52
void setError(const TQString &msg)
Sets this value to hold error message.
The KSpread namespace.
Definition commands.h:39
Range dependency - stores information about one dependency of one cell on one range of cells.
range-list and cell-list TODO: move to a separate file, improve structure, add iterators and all that...