1 /* This file is part of the KDE project
2 Copyright 2010 Marijn Kruisselbrink <mkruisselbrink@kde.org>
3 Copyright 2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
4 Copyright 1998,1999 Torben Weis <weis@kde.org>
5 Copyright 1999-2007 The KSpread Team <calligra-devel@kde.org>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21 */
22
23 // Local
24 #include "Sheet.h"
25
26 #include <QApplication>
27
28 #include <kcodecs.h>
29
30 #include <KoShape.h>
31 #include <KoDocumentResourceManager.h>
32 #include <KoUnit.h>
33 #include <KoCanvasResourceIdentities.h>
34
35 #include "CellStorage.h"
36 #include "Cluster.h"
37 #include "Damages.h"
38 #include "DependencyManager.h"
39 #include "DocBase.h"
40 #include "FormulaStorage.h"
41 #include "HeaderFooter.h"
42 #include "LoadingInfo.h"
43 #include "Map.h"
44 #include "NamedAreaManager.h"
45 #include "PrintSettings.h"
46 #include "RecalcManager.h"
47 #include "RowColumnFormat.h"
48 #include "RowFormatStorage.h"
49 #include "ShapeApplicationData.h"
50 #include "SheetPrint.h"
51 #include "SheetModel.h"
52 #include "StyleStorage.h"
53 #include "Validity.h"
54 #include "ValueConverter.h"
55 #include "ValueStorage.h"
56 #include "database/Filter.h"
57
58 namespace Calligra
59 {
60 namespace Sheets
61 {
62
createObjectName(const QString & sheetName)63 static QString createObjectName(const QString &sheetName)
64 {
65 QString objectName;
66 for (int i = 0; i < sheetName.count(); ++i) {
67 if (sheetName[i].isLetterOrNumber() || sheetName[i] == '_')
68 objectName.append(sheetName[i]);
69 else
70 objectName.append('_');
71 }
72 return objectName;
73 }
74
75
76 class Q_DECL_HIDDEN Sheet::Private
77 {
78 public:
Private(Sheet * sheet)79 Private(Sheet* sheet) : rows(sheet) {}
80
81 Map* workbook;
82 SheetModel *model;
83
84 QString name;
85
86 Qt::LayoutDirection layoutDirection;
87
88 // true if sheet is hidden
89 bool hide;
90
91 bool showGrid;
92 bool showFormula;
93 bool showFormulaIndicator;
94 bool showCommentIndicator;
95 bool autoCalc;
96 bool lcMode;
97 bool showColumnNumber;
98 bool hideZero;
99 bool firstLetterUpper;
100
101 // clusters to hold objects
102 CellStorage* cellStorage;
103 RowFormatStorage rows;
104 ColumnCluster columns;
105 QList<KoShape*> shapes;
106
107 // hold the print object
108 SheetPrint* print;
109
110 // Indicates whether the sheet should paint the page breaks.
111 // Doing so costs some time, so by default it should be turned off.
112 bool showPageOutline;
113
114 // Max range of canvas in x and y direction.
115 // Depends on KS_colMax/KS_rowMax and the width/height of all columns/rows
116 QSizeF documentSize;
117
118 QImage backgroundImage;
119 Sheet::BackgroundImageProperties backgroundProperties;
120 };
121
122
Sheet(Map * map,const QString & sheetName)123 Sheet::Sheet(Map* map, const QString &sheetName)
124 : KoShapeUserData(map)
125 , KoShapeBasedDocumentBase()
126 , d(new Private(this))
127 {
128 d->workbook = map;
129 if (map->doc()) {
130 resourceManager()->setUndoStack(map->doc()->undoStack());
131 QVariant variant;
132 variant.setValue<void*>(map->doc()->sheetAccessModel());
133 resourceManager()->setResource(::Sheets::CanvasResource::AccessModel, variant);
134 }
135 d->model = new SheetModel(this);
136
137 d->layoutDirection = QApplication::layoutDirection();
138
139 d->name = sheetName;
140
141 // Set a valid object name, so that we can offer scripting.
142 setObjectName(createObjectName(d->name));
143
144 d->cellStorage = new CellStorage(this);
145 d->columns.setAutoDelete(true);
146
147 d->documentSize = QSizeF(KS_colMax * d->workbook->defaultColumnFormat()->width(),
148 KS_rowMax * d->workbook->defaultRowFormat()->height());
149
150 d->hide = false;
151 d->showGrid = true;
152 d->showFormula = false;
153 d->showFormulaIndicator = false;
154 d->showCommentIndicator = true;
155 d->showPageOutline = false;
156
157 d->lcMode = false;
158 d->showColumnNumber = false;
159 d->hideZero = false;
160 d->firstLetterUpper = false;
161 d->autoCalc = true;
162 d->print = new SheetPrint(this);
163
164 // document size changes always trigger a visible size change
165 connect(this, SIGNAL(documentSizeChanged(QSizeF)), SIGNAL(visibleSizeChanged()));
166 // CellStorage connections
167 connect(d->cellStorage, SIGNAL(insertNamedArea(Region,QString)),
168 d->workbook->namedAreaManager(), SLOT(insert(Region,QString)));
169 connect(d->cellStorage, SIGNAL(namedAreaRemoved(QString)),
170 d->workbook->namedAreaManager(), SLOT(remove(QString)));
171 }
172
Sheet(const Sheet & other)173 Sheet::Sheet(const Sheet &other)
174 : KoShapeUserData(other.d->workbook)
175 , KoShapeBasedDocumentBase()
176 , ProtectableObject(other)
177 , d(new Private(this))
178 {
179 d->workbook = other.d->workbook;
180 d->model = new SheetModel(this);
181
182 // create a unique name
183 int i = 1;
184 do
185 d->name = other.d->name + QString("_%1").arg(i++);
186 while (d->workbook->findSheet(d->name));
187
188 // Set a valid object name, so that we can offer scripting.
189 setObjectName(createObjectName(d->name));
190
191 d->layoutDirection = other.d->layoutDirection;
192 d->hide = other.d->hide;
193 d->showGrid = other.d->showGrid;
194 d->showFormula = other.d->showFormula;
195 d->showFormulaIndicator = other.d->showFormulaIndicator;
196 d->showCommentIndicator = other.d->showCommentIndicator;
197 d->autoCalc = other.d->autoCalc;
198 d->lcMode = other.d->lcMode;
199 d->showColumnNumber = other.d->showColumnNumber;
200 d->hideZero = other.d->hideZero;
201 d->firstLetterUpper = other.d->firstLetterUpper;
202
203 d->cellStorage = new CellStorage(*other.d->cellStorage, this);
204 d->rows = other.d->rows;
205 d->columns = other.d->columns;
206
207 // flake
208 #if 0 // CALLIGRA_SHEETS_WIP_COPY_SHEET_(SHAPES)
209 //FIXME This does not work as copySettings does not work. Also createDefaultShapeAndInit without the correct settings can not work
210 //I think this should use ODF load/save for copying
211 KoShape* shape;
212 const QList<KoShape*> shapes = other.d->shapes;
213 for (int i = 0; i < shapes.count(); ++i) {
214 KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(shapes[i]->shapeId());
215 if (factory) {
216 shape = factory->createDefaultShapeAndInit(0);
217 shape->copySettings(shapes[i]);
218 addShape(shape);
219 }
220 }
221 #endif // CALLIGRA_SHEETS_WIP_COPY_SHEET_(SHAPES)
222
223 d->print = new SheetPrint(this); // FIXME = new SheetPrint(*other.d->print);
224
225 d->showPageOutline = other.d->showPageOutline;
226 d->documentSize = other.d->documentSize;
227 }
228
~Sheet()229 Sheet::~Sheet()
230 {
231 //Disable automatic recalculation of dependencies on this sheet to prevent crashes
232 //in certain situations:
233 //
234 //For example, suppose a cell in SheetB depends upon a cell in SheetA. If the cell in SheetB is emptied
235 //after SheetA has already been deleted, the program would try to remove dependencies from the cell in SheetA
236 //causing a crash.
237 setAutoCalculationEnabled(false);
238
239 delete d->print;
240 delete d->cellStorage;
241 qDeleteAll(d->shapes);
242 delete d;
243 }
244
model() const245 QAbstractItemModel* Sheet::model() const
246 {
247 return d->model;
248 }
249
sheetName() const250 QString Sheet::sheetName() const
251 {
252 return d->name;
253 }
254
map() const255 Map* Sheet::map() const
256 {
257 return d->workbook;
258 }
259
doc() const260 DocBase* Sheet::doc() const
261 {
262 return d->workbook->doc();
263 }
264
addShape(KoShape * shape)265 void Sheet::addShape(KoShape* shape)
266 {
267 if (!shape)
268 return;
269 d->shapes.append(shape);
270 shape->setApplicationData(new ShapeApplicationData());
271 emit shapeAdded(this, shape);
272 }
273
removeShape(KoShape * shape)274 void Sheet::removeShape(KoShape* shape)
275 {
276 if (!shape)
277 return;
278 d->shapes.removeAll(shape);
279 emit shapeRemoved(this, shape);
280 }
281
deleteShapes()282 void Sheet::deleteShapes()
283 {
284 qDeleteAll(d->shapes);
285 d->shapes.clear();
286 }
287
resourceManager() const288 KoDocumentResourceManager* Sheet::resourceManager() const
289 {
290 return map()->resourceManager();
291 }
292
shapes() const293 QList<KoShape*> Sheet::shapes() const
294 {
295 return d->shapes;
296 }
297
layoutDirection() const298 Qt::LayoutDirection Sheet::layoutDirection() const
299 {
300 return d->layoutDirection;
301 }
302
setLayoutDirection(Qt::LayoutDirection dir)303 void Sheet::setLayoutDirection(Qt::LayoutDirection dir)
304 {
305 d->layoutDirection = dir;
306 }
307
isHidden() const308 bool Sheet::isHidden() const
309 {
310 return d->hide;
311 }
312
setHidden(bool hidden)313 void Sheet::setHidden(bool hidden)
314 {
315 d->hide = hidden;
316 }
317
getShowGrid() const318 bool Sheet::getShowGrid() const
319 {
320 return d->showGrid;
321 }
322
setShowGrid(bool _showGrid)323 void Sheet::setShowGrid(bool _showGrid)
324 {
325 d->showGrid = _showGrid;
326 }
327
getShowFormula() const328 bool Sheet::getShowFormula() const
329 {
330 return d->showFormula;
331 }
332
setShowFormula(bool _showFormula)333 void Sheet::setShowFormula(bool _showFormula)
334 {
335 d->showFormula = _showFormula;
336 }
337
getShowFormulaIndicator() const338 bool Sheet::getShowFormulaIndicator() const
339 {
340 return d->showFormulaIndicator;
341 }
342
setShowFormulaIndicator(bool _showFormulaIndicator)343 void Sheet::setShowFormulaIndicator(bool _showFormulaIndicator)
344 {
345 d->showFormulaIndicator = _showFormulaIndicator;
346 }
347
getShowCommentIndicator() const348 bool Sheet::getShowCommentIndicator() const
349 {
350 return d->showCommentIndicator;
351 }
352
setShowCommentIndicator(bool _indic)353 void Sheet::setShowCommentIndicator(bool _indic)
354 {
355 d->showCommentIndicator = _indic;
356 }
357
getLcMode() const358 bool Sheet::getLcMode() const
359 {
360 return d->lcMode;
361 }
362
setLcMode(bool _lcMode)363 void Sheet::setLcMode(bool _lcMode)
364 {
365 d->lcMode = _lcMode;
366 }
367
isAutoCalculationEnabled() const368 bool Sheet::isAutoCalculationEnabled() const
369 {
370 return d->autoCalc;
371 }
372
setAutoCalculationEnabled(bool enable)373 void Sheet::setAutoCalculationEnabled(bool enable)
374 {
375 //Avoid possible recalculation of dependencies if the auto calc setting hasn't changed
376 if (d->autoCalc == enable)
377 return;
378
379 d->autoCalc = enable;
380 //If enabling automatic calculation, make sure that the dependencies are up-to-date
381 if (enable == true) {
382 map()->dependencyManager()->addSheet(this);
383 map()->recalcManager()->recalcSheet(this);
384 } else {
385 map()->dependencyManager()->removeSheet(this);
386 }
387 }
388
getShowColumnNumber() const389 bool Sheet::getShowColumnNumber() const
390 {
391 return d->showColumnNumber;
392 }
393
setShowColumnNumber(bool _showColumnNumber)394 void Sheet::setShowColumnNumber(bool _showColumnNumber)
395 {
396 d->showColumnNumber = _showColumnNumber;
397 }
398
getHideZero() const399 bool Sheet::getHideZero() const
400 {
401 return d->hideZero;
402 }
403
setHideZero(bool _hideZero)404 void Sheet::setHideZero(bool _hideZero)
405 {
406 d->hideZero = _hideZero;
407 }
408
getFirstLetterUpper() const409 bool Sheet::getFirstLetterUpper() const
410 {
411 return d->firstLetterUpper;
412 }
413
setFirstLetterUpper(bool _firstUpper)414 void Sheet::setFirstLetterUpper(bool _firstUpper)
415 {
416 d->firstLetterUpper = _firstUpper;
417 }
418
isShowPageOutline() const419 bool Sheet::isShowPageOutline() const
420 {
421 return d->showPageOutline;
422 }
423
columnFormat(int _column) const424 const ColumnFormat* Sheet::columnFormat(int _column) const
425 {
426 const ColumnFormat *p = d->columns.lookup(_column);
427 if (p != 0)
428 return p;
429
430 return map()->defaultColumnFormat();
431 }
432
433 // needed in loading code
nextColumn(int col) const434 ColumnFormat *Sheet::nextColumn(int col) const
435 {
436 return d->columns.next(col);
437 }
438
cellStorage() const439 CellStorage* Sheet::cellStorage() const
440 {
441 return d->cellStorage;
442 }
443
commentStorage() const444 const CommentStorage* Sheet::commentStorage() const
445 {
446 return d->cellStorage->commentStorage();
447 }
448
conditionsStorage() const449 const ConditionsStorage* Sheet::conditionsStorage() const
450 {
451 return d->cellStorage->conditionsStorage();
452 }
453
formulaStorage() const454 const FormulaStorage* Sheet::formulaStorage() const
455 {
456 return d->cellStorage->formulaStorage();
457 }
458
fusionStorage() const459 const FusionStorage* Sheet::fusionStorage() const
460 {
461 return d->cellStorage->fusionStorage();
462 }
463
linkStorage() const464 const LinkStorage* Sheet::linkStorage() const
465 {
466 return d->cellStorage->linkStorage();
467 }
468
styleStorage() const469 const StyleStorage* Sheet::styleStorage() const
470 {
471 return d->cellStorage->styleStorage();
472 }
473
validityStorage() const474 const ValidityStorage* Sheet::validityStorage() const
475 {
476 return d->cellStorage->validityStorage();
477 }
478
valueStorage() const479 const ValueStorage* Sheet::valueStorage() const
480 {
481 return d->cellStorage->valueStorage();
482 }
483
print() const484 SheetPrint* Sheet::print() const
485 {
486 return d->print;
487 }
488
printSettings() const489 PrintSettings* Sheet::printSettings() const
490 {
491 return d->print->settings();
492 }
493
setPrintSettings(const PrintSettings & settings)494 void Sheet::setPrintSettings(const PrintSettings &settings)
495 {
496 d->print->setSettings(settings);
497 // Repaint, if page borders are shown and this is the active sheet.
498 if (isShowPageOutline()) {
499 // Just repaint everything visible; no need to invalidate the visual cache.
500 map()->addDamage(new SheetDamage(this, SheetDamage::ContentChanged));
501 }
502 }
503
headerFooter() const504 HeaderFooter *Sheet::headerFooter() const
505 {
506 return d->print->headerFooter();
507 }
508
documentSize() const509 QSizeF Sheet::documentSize() const
510 {
511 return d->documentSize;
512 }
513
adjustDocumentWidth(double deltaWidth)514 void Sheet::adjustDocumentWidth(double deltaWidth)
515 {
516 d->documentSize.rwidth() += deltaWidth;
517 emit documentSizeChanged(d->documentSize);
518 }
519
adjustDocumentHeight(double deltaHeight)520 void Sheet::adjustDocumentHeight(double deltaHeight)
521 {
522 d->documentSize.rheight() += deltaHeight;
523 emit documentSizeChanged(d->documentSize);
524 }
525
adjustCellAnchoredShapesX(qreal minX,qreal maxX,qreal delta)526 void Sheet::adjustCellAnchoredShapesX(qreal minX, qreal maxX, qreal delta)
527 {
528 foreach (KoShape* s, d->shapes) {
529 if (dynamic_cast<ShapeApplicationData*>(s->applicationData())->isAnchoredToCell()) {
530 if (s->position().x() >= minX && s->position().x() < maxX) {
531 QPointF p = s->position();
532 p.setX(qMax(minX, p.x() + delta));
533 s->setPosition(p);
534 }
535 }
536 }
537 }
538
adjustCellAnchoredShapesX(qreal delta,int firstCol,int lastCol)539 void Sheet::adjustCellAnchoredShapesX(qreal delta, int firstCol, int lastCol)
540 {
541 adjustCellAnchoredShapesX(columnPosition(firstCol), columnPosition(lastCol+1), delta);
542 }
543
adjustCellAnchoredShapesY(qreal minY,qreal maxY,qreal delta)544 void Sheet::adjustCellAnchoredShapesY(qreal minY, qreal maxY, qreal delta)
545 {
546 foreach (KoShape* s, d->shapes) {
547 if (dynamic_cast<ShapeApplicationData*>(s->applicationData())->isAnchoredToCell()) {
548 if (s->position().y() >= minY && s->position().y() < maxY) {
549 QPointF p = s->position();
550 p.setY(qMax(minY, p.y() + delta));
551 s->setPosition(p);
552 }
553 }
554 }
555 }
556
adjustCellAnchoredShapesY(qreal delta,int firstRow,int lastRow)557 void Sheet::adjustCellAnchoredShapesY(qreal delta, int firstRow, int lastRow)
558 {
559 adjustCellAnchoredShapesY(rowPosition(firstRow), rowPosition(lastRow+1), delta);
560 }
561
leftColumn(qreal _xpos,qreal & _left) const562 int Sheet::leftColumn(qreal _xpos, qreal &_left) const
563 {
564 _left = 0.0;
565 int col = 1;
566 double x = columnFormat(col)->visibleWidth();
567 while (x < _xpos && col < KS_colMax) {
568 _left += columnFormat(col)->visibleWidth();
569 x += columnFormat(++col)->visibleWidth();
570 }
571 return col;
572 }
573
rightColumn(double _xpos) const574 int Sheet::rightColumn(double _xpos) const
575 {
576 int col = 1;
577 double x = columnFormat(col)->visibleWidth();
578 while (x <= _xpos && col < KS_colMax)
579 x += columnFormat(++col)->visibleWidth();
580 return col;
581 }
582
topRow(qreal _ypos,qreal & _top) const583 int Sheet::topRow(qreal _ypos, qreal & _top) const
584 {
585 qreal top;
586 int row = rowFormats()->rowForPosition(_ypos, &top);
587 _top = top;
588 return row;
589 }
590
bottomRow(double _ypos) const591 int Sheet::bottomRow(double _ypos) const
592 {
593 return rowFormats()->rowForPosition(_ypos+1e-9);
594 }
595
cellCoordinatesToDocument(const QRect & cellRange) const596 QRectF Sheet::cellCoordinatesToDocument(const QRect& cellRange) const
597 {
598 // TODO Stefan: Rewrite to save some iterations over the columns/rows.
599 QRectF rect;
600 rect.setLeft(columnPosition(cellRange.left()));
601 rect.setRight(columnPosition(cellRange.right()) + columnFormat(cellRange.right())->width());
602 rect.setTop(rowPosition(cellRange.top()));
603 rect.setBottom(rowPosition(cellRange.bottom()) + rowFormats()->rowHeight(cellRange.bottom()));
604 return rect;
605 }
606
documentToCellCoordinates(const QRectF & area) const607 QRect Sheet::documentToCellCoordinates(const QRectF &area) const
608 {
609 double width = 0.0;
610 int left = 0;
611 while (width <= area.left())
612 width += columnFormat(++left)->visibleWidth();
613 int right = left;
614 while (width < area.right())
615 width += columnFormat(++right)->visibleWidth();
616 int top = rowFormats()->rowForPosition(area.top());
617 int bottom = rowFormats()->rowForPosition(area.bottom());
618 return QRect(left, top, right - left + 1, bottom - top + 1);
619 }
620
columnPosition(int _col) const621 double Sheet::columnPosition(int _col) const
622 {
623 const int max = qMin(_col, KS_colMax);
624 double x = 0.0;
625 for (int col = 1; col < max; ++col)
626 x += columnFormat(col)->visibleWidth();
627 return x;
628 }
629
630
rowPosition(int _row) const631 double Sheet::rowPosition(int _row) const
632 {
633 const int max = qMin(_row, KS_rowMax+1);
634 return rowFormats()->totalVisibleRowHeight(1, max-1);
635 }
636
firstCol() const637 ColumnFormat* Sheet::firstCol() const
638 {
639 return d->columns.first();
640 }
641
nonDefaultColumnFormat(int _column,bool force_creation)642 ColumnFormat* Sheet::nonDefaultColumnFormat(int _column, bool force_creation)
643 {
644 Q_ASSERT(_column >= 1 && _column <= KS_colMax);
645 ColumnFormat *p = d->columns.lookup(_column);
646 if (p != 0 || !force_creation)
647 return p;
648
649 p = new ColumnFormat(*map()->defaultColumnFormat());
650 p->setSheet(this);
651 p->setColumn(_column);
652
653 d->columns.insertElement(p, _column);
654
655 return p;
656 }
657
changeCellTabName(QString const & old_name,QString const & new_name)658 void Sheet::changeCellTabName(QString const & old_name, QString const & new_name)
659 {
660 for (int c = 0; c < formulaStorage()->count(); ++c) {
661 if (formulaStorage()->data(c).expression().contains(old_name)) {
662 int nb = formulaStorage()->data(c).expression().count(old_name + '!');
663 QString tmp = old_name + '!';
664 int len = tmp.length();
665 tmp = formulaStorage()->data(c).expression();
666
667 for (int i = 0; i < nb; ++i) {
668 int pos = tmp.indexOf(old_name + '!');
669 tmp.replace(pos, len, new_name + '!');
670 }
671 Cell cell(this, formulaStorage()->col(c), formulaStorage()->row(c));
672 Formula formula(this, cell);
673 formula.setExpression(tmp);
674 cell.setFormula(formula);
675 cell.makeFormula();
676 }
677 }
678 }
679
insertShiftRight(const QRect & rect)680 void Sheet::insertShiftRight(const QRect& rect)
681 {
682 foreach(Sheet* sheet, map()->sheetList()) {
683 for (int i = rect.top(); i <= rect.bottom(); ++i) {
684 sheet->changeNameCellRef(QPoint(rect.left(), i), false,
685 Sheet::ColumnInsert, sheetName(),
686 rect.right() - rect.left() + 1);
687 }
688 }
689 }
690
insertShiftDown(const QRect & rect)691 void Sheet::insertShiftDown(const QRect& rect)
692 {
693 foreach(Sheet* sheet, map()->sheetList()) {
694 for (int i = rect.left(); i <= rect.right(); ++i) {
695 sheet->changeNameCellRef(QPoint(i, rect.top()), false,
696 Sheet::RowInsert, sheetName(),
697 rect.bottom() - rect.top() + 1);
698 }
699 }
700 }
701
removeShiftUp(const QRect & rect)702 void Sheet::removeShiftUp(const QRect& rect)
703 {
704 foreach(Sheet* sheet, map()->sheetList()) {
705 for (int i = rect.left(); i <= rect.right(); ++i) {
706 sheet->changeNameCellRef(QPoint(i, rect.top()), false,
707 Sheet::RowRemove, sheetName(),
708 rect.bottom() - rect.top() + 1);
709 }
710 }
711 }
712
removeShiftLeft(const QRect & rect)713 void Sheet::removeShiftLeft(const QRect& rect)
714 {
715 foreach(Sheet* sheet, map()->sheetList()) {
716 for (int i = rect.top(); i <= rect.bottom(); ++i) {
717 sheet->changeNameCellRef(QPoint(rect.left(), i), false,
718 Sheet::ColumnRemove, sheetName(),
719 rect.right() - rect.left() + 1);
720 }
721 }
722 }
723
insertColumns(int col,int number)724 void Sheet::insertColumns(int col, int number)
725 {
726 double deltaWidth = 0.0;
727 for (int i = 0; i < number; i++) {
728 deltaWidth -= columnFormat(KS_colMax)->width();
729 d->columns.insertColumn(col);
730 deltaWidth += columnFormat(col + i)->width();
731 }
732 // Adjust document width (plus widths of new columns; minus widths of removed columns).
733 adjustDocumentWidth(deltaWidth);
734
735 foreach(Sheet* sheet, map()->sheetList()) {
736 sheet->changeNameCellRef(QPoint(col, 1), true,
737 Sheet::ColumnInsert, sheetName(),
738 number);
739 }
740 //update print settings
741 d->print->insertColumn(col, number);
742 }
743
insertRows(int row,int number)744 void Sheet::insertRows(int row, int number)
745 {
746 d->rows.insertRows(row, number);
747
748 foreach(Sheet* sheet, map()->sheetList()) {
749 sheet->changeNameCellRef(QPoint(1, row), true,
750 Sheet::RowInsert, sheetName(),
751 number);
752 }
753 //update print settings
754 d->print->insertRow(row, number);
755 }
756
removeColumns(int col,int number)757 void Sheet::removeColumns(int col, int number)
758 {
759 double deltaWidth = 0.0;
760 for (int i = 0; i < number; ++i) {
761 deltaWidth -= columnFormat(col)->width();
762 d->columns.removeColumn(col);
763 deltaWidth += columnFormat(KS_colMax)->width();
764 }
765 // Adjust document width (plus widths of new columns; minus widths of removed columns).
766 adjustDocumentWidth(deltaWidth);
767
768 foreach(Sheet* sheet, map()->sheetList()) {
769 sheet->changeNameCellRef(QPoint(col, 1), true,
770 Sheet::ColumnRemove, sheetName(),
771 number);
772 }
773 //update print settings
774 d->print->removeColumn(col, number);
775 }
776
removeRows(int row,int number)777 void Sheet::removeRows(int row, int number)
778 {
779 d->rows.removeRows(row, number);
780
781 foreach(Sheet* sheet, map()->sheetList()) {
782 sheet->changeNameCellRef(QPoint(1, row), true,
783 Sheet::RowRemove, sheetName(),
784 number);
785 }
786
787 //update print settings
788 d->print->removeRow(row, number);
789 }
790
changeNameCellRefHelper(const QPoint & pos,bool fullRowOrColumn,ChangeRef ref,int nbCol,const QPoint & point,bool isColumnFixed,bool isRowFixed)791 QString Sheet::changeNameCellRefHelper(const QPoint& pos, bool fullRowOrColumn, ChangeRef ref,
792 int nbCol, const QPoint& point, bool isColumnFixed,
793 bool isRowFixed)
794 {
795 QString newPoint;
796 int col = point.x();
797 int row = point.y();
798 // update column
799 if (isColumnFixed)
800 newPoint.append('$');
801 if (ref == ColumnInsert &&
802 col + nbCol <= KS_colMax &&
803 col >= pos.x() && // Column after the new one : +1
804 (fullRowOrColumn || row == pos.y())) { // All rows or just one
805 newPoint += Cell::columnName(col + nbCol);
806 } else if (ref == ColumnRemove &&
807 col > pos.x() && // Column after the deleted one : -1
808 (fullRowOrColumn || row == pos.y())) { // All rows or just one
809 newPoint += Cell::columnName(col - nbCol);
810 } else
811 newPoint += Cell::columnName(col);
812
813 // Update row
814 if (isRowFixed)
815 newPoint.append('$');
816 if (ref == RowInsert &&
817 row + nbCol <= KS_rowMax &&
818 row >= pos.y() && // Row after the new one : +1
819 (fullRowOrColumn || col == pos.x())) { // All columns or just one
820 newPoint += QString::number(row + nbCol);
821 } else if (ref == RowRemove &&
822 row > pos.y() && // Row after the deleted one : -1
823 (fullRowOrColumn || col == pos.x())) { // All columns or just one
824 newPoint += QString::number(row - nbCol);
825 } else
826 newPoint += QString::number(row);
827
828 if (((ref == ColumnRemove
829 && (col >= pos.x() && col < pos.x() + nbCol) // Column is the deleted one : error
830 && (fullRowOrColumn || row == pos.y())) ||
831 (ref == RowRemove
832 && (row >= pos.y() && row < pos.y() + nbCol) // Row is the deleted one : error
833 && (fullRowOrColumn || col == pos.x())) ||
834 (ref == ColumnInsert
835 && col + nbCol > KS_colMax
836 && col >= pos.x() // Column after the new one : +1
837 && (fullRowOrColumn || row == pos.y())) ||
838 (ref == RowInsert
839 && row + nbCol > KS_rowMax
840 && row >= pos.y() // Row after the new one : +1
841 && (fullRowOrColumn || col == pos.x())))) {
842 newPoint = '#' + i18n("Dependency") + '!';
843 }
844 return newPoint;
845 }
846
changeNameCellRefHelper(const QPoint & pos,const QRect & rect,bool fullRowOrColumn,ChangeRef ref,int nbCol,const QPoint & point,bool isColumnFixed,bool isRowFixed)847 QString Sheet::changeNameCellRefHelper(const QPoint& pos, const QRect& rect, bool fullRowOrColumn, ChangeRef ref,
848 int nbCol, const QPoint& point, bool isColumnFixed,
849 bool isRowFixed)
850 {
851 const bool isFirstColumn = pos.x() == rect.left();
852 const bool isLastColumn = pos.x() == rect.right();
853 const bool isFirstRow = pos.y() == rect.top();
854 const bool isLastRow = pos.y() == rect.bottom();
855
856 QString newPoint;
857 int col = point.x();
858 int row = point.y();
859 // update column
860 if (isColumnFixed)
861 newPoint.append('$');
862 if (ref == ColumnInsert &&
863 col + nbCol <= KS_colMax &&
864 col >= pos.x() && // Column after the new one : +1
865 (fullRowOrColumn || row == pos.y())) { // All rows or just one
866 newPoint += Cell::columnName(col + nbCol);
867 } else if (ref == ColumnRemove &&
868 (col > pos.x() ||
869 (col == pos.x() && isLastColumn)) && // Column after the deleted one : -1
870 (fullRowOrColumn || row == pos.y())) { // All rows or just one
871 newPoint += Cell::columnName(col - nbCol);
872 } else
873 newPoint += Cell::columnName(col);
874
875 // Update row
876 if (isRowFixed)
877 newPoint.append('$');
878 if (ref == RowInsert &&
879 row + nbCol <= KS_rowMax &&
880 row >= pos.y() && // Row after the new one : +1
881 (fullRowOrColumn || col == pos.x())) { // All columns or just one
882 newPoint += QString::number(row + nbCol);
883 } else if (ref == RowRemove &&
884 (row > pos.y() ||
885 (row == pos.y() && isLastRow)) && // Row after the deleted one : -1
886 (fullRowOrColumn || col == pos.x())) { // All columns or just one
887 newPoint += QString::number(row - nbCol);
888 } else
889 newPoint += QString::number(row);
890
891 if (((ref == ColumnRemove
892 && col == pos.x() // Column is the deleted one : error
893 && (fullRowOrColumn || row == pos.y())
894 && (isFirstColumn && isLastColumn)) ||
895 (ref == RowRemove
896 && row == pos.y() // Row is the deleted one : error
897 && (fullRowOrColumn || col == pos.x())
898 && (isFirstRow && isLastRow)) ||
899 (ref == ColumnInsert
900 && col + nbCol > KS_colMax
901 && col >= pos.x() // Column after the new one : +1
902 && (fullRowOrColumn || row == pos.y())) ||
903 (ref == RowInsert
904 && row + nbCol > KS_rowMax
905 && row >= pos.y() // Row after the new one : +1
906 && (fullRowOrColumn || col == pos.x())))) {
907 newPoint = '#' + i18n("Dependency") + '!';
908 }
909 return newPoint;
910 }
911
changeNameCellRef(const QPoint & pos,bool fullRowOrColumn,ChangeRef ref,const QString & tabname,int nbCol)912 void Sheet::changeNameCellRef(const QPoint& pos, bool fullRowOrColumn, ChangeRef ref,
913 const QString& tabname, int nbCol)
914 {
915 for (int c = 0; c < formulaStorage()->count(); ++c) {
916 QString newText('=');
917 const Tokens tokens = formulaStorage()->data(c).tokens();
918 for (int t = 0; t < tokens.count(); ++t) {
919 const Token token = tokens[t];
920 switch (token.type()) {
921 case Token::Cell:
922 case Token::Range: {
923 if (map()->namedAreaManager()->contains(token.text())) {
924 newText.append(token.text()); // simply keep the area name
925 break;
926 }
927 const Region region(token.text(), map());
928 if (!region.isValid() || !region.isContiguous()) {
929 newText.append(token.text());
930 break;
931 }
932 if (!region.firstSheet() && tabname != sheetName()) {
933 // nothing to do here
934 newText.append(token.text());
935 break;
936 }
937 // actually only one element in here, but we need extended access to the element
938 Region::ConstIterator end(region.constEnd());
939 for (Region::ConstIterator it(region.constBegin()); it != end; ++it) {
940 Region::Element* element = (*it);
941 if (element->type() == Region::Element::Point) {
942 if (element->sheet())
943 newText.append(element->sheet()->sheetName() + '!');
944 QString newPoint = changeNameCellRefHelper(pos, fullRowOrColumn, ref,
945 nbCol,
946 element->rect().topLeft(),
947 element->isColumnFixed(),
948 element->isRowFixed());
949 newText.append(newPoint);
950 } else { // (element->type() == Region::Element::Range)
951 if (element->sheet())
952 newText.append(element->sheet()->sheetName() + '!');
953 QString newPoint;
954 newPoint = changeNameCellRefHelper(pos, element->rect(), fullRowOrColumn, ref,
955 nbCol, element->rect().topLeft(),
956 element->isColumnFixed(),
957 element->isRowFixed());
958 newText.append(newPoint + ':');
959 newPoint = changeNameCellRefHelper(pos, element->rect(), fullRowOrColumn, ref,
960 nbCol, element->rect().bottomRight(),
961 element->isColumnFixed(),
962 element->isRowFixed());
963 newText.append(newPoint);
964 }
965 }
966 break;
967 }
968 default: {
969 newText.append(token.text());
970 break;
971 }
972 }
973 }
974
975 Cell cell(this, formulaStorage()->col(c), formulaStorage()->row(c));
976 Formula formula(this, cell);
977 formula.setExpression(newText);
978 cell.setFormula(formula);
979 }
980 }
981
982 // helper function for Sheet::areaIsEmpty
cellIsEmpty(const Cell & cell,TestType _type)983 bool Sheet::cellIsEmpty(const Cell& cell, TestType _type)
984 {
985 if (!cell.isPartOfMerged()) {
986 switch (_type) {
987 case Text :
988 if (!cell.userInput().isEmpty())
989 return false;
990 break;
991 case Validity:
992 if (!cell.validity().isEmpty())
993 return false;
994 break;
995 case Comment:
996 if (!cell.comment().isEmpty())
997 return false;
998 break;
999 case ConditionalCellAttribute:
1000 if (cell.conditions().conditionList().count() > 0)
1001 return false;
1002 break;
1003 }
1004 }
1005 return true;
1006 }
1007
1008 // TODO: convert into a manipulator, similar to the Dilation one
areaIsEmpty(const Region & region,TestType _type)1009 bool Sheet::areaIsEmpty(const Region& region, TestType _type)
1010 {
1011 Region::ConstIterator endOfList = region.constEnd();
1012 for (Region::ConstIterator it = region.constBegin(); it != endOfList; ++it) {
1013 QRect range = (*it)->rect();
1014 // Complete rows selected ?
1015 if ((*it)->isRow()) {
1016 for (int row = range.top(); row <= range.bottom(); ++row) {
1017 Cell cell = d->cellStorage->firstInRow(row);
1018 while (!cell.isNull()) {
1019 if (!cellIsEmpty(cell, _type))
1020 return false;
1021 cell = d->cellStorage->nextInRow(cell.column(), row);
1022 }
1023 }
1024 }
1025 // Complete columns selected ?
1026 else if ((*it)->isColumn()) {
1027 for (int col = range.left(); col <= range.right(); ++col) {
1028 Cell cell = d->cellStorage->firstInColumn(col);
1029 while (!cell.isNull()) {
1030 if (!cellIsEmpty(cell, _type))
1031 return false;
1032 cell = d->cellStorage->nextInColumn(col, cell.row());
1033 }
1034 }
1035 } else {
1036 Cell cell;
1037 int right = range.right();
1038 int bottom = range.bottom();
1039 for (int x = range.left(); x <= right; ++x)
1040 for (int y = range.top(); y <= bottom; ++y) {
1041 cell = Cell(this, x, y);
1042 if (!cellIsEmpty(cell, _type))
1043 return false;
1044 }
1045 }
1046 }
1047 return true;
1048 }
1049
saveXML(QDomDocument & dd)1050 QDomElement Sheet::saveXML(QDomDocument& dd)
1051 {
1052 QDomElement sheet = dd.createElement("table");
1053
1054 // backward compatibility
1055 QString sheetName;
1056 for (int i = 0; i < d->name.count(); ++i) {
1057 if (d->name[i].isLetterOrNumber() || d->name[i] == ' ' || d->name[i] == '.')
1058 sheetName.append(d->name[i]);
1059 else
1060 sheetName.append('_');
1061 }
1062 sheet.setAttribute("name", sheetName);
1063
1064 //Laurent: for oasis format I think that we must use style:direction...
1065 sheet.setAttribute("layoutDirection", (layoutDirection() == Qt::RightToLeft) ? "rtl" : "ltr");
1066 sheet.setAttribute("columnnumber", QString::number((int)getShowColumnNumber()));
1067 sheet.setAttribute("borders", QString::number((int)isShowPageOutline()));
1068 sheet.setAttribute("hide", QString::number((int)isHidden()));
1069 sheet.setAttribute("hidezero", QString::number((int)getHideZero()));
1070 sheet.setAttribute("firstletterupper", QString::number((int)getFirstLetterUpper()));
1071 sheet.setAttribute("grid", QString::number((int)getShowGrid()));
1072 sheet.setAttribute("printGrid", QString::number((int)print()->settings()->printGrid()));
1073 sheet.setAttribute("printCommentIndicator", QString::number((int)print()->settings()->printCommentIndicator()));
1074 sheet.setAttribute("printFormulaIndicator", QString::number((int)print()->settings()->printFormulaIndicator()));
1075 sheet.setAttribute("showFormula", QString::number((int)getShowFormula()));
1076 sheet.setAttribute("showFormulaIndicator", QString::number((int)getShowFormulaIndicator()));
1077 sheet.setAttribute("showCommentIndicator", QString::number((int)getShowCommentIndicator()));
1078 sheet.setAttribute("lcmode", QString::number((int)getLcMode()));
1079 sheet.setAttribute("autoCalc", QString::number((int)isAutoCalculationEnabled()));
1080 sheet.setAttribute("borders1.2", "1");
1081 QByteArray pwd;
1082 password(pwd);
1083 if (!pwd.isNull()) {
1084 if (pwd.size() > 0) {
1085 QByteArray str = KCodecs::base64Encode(pwd);
1086 sheet.setAttribute("protected", QString(str.data()));
1087 } else
1088 sheet.setAttribute("protected", "");
1089 }
1090
1091 // paper parameters
1092 QDomElement paper = dd.createElement("paper");
1093 paper.setAttribute("format", printSettings()->paperFormatString());
1094 paper.setAttribute("orientation", printSettings()->orientationString());
1095 sheet.appendChild(paper);
1096
1097 QDomElement borders = dd.createElement("borders");
1098 KoPageLayout pageLayout = print()->settings()->pageLayout();
1099 borders.setAttribute("left", QString::number(pageLayout.leftMargin));
1100 borders.setAttribute("top", QString::number(pageLayout.topMargin));
1101 borders.setAttribute("right", QString::number(pageLayout.rightMargin));
1102 borders.setAttribute("bottom", QString::number(pageLayout.bottomMargin));
1103 paper.appendChild(borders);
1104
1105 QDomElement head = dd.createElement("head");
1106 paper.appendChild(head);
1107 if (!print()->headerFooter()->headLeft().isEmpty()) {
1108 QDomElement left = dd.createElement("left");
1109 head.appendChild(left);
1110 left.appendChild(dd.createTextNode(print()->headerFooter()->headLeft()));
1111 }
1112 if (!print()->headerFooter()->headMid().isEmpty()) {
1113 QDomElement center = dd.createElement("center");
1114 head.appendChild(center);
1115 center.appendChild(dd.createTextNode(print()->headerFooter()->headMid()));
1116 }
1117 if (!print()->headerFooter()->headRight().isEmpty()) {
1118 QDomElement right = dd.createElement("right");
1119 head.appendChild(right);
1120 right.appendChild(dd.createTextNode(print()->headerFooter()->headRight()));
1121 }
1122 QDomElement foot = dd.createElement("foot");
1123 paper.appendChild(foot);
1124 if (!print()->headerFooter()->footLeft().isEmpty()) {
1125 QDomElement left = dd.createElement("left");
1126 foot.appendChild(left);
1127 left.appendChild(dd.createTextNode(print()->headerFooter()->footLeft()));
1128 }
1129 if (!print()->headerFooter()->footMid().isEmpty()) {
1130 QDomElement center = dd.createElement("center");
1131 foot.appendChild(center);
1132 center.appendChild(dd.createTextNode(print()->headerFooter()->footMid()));
1133 }
1134 if (!print()->headerFooter()->footRight().isEmpty()) {
1135 QDomElement right = dd.createElement("right");
1136 foot.appendChild(right);
1137 right.appendChild(dd.createTextNode(print()->headerFooter()->footRight()));
1138 }
1139
1140 // print range
1141 QDomElement printrange = dd.createElement("printrange-rect");
1142 QRect _printRange = printSettings()->printRegion().lastRange();
1143 int left = _printRange.left();
1144 int right = _printRange.right();
1145 int top = _printRange.top();
1146 int bottom = _printRange.bottom();
1147 //If whole rows are selected, then we store zeros, as KS_colMax may change in future
1148 if (left == 1 && right == KS_colMax) {
1149 left = 0;
1150 right = 0;
1151 }
1152 //If whole columns are selected, then we store zeros, as KS_rowMax may change in future
1153 if (top == 1 && bottom == KS_rowMax) {
1154 top = 0;
1155 bottom = 0;
1156 }
1157 printrange.setAttribute("left-rect", QString::number(left));
1158 printrange.setAttribute("right-rect", QString::number(right));
1159 printrange.setAttribute("bottom-rect", QString::number(bottom));
1160 printrange.setAttribute("top-rect", QString::number(top));
1161 sheet.appendChild(printrange);
1162
1163 // Print repeat columns
1164 QDomElement printRepeatColumns = dd.createElement("printrepeatcolumns");
1165 printRepeatColumns.setAttribute("left", QString::number(printSettings()->repeatedColumns().first));
1166 printRepeatColumns.setAttribute("right", QString::number(printSettings()->repeatedColumns().second));
1167 sheet.appendChild(printRepeatColumns);
1168
1169 // Print repeat rows
1170 QDomElement printRepeatRows = dd.createElement("printrepeatrows");
1171 printRepeatRows.setAttribute("top", QString::number(printSettings()->repeatedRows().first));
1172 printRepeatRows.setAttribute("bottom", QString::number(printSettings()->repeatedRows().second));
1173 sheet.appendChild(printRepeatRows);
1174
1175 //Save print zoom
1176 sheet.setAttribute("printZoom", QString::number(printSettings()->zoom()));
1177
1178 //Save page limits
1179 const QSize pageLimits = printSettings()->pageLimits();
1180 sheet.setAttribute("printPageLimitX", QString::number(pageLimits.width()));
1181 sheet.setAttribute("printPageLimitY", QString::number(pageLimits.height()));
1182
1183 // Save all cells.
1184 const QRect usedArea = this->usedArea();
1185 for (int row = 1; row <= usedArea.height(); ++row) {
1186 Cell cell = d->cellStorage->firstInRow(row);
1187 while (!cell.isNull()) {
1188 QDomElement e = cell.save(dd);
1189 if (!e.isNull())
1190 sheet.appendChild(e);
1191 cell = d->cellStorage->nextInRow(cell.column(), row);
1192 }
1193 }
1194
1195 // Save all RowFormat objects.
1196 int styleIndex = styleStorage()->nextRowStyleIndex(0);
1197 int rowFormatRow = 0, lastRowFormatRow = rowFormats()->lastNonDefaultRow();
1198 while (styleIndex || rowFormatRow <= lastRowFormatRow) {
1199 int lastRow;
1200 bool isDefault = rowFormats()->isDefaultRow(rowFormatRow, &lastRow);
1201 if (isDefault && styleIndex <= lastRow) {
1202 RowFormat rowFormat(*map()->defaultRowFormat());
1203 rowFormat.setSheet(this);
1204 rowFormat.setRow(styleIndex);
1205 QDomElement e = rowFormat.save(dd);
1206 if (e.isNull())
1207 return QDomElement();
1208 sheet.appendChild(e);
1209 styleIndex = styleStorage()->nextRowStyleIndex(styleIndex);
1210 } else if (!isDefault) {
1211 RowFormat rowFormat(rowFormats(), rowFormatRow);
1212 QDomElement e = rowFormat.save(dd);
1213 if (e.isNull())
1214 return QDomElement();
1215 sheet.appendChild(e);
1216 if (styleIndex == rowFormatRow)
1217 styleIndex = styleStorage()->nextRowStyleIndex(styleIndex);
1218 }
1219 if (isDefault) rowFormatRow = qMin(lastRow+1, styleIndex == 0 ? KS_rowMax : styleIndex);
1220 else rowFormatRow++;
1221 }
1222
1223 // Save all ColumnFormat objects.
1224 ColumnFormat* columnFormat = firstCol();
1225 styleIndex = styleStorage()->nextColumnStyleIndex(0);
1226 while (columnFormat || styleIndex) {
1227 if (columnFormat && (!styleIndex || columnFormat->column() <= styleIndex)) {
1228 QDomElement e = columnFormat->save(dd);
1229 if (e.isNull())
1230 return QDomElement();
1231 sheet.appendChild(e);
1232 if (columnFormat->column() == styleIndex)
1233 styleIndex = styleStorage()->nextColumnStyleIndex(styleIndex);
1234 columnFormat = columnFormat->next();
1235 } else if (styleIndex) {
1236 ColumnFormat columnFormat(*map()->defaultColumnFormat());
1237 columnFormat.setSheet(this);
1238 columnFormat.setColumn(styleIndex);
1239 QDomElement e = columnFormat.save(dd);
1240 if (e.isNull())
1241 return QDomElement();
1242 sheet.appendChild(e);
1243 styleIndex = styleStorage()->nextColumnStyleIndex(styleIndex);
1244 }
1245 }
1246 #if 0 // CALLIGRA_SHEETS_KOPART_EMBEDDING
1247 foreach(EmbeddedObject* object, doc()->embeddedObjects()) {
1248 if (object->sheet() == this) {
1249 QDomElement e = object->save(dd);
1250
1251 if (e.isNull())
1252 return QDomElement();
1253 sheet.appendChild(e);
1254 }
1255 }
1256 #endif // CALLIGRA_SHEETS_KOPART_EMBEDDING
1257 return sheet;
1258 }
1259
isLoading()1260 bool Sheet::isLoading()
1261 {
1262 return map()->isLoading();
1263 }
1264
checkContentDirection(QString const & name)1265 void Sheet::checkContentDirection(QString const & name)
1266 {
1267 /* set sheet's direction to RTL if sheet name is an RTL string */
1268 if ((name.isRightToLeft()))
1269 setLayoutDirection(Qt::RightToLeft);
1270 else
1271 setLayoutDirection(Qt::LeftToRight);
1272 }
1273
usedArea(bool onlyContent) const1274 QRect Sheet::usedArea(bool onlyContent) const
1275 {
1276 int maxCols = d->cellStorage->columns(!onlyContent);
1277 int maxRows = d->cellStorage->rows(!onlyContent);
1278
1279 if (!onlyContent) {
1280 maxRows = qMax(maxRows, d->rows.lastNonDefaultRow());
1281
1282 const ColumnFormat* col = firstCol();
1283 while (col) {
1284 if (col->column() > maxCols)
1285 maxCols = col->column();
1286
1287 col = col->next();
1288 }
1289 }
1290
1291 // flake
1292 QRectF shapesBoundingRect;
1293 for (int i = 0; i < d->shapes.count(); ++i)
1294 shapesBoundingRect |= d->shapes[i]->boundingRect();
1295 const QRect shapesCellRange = documentToCellCoordinates(shapesBoundingRect);
1296 maxCols = qMax(maxCols, shapesCellRange.right());
1297 maxRows = qMax(maxRows, shapesCellRange.bottom());
1298
1299 return QRect(1, 1, maxCols, maxRows);
1300 }
1301
loadXML(const KoXmlElement & sheet)1302 bool Sheet::loadXML(const KoXmlElement& sheet)
1303 {
1304 bool ok = false;
1305 QString sname = sheetName();
1306 if (!map()->loadingInfo()->loadTemplate()) {
1307 sname = sheet.attribute("name");
1308 if (sname.isEmpty()) {
1309 doc()->setErrorMessage(i18n("Invalid document. Sheet name is empty."));
1310 return false;
1311 }
1312 }
1313
1314 bool detectDirection = true;
1315 QString layoutDir = sheet.attribute("layoutDirection");
1316 if (!layoutDir.isEmpty()) {
1317 if (layoutDir == "rtl") {
1318 detectDirection = false;
1319 setLayoutDirection(Qt::RightToLeft);
1320 } else if (layoutDir == "ltr") {
1321 detectDirection = false;
1322 setLayoutDirection(Qt::LeftToRight);
1323 } else
1324 debugSheets << " Direction not implemented :" << layoutDir;
1325 }
1326 if (detectDirection)
1327 checkContentDirection(sname);
1328
1329 /* older versions of KSpread allowed all sorts of characters that
1330 the parser won't actually understand. Replace these with '_'
1331 Also, the initial character cannot be a space.
1332 */
1333 while (sname[0] == ' ') {
1334 sname.remove(0, 1);
1335 }
1336 for (int i = 0; i < sname.length(); i++) {
1337 if (!(sname[i].isLetterOrNumber() ||
1338 sname[i] == ' ' || sname[i] == '.' || sname[i] == '_')) {
1339 sname[i] = '_';
1340 }
1341 }
1342
1343 // validate sheet name, if it differs from the current one
1344 if (sname != sheetName()) {
1345 /* make sure there are no name collisions with the altered name */
1346 QString testName = sname;
1347 QString baseName = sname;
1348 int nameSuffix = 0;
1349
1350 /* so we don't panic over finding ourself in the following test*/
1351 sname.clear();
1352 while (map()->findSheet(testName) != 0) {
1353 nameSuffix++;
1354 testName = baseName + '_' + QString::number(nameSuffix);
1355 }
1356 sname = testName;
1357
1358 debugSheets << "Sheet::loadXML: table name =" << sname;
1359 setObjectName(sname);
1360 setSheetName(sname, true);
1361 }
1362
1363 // (dynamic_cast<SheetIface*>(dcopObject()))->sheetNameHasChanged();
1364
1365 if (sheet.hasAttribute("grid")) {
1366 setShowGrid((int)sheet.attribute("grid").toInt(&ok));
1367 // we just ignore 'ok' - if it didn't work, go on
1368 }
1369 if (sheet.hasAttribute("printGrid")) {
1370 print()->settings()->setPrintGrid((bool)sheet.attribute("printGrid").toInt(&ok));
1371 // we just ignore 'ok' - if it didn't work, go on
1372 }
1373 if (sheet.hasAttribute("printCommentIndicator")) {
1374 print()->settings()->setPrintCommentIndicator((bool)sheet.attribute("printCommentIndicator").toInt(&ok));
1375 // we just ignore 'ok' - if it didn't work, go on
1376 }
1377 if (sheet.hasAttribute("printFormulaIndicator")) {
1378 print()->settings()->setPrintFormulaIndicator((bool)sheet.attribute("printFormulaIndicator").toInt(&ok));
1379 // we just ignore 'ok' - if it didn't work, go on
1380 }
1381 if (sheet.hasAttribute("hide")) {
1382 setHidden((bool)sheet.attribute("hide").toInt(&ok));
1383 // we just ignore 'ok' - if it didn't work, go on
1384 }
1385 if (sheet.hasAttribute("showFormula")) {
1386 setShowFormula((bool)sheet.attribute("showFormula").toInt(&ok));
1387 // we just ignore 'ok' - if it didn't work, go on
1388 }
1389 //Compatibility with KSpread 1.1.x
1390 if (sheet.hasAttribute("formular")) {
1391 setShowFormula((bool)sheet.attribute("formular").toInt(&ok));
1392 // we just ignore 'ok' - if it didn't work, go on
1393 }
1394 if (sheet.hasAttribute("showFormulaIndicator")) {
1395 setShowFormulaIndicator((bool)sheet.attribute("showFormulaIndicator").toInt(&ok));
1396 // we just ignore 'ok' - if it didn't work, go on
1397 }
1398 if (sheet.hasAttribute("showCommentIndicator")) {
1399 setShowCommentIndicator((bool)sheet.attribute("showCommentIndicator").toInt(&ok));
1400 // we just ignore 'ok' - if it didn't work, go on
1401 }
1402 if (sheet.hasAttribute("borders")) {
1403 setShowPageOutline((bool)sheet.attribute("borders").toInt(&ok));
1404 // we just ignore 'ok' - if it didn't work, go on
1405 }
1406 if (sheet.hasAttribute("lcmode")) {
1407 setLcMode((bool)sheet.attribute("lcmode").toInt(&ok));
1408 // we just ignore 'ok' - if it didn't work, go on
1409 }
1410 if (sheet.hasAttribute("autoCalc")) {
1411 setAutoCalculationEnabled((bool)sheet.attribute("autoCalc").toInt(&ok));
1412 // we just ignore 'ok' - if it didn't work, go on
1413 }
1414 if (sheet.hasAttribute("columnnumber")) {
1415 setShowColumnNumber((bool)sheet.attribute("columnnumber").toInt(&ok));
1416 // we just ignore 'ok' - if it didn't work, go on
1417 }
1418 if (sheet.hasAttribute("hidezero")) {
1419 setHideZero((bool)sheet.attribute("hidezero").toInt(&ok));
1420 // we just ignore 'ok' - if it didn't work, go on
1421 }
1422 if (sheet.hasAttribute("firstletterupper")) {
1423 setFirstLetterUpper((bool)sheet.attribute("firstletterupper").toInt(&ok));
1424 // we just ignore 'ok' - if it didn't work, go on
1425 }
1426
1427 // Load the paper layout
1428 KoXmlElement paper = sheet.namedItem("paper").toElement();
1429 if (!paper.isNull()) {
1430 KoPageLayout pageLayout;
1431 pageLayout.format = KoPageFormat::formatFromString(paper.attribute("format"));
1432 pageLayout.orientation = (paper.attribute("orientation") == "Portrait")
1433 ? KoPageFormat::Portrait : KoPageFormat::Landscape;
1434
1435 // <borders>
1436 KoXmlElement borders = paper.namedItem("borders").toElement();
1437 if (!borders.isNull()) {
1438 pageLayout.leftMargin = MM_TO_POINT(borders.attribute("left").toFloat());
1439 pageLayout.rightMargin = MM_TO_POINT(borders.attribute("right").toFloat());
1440 pageLayout.topMargin = MM_TO_POINT(borders.attribute("top").toFloat());
1441 pageLayout.bottomMargin = MM_TO_POINT(borders.attribute("bottom").toFloat());
1442 }
1443 print()->settings()->setPageLayout(pageLayout);
1444
1445 QString hleft, hright, hcenter;
1446 QString fleft, fright, fcenter;
1447 // <head>
1448 KoXmlElement head = paper.namedItem("head").toElement();
1449 if (!head.isNull()) {
1450 KoXmlElement left = head.namedItem("left").toElement();
1451 if (!left.isNull())
1452 hleft = left.text();
1453 KoXmlElement center = head.namedItem("center").toElement();
1454 if (!center.isNull())
1455 hcenter = center.text();
1456 KoXmlElement right = head.namedItem("right").toElement();
1457 if (!right.isNull())
1458 hright = right.text();
1459 }
1460 // <foot>
1461 KoXmlElement foot = paper.namedItem("foot").toElement();
1462 if (!foot.isNull()) {
1463 KoXmlElement left = foot.namedItem("left").toElement();
1464 if (!left.isNull())
1465 fleft = left.text();
1466 KoXmlElement center = foot.namedItem("center").toElement();
1467 if (!center.isNull())
1468 fcenter = center.text();
1469 KoXmlElement right = foot.namedItem("right").toElement();
1470 if (!right.isNull())
1471 fright = right.text();
1472 }
1473 print()->headerFooter()->setHeadFootLine(hleft, hcenter, hright, fleft, fcenter, fright);
1474 }
1475
1476 // load print range
1477 KoXmlElement printrange = sheet.namedItem("printrange-rect").toElement();
1478 if (!printrange.isNull()) {
1479 int left = printrange.attribute("left-rect").toInt();
1480 int right = printrange.attribute("right-rect").toInt();
1481 int bottom = printrange.attribute("bottom-rect").toInt();
1482 int top = printrange.attribute("top-rect").toInt();
1483 if (left == 0) { //whole row(s) selected
1484 left = 1;
1485 right = KS_colMax;
1486 }
1487 if (top == 0) { //whole column(s) selected
1488 top = 1;
1489 bottom = KS_rowMax;
1490 }
1491 const Region region(QRect(QPoint(left, top), QPoint(right, bottom)), this);
1492 printSettings()->setPrintRegion(region);
1493 }
1494
1495 // load print zoom
1496 if (sheet.hasAttribute("printZoom")) {
1497 double zoom = sheet.attribute("printZoom").toDouble(&ok);
1498 if (ok) {
1499 printSettings()->setZoom(zoom);
1500 }
1501 }
1502
1503 // load page limits
1504 if (sheet.hasAttribute("printPageLimitX")) {
1505 int pageLimit = sheet.attribute("printPageLimitX").toInt(&ok);
1506 if (ok) {
1507 printSettings()->setPageLimits(QSize(pageLimit, 0));
1508 }
1509 }
1510
1511 // load page limits
1512 if (sheet.hasAttribute("printPageLimitY")) {
1513 int pageLimit = sheet.attribute("printPageLimitY").toInt(&ok);
1514 if (ok) {
1515 const int horizontalLimit = printSettings()->pageLimits().width();
1516 printSettings()->setPageLimits(QSize(horizontalLimit, pageLimit));
1517 }
1518 }
1519
1520 // Load the cells
1521 KoXmlNode n = sheet.firstChild();
1522 while (!n.isNull()) {
1523 KoXmlElement e = n.toElement();
1524 if (!e.isNull()) {
1525 QString tagName = e.tagName();
1526 if (tagName == "cell")
1527 Cell(this, 1, 1).load(e, 0, 0); // col, row will get overridden in all cases
1528 else if (tagName == "row") {
1529 RowFormat *rl = new RowFormat();
1530 rl->setSheet(this);
1531 if (rl->load(e))
1532 insertRowFormat(rl);
1533 delete rl;
1534 } else if (tagName == "column") {
1535 ColumnFormat *cl = new ColumnFormat();
1536 cl->setSheet(this);
1537 if (cl->load(e))
1538 insertColumnFormat(cl);
1539 else
1540 delete cl;
1541 }
1542 #if 0 // CALLIGRA_SHEETS_KOPART_EMBEDDING
1543 else if (tagName == "object") {
1544 EmbeddedCalligraObject *ch = new EmbeddedCalligraObject(doc(), this);
1545 if (ch->load(e))
1546 insertObject(ch);
1547 else {
1548 ch->embeddedObject()->setDeleted(true);
1549 delete ch;
1550 }
1551 } else if (tagName == "chart") {
1552 EmbeddedChart *ch = new EmbeddedChart(doc(), this);
1553 if (ch->load(e))
1554 insertObject(ch);
1555 else {
1556 ch->embeddedObject()->setDeleted(true);
1557 delete ch;
1558 }
1559 }
1560 #endif // CALLIGRA_SHEETS_KOPART_EMBEDDING
1561 }
1562 n = n.nextSibling();
1563 }
1564
1565 // load print repeat columns
1566 KoXmlElement printrepeatcolumns = sheet.namedItem("printrepeatcolumns").toElement();
1567 if (!printrepeatcolumns.isNull()) {
1568 int left = printrepeatcolumns.attribute("left").toInt();
1569 int right = printrepeatcolumns.attribute("right").toInt();
1570 printSettings()->setRepeatedColumns(qMakePair(left, right));
1571 }
1572
1573 // load print repeat rows
1574 KoXmlElement printrepeatrows = sheet.namedItem("printrepeatrows").toElement();
1575 if (!printrepeatrows.isNull()) {
1576 int top = printrepeatrows.attribute("top").toInt();
1577 int bottom = printrepeatrows.attribute("bottom").toInt();
1578 printSettings()->setRepeatedRows(qMakePair(top, bottom));
1579 }
1580
1581 if (!sheet.hasAttribute("borders1.2")) {
1582 convertObscuringBorders();
1583 }
1584
1585 loadXmlProtection(sheet);
1586
1587 return true;
1588 }
1589
1590
loadChildren(KoStore * _store)1591 bool Sheet::loadChildren(KoStore* _store)
1592 {
1593 Q_UNUSED(_store);
1594 #if 0 // CALLIGRA_SHEETS_KOPART_EMBEDDING
1595 foreach(EmbeddedObject* object, doc()->embeddedObjects()) {
1596 if (object->sheet() == this && (object->getType() == OBJECT_CALLIGRA_PART || object->getType() == OBJECT_CHART)) {
1597 debugSheets << "Calligra::Sheets::Sheet::loadChildren";
1598 if (!dynamic_cast<EmbeddedCalligraObject*>(object)->embeddedObject()->loadDocument(_store))
1599 return false;
1600 }
1601 }
1602 #endif // CALLIGRA_SHEETS_KOPART_EMBEDDING
1603 return true;
1604 }
1605
1606
setShowPageOutline(bool b)1607 void Sheet::setShowPageOutline(bool b)
1608 {
1609 if (b == d->showPageOutline)
1610 return;
1611
1612 d->showPageOutline = b;
1613 // Just repaint everything visible; no need to invalidate the visual cache.
1614 if (!map()->isLoading()) {
1615 map()->addDamage(new SheetDamage(this, SheetDamage::ContentChanged));
1616 }
1617 }
1618
backgroundImage() const1619 QImage Sheet::backgroundImage() const
1620 {
1621 return d->backgroundImage;
1622 }
1623
setBackgroundImage(const QImage & image)1624 void Sheet::setBackgroundImage(const QImage& image)
1625 {
1626 d->backgroundImage = image;
1627 }
1628
backgroundImageProperties() const1629 Sheet::BackgroundImageProperties Sheet::backgroundImageProperties() const
1630 {
1631 return d->backgroundProperties;
1632 }
1633
setBackgroundImageProperties(const Sheet::BackgroundImageProperties & properties)1634 void Sheet::setBackgroundImageProperties(const Sheet::BackgroundImageProperties& properties)
1635 {
1636 d->backgroundProperties = properties;
1637 }
1638
insertColumnFormat(ColumnFormat * l)1639 void Sheet::insertColumnFormat(ColumnFormat *l)
1640 {
1641 d->columns.insertElement(l, l->column());
1642 if (!map()->isLoading()) {
1643 map()->addDamage(new SheetDamage(this, SheetDamage::ColumnsChanged));
1644 }
1645 }
1646
insertRowFormat(RowFormat * l)1647 void Sheet::insertRowFormat(RowFormat *l)
1648 {
1649 const int row = l->row();
1650 d->rows.setRowHeight(row, row, l->height());
1651 d->rows.setHidden(row, row, l->isHidden());
1652 d->rows.setFiltered(row, row, l->isFiltered());
1653 d->rows.setPageBreak(row, row, l->hasPageBreak());
1654 if (!map()->isLoading()) {
1655 map()->addDamage(new SheetDamage(this, SheetDamage::RowsChanged));
1656 }
1657 }
1658
deleteColumnFormat(int column)1659 void Sheet::deleteColumnFormat(int column)
1660 {
1661 d->columns.removeElement(column);
1662 if (!map()->isLoading()) {
1663 map()->addDamage(new SheetDamage(this, SheetDamage::ColumnsChanged));
1664 }
1665 }
1666
deleteRowFormat(int row)1667 void Sheet::deleteRowFormat(int row)
1668 {
1669 d->rows.setDefault(row, row);
1670 if (!map()->isLoading()) {
1671 map()->addDamage(new SheetDamage(this, SheetDamage::RowsChanged));
1672 }
1673 }
1674
1675
rowFormats()1676 RowFormatStorage* Sheet::rowFormats()
1677 {
1678 return &d->rows;
1679 }
1680
rowFormats() const1681 const RowFormatStorage* Sheet::rowFormats() const
1682 {
1683 return &d->rows;
1684 }
1685
showStatusMessage(const QString & message,int timeout)1686 void Sheet::showStatusMessage(const QString &message, int timeout)
1687 {
1688 emit statusMessage(message, timeout);
1689 }
1690
hideSheet(bool _hide)1691 void Sheet::hideSheet(bool _hide)
1692 {
1693 setHidden(_hide);
1694 if (_hide)
1695 map()->addDamage(new SheetDamage(this, SheetDamage::Hidden));
1696 else
1697 map()->addDamage(new SheetDamage(this, SheetDamage::Shown));
1698 }
1699
setSheetName(const QString & name,bool init)1700 bool Sheet::setSheetName(const QString& name, bool init)
1701 {
1702 Q_UNUSED(init);
1703 if (map()->findSheet(name))
1704 return false;
1705
1706 if (isProtected())
1707 return false;
1708
1709 if (d->name == name)
1710 return true;
1711
1712 QString old_name = d->name;
1713 d->name = name;
1714
1715 // FIXME: Why is the change of a sheet's name not supposed to be propagated here?
1716 // If it is not, we have to manually do so in the loading process, e.g. for the
1717 // SheetAccessModel in the document's data center map.
1718 //if (init)
1719 // return true;
1720
1721 foreach(Sheet* sheet, map()->sheetList()) {
1722 sheet->changeCellTabName(old_name, name);
1723 }
1724
1725 map()->addDamage(new SheetDamage(this, SheetDamage::Name));
1726
1727 setObjectName(name);
1728 // (dynamic_cast<SheetIface*>(dcopObject()))->sheetNameHasChanged();
1729
1730 return true;
1731 }
1732
1733
updateLocale()1734 void Sheet::updateLocale()
1735 {
1736 for (int c = 0; c < valueStorage()->count(); ++c) {
1737 Cell cell(this, valueStorage()->col(c), valueStorage()->row(c));
1738 QString text = cell.userInput();
1739 cell.parseUserInput(text);
1740 }
1741 // Affects the displayed value; rebuild the visual cache.
1742 const Region region(1, 1, KS_colMax, KS_rowMax, this);
1743 map()->addDamage(new CellDamage(this, region, CellDamage::Appearance));
1744 }
1745
convertObscuringBorders()1746 void Sheet::convertObscuringBorders()
1747 {
1748 // FIXME Stefan: Verify that this is not needed anymore.
1749 #if 0
1750 /* a word of explanation here:
1751 beginning with KSpread 1.2 (actually, cvs of Mar 28, 2002), border information
1752 is stored differently. Previously, for a cell obscuring a region, the entire
1753 region's border's data would be stored in the obscuring cell. This caused
1754 some data loss in certain situations. After that date, each cell stores
1755 its own border data, and prints it even if it is an obscured cell (as long
1756 as that border isn't across an obscuring border).
1757 Anyway, this function is used when loading a file that was stored with the
1758 old way of borders. All new files have the sheet attribute "borders1.2" so
1759 if that isn't in the file, all the border data will be converted here.
1760 It's a bit of a hack but I can't think of a better way and it's not *that*
1761 bad of a hack.:-)
1762 */
1763 Cell c = d->cellStorage->firstCell();
1764 QPen topPen, bottomPen, leftPen, rightPen;
1765 for (; c; c = c->nextCell()) {
1766 if (c->extraXCells() > 0 || c->extraYCells() > 0) {
1767 const Style* style = this->style(c->column(), c->row());
1768 topPen = style->topBorderPen();
1769 leftPen = style->leftBorderPen();
1770 rightPen = style->rightBorderPen();
1771 bottomPen = style->bottomBorderPen();
1772
1773 c->format()->setTopBorderStyle(Qt::NoPen);
1774 c->format()->setLeftBorderStyle(Qt::NoPen);
1775 c->format()->setRightBorderStyle(Qt::NoPen);
1776 c->format()->setBottomBorderStyle(Qt::NoPen);
1777
1778 for (int x = c->column(); x < c->column() + c->extraXCells(); x++) {
1779 Cell(this, x, c->row())->setTopBorderPen(topPen);
1780 Cell(this, x, c->row() + c->extraYCells())->
1781 setBottomBorderPen(bottomPen);
1782 }
1783 for (int y = c->row(); y < c->row() + c->extraYCells(); y++) {
1784 Cell(this, c->column(), y)->setLeftBorderPen(leftPen);
1785 Cell(this, c->column() + c->extraXCells(), y)->
1786 setRightBorderPen(rightPen);
1787 }
1788 }
1789 }
1790 #endif
1791 }
1792
applyDatabaseFilter(const Database & database)1793 void Sheet::applyDatabaseFilter(const Database &database)
1794 {
1795 Sheet* const sheet = database.range().lastSheet();
1796 const QRect range = database.range().lastRange();
1797 const int start = database.orientation() == Qt::Vertical ? range.top() : range.left();
1798 const int end = database.orientation() == Qt::Vertical ? range.bottom() : range.right();
1799 for (int i = start + 1; i <= end; ++i) {
1800 const bool isFiltered = !database.filter().evaluate(database, i);
1801 // debugSheets <<"Filtering column/row" << i <<"?" << isFiltered;
1802 if (database.orientation() == Qt::Vertical) {
1803 sheet->rowFormats()->setFiltered(i, i, isFiltered);
1804 } else { // database.orientation() == Qt::Horizontal
1805 sheet->nonDefaultColumnFormat(i)->setFiltered(isFiltered);
1806 }
1807 }
1808 if (database.orientation() == Qt::Vertical)
1809 sheet->map()->addDamage(new SheetDamage(sheet, SheetDamage::RowsChanged));
1810 else // database.orientation() == Qt::Horizontal
1811 sheet->map()->addDamage(new SheetDamage(sheet, SheetDamage::ColumnsChanged));
1812
1813 cellStorage()->setDatabase(database.range(), Database());
1814 cellStorage()->setDatabase(database.range(), database);
1815 map()->addDamage(new CellDamage(this, database.range(), CellDamage::Appearance));
1816 }
1817
1818 /**********************
1819 * Printout Functions *
1820 **********************/
1821
1822 #ifndef NDEBUG
printDebug()1823 void Sheet::printDebug()
1824 {
1825 int iMaxColumn = d->cellStorage->columns();
1826 int iMaxRow = d->cellStorage->rows();
1827
1828 debugSheets << "Cell | Content | Value [UserInput]";
1829 Cell cell;
1830 for (int currentrow = 1 ; currentrow <= iMaxRow ; ++currentrow) {
1831 for (int currentcolumn = 1 ; currentcolumn <= iMaxColumn ; currentcolumn++) {
1832 cell = Cell(this, currentcolumn, currentrow);
1833 if (!cell.isEmpty()) {
1834 QString cellDescr = Cell::name(currentcolumn, currentrow).rightJustified(4) +
1835 //QString cellDescr = "Cell ";
1836 //cellDescr += QString::number(currentrow).rightJustified(3,'0') + ',';
1837 //cellDescr += QString::number(currentcolumn).rightJustified(3,'0') + ' ';
1838 " | ";
1839 QString valueType;
1840 QTextStream stream(&valueType);
1841 stream << cell.value().type();
1842 cellDescr += valueType.rightJustified(7) +
1843 " | " +
1844 map()->converter()->asString(cell.value()).asString().rightJustified(5) +
1845 QString(" [%1]").arg(cell.userInput());
1846 debugSheets << cellDescr;
1847 }
1848 }
1849 }
1850 }
1851 #endif
1852
1853 } // namespace Sheets
1854 } // namespace Calligra
1855