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#include <QBuffer>
28#include <QClipboard>
29#include <QList>
30#include <QMap>
31#include <QMimeData>
32#include <QStack>
33#include <QTextStream>
34#include <QImage>
35
36#include <kdebug.h>
37#include <kcodecs.h>
38#include <kfind.h>
39#include <kfinddialog.h>
40#include <kreplace.h>
41#include <kreplacedialog.h>
42#include <kurl.h>
43
44#include <KoDocumentInfo.h>
45#include <KoOdfLoadingContext.h>
46#include <KoOasisSettings.h>
47#include <KoOdfStylesReader.h>
48
49#include <KoShape.h>
50#include <KoDocumentResourceManager.h>
51#include <KoShapeLoadingContext.h>
52#include <KoShapeManager.h>
53#include <KoShapeRegistry.h>
54#include <KoShapeSavingContext.h>
55#include <KoStyleStack.h>
56#include <KoGenStyles.h>
57#include <KoUnit.h>
58#include <KoXmlNS.h>
59#include <KoXmlWriter.h>
60#include <KoStore.h>
61#include <KoText.h>
62#include <KoStyleManager.h>
63#include <KoTextSharedLoadingData.h>
64#include <KoParagraphStyle.h>
65#include <KoUpdater.h>
66#include <KoProgressUpdater.h>
67
68#include "CellStorage.h"
69#include "Cluster.h"
70#include "Condition.h"
71#include "Damages.h"
72#include "DependencyManager.h"
73#include "DocBase.h"
74#include "FormulaStorage.h"
75#include "Global.h"
76#include "HeaderFooter.h"
77#include "LoadingInfo.h"
78#include "Localization.h"
79#include "Map.h"
80#include "NamedAreaManager.h"
81#include "OdfLoadingContext.h"
82#include "OdfSavingContext.h"
83#include "PrintSettings.h"
84#include "RecalcManager.h"
85#include "RowColumnFormat.h"
86#include "RowFormatStorage.h"
87#include "ShapeApplicationData.h"
88#include "SheetPrint.h"
89#include "RectStorage.h"
90#include "SheetModel.h"
91#include "Style.h"
92#include "StyleManager.h"
93#include "StyleStorage.h"
94#include "Util.h"
95#include "Validity.h"
96#include "ValueConverter.h"
97#include "ValueStorage.h"
98#include "database/Filter.h"
99
100namespace Calligra
101{
102namespace Sheets
103{
104
105template<typename T> class IntervalMap
106{
107public:
108 IntervalMap() {}
109 // from and to are inclusive, assumes no overlapping ranges
110 // even though no checks are done
111 void insert(int from, int to, const T& data) {
112 m_data.insert(to, qMakePair(from, data));
113 }
114 T get(int idx) const {
115 typename QMap<int, QPair<int, T> >::ConstIterator it = m_data.lowerBound(idx);
116 if (it != m_data.end() && it.value().first <= idx) {
117 return it.value().second;
118 }
119 return T();
120 }
121private:
122 QMap<int, QPair<int, T> > m_data;
123};
124
125static QString createObjectName(const QString &sheetName)
126{
127 QString objectName;
128 for (int i = 0; i < sheetName.count(); ++i) {
129 if (sheetName[i].isLetterOrNumber() || sheetName[i] == '_')
130 objectName.append(sheetName[i]);
131 else
132 objectName.append('_');
133 }
134 return objectName;
135}
136
137
138class Sheet::Private
139{
140public:
141 Private(Sheet* sheet) : rows(sheet) {}
142
143 Map* workbook;
144 SheetModel *model;
145
146 QString name;
147
148 Qt::LayoutDirection layoutDirection;
149
150 // true if sheet is hidden
151 bool hide;
152
153 bool showGrid;
154 bool showFormula;
155 bool showFormulaIndicator;
156 bool showCommentIndicator;
157 bool autoCalc;
158 bool lcMode;
159 bool showColumnNumber;
160 bool hideZero;
161 bool firstLetterUpper;
162
163 // clusters to hold objects
164 CellStorage* cellStorage;
165 RowFormatStorage rows;
166 ColumnCluster columns;
167 QList<KoShape*> shapes;
168
169 // hold the print object
170 SheetPrint* print;
171
172 // Indicates whether the sheet should paint the page breaks.
173 // Doing so costs some time, so by default it should be turned off.
174 bool showPageOutline;
175
176 // Max range of canvas in x and y direction.
177 // Depends on KS_colMax/KS_rowMax and the width/height of all columns/rows
178 QSizeF documentSize;
179
180 QImage backgroundImage;
181 Sheet::BackgroundImageProperties backgroundProperties;
182};
183
184
185Sheet::Sheet(Map* map, const QString &sheetName)
186 : KoShapeUserData(map)
187 , KoShapeBasedDocumentBase()
188 , d(new Private(this))
189{
190 d->workbook = map;
191 if (map->doc()) {
192 resourceManager()->setUndoStack(map->doc()->undoStack());
193 QVariant variant;
194 variant.setValue<void*>(map->doc()->sheetAccessModel());
195 resourceManager()->setResource(75751149, variant); // duplicated in kchart.
196 }
197 d->model = new SheetModel(this);
198
199 d->layoutDirection = QApplication::layoutDirection();
200
201 d->name = sheetName;
202
203 // Set a valid object name, so that we can offer scripting.
204 setObjectName(createObjectName(d->name));
205
206 d->cellStorage = new CellStorage(this);
207 d->columns.setAutoDelete(true);
208
209 d->documentSize = QSizeF(KS_colMax * d->workbook->defaultColumnFormat()->width(),
210 KS_rowMax * d->workbook->defaultRowFormat()->height());
211
212 d->hide = false;
213 d->showGrid = true;
214 d->showFormula = false;
215 d->showFormulaIndicator = false;
216 d->showCommentIndicator = true;
217 d->showPageOutline = false;
218
219 d->lcMode = false;
220 d->showColumnNumber = false;
221 d->hideZero = false;
222 d->firstLetterUpper = false;
223 d->autoCalc = true;
224 d->print = new SheetPrint(this);
225
226 // document size changes always trigger a visible size change
227 connect(this, SIGNAL(documentSizeChanged(QSizeF)), SIGNAL(visibleSizeChanged()));
228 // CellStorage connections
229 connect(d->cellStorage, SIGNAL(insertNamedArea(Region,QString)),
230 d->workbook->namedAreaManager(), SLOT(insert(Region,QString)));
231 connect(d->cellStorage, SIGNAL(namedAreaRemoved(QString)),
232 d->workbook->namedAreaManager(), SLOT(remove(QString)));
233}
234
235Sheet::Sheet(const Sheet &other)
236 : KoShapeUserData(other.d->workbook)
237 , KoShapeBasedDocumentBase()
238 , ProtectableObject(other)
239 , d(new Private(this))
240{
241 d->workbook = other.d->workbook;
242 d->model = new SheetModel(this);
243
244 // create a unique name
245 int i = 1;
246 do
247 d->name = other.d->name + QString("_%1").arg(i++);
248 while (d->workbook->findSheet(d->name));
249
250 // Set a valid object name, so that we can offer scripting.
251 setObjectName(createObjectName(d->name));
252
253 d->layoutDirection = other.d->layoutDirection;
254 d->hide = other.d->hide;
255 d->showGrid = other.d->showGrid;
256 d->showFormula = other.d->showFormula;
257 d->showFormulaIndicator = other.d->showFormulaIndicator;
258 d->showCommentIndicator = other.d->showCommentIndicator;
259 d->autoCalc = other.d->autoCalc;
260 d->lcMode = other.d->lcMode;
261 d->showColumnNumber = other.d->showColumnNumber;
262 d->hideZero = other.d->hideZero;
263 d->firstLetterUpper = other.d->firstLetterUpper;
264
265 d->cellStorage = new CellStorage(*other.d->cellStorage, this);
266 d->rows = other.d->rows;
267 d->columns = other.d->columns;
268
269 // flake
270#if 0 // CALLIGRA_SHEETS_WIP_COPY_SHEET_(SHAPES)
271 //FIXME This does not work as copySettings does not work. Also createDefaultShapeAndInit without the correct settings can not work
272 //I think this should use saveOdf and loadOdf for copying
273 KoShape* shape;
274 const QList<KoShape*> shapes = other.d->shapes;
275 for (int i = 0; i < shapes.count(); ++i) {
276 shape = KoShapeRegistry::instance()->value(shapes[i]->shapeId())->createDefaultShapeAndInit(0);
277 shape->copySettings(shapes[i]);
278 addShape(shape);
279 }
280#endif // CALLIGRA_SHEETS_WIP_COPY_SHEET_(SHAPES)
281
282 d->print = new SheetPrint(this); // FIXME = new SheetPrint(*other.d->print);
283
284 d->showPageOutline = other.d->showPageOutline;
285 d->documentSize = other.d->documentSize;
286}
287
288Sheet::~Sheet()
289{
290 //Disable automatic recalculation of dependancies on this sheet to prevent crashes
291 //in certain situations:
292 //
293 //For example, suppose a cell in SheetB depends upon a cell in SheetA. If the cell in SheetB is emptied
294 //after SheetA has already been deleted, the program would try to remove dependancies from the cell in SheetA
295 //causing a crash.
296 setAutoCalculationEnabled(false);
297
298 delete d->print;
299 delete d->cellStorage;
300 qDeleteAll(d->shapes);
301 delete d;
302}
303
304QAbstractItemModel* Sheet::model() const
305{
306 return d->model;
307}
308
309QString Sheet::sheetName() const
310{
311 return d->name;
312}
313
314Map* Sheet::map() const
315{
316 return d->workbook;
317}
318
319DocBase* Sheet::doc() const
320{
321 return d->workbook->doc();
322}
323
324void Sheet::addShape(KoShape* shape)
325{
326 if (!shape)
327 return;
328 d->shapes.append(shape);
329 shape->setApplicationData(new ShapeApplicationData());
330 emit shapeAdded(this, shape);
331}
332
333void Sheet::removeShape(KoShape* shape)
334{
335 if (!shape)
336 return;
337 d->shapes.removeAll(shape);
338 emit shapeRemoved(this, shape);
339}
340
341void Sheet::deleteShapes()
342{
343 qDeleteAll(d->shapes);
344 d->shapes.clear();
345}
346
347KoDocumentResourceManager* Sheet::resourceManager() const
348{
349 return map()->resourceManager();
350}
351
352QList<KoShape*> Sheet::shapes() const
353{
354 return d->shapes;
355}
356
357Qt::LayoutDirection Sheet::layoutDirection() const
358{
359 return d->layoutDirection;
360}
361
362void Sheet::setLayoutDirection(Qt::LayoutDirection dir)
363{
364 d->layoutDirection = dir;
365}
366
367bool Sheet::isHidden() const
368{
369 return d->hide;
370}
371
372void Sheet::setHidden(bool hidden)
373{
374 d->hide = hidden;
375}
376
377bool Sheet::getShowGrid() const
378{
379 return d->showGrid;
380}
381
382void Sheet::setShowGrid(bool _showGrid)
383{
384 d->showGrid = _showGrid;
385}
386
387bool Sheet::getShowFormula() const
388{
389 return d->showFormula;
390}
391
392void Sheet::setShowFormula(bool _showFormula)
393{
394 d->showFormula = _showFormula;
395}
396
397bool Sheet::getShowFormulaIndicator() const
398{
399 return d->showFormulaIndicator;
400}
401
402void Sheet::setShowFormulaIndicator(bool _showFormulaIndicator)
403{
404 d->showFormulaIndicator = _showFormulaIndicator;
405}
406
407bool Sheet::getShowCommentIndicator() const
408{
409 return d->showCommentIndicator;
410}
411
412void Sheet::setShowCommentIndicator(bool _indic)
413{
414 d->showCommentIndicator = _indic;
415}
416
417bool Sheet::getLcMode() const
418{
419 return d->lcMode;
420}
421
422void Sheet::setLcMode(bool _lcMode)
423{
424 d->lcMode = _lcMode;
425}
426
427bool Sheet::isAutoCalculationEnabled() const
428{
429 return d->autoCalc;
430}
431
432void Sheet::setAutoCalculationEnabled(bool enable)
433{
434 //Avoid possible recalculation of dependancies if the auto calc setting hasn't changed
435 if (d->autoCalc == enable)
436 return;
437
438 d->autoCalc = enable;
439 //If enabling automatic calculation, make sure that the dependencies are up-to-date
440 if (enable == true) {
441 map()->dependencyManager()->addSheet(this);
442 map()->recalcManager()->recalcSheet(this);
443 } else {
444 map()->dependencyManager()->removeSheet(this);
445 }
446}
447
448bool Sheet::getShowColumnNumber() const
449{
450 return d->showColumnNumber;
451}
452
453void Sheet::setShowColumnNumber(bool _showColumnNumber)
454{
455 d->showColumnNumber = _showColumnNumber;
456}
457
458bool Sheet::getHideZero() const
459{
460 return d->hideZero;
461}
462
463void Sheet::setHideZero(bool _hideZero)
464{
465 d->hideZero = _hideZero;
466}
467
468bool Sheet::getFirstLetterUpper() const
469{
470 return d->firstLetterUpper;
471}
472
473void Sheet::setFirstLetterUpper(bool _firstUpper)
474{
475 d->firstLetterUpper = _firstUpper;
476}
477
478bool Sheet::isShowPageOutline() const
479{
480 return d->showPageOutline;
481}
482
483const ColumnFormat* Sheet::columnFormat(int _column) const
484{
485 const ColumnFormat *p = d->columns.lookup(_column);
486 if (p != 0)
487 return p;
488
489 return map()->defaultColumnFormat();
490}
491
492CellStorage* Sheet::cellStorage() const
493{
494 return d->cellStorage;
495}
496
497const CommentStorage* Sheet::commentStorage() const
498{
499 return d->cellStorage->commentStorage();
500}
501
502const ConditionsStorage* Sheet::conditionsStorage() const
503{
504 return d->cellStorage->conditionsStorage();
505}
506
507const FormulaStorage* Sheet::formulaStorage() const
508{
509 return d->cellStorage->formulaStorage();
510}
511
512const FusionStorage* Sheet::fusionStorage() const
513{
514 return d->cellStorage->fusionStorage();
515}
516
517const LinkStorage* Sheet::linkStorage() const
518{
519 return d->cellStorage->linkStorage();
520}
521
522const StyleStorage* Sheet::styleStorage() const
523{
524 return d->cellStorage->styleStorage();
525}
526
527const ValidityStorage* Sheet::validityStorage() const
528{
529 return d->cellStorage->validityStorage();
530}
531
532const ValueStorage* Sheet::valueStorage() const
533{
534 return d->cellStorage->valueStorage();
535}
536
537SheetPrint* Sheet::print() const
538{
539 return d->print;
540}
541
542PrintSettings* Sheet::printSettings() const
543{
544 return d->print->settings();
545}
546
547void Sheet::setPrintSettings(const PrintSettings &settings)
548{
549 d->print->setSettings(settings);
550 // Repaint, if page borders are shown and this is the active sheet.
551 if (isShowPageOutline()) {
552 // Just repaint everything visible; no need to invalidate the visual cache.
553 map()->addDamage(new SheetDamage(this, SheetDamage::ContentChanged));
554 }
555}
556
557HeaderFooter *Sheet::headerFooter() const
558{
559 return d->print->headerFooter();
560}
561
562QSizeF Sheet::documentSize() const
563{
564 return d->documentSize;
565}
566
567void Sheet::adjustDocumentWidth(double deltaWidth)
568{
569 d->documentSize.rwidth() += deltaWidth;
570 emit documentSizeChanged(d->documentSize);
571}
572
573void Sheet::adjustDocumentHeight(double deltaHeight)
574{
575 d->documentSize.rheight() += deltaHeight;
576 emit documentSizeChanged(d->documentSize);
577}
578
579void Sheet::adjustCellAnchoredShapesX(qreal minX, qreal maxX, qreal delta)
580{
581 foreach (KoShape* s, d->shapes) {
582 if (dynamic_cast<ShapeApplicationData*>(s->applicationData())->isAnchoredToCell()) {
583 if (s->position().x() >= minX && s->position().x() < maxX) {
584 QPointF p = s->position();
585 p.setX(qMax(minX, p.x() + delta));
586 s->setPosition(p);
587 }
588 }
589 }
590}
591
592void Sheet::adjustCellAnchoredShapesX(qreal delta, int firstCol, int lastCol)
593{
594 adjustCellAnchoredShapesX(columnPosition(firstCol), columnPosition(lastCol+1), delta);
595}
596
597void Sheet::adjustCellAnchoredShapesY(qreal minY, qreal maxY, qreal delta)
598{
599 foreach (KoShape* s, d->shapes) {
600 if (dynamic_cast<ShapeApplicationData*>(s->applicationData())->isAnchoredToCell()) {
601 if (s->position().y() >= minY && s->position().y() < maxY) {
602 QPointF p = s->position();
603 p.setY(qMax(minY, p.y() + delta));
604 s->setPosition(p);
605 }
606 }
607 }
608}
609
610void Sheet::adjustCellAnchoredShapesY(qreal delta, int firstRow, int lastRow)
611{
612 adjustCellAnchoredShapesY(rowPosition(firstRow), rowPosition(lastRow+1), delta);
613}
614
615int Sheet::leftColumn(qreal _xpos, qreal &_left) const
616{
617 _left = 0.0;
618 int col = 1;
619 double x = columnFormat(col)->visibleWidth();
620 while (x < _xpos && col < KS_colMax) {
621 _left += columnFormat(col)->visibleWidth();
622 x += columnFormat(++col)->visibleWidth();
623 }
624 return col;
625}
626
627int Sheet::rightColumn(double _xpos) const
628{
629 int col = 1;
630 double x = columnFormat(col)->visibleWidth();
631 while (x <= _xpos && col < KS_colMax)
632 x += columnFormat(++col)->visibleWidth();
633 return col;
634}
635
636int Sheet::topRow(qreal _ypos, qreal & _top) const
637{
638 qreal top;
639 int row = rowFormats()->rowForPosition(_ypos, &top);
640 _top = top;
641 return row;
642}
643
644int Sheet::bottomRow(double _ypos) const
645{
646 return rowFormats()->rowForPosition(_ypos+1e-9);
647}
648
649QRectF Sheet::cellCoordinatesToDocument(const QRect& cellRange) const
650{
651 // TODO Stefan: Rewrite to save some iterations over the columns/rows.
652 QRectF rect;
653 rect.setLeft(columnPosition(cellRange.left()));
654 rect.setRight(columnPosition(cellRange.right()) + columnFormat(cellRange.right())->width());
655 rect.setTop(rowPosition(cellRange.top()));
656 rect.setBottom(rowPosition(cellRange.bottom()) + rowFormats()->rowHeight(cellRange.bottom()));
657 return rect;
658}
659
660QRect Sheet::documentToCellCoordinates(const QRectF &area) const
661{
662 double width = 0.0;
663 int left = 0;
664 while (width <= area.left())
665 width += columnFormat(++left)->visibleWidth();
666 int right = left;
667 while (width < area.right())
668 width += columnFormat(++right)->visibleWidth();
669 int top = rowFormats()->rowForPosition(area.top());
670 int bottom = rowFormats()->rowForPosition(area.bottom());
671 return QRect(left, top, right - left + 1, bottom - top + 1);
672}
673
674double Sheet::columnPosition(int _col) const
675{
676 const int max = qMin(_col, KS_colMax);
677 double x = 0.0;
678 for (int col = 1; col < max; ++col)
679 x += columnFormat(col)->visibleWidth();
680 return x;
681}
682
683
684double Sheet::rowPosition(int _row) const
685{
686 const int max = qMin(_row, KS_rowMax+1);
687 return rowFormats()->totalVisibleRowHeight(1, max-1);
688}
689
690ColumnFormat* Sheet::firstCol() const
691{
692 return d->columns.first();
693}
694
695ColumnFormat* Sheet::nonDefaultColumnFormat(int _column, bool force_creation)
696{
697 Q_ASSERT(_column >= 1 && _column <= KS_colMax);
698 ColumnFormat *p = d->columns.lookup(_column);
699 if (p != 0 || !force_creation)
700 return p;
701
702 p = new ColumnFormat(*map()->defaultColumnFormat());
703 p->setSheet(this);
704 p->setColumn(_column);
705
706 d->columns.insertElement(p, _column);
707
708 return p;
709}
710
711void Sheet::changeCellTabName(QString const & old_name, QString const & new_name)
712{
713 for (int c = 0; c < formulaStorage()->count(); ++c) {
714 if (formulaStorage()->data(c).expression().contains(old_name)) {
715 int nb = formulaStorage()->data(c).expression().count(old_name + '!');
716 QString tmp = old_name + '!';
717 int len = tmp.length();
718 tmp = formulaStorage()->data(c).expression();
719
720 for (int i = 0; i < nb; ++i) {
721 int pos = tmp.indexOf(old_name + '!');
722 tmp.replace(pos, len, new_name + '!');
723 }
724 Cell cell(this, formulaStorage()->col(c), formulaStorage()->row(c));
725 Formula formula(this, cell);
726 formula.setExpression(tmp);
727 cell.setFormula(formula);
728 cell.makeFormula();
729 }
730 }
731}
732
733void Sheet::insertShiftRight(const QRect& rect)
734{
735 foreach(Sheet* sheet, map()->sheetList()) {
736 for (int i = rect.top(); i <= rect.bottom(); ++i) {
737 sheet->changeNameCellRef(QPoint(rect.left(), i), false,
738 Sheet::ColumnInsert, sheetName(),
739 rect.right() - rect.left() + 1);
740 }
741 }
742}
743
744void Sheet::insertShiftDown(const QRect& rect)
745{
746 foreach(Sheet* sheet, map()->sheetList()) {
747 for (int i = rect.left(); i <= rect.right(); ++i) {
748 sheet->changeNameCellRef(QPoint(i, rect.top()), false,
749 Sheet::RowInsert, sheetName(),
750 rect.bottom() - rect.top() + 1);
751 }
752 }
753}
754
755void Sheet::removeShiftUp(const QRect& rect)
756{
757 foreach(Sheet* sheet, map()->sheetList()) {
758 for (int i = rect.left(); i <= rect.right(); ++i) {
759 sheet->changeNameCellRef(QPoint(i, rect.top()), false,
760 Sheet::RowRemove, sheetName(),
761 rect.bottom() - rect.top() + 1);
762 }
763 }
764}
765
766void Sheet::removeShiftLeft(const QRect& rect)
767{
768 foreach(Sheet* sheet, map()->sheetList()) {
769 for (int i = rect.top(); i <= rect.bottom(); ++i) {
770 sheet->changeNameCellRef(QPoint(rect.left(), i), false,
771 Sheet::ColumnRemove, sheetName(),
772 rect.right() - rect.left() + 1);
773 }
774 }
775}
776
777void Sheet::insertColumns(int col, int number)
778{
779 double deltaWidth = 0.0;
780 for (int i = 0; i < number; i++) {
781 deltaWidth -= columnFormat(KS_colMax)->width();
782 d->columns.insertColumn(col);
783 deltaWidth += columnFormat(col + i)->width();
784 }
785 // Adjust document width (plus widths of new columns; minus widths of removed columns).
786 adjustDocumentWidth(deltaWidth);
787
788 foreach(Sheet* sheet, map()->sheetList()) {
789 sheet->changeNameCellRef(QPoint(col, 1), true,
790 Sheet::ColumnInsert, sheetName(),
791 number);
792 }
793 //update print settings
794 d->print->insertColumn(col, number);
795}
796
797void Sheet::insertRows(int row, int number)
798{
799 d->rows.insertRows(row, number);
800
801 foreach(Sheet* sheet, map()->sheetList()) {
802 sheet->changeNameCellRef(QPoint(1, row), true,
803 Sheet::RowInsert, sheetName(),
804 number);
805 }
806 //update print settings
807 d->print->insertRow(row, number);
808}
809
810void Sheet::removeColumns(int col, int number)
811{
812 double deltaWidth = 0.0;
813 for (int i = 0; i < number; ++i) {
814 deltaWidth -= columnFormat(col)->width();
815 d->columns.removeColumn(col);
816 deltaWidth += columnFormat(KS_colMax)->width();
817 }
818 // Adjust document width (plus widths of new columns; minus widths of removed columns).
819 adjustDocumentWidth(deltaWidth);
820
821 foreach(Sheet* sheet, map()->sheetList()) {
822 sheet->changeNameCellRef(QPoint(col, 1), true,
823 Sheet::ColumnRemove, sheetName(),
824 number);
825 }
826 //update print settings
827 d->print->removeColumn(col, number);
828}
829
830void Sheet::removeRows(int row, int number)
831{
832 d->rows.removeRows(row, number);
833
834 foreach(Sheet* sheet, map()->sheetList()) {
835 sheet->changeNameCellRef(QPoint(1, row), true,
836 Sheet::RowRemove, sheetName(),
837 number);
838 }
839
840 //update print settings
841 d->print->removeRow(row, number);
842}
843
844QString Sheet::changeNameCellRefHelper(const QPoint& pos, bool fullRowOrColumn, ChangeRef ref,
845 int nbCol, const QPoint& point, bool isColumnFixed,
846 bool isRowFixed)
847{
848 QString newPoint;
849 int col = point.x();
850 int row = point.y();
851 // update column
852 if (isColumnFixed)
853 newPoint.append('$');
854 if (ref == ColumnInsert &&
855 col + nbCol <= KS_colMax &&
856 col >= pos.x() && // Column after the new one : +1
857 (fullRowOrColumn || row == pos.y())) { // All rows or just one
858 newPoint += Cell::columnName(col + nbCol);
859 } else if (ref == ColumnRemove &&
860 col > pos.x() && // Column after the deleted one : -1
861 (fullRowOrColumn || row == pos.y())) { // All rows or just one
862 newPoint += Cell::columnName(col - nbCol);
863 } else
864 newPoint += Cell::columnName(col);
865
866 // Update row
867 if (isRowFixed)
868 newPoint.append('$');
869 if (ref == RowInsert &&
870 row + nbCol <= KS_rowMax &&
871 row >= pos.y() && // Row after the new one : +1
872 (fullRowOrColumn || col == pos.x())) { // All columns or just one
873 newPoint += QString::number(row + nbCol);
874 } else if (ref == RowRemove &&
875 row > pos.y() && // Row after the deleted one : -1
876 (fullRowOrColumn || col == pos.x())) { // All columns or just one
877 newPoint += QString::number(row - nbCol);
878 } else
879 newPoint += QString::number(row);
880
881 if (((ref == ColumnRemove
882 && (col >= pos.x() && col < pos.x() + nbCol) // Column is the deleted one : error
883 && (fullRowOrColumn || row == pos.y())) ||
884 (ref == RowRemove
885 && (row >= pos.y() && row < pos.y() + nbCol) // Row is the deleted one : error
886 && (fullRowOrColumn || col == pos.x())) ||
887 (ref == ColumnInsert
888 && col + nbCol > KS_colMax
889 && col >= pos.x() // Column after the new one : +1
890 && (fullRowOrColumn || row == pos.y())) ||
891 (ref == RowInsert
892 && row + nbCol > KS_rowMax
893 && row >= pos.y() // Row after the new one : +1
894 && (fullRowOrColumn || col == pos.x())))) {
895 newPoint = '#' + i18n("Dependency") + '!';
896 }
897 return newPoint;
898}
899
900QString Sheet::changeNameCellRefHelper(const QPoint& pos, const QRect& rect, bool fullRowOrColumn, ChangeRef ref,
901 int nbCol, const QPoint& point, bool isColumnFixed,
902 bool isRowFixed)
903{
904 const bool isFirstColumn = pos.x() == rect.left();
905 const bool isLastColumn = pos.x() == rect.right();
906 const bool isFirstRow = pos.y() == rect.top();
907 const bool isLastRow = pos.y() == rect.bottom();
908
909 QString newPoint;
910 int col = point.x();
911 int row = point.y();
912 // update column
913 if (isColumnFixed)
914 newPoint.append('$');
915 if (ref == ColumnInsert &&
916 col + nbCol <= KS_colMax &&
917 col >= pos.x() && // Column after the new one : +1
918 (fullRowOrColumn || row == pos.y())) { // All rows or just one
919 newPoint += Cell::columnName(col + nbCol);
920 } else if (ref == ColumnRemove &&
921 (col > pos.x() ||
922 (col == pos.x() && isLastColumn)) && // Column after the deleted one : -1
923 (fullRowOrColumn || row == pos.y())) { // All rows or just one
924 newPoint += Cell::columnName(col - nbCol);
925 } else
926 newPoint += Cell::columnName(col);
927
928 // Update row
929 if (isRowFixed)
930 newPoint.append('$');
931 if (ref == RowInsert &&
932 row + nbCol <= KS_rowMax &&
933 row >= pos.y() && // Row after the new one : +1
934 (fullRowOrColumn || col == pos.x())) { // All columns or just one
935 newPoint += QString::number(row + nbCol);
936 } else if (ref == RowRemove &&
937 (row > pos.y() ||
938 (row == pos.y() && isLastRow)) && // Row after the deleted one : -1
939 (fullRowOrColumn || col == pos.x())) { // All columns or just one
940 newPoint += QString::number(row - nbCol);
941 } else
942 newPoint += QString::number(row);
943
944 if (((ref == ColumnRemove
945 && col == pos.x() // Column is the deleted one : error
946 && (fullRowOrColumn || row == pos.y())
947 && (isFirstColumn && isLastColumn)) ||
948 (ref == RowRemove
949 && row == pos.y() // Row is the deleted one : error
950 && (fullRowOrColumn || col == pos.x())
951 && (isFirstRow && isLastRow)) ||
952 (ref == ColumnInsert
953 && col + nbCol > KS_colMax
954 && col >= pos.x() // Column after the new one : +1
955 && (fullRowOrColumn || row == pos.y())) ||
956 (ref == RowInsert
957 && row + nbCol > KS_rowMax
958 && row >= pos.y() // Row after the new one : +1
959 && (fullRowOrColumn || col == pos.x())))) {
960 newPoint = '#' + i18n("Dependency") + '!';
961 }
962 return newPoint;
963}
964
965void Sheet::changeNameCellRef(const QPoint& pos, bool fullRowOrColumn, ChangeRef ref,
966 const QString& tabname, int nbCol)
967{
968 for (int c = 0; c < formulaStorage()->count(); ++c) {
969 QString newText('=');
970 const Tokens tokens = formulaStorage()->data(c).tokens();
971 for (int t = 0; t < tokens.count(); ++t) {
972 const Token token = tokens[t];
973 switch (token.type()) {
974 case Token::Cell:
975 case Token::Range: {
976 if (map()->namedAreaManager()->contains(token.text())) {
977 newText.append(token.text()); // simply keep the area name
978 break;
979 }
980 const Region region(token.text(), map());
981 if (!region.isValid() || !region.isContiguous()) {
982 newText.append(token.text());
983 break;
984 }
985 if (!region.firstSheet() && tabname != sheetName()) {
986 // nothing to do here
987 newText.append(token.text());
988 break;
989 }
990 // actually only one element in here, but we need extended access to the element
991 Region::ConstIterator end(region.constEnd());
992 for (Region::ConstIterator it(region.constBegin()); it != end; ++it) {
993 Region::Element* element = (*it);
994 if (element->type() == Region::Element::Point) {
995 if (element->sheet())
996 newText.append(element->sheet()->sheetName() + '!');
997 QString newPoint = changeNameCellRefHelper(pos, fullRowOrColumn, ref,
998 nbCol,
999 element->rect().topLeft(),
1000 element->isColumnFixed(),
1001 element->isRowFixed());
1002 newText.append(newPoint);
1003 } else { // (element->type() == Region::Element::Range)
1004 if (element->sheet())
1005 newText.append(element->sheet()->sheetName() + '!');
1006 QString newPoint;
1007 newPoint = changeNameCellRefHelper(pos, element->rect(), fullRowOrColumn, ref,
1008 nbCol, element->rect().topLeft(),
1009 element->isColumnFixed(),
1010 element->isRowFixed());
1011 newText.append(newPoint + ':');
1012 newPoint = changeNameCellRefHelper(pos, element->rect(), fullRowOrColumn, ref,
1013 nbCol, element->rect().bottomRight(),
1014 element->isColumnFixed(),
1015 element->isRowFixed());
1016 newText.append(newPoint);
1017 }
1018 }
1019 break;
1020 }
1021 default: {
1022 newText.append(token.text());
1023 break;
1024 }
1025 }
1026 }
1027
1028 Cell cell(this, formulaStorage()->col(c), formulaStorage()->row(c));
1029 Formula formula(this, cell);
1030 formula.setExpression(newText);
1031 cell.setFormula(formula);
1032 }
1033}
1034
1035// helper function for Sheet::areaIsEmpty
1036bool Sheet::cellIsEmpty(const Cell& cell, TestType _type)
1037{
1038 if (!cell.isPartOfMerged()) {
1039 switch (_type) {
1040 case Text :
1041 if (!cell.userInput().isEmpty())
1042 return false;
1043 break;
1044 case Validity:
1045 if (!cell.validity().isEmpty())
1046 return false;
1047 break;
1048 case Comment:
1049 if (!cell.comment().isEmpty())
1050 return false;
1051 break;
1052 case ConditionalCellAttribute:
1053 if (cell.conditions().conditionList().count() > 0)
1054 return false;
1055 break;
1056 }
1057 }
1058 return true;
1059}
1060
1061// TODO: convert into a manipulator, similar to the Dilation one
1062bool Sheet::areaIsEmpty(const Region& region, TestType _type)
1063{
1064 Region::ConstIterator endOfList = region.constEnd();
1065 for (Region::ConstIterator it = region.constBegin(); it != endOfList; ++it) {
1066 QRect range = (*it)->rect();
1067 // Complete rows selected ?
1068 if ((*it)->isRow()) {
1069 for (int row = range.top(); row <= range.bottom(); ++row) {
1070 Cell cell = d->cellStorage->firstInRow(row);
1071 while (!cell.isNull()) {
1072 if (!cellIsEmpty(cell, _type))
1073 return false;
1074 cell = d->cellStorage->nextInRow(cell.column(), row);
1075 }
1076 }
1077 }
1078 // Complete columns selected ?
1079 else if ((*it)->isColumn()) {
1080 for (int col = range.left(); col <= range.right(); ++col) {
1081 Cell cell = d->cellStorage->firstInColumn(col);
1082 while (!cell.isNull()) {
1083 if (!cellIsEmpty(cell, _type))
1084 return false;
1085 cell = d->cellStorage->nextInColumn(col, cell.row());
1086 }
1087 }
1088 } else {
1089 Cell cell;
1090 int right = range.right();
1091 int bottom = range.bottom();
1092 for (int x = range.left(); x <= right; ++x)
1093 for (int y = range.top(); y <= bottom; ++y) {
1094 cell = Cell(this, x, y);
1095 if (!cellIsEmpty(cell, _type))
1096 return false;
1097 }
1098 }
1099 }
1100 return true;
1101}
1102
1103QDomElement Sheet::saveXML(QDomDocument& dd)
1104{
1105 QDomElement sheet = dd.createElement("table");
1106
1107 // backward compatibility
1108 QString sheetName;
1109 for (int i = 0; i < d->name.count(); ++i) {
1110 if (d->name[i].isLetterOrNumber() || d->name[i] == ' ' || d->name[i] == '.')
1111 sheetName.append(d->name[i]);
1112 else
1113 sheetName.append('_');
1114 }
1115 sheet.setAttribute("name", sheetName);
1116
1117 //Laurent: for oasis format I think that we must use style:direction...
1118 sheet.setAttribute("layoutDirection", (layoutDirection() == Qt::RightToLeft) ? "rtl" : "ltr");
1119 sheet.setAttribute("columnnumber", (int)getShowColumnNumber());
1120 sheet.setAttribute("borders", (int)isShowPageOutline());
1121 sheet.setAttribute("hide", (int)isHidden());
1122 sheet.setAttribute("hidezero", (int)getHideZero());
1123 sheet.setAttribute("firstletterupper", (int)getFirstLetterUpper());
1124 sheet.setAttribute("grid", (int)getShowGrid());
1125 sheet.setAttribute("printGrid", (int)print()->settings()->printGrid());
1126 sheet.setAttribute("printCommentIndicator", (int)print()->settings()->printCommentIndicator());
1127 sheet.setAttribute("printFormulaIndicator", (int)print()->settings()->printFormulaIndicator());
1128 sheet.setAttribute("showFormula", (int)getShowFormula());
1129 sheet.setAttribute("showFormulaIndicator", (int)getShowFormulaIndicator());
1130 sheet.setAttribute("showCommentIndicator", (int)getShowCommentIndicator());
1131 sheet.setAttribute("lcmode", (int)getLcMode());
1132 sheet.setAttribute("autoCalc", (int)isAutoCalculationEnabled());
1133 sheet.setAttribute("borders1.2", 1);
1134 QByteArray pwd;
1135 password(pwd);
1136 if (!pwd.isNull()) {
1137 if (pwd.size() > 0) {
1138 QByteArray str = KCodecs::base64Encode(pwd);
1139 sheet.setAttribute("protected", QString(str.data()));
1140 } else
1141 sheet.setAttribute("protected", "");
1142 }
1143
1144 // paper parameters
1145 QDomElement paper = dd.createElement("paper");
1146 paper.setAttribute("format", printSettings()->paperFormatString());
1147 paper.setAttribute("orientation", printSettings()->orientationString());
1148 sheet.appendChild(paper);
1149
1150 QDomElement borders = dd.createElement("borders");
1151 KoPageLayout pageLayout = print()->settings()->pageLayout();
1152 borders.setAttribute("left", pageLayout.leftMargin);
1153 borders.setAttribute("top", pageLayout.topMargin);
1154 borders.setAttribute("right", pageLayout.rightMargin);
1155 borders.setAttribute("bottom", pageLayout.bottomMargin);
1156 paper.appendChild(borders);
1157
1158 QDomElement head = dd.createElement("head");
1159 paper.appendChild(head);
1160 if (!print()->headerFooter()->headLeft().isEmpty()) {
1161 QDomElement left = dd.createElement("left");
1162 head.appendChild(left);
1163 left.appendChild(dd.createTextNode(print()->headerFooter()->headLeft()));
1164 }
1165 if (!print()->headerFooter()->headMid().isEmpty()) {
1166 QDomElement center = dd.createElement("center");
1167 head.appendChild(center);
1168 center.appendChild(dd.createTextNode(print()->headerFooter()->headMid()));
1169 }
1170 if (!print()->headerFooter()->headRight().isEmpty()) {
1171 QDomElement right = dd.createElement("right");
1172 head.appendChild(right);
1173 right.appendChild(dd.createTextNode(print()->headerFooter()->headRight()));
1174 }
1175 QDomElement foot = dd.createElement("foot");
1176 paper.appendChild(foot);
1177 if (!print()->headerFooter()->footLeft().isEmpty()) {
1178 QDomElement left = dd.createElement("left");
1179 foot.appendChild(left);
1180 left.appendChild(dd.createTextNode(print()->headerFooter()->footLeft()));
1181 }
1182 if (!print()->headerFooter()->footMid().isEmpty()) {
1183 QDomElement center = dd.createElement("center");
1184 foot.appendChild(center);
1185 center.appendChild(dd.createTextNode(print()->headerFooter()->footMid()));
1186 }
1187 if (!print()->headerFooter()->footRight().isEmpty()) {
1188 QDomElement right = dd.createElement("right");
1189 foot.appendChild(right);
1190 right.appendChild(dd.createTextNode(print()->headerFooter()->footRight()));
1191 }
1192
1193 // print range
1194 QDomElement printrange = dd.createElement("printrange-rect");
1195 QRect _printRange = printSettings()->printRegion().lastRange();
1196 int left = _printRange.left();
1197 int right = _printRange.right();
1198 int top = _printRange.top();
1199 int bottom = _printRange.bottom();
1200 //If whole rows are selected, then we store zeros, as KS_colMax may change in future
1201 if (left == 1 && right == KS_colMax) {
1202 left = 0;
1203 right = 0;
1204 }
1205 //If whole columns are selected, then we store zeros, as KS_rowMax may change in future
1206 if (top == 1 && bottom == KS_rowMax) {
1207 top = 0;
1208 bottom = 0;
1209 }
1210 printrange.setAttribute("left-rect", left);
1211 printrange.setAttribute("right-rect", right);
1212 printrange.setAttribute("bottom-rect", bottom);
1213 printrange.setAttribute("top-rect", top);
1214 sheet.appendChild(printrange);
1215
1216 // Print repeat columns
1217 QDomElement printRepeatColumns = dd.createElement("printrepeatcolumns");
1218 printRepeatColumns.setAttribute("left", printSettings()->repeatedColumns().first);
1219 printRepeatColumns.setAttribute("right", printSettings()->repeatedColumns().second);
1220 sheet.appendChild(printRepeatColumns);
1221
1222 // Print repeat rows
1223 QDomElement printRepeatRows = dd.createElement("printrepeatrows");
1224 printRepeatRows.setAttribute("top", printSettings()->repeatedRows().first);
1225 printRepeatRows.setAttribute("bottom", printSettings()->repeatedRows().second);
1226 sheet.appendChild(printRepeatRows);
1227
1228 //Save print zoom
1229 sheet.setAttribute("printZoom", printSettings()->zoom());
1230
1231 //Save page limits
1232 const QSize pageLimits = printSettings()->pageLimits();
1233 sheet.setAttribute("printPageLimitX", pageLimits.width());
1234 sheet.setAttribute("printPageLimitY", pageLimits.height());
1235
1236 // Save all cells.
1237 const QRect usedArea = this->usedArea();
1238 for (int row = 1; row <= usedArea.height(); ++row) {
1239 Cell cell = d->cellStorage->firstInRow(row);
1240 while (!cell.isNull()) {
1241 QDomElement e = cell.save(dd);
1242 if (!e.isNull())
1243 sheet.appendChild(e);
1244 cell = d->cellStorage->nextInRow(cell.column(), row);
1245 }
1246 }
1247
1248 // Save all RowFormat objects.
1249 int styleIndex = styleStorage()->nextRowStyleIndex(0);
1250 int rowFormatRow = 0, lastRowFormatRow = rowFormats()->lastNonDefaultRow();
1251 while (styleIndex || rowFormatRow <= lastRowFormatRow) {
1252 int lastRow;
1253 bool isDefault = rowFormats()->isDefaultRow(rowFormatRow, &lastRow);
1254 if (isDefault && styleIndex <= lastRow) {
1255 RowFormat rowFormat(*map()->defaultRowFormat());
1256 rowFormat.setSheet(this);
1257 rowFormat.setRow(styleIndex);
1258 QDomElement e = rowFormat.save(dd);
1259 if (e.isNull())
1260 return QDomElement();
1261 sheet.appendChild(e);
1262 styleIndex = styleStorage()->nextRowStyleIndex(styleIndex);
1263 } else if (!isDefault) {
1264 RowFormat rowFormat(rowFormats(), rowFormatRow);
1265 QDomElement e = rowFormat.save(dd);
1266 if (e.isNull())
1267 return QDomElement();
1268 sheet.appendChild(e);
1269 if (styleIndex == rowFormatRow)
1270 styleIndex = styleStorage()->nextRowStyleIndex(styleIndex);
1271 }
1272 if (isDefault) rowFormatRow = qMin(lastRow+1, styleIndex == 0 ? KS_rowMax : styleIndex);
1273 else rowFormatRow++;
1274 }
1275
1276 // Save all ColumnFormat objects.
1277 ColumnFormat* columnFormat = firstCol();
1278 styleIndex = styleStorage()->nextColumnStyleIndex(0);
1279 while (columnFormat || styleIndex) {
1280 if (columnFormat && (!styleIndex || columnFormat->column() <= styleIndex)) {
1281 QDomElement e = columnFormat->save(dd);
1282 if (e.isNull())
1283 return QDomElement();
1284 sheet.appendChild(e);
1285 if (columnFormat->column() == styleIndex)
1286 styleIndex = styleStorage()->nextColumnStyleIndex(styleIndex);
1287 columnFormat = columnFormat->next();
1288 } else if (styleIndex) {
1289 ColumnFormat columnFormat(*map()->defaultColumnFormat());
1290 columnFormat.setSheet(this);
1291 columnFormat.setColumn(styleIndex);
1292 QDomElement e = columnFormat.save(dd);
1293 if (e.isNull())
1294 return QDomElement();
1295 sheet.appendChild(e);
1296 styleIndex = styleStorage()->nextColumnStyleIndex(styleIndex);
1297 }
1298 }
1299#if 0 // CALLIGRA_SHEETS_KOPART_EMBEDDING
1300 foreach(EmbeddedObject* object, doc()->embeddedObjects()) {
1301 if (object->sheet() == this) {
1302 QDomElement e = object->save(dd);
1303
1304 if (e.isNull())
1305 return QDomElement();
1306 sheet.appendChild(e);
1307 }
1308 }
1309#endif // CALLIGRA_SHEETS_KOPART_EMBEDDING
1310 return sheet;
1311}
1312
1313bool Sheet::isLoading()
1314{
1315 return map()->isLoading();
1316}
1317
1318void Sheet::checkContentDirection(QString const & name)
1319{
1320 /* set sheet's direction to RTL if sheet name is an RTL string */
1321 if ((name.isRightToLeft()))
1322 setLayoutDirection(Qt::RightToLeft);
1323 else
1324 setLayoutDirection(Qt::LeftToRight);
1325}
1326
1327bool Sheet::loadSheetStyleFormat(KoXmlElement *style)
1328{
1329 QString hleft, hmiddle, hright;
1330 QString fleft, fmiddle, fright;
1331 KoXmlNode header = KoXml::namedItemNS(*style, KoXmlNS::style, "header");
1332
1333 if (!header.isNull()) {
1334 kDebug(36003) << "Header exists";
1335 KoXmlNode part = KoXml::namedItemNS(header, KoXmlNS::style, "region-left");
1336 if (!part.isNull()) {
1337 hleft = getPart(part);
1338 kDebug(36003) << "Header left:" << hleft;
1339 } else
1340 kDebug(36003) << "Style:region:left doesn't exist!";
1341 part = KoXml::namedItemNS(header, KoXmlNS::style, "region-center");
1342 if (!part.isNull()) {
1343 hmiddle = getPart(part);
1344 kDebug(36003) << "Header middle:" << hmiddle;
1345 }
1346 part = KoXml::namedItemNS(header, KoXmlNS::style, "region-right");
1347 if (!part.isNull()) {
1348 hright = getPart(part);
1349 kDebug(36003) << "Header right:" << hright;
1350 }
1351 //If Header doesn't have region tag add it to Left
1352 hleft.append(getPart(header));
1353 }
1354 //TODO implement it under kspread
1355 KoXmlNode headerleft = KoXml::namedItemNS(*style, KoXmlNS::style, "header-left");
1356 if (!headerleft.isNull()) {
1357 KoXmlElement e = headerleft.toElement();
1358 if (e.hasAttributeNS(KoXmlNS::style, "display"))
1359 kDebug(36003) << "header.hasAttribute( style:display ) :" << e.hasAttributeNS(KoXmlNS::style, "display");
1360 else
1361 kDebug(36003) << "header left doesn't has attribute style:display";
1362 }
1363 //TODO implement it under kspread
1364 KoXmlNode footerleft = KoXml::namedItemNS(*style, KoXmlNS::style, "footer-left");
1365 if (!footerleft.isNull()) {
1366 KoXmlElement e = footerleft.toElement();
1367 if (e.hasAttributeNS(KoXmlNS::style, "display"))
1368 kDebug(36003) << "footer.hasAttribute( style:display ) :" << e.hasAttributeNS(KoXmlNS::style, "display");
1369 else
1370 kDebug(36003) << "footer left doesn't has attribute style:display";
1371 }
1372
1373 KoXmlNode footer = KoXml::namedItemNS(*style, KoXmlNS::style, "footer");
1374
1375 if (!footer.isNull()) {
1376 KoXmlNode part = KoXml::namedItemNS(footer, KoXmlNS::style, "region-left");
1377 if (!part.isNull()) {
1378 fleft = getPart(part);
1379 kDebug(36003) << "Footer left:" << fleft;
1380 }
1381 part = KoXml::namedItemNS(footer, KoXmlNS::style, "region-center");
1382 if (!part.isNull()) {
1383 fmiddle = getPart(part);
1384 kDebug(36003) << "Footer middle:" << fmiddle;
1385 }
1386 part = KoXml::namedItemNS(footer, KoXmlNS::style, "region-right");
1387 if (!part.isNull()) {
1388 fright = getPart(part);
1389 kDebug(36003) << "Footer right:" << fright;
1390 }
1391 //If Footer doesn't have region tag add it to Left
1392 fleft.append(getPart(footer));
1393 }
1394
1395 print()->headerFooter()->setHeadFootLine(hleft, hmiddle, hright,
1396 fleft, fmiddle, fright);
1397 return true;
1398}
1399
1400void Sheet::replaceMacro(QString & text, const QString & old, const QString & newS)
1401{
1402 int n = text.indexOf(old);
1403 if (n != -1)
1404 text = text.replace(n, old.length(), newS);
1405}
1406
1407QString Sheet::getPart(const KoXmlNode & part)
1408{
1409 QString result;
1410 KoXmlElement e = KoXml::namedItemNS(part, KoXmlNS::text, "p");
1411 while (!e.isNull()) {
1412 QString text = e.text();
1413
1414 KoXmlElement macro = KoXml::namedItemNS(e, KoXmlNS::text, "time");
1415 if (!macro.isNull())
1416 replaceMacro(text, macro.text(), "<time>");
1417
1418 macro = KoXml::namedItemNS(e, KoXmlNS::text, "date");
1419 if (!macro.isNull())
1420 replaceMacro(text, macro.text(), "<date>");
1421
1422 macro = KoXml::namedItemNS(e, KoXmlNS::text, "page-number");
1423 if (!macro.isNull())
1424 replaceMacro(text, macro.text(), "<page>");
1425
1426 macro = KoXml::namedItemNS(e, KoXmlNS::text, "page-count");
1427 if (!macro.isNull())
1428 replaceMacro(text, macro.text(), "<pages>");
1429
1430 macro = KoXml::namedItemNS(e, KoXmlNS::text, "sheet-name");
1431 if (!macro.isNull())
1432 replaceMacro(text, macro.text(), "<sheet>");
1433
1434 macro = KoXml::namedItemNS(e, KoXmlNS::text, "title");
1435 if (!macro.isNull())
1436 replaceMacro(text, macro.text(), "<name>");
1437
1438 macro = KoXml::namedItemNS(e, KoXmlNS::text, "file-name");
1439 if (!macro.isNull())
1440 replaceMacro(text, macro.text(), "<file>");
1441
1442 //add support for multi line into kspread
1443 if (!result.isEmpty())
1444 result += '\n';
1445 result += text;
1446 e = e.nextSibling().toElement();
1447 }
1448
1449 return result;
1450}
1451
1452void Sheet::loadColumnNodes(const KoXmlElement& parent,
1453 int& indexCol,
1454 int& maxColumn,
1455 KoOdfLoadingContext& odfContext,
1456 QHash<QString, QRegion>& columnStyleRegions,
1457 IntervalMap<QString>& columnStyles
1458 )
1459{
1460 KoXmlNode node = parent.firstChild();
1461 while (!node.isNull()) {
1462 KoXmlElement elem = node.toElement();
1463 if (!elem.isNull() && elem.namespaceURI() == KoXmlNS::table) {
1464 if (elem.localName() == "table-column") {
1465 loadColumnFormat(elem, odfContext.stylesReader(), indexCol, columnStyleRegions, columnStyles);
1466 maxColumn = qMax(maxColumn, indexCol - 1);
1467 } else if (elem.localName() == "table-column-group") {
1468 loadColumnNodes(elem, indexCol, maxColumn, odfContext, columnStyleRegions, columnStyles);
1469 }
1470 }
1471 node = node.nextSibling();
1472 }
1473}
1474
1475void Sheet::loadRowNodes(const KoXmlElement& parent,
1476 int& rowIndex,
1477 int& maxColumn,
1478 OdfLoadingContext& tableContext,
1479 QHash<QString, QRegion>& rowStyleRegions,
1480 QHash<QString, QRegion>& cellStyleRegions,
1481 const IntervalMap<QString>& columnStyles,
1482 const Styles& autoStyles,
1483 QList<ShapeLoadingData>& shapeData
1484 )
1485{
1486 KoXmlNode node = parent.firstChild();
1487 while (!node.isNull()) {
1488 KoXmlElement elem = node.toElement();
1489 if (!elem.isNull() && elem.namespaceURI() == KoXmlNS::table) {
1490 if (elem.localName() == "table-row") {
1491 int columnMaximal = loadRowFormat(elem, rowIndex, tableContext,
1492 rowStyleRegions, cellStyleRegions,
1493 columnStyles, autoStyles, shapeData);
1494 // allow the row to define more columns then defined via table-column
1495 maxColumn = qMax(maxColumn, columnMaximal);
1496 } else if (elem.localName() == "table-row-group") {
1497 loadRowNodes(elem, rowIndex, maxColumn, tableContext, rowStyleRegions, cellStyleRegions, columnStyles, autoStyles, shapeData);
1498 }
1499 }
1500 node = node.nextSibling();
1501 }
1502}
1503
1504
1505bool Sheet::loadOdf(const KoXmlElement& sheetElement,
1506 OdfLoadingContext& tableContext,
1507 const Styles& autoStyles,
1508 const QHash<QString, Conditions>& conditionalStyles)
1509{
1510
1511 QPointer<KoUpdater> updater;
1512 if (doc() && doc()->progressUpdater()) {
1513 updater = doc()->progressUpdater()->startSubtask(1,
1514 "Calligra::Sheets::Sheet::loadOdf");
1515 updater->setProgress(0);
1516 }
1517
1518 KoOdfLoadingContext& odfContext = tableContext.odfContext;
1519 if (sheetElement.hasAttributeNS(KoXmlNS::table, "style-name")) {
1520 QString stylename = sheetElement.attributeNS(KoXmlNS::table, "style-name", QString());
1521 //kDebug(36003)<<" style of table :"<<stylename;
1522 const KoXmlElement *style = odfContext.stylesReader().findStyle(stylename, "table");
1523 Q_ASSERT(style);
1524 //kDebug(36003)<<" style :"<<style;
1525 if (style) {
1526 KoXmlElement properties(KoXml::namedItemNS(*style, KoXmlNS::style, "table-properties"));
1527 if (!properties.isNull()) {
1528 if (properties.hasAttributeNS(KoXmlNS::table, "display")) {
1529 bool visible = (properties.attributeNS(KoXmlNS::table, "display", QString()) == "true" ? true : false);
1530 setHidden(!visible);
1531 }
1532 }
1533 if (style->hasAttributeNS(KoXmlNS::style, "master-page-name")) {
1534 QString masterPageStyleName = style->attributeNS(KoXmlNS::style, "master-page-name", QString());
1535 //kDebug()<<"style->attribute( style:master-page-name ) :"<<masterPageStyleName;
1536 KoXmlElement *masterStyle = odfContext.stylesReader().masterPages()[masterPageStyleName];
1537 //kDebug()<<"stylesReader.styles()[masterPageStyleName] :"<<masterStyle;
1538 if (masterStyle) {
1539 loadSheetStyleFormat(masterStyle);
1540 if (masterStyle->hasAttributeNS(KoXmlNS::style, "page-layout-name")) {
1541 QString masterPageLayoutStyleName = masterStyle->attributeNS(KoXmlNS::style, "page-layout-name", QString());
1542 //kDebug(36003)<<"masterPageLayoutStyleName :"<<masterPageLayoutStyleName;
1543 const KoXmlElement *masterLayoutStyle = odfContext.stylesReader().findStyle(masterPageLayoutStyleName);
1544 if (masterLayoutStyle) {
1545 //kDebug(36003)<<"masterLayoutStyle :"<<masterLayoutStyle;
1546 KoStyleStack styleStack;
1547 styleStack.setTypeProperties("page-layout");
1548 styleStack.push(*masterLayoutStyle);
1549 loadOdfMasterLayoutPage(styleStack);
1550 }
1551 }
1552 }
1553 }
1554
1555 if (style->hasChildNodes() ) {
1556 KoXmlElement element;
1557 forEachElement(element, properties) {
1558 if (element.nodeName() == "style:background-image") {
1559 QString imagePath = element.attributeNS(KoXmlNS::xlink, "href");
1560 KoStore* store = tableContext.odfContext.store();
1561 if (store->hasFile(imagePath)) {
1562 QByteArray data;
1563 store->extractFile(imagePath, data);
1564 QImage image = QImage::fromData(data);
1565
1566 if( image.isNull() ) {
1567 continue;
1568 }
1569
1570 setBackgroundImage(image);
1571
1572 BackgroundImageProperties bgProperties;
1573 if( element.hasAttribute("draw:opacity") ) {
1574 QString opacity = element.attribute("draw:opacity", "");
1575 if( opacity.endsWith(QLatin1Char('%')) ) {
1576 opacity.chop(1);
1577 }
1578 bool ok;
1579 float opacityFloat = opacity.toFloat( &ok );
1580 if( ok ) {
1581 bgProperties.opacity = opacityFloat;
1582 }
1583 }
1584 //TODO
1585 //if( element.hasAttribute("style:filterName") ) {
1586 //}
1587 if( element.hasAttribute("style:position") ) {
1588 const QString positionAttribute = element.attribute("style:position","");
1589 const QStringList positionList = positionAttribute.split(' ', QString::SkipEmptyParts);
1590 if( positionList.size() == 1) {
1591 const QString position = positionList.at(0);
1592 if( position == "left" ) {
1593 bgProperties.horizontalPosition = BackgroundImageProperties::Left;
1594 }
1595 if( position == "center" ) {
1596 //NOTE the standard is too vague to know what center alone means, we assume that it means both centered
1597 bgProperties.horizontalPosition = BackgroundImageProperties::HorizontalCenter;
1598 bgProperties.verticalPosition = BackgroundImageProperties::VerticalCenter;
1599 }
1600 if( position == "right" ) {
1601 bgProperties.horizontalPosition = BackgroundImageProperties::Right;
1602 }
1603 if( position == "top" ) {
1604 bgProperties.verticalPosition = BackgroundImageProperties::Top;
1605 }
1606 if( position == "bottom" ) {
1607 bgProperties.verticalPosition = BackgroundImageProperties::Bottom;
1608 }
1609 }
1610 else if (positionList.size() == 2) {
1611 const QString verticalPosition = positionList.at(0);
1612 const QString horizontalPosition = positionList.at(1);
1613 if( horizontalPosition == "left" ) {
1614 bgProperties.horizontalPosition = BackgroundImageProperties::Left;
1615 }
1616 if( horizontalPosition == "center" ) {
1617 bgProperties.horizontalPosition = BackgroundImageProperties::HorizontalCenter;
1618 }
1619 if( horizontalPosition == "right" ) {
1620 bgProperties.horizontalPosition = BackgroundImageProperties::Right;
1621 }
1622 if( verticalPosition == "top" ) {
1623 bgProperties.verticalPosition = BackgroundImageProperties::Top;
1624 }
1625 if( verticalPosition == "center" ) {
1626 bgProperties.verticalPosition = BackgroundImageProperties::VerticalCenter;
1627 }
1628 if( verticalPosition == "bottom" ) {
1629 bgProperties.verticalPosition = BackgroundImageProperties::Bottom;
1630 }
1631 }
1632 }
1633 if( element.hasAttribute("style:repeat") ) {
1634 const QString repeat = element.attribute("style:repeat");
1635 if( repeat == "no-repeat" ) {
1636 bgProperties.repeat = BackgroundImageProperties::NoRepeat;
1637 }
1638 if( repeat == "repeat" ) {
1639 bgProperties.repeat = BackgroundImageProperties::Repeat;
1640 }
1641 if( repeat == "stretch" ) {
1642 bgProperties.repeat = BackgroundImageProperties::Stretch;
1643 }
1644 }
1645 setBackgroundImageProperties(bgProperties);
1646 }
1647 }
1648 }
1649
1650 }
1651 }
1652 }
1653
1654 // Cell style regions
1655 QHash<QString, QRegion> cellStyleRegions;
1656 // Cell style regions (row defaults)
1657 QHash<QString, QRegion> rowStyleRegions;
1658 // Cell style regions (column defaults)
1659 QHash<QString, QRegion> columnStyleRegions;
1660 IntervalMap<QString> columnStyles;
1661
1662 // List of shapes that need to have their size recalculated after loading is complete
1663 QList<ShapeLoadingData> shapeData;
1664
1665 int rowIndex = 1;
1666 int indexCol = 1;
1667 int maxColumn = 1;
1668 KoXmlNode rowNode = sheetElement.firstChild();
1669 // Some spreadsheet programs may support more rows than
1670 // KSpread so limit the number of repeated rows.
1671 // FIXME POSSIBLE DATA LOSS!
1672
1673 // First load all style information for rows, columns and cells
1674 while (!rowNode.isNull() && rowIndex <= KS_rowMax) {
1675 //kDebug(36003) << " rowIndex :" << rowIndex << " indexCol :" << indexCol;
1676 KoXmlElement rowElement = rowNode.toElement();
1677 if (!rowElement.isNull()) {
1678 // slightly faster
1679 KoXml::load(rowElement);
1680
1681 //kDebug(36003) << " Sheet::loadOdf rowElement.tagName() :" << rowElement.localName();
1682 if (rowElement.namespaceURI() == KoXmlNS::table) {
1683 if (rowElement.localName() == "table-header-columns") {
1684 // NOTE Handle header cols as ordinary ones
1685 // as long as they're not supported.
1686 loadColumnNodes(rowElement, indexCol, maxColumn, odfContext, columnStyleRegions, columnStyles);
1687 } else if (rowElement.localName() == "table-column-group") {
1688 loadColumnNodes(rowElement, indexCol, maxColumn, odfContext, columnStyleRegions, columnStyles);
1689 } else if (rowElement.localName() == "table-column" && indexCol <= KS_colMax) {
1690 //kDebug(36003) << " table-column found : index column before" << indexCol;
1691 loadColumnFormat(rowElement, odfContext.stylesReader(), indexCol, columnStyleRegions, columnStyles);
1692 //kDebug(36003) << " table-column found : index column after" << indexCol;
1693 maxColumn = qMax(maxColumn, indexCol - 1);
1694 } else if (rowElement.localName() == "table-header-rows") {
1695 // NOTE Handle header rows as ordinary ones
1696 // as long as they're not supported.
1697 loadRowNodes(rowElement, rowIndex, maxColumn, tableContext, rowStyleRegions, cellStyleRegions, columnStyles, autoStyles, shapeData);
1698 } else if (rowElement.localName() == "table-row-group") {
1699 loadRowNodes(rowElement, rowIndex, maxColumn, tableContext, rowStyleRegions, cellStyleRegions, columnStyles, autoStyles, shapeData);
1700 } else if (rowElement.localName() == "table-row") {
1701 //kDebug(36003) << " table-row found :index row before" << rowIndex;
1702 int columnMaximal = loadRowFormat(rowElement, rowIndex, tableContext,
1703 rowStyleRegions, cellStyleRegions, columnStyles, autoStyles, shapeData);
1704 // allow the row to define more columns then defined via table-column
1705 maxColumn = qMax(maxColumn, columnMaximal);
1706 //kDebug(36003) << " table-row found :index row after" << rowIndex;
1707 } else if (rowElement.localName() == "shapes") {
1708 // OpenDocument v1.1, 8.3.4 Shapes:
1709 // The <table:shapes> element contains all graphic shapes
1710 // with an anchor on the table this element is a child of.
1711 KoShapeLoadingContext* shapeLoadingContext = tableContext.shapeContext;
1712 KoXmlElement element;
1713 forEachElement(element, rowElement) {
1714 if (element.namespaceURI() != KoXmlNS::draw)
1715 continue;
1716 loadOdfObject(element, *shapeLoadingContext);
1717 }
1718 }
1719 }
1720
1721 // don't need it anymore
1722 KoXml::unload(rowElement);
1723 }
1724
1725 rowNode = rowNode.nextSibling();
1726
1727 int count = map()->increaseLoadedRowsCounter();
1728 if (updater && count >= 0) updater->setProgress(count);
1729 }
1730
1731 // now recalculate the size for embedded shapes that had sizes specified relative to a bottom-right corner cell
1732 foreach (const ShapeLoadingData& sd, shapeData) {
1733 // subtract offset because the accumulated width and height we calculate below starts
1734 // at the top-left corner of this cell, but the shape can have an offset to that corner
1735 QSizeF size = QSizeF( sd.endPoint.x() - sd.offset.x(), sd.endPoint.y() - sd.offset.y());
1736 for (int col = sd.startCell.x(); col < sd.endCell.firstRange().left(); ++col)
1737 size += QSizeF(columnFormat(col)->width(), 0.0);
1738 if (sd.endCell.firstRange().top() > sd.startCell.y())
1739 size += QSizeF(0.0, rowFormats()->totalRowHeight(sd.startCell.y(), sd.endCell.firstRange().top() - 1));
1740 sd.shape->setSize(size);
1741 }
1742
1743 QList<QPair<QRegion, Style> > styleRegions;
1744 QList<QPair<QRegion, Conditions> > conditionRegions;
1745 // insert the styles into the storage (column defaults)
1746 kDebug(36003) << "Inserting column default cell styles ...";
1747 loadOdfInsertStyles(autoStyles, columnStyleRegions, conditionalStyles,
1748 QRect(1, 1, maxColumn, rowIndex - 1), styleRegions, conditionRegions);
1749 // insert the styles into the storage (row defaults)
1750 kDebug(36003) << "Inserting row default cell styles ...";
1751 loadOdfInsertStyles(autoStyles, rowStyleRegions, conditionalStyles,
1752 QRect(1, 1, maxColumn, rowIndex - 1), styleRegions, conditionRegions);
1753 // insert the styles into the storage
1754 kDebug(36003) << "Inserting cell styles ...";
1755 loadOdfInsertStyles(autoStyles, cellStyleRegions, conditionalStyles,
1756 QRect(1, 1, maxColumn, rowIndex - 1), styleRegions, conditionRegions);
1757
1758 cellStorage()->loadStyles(styleRegions);
1759 cellStorage()->loadConditions(conditionRegions);
1760
1761 if (sheetElement.hasAttributeNS(KoXmlNS::table, "print-ranges")) {
1762 // e.g.: Sheet4.A1:Sheet4.E28
1763 QString range = sheetElement.attributeNS(KoXmlNS::table, "print-ranges", QString());
1764 Region region(Region::loadOdf(range));
1765 if (!region.firstSheet() || sheetName() == region.firstSheet()->sheetName())
1766 printSettings()->setPrintRegion(region);
1767 }
1768
1769 if (sheetElement.attributeNS(KoXmlNS::table, "protected", QString()) == "true") {
1770 loadOdfProtection(sheetElement);
1771 }
1772 return true;
1773}
1774
1775void Sheet::loadOdfObject(const KoXmlElement& element, KoShapeLoadingContext& shapeContext)
1776{
1777 KoShape* shape = KoShapeRegistry::instance()->createShapeFromOdf(element, shapeContext);
1778 if (!shape)
1779 return;
1780 addShape(shape);
1781 dynamic_cast<ShapeApplicationData*>(shape->applicationData())->setAnchoredToCell(false);
1782}
1783
1784void Sheet::loadOdfMasterLayoutPage(KoStyleStack &styleStack)
1785{
1786 KoPageLayout pageLayout;
1787
1788 if (styleStack.hasProperty(KoXmlNS::fo, "page-width")) {
1789 pageLayout.width = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "page-width"));
1790 }
1791 if (styleStack.hasProperty(KoXmlNS::fo, "page-height")) {
1792 pageLayout.height = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "page-height"));
1793 }
1794 if (styleStack.hasProperty(KoXmlNS::fo, "margin-top")) {
1795 pageLayout.topMargin = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "margin-top"));
1796 }
1797 if (styleStack.hasProperty(KoXmlNS::fo, "margin-bottom")) {
1798 pageLayout.bottomMargin = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "margin-bottom"));
1799 }
1800 if (styleStack.hasProperty(KoXmlNS::fo, "margin-left")) {
1801 pageLayout.leftMargin = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "margin-left"));
1802 }
1803 if (styleStack.hasProperty(KoXmlNS::fo, "margin-right")) {
1804 pageLayout.rightMargin = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "margin-right"));
1805 }
1806 if (styleStack.hasProperty(KoXmlNS::style, "writing-mode")) {
1807 kDebug(36003) << "styleStack.hasAttribute( style:writing-mode ) :" << styleStack.hasProperty(KoXmlNS::style, "writing-mode");
1808 const QString writingMode = styleStack.property(KoXmlNS::style, "writing-mode");
1809 if (writingMode == "lr-tb") {
1810 setLayoutDirection(Qt::LeftToRight);
1811 } else if (writingMode == "rl-tb") {
1812 setLayoutDirection(Qt::RightToLeft);
1813 } else {
1814 // Set the layout direction to the direction of the sheet name.
1815 checkContentDirection(sheetName());
1816 }
1817 //TODO
1818 //<value>lr-tb</value>
1819 //<value>rl-tb</value>
1820 //<value>tb-rl</value>
1821 //<value>tb-lr</value>
1822 //<value>lr</value>
1823 //<value>rl</value>
1824 //<value>tb</value>
1825 //<value>page</value>
1826
1827 } else {
1828 // Set the layout direction to the direction of the sheet name.
1829 checkContentDirection(sheetName());
1830 }
1831 if (styleStack.hasProperty(KoXmlNS::style, "print-orientation")) {
1832 pageLayout.orientation = (styleStack.property(KoXmlNS::style, "print-orientation") == "landscape")
1833 ? KoPageFormat::Landscape : KoPageFormat::Portrait;
1834 }
1835 if (styleStack.hasProperty(KoXmlNS::style, "num-format")) {
1836 //not implemented into kspread
1837 //These attributes specify the numbering style to use.
1838 //If a numbering style is not specified, the numbering style is inherited from
1839 //the page style. See section 6.7.8 for information on these attributes
1840 kDebug(36003) << " num-format :" << styleStack.property(KoXmlNS::style, "num-format");
1841
1842 }
1843 if (styleStack.hasProperty(KoXmlNS::fo, "background-color")) {
1844 //TODO
1845 kDebug(36003) << " fo:background-color :" << styleStack.property(KoXmlNS::fo, "background-color");
1846 }
1847 if (styleStack.hasProperty(KoXmlNS::style, "print")) {
1848 //todo parsing
1849 QString str = styleStack.property(KoXmlNS::style, "print");
1850 kDebug(36003) << " style:print :" << str;
1851
1852 if (str.contains("headers")) {
1853 //TODO implement it into kspread
1854 }
1855 if (str.contains("grid")) {
1856 print()->settings()->setPrintGrid(true);
1857 }
1858 if (str.contains("annotations")) {
1859 //TODO it's not implemented
1860 }
1861 if (str.contains("objects")) {
1862 //TODO it's not implemented
1863 }
1864 if (str.contains("charts")) {
1865 //TODO it's not implemented
1866 }
1867 if (str.contains("drawings")) {
1868 //TODO it's not implemented
1869 }
1870 if (str.contains("formulas")) {
1871 d->showFormula = true;
1872 }
1873 if (str.contains("zero-values")) {
1874 //TODO it's not implemented
1875 }
1876 }
1877 if (styleStack.hasProperty(KoXmlNS::style, "table-centering")) {
1878 QString str = styleStack.property(KoXmlNS::style, "table-centering");
1879 //TODO not implemented into kspread
1880 kDebug(36003) << " styleStack.attribute( style:table-centering ) :" << str;
1881#if 0
1882 if (str == "horizontal") {
1883 } else if (str == "vertical") {
1884 } else if (str == "both") {
1885 } else if (str == "none") {
1886 } else
1887 kDebug(36003) << " table-centering unknown :" << str;
1888#endif
1889 }
1890 print()->settings()->setPageLayout(pageLayout);
1891}
1892
1893
1894bool Sheet::loadColumnFormat(const KoXmlElement& column,
1895 const KoOdfStylesReader& stylesReader, int & indexCol,
1896 QHash<QString, QRegion>& columnStyleRegions, IntervalMap<QString>& columnStyles)
1897{
1898// kDebug(36003)<<"bool Sheet::loadColumnFormat(const KoXmlElement& column, const KoOdfStylesReader& stylesReader, unsigned int & indexCol ) index Col :"<<indexCol;
1899
1900 bool isNonDefaultColumn = false;
1901
1902 int number = 1;
1903 if (column.hasAttributeNS(KoXmlNS::table, "number-columns-repeated")) {
1904 bool ok = true;
1905 int n = column.attributeNS(KoXmlNS::table, "number-columns-repeated", QString()).toInt(&ok);
1906 if (ok)
1907 // Some spreadsheet programs may support more rows than KSpread so
1908 // limit the number of repeated rows.
1909 // FIXME POSSIBLE DATA LOSS!
1910 number = qMin(n, KS_colMax - indexCol + 1);
1911 //kDebug(36003) << "Repeated:" << number;
1912 }
1913
1914 if (column.hasAttributeNS(KoXmlNS::table, "default-cell-style-name")) {
1915 const QString styleName = column.attributeNS(KoXmlNS::table, "default-cell-style-name", QString());
1916 if (!styleName.isEmpty()) {
1917 columnStyleRegions[styleName] += QRect(indexCol, 1, number, KS_rowMax);
1918 columnStyles.insert(indexCol, indexCol+number-1, styleName);
1919 }
1920 }
1921
1922 enum { Visible, Collapsed, Filtered } visibility = Visible;
1923 if (column.hasAttributeNS(KoXmlNS::table, "visibility")) {
1924 const QString string = column.attributeNS(KoXmlNS::table, "visibility", "visible");
1925 if (string == "collapse")
1926 visibility = Collapsed;
1927 else if (string == "filter")
1928 visibility = Filtered;
1929 isNonDefaultColumn = true;
1930 }
1931
1932 KoStyleStack styleStack;
1933 if (column.hasAttributeNS(KoXmlNS::table, "style-name")) {
1934 QString str = column.attributeNS(KoXmlNS::table, "style-name", QString());
1935 const KoXmlElement *style = stylesReader.findStyle(str, "table-column");
1936 if (style) {
1937 styleStack.push(*style);
1938 isNonDefaultColumn = true;
1939 }
1940 }
1941 styleStack.setTypeProperties("table-column"); //style for column
1942
1943 double width = -1.0;
1944 if (styleStack.hasProperty(KoXmlNS::style, "column-width")) {
1945 width = KoUnit::parseValue(styleStack.property(KoXmlNS::style, "column-width") , -1.0);
1946 //kDebug(36003) << " style:column-width : width :" << width;
1947 isNonDefaultColumn = true;
1948 }
1949
1950 bool insertPageBreak = false;
1951 if (styleStack.hasProperty(KoXmlNS::fo, "break-before")) {
1952 QString str = styleStack.property(KoXmlNS::fo, "break-before");
1953 if (str == "page") {
1954 insertPageBreak = true;
1955 } else {
1956 // kDebug(36003) << " str :" << str;
1957 }
1958 isNonDefaultColumn = true;
1959 } else if (styleStack.hasProperty(KoXmlNS::fo, "break-after")) {
1960 // TODO
1961 }
1962
1963 // If it's a default column, we can return here.
1964 // This saves the iteration, which can be caused by column cell default styles,
1965 // but which are not inserted here.
1966 if (!isNonDefaultColumn) {
1967 indexCol += number;
1968 return true;
1969 }
1970
1971 for (int i = 0; i < number; ++i) {
1972 //kDebug(36003) << " insert new column: pos :" << indexCol << " width :" << width << " hidden ?" << visibility;
1973
1974 if (isNonDefaultColumn) {
1975 ColumnFormat* cf = nonDefaultColumnFormat(indexCol);
1976
1977 if (width != -1.0) //safe
1978 cf->setWidth(width);
1979 if (insertPageBreak) {
1980 cf->setPageBreak(true);
1981 }
1982 if (visibility == Collapsed)
1983 cf->setHidden(true);
1984 else if (visibility == Filtered)
1985 cf->setFiltered(true);
1986
1987 cf->setPageBreak(insertPageBreak);
1988 }
1989 ++indexCol;
1990 }
1991// kDebug(36003)<<" after index column !!!!!!!!!!!!!!!!!! :"<<indexCol;
1992 return true;
1993}
1994
1995void Sheet::loadOdfInsertStyles(const Styles& autoStyles,
1996 const QHash<QString, QRegion>& styleRegions,
1997 const QHash<QString, Conditions>& conditionalStyles,
1998 const QRect& usedArea,
1999 QList<QPair<QRegion, Style> >& outStyleRegions,
2000 QList<QPair<QRegion, Conditions> >& outConditionalStyles)
2001{
2002 const QList<QString> styleNames = styleRegions.keys();
2003 for (int i = 0; i < styleNames.count(); ++i) {
2004 if (!autoStyles.contains(styleNames[i]) && !map()->styleManager()->style(styleNames[i])) {
2005 kWarning(36003) << "\t" << styleNames[i] << " not used";
2006 continue;
2007 }
2008 const bool hasConditions = conditionalStyles.contains(styleNames[i]);
2009 const QRegion styleRegion = styleRegions[styleNames[i]] & QRegion(usedArea);
2010 if (hasConditions)
2011 outConditionalStyles.append(qMakePair(styleRegion, conditionalStyles[styleNames[i]]));
2012 if (autoStyles.contains(styleNames[i])) {
2013 //kDebug(36003) << "\tautomatic:" << styleNames[i] << " at" << styleRegion.rectCount() << "rects";
2014 Style style;
2015 style.setDefault(); // "overwrite" existing style
2016 style.merge(autoStyles[styleNames[i]]);
2017 outStyleRegions.append(qMakePair(styleRegion, style));
2018 } else {
2019 const CustomStyle* namedStyle = map()->styleManager()->style(styleNames[i]);
2020 //kDebug(36003) << "\tcustom:" << namedStyle->name() << " at" << styleRegion.rectCount() << "rects";
2021 Style style;
2022 style.setDefault(); // "overwrite" existing style
2023 style.merge(*namedStyle);
2024 outStyleRegions.append(qMakePair(styleRegion, style));
2025 }
2026 }
2027}
2028
2029int Sheet::loadRowFormat(const KoXmlElement& row, int &rowIndex,
2030 OdfLoadingContext& tableContext,
2031 QHash<QString, QRegion>& rowStyleRegions,
2032 QHash<QString, QRegion>& cellStyleRegions,
2033 const IntervalMap<QString>& columnStyles,
2034 const Styles& autoStyles,
2035 QList<ShapeLoadingData>& shapeData)
2036{
2037 static const QString sStyleName = QString::fromLatin1("style-name");
2038 static const QString sNumberRowsRepeated = QString::fromLatin1("number-rows-repeated");
2039 static const QString sDefaultCellStyleName = QString::fromLatin1("default-cell-style-name");
2040 static const QString sVisibility = QString::fromLatin1("visibility");
2041 static const QString sVisible = QString::fromLatin1("visible");
2042 static const QString sCollapse = QString::fromLatin1("collapse");
2043 static const QString sFilter = QString::fromLatin1("filter");
2044 static const QString sPage = QString::fromLatin1("page");
2045 static const QString sTableCell = QString::fromLatin1("table-cell");
2046 static const QString sCoveredTableCell = QString::fromLatin1("covered-table-cell");
2047 static const QString sNumberColumnsRepeated = QString::fromLatin1("number-columns-repeated");
2048
2049// kDebug(36003)<<"Sheet::loadRowFormat( const KoXmlElement& row, int &rowIndex,const KoOdfStylesReader& stylesReader, bool isLast )***********";
2050 KoOdfLoadingContext& odfContext = tableContext.odfContext;
2051 bool isNonDefaultRow = false;
2052
2053 KoStyleStack styleStack;
2054 if (row.hasAttributeNS(KoXmlNS::table, sStyleName)) {
2055 QString str = row.attributeNS(KoXmlNS::table, sStyleName, QString());
2056 const KoXmlElement *style = odfContext.stylesReader().findStyle(str, "table-row");
2057 if (style) {
2058 styleStack.push(*style);
2059 isNonDefaultRow = true;
2060 }
2061 }
2062 styleStack.setTypeProperties("table-row");
2063
2064 int number = 1;
2065 if (row.hasAttributeNS(KoXmlNS::table, sNumberRowsRepeated)) {
2066 bool ok = true;
2067 int n = row.attributeNS(KoXmlNS::table, sNumberRowsRepeated, QString()).toInt(&ok);
2068 if (ok)
2069 // Some spreadsheet programs may support more rows than KSpread so
2070 // limit the number of repeated rows.
2071 // FIXME POSSIBLE DATA LOSS!
2072 number = qMin(n, KS_rowMax - rowIndex + 1);
2073 }
2074
2075 QString rowCellStyleName;
2076 if (row.hasAttributeNS(KoXmlNS::table, sDefaultCellStyleName)) {
2077 rowCellStyleName = row.attributeNS(KoXmlNS::table, sDefaultCellStyleName, QString());
2078 if (!rowCellStyleName.isEmpty()) {
2079 rowStyleRegions[rowCellStyleName] += QRect(1, rowIndex, KS_colMax, number);
2080 }
2081 }
2082
2083 double height = -1.0;
2084 if (styleStack.hasProperty(KoXmlNS::style, "row-height")) {
2085 height = KoUnit::parseValue(styleStack.property(KoXmlNS::style, "row-height") , -1.0);
2086 // kDebug(36003)<<" properties style:row-height : height :"<<height;
2087 isNonDefaultRow = true;
2088 }
2089
2090 enum { Visible, Collapsed, Filtered } visibility = Visible;
2091 if (row.hasAttributeNS(KoXmlNS::table, sVisibility)) {
2092 const QString string = row.attributeNS(KoXmlNS::table, sVisibility, sVisible);
2093 if (string == sCollapse)
2094 visibility = Collapsed;
2095 else if (string == sFilter)
2096 visibility = Filtered;
2097 isNonDefaultRow = true;
2098 }
2099
2100 bool insertPageBreak = false;
2101 if (styleStack.hasProperty(KoXmlNS::fo, "break-before")) {
2102 QString str = styleStack.property(KoXmlNS::fo, "break-before");
2103 if (str == sPage) {
2104 insertPageBreak = true;
2105 }
2106 // else
2107 // kDebug(36003)<<" str :"<<str;
2108 isNonDefaultRow = true;
2109 } else if (styleStack.hasProperty(KoXmlNS::fo, "break-after")) {
2110 // TODO
2111 }
2112
2113// kDebug(36003)<<" create non defaultrow format :"<<rowIndex<<" repeate :"<<number<<" height :"<<height;
2114 if (isNonDefaultRow) {
2115 if (height != -1.0)
2116 d->rows.setRowHeight(rowIndex, rowIndex + number - 1, height);
2117 d->rows.setPageBreak(rowIndex, rowIndex + number - 1, insertPageBreak);
2118 if (visibility == Collapsed)
2119 d->rows.setHidden(rowIndex, rowIndex + number - 1, true);
2120 else if (visibility == Filtered)
2121 d->rows.setFiltered(rowIndex, rowIndex + number - 1, true);
2122 }
2123
2124 int columnIndex = 1;
2125 int columnMaximal = 0;
2126 const int endRow = qMin(rowIndex + number - 1, KS_rowMax);
2127
2128 KoXmlElement cellElement;
2129 forEachElement(cellElement, row) {
2130 if (cellElement.namespaceURI() != KoXmlNS::table)
2131 continue;
2132 if (cellElement.localName() != sTableCell && cellElement.localName() != sCoveredTableCell)
2133 continue;
2134
2135
2136 bool ok = false;
2137 const int n = cellElement.attributeNS(KoXmlNS::table, sNumberColumnsRepeated, QString()).toInt(&ok);
2138 // Some spreadsheet programs may support more columns than
2139 // KSpread so limit the number of repeated columns.
2140 const int numberColumns = ok ? qMin(n, KS_colMax - columnIndex + 1) : 1;
2141 columnMaximal = qMax(numberColumns, columnMaximal);
2142
2143 // Styles are inserted at the end of the loading process, so check the XML directly here.
2144 const QString styleName = cellElement.attributeNS(KoXmlNS::table , sStyleName, QString());
2145 if (!styleName.isEmpty())
2146 cellStyleRegions[styleName] += QRect(columnIndex, rowIndex, numberColumns, number);
2147
2148 // figure out exact cell style for loading of cell content
2149 QString cellStyleName = styleName;
2150 if (cellStyleName.isEmpty())
2151 cellStyleName = rowCellStyleName;
2152 if (cellStyleName.isEmpty())
2153 cellStyleName = columnStyles.get(columnIndex);
2154
2155 Cell cell(this, columnIndex, rowIndex);
2156 cell.loadOdf(cellElement, tableContext, autoStyles, cellStyleName, shapeData);
2157
2158 if (!cell.comment().isEmpty())
2159 cellStorage()->setComment(Region(columnIndex, rowIndex, numberColumns, number, this), cell.comment());
2160 if (!cell.conditions().isEmpty())
2161 cellStorage()->setConditions(Region(columnIndex, rowIndex, numberColumns, number, this), cell.conditions());
2162 if (!cell.validity().isEmpty())
2163 cellStorage()->setValidity(Region(columnIndex, rowIndex, numberColumns, number, this), cell.validity());
2164
2165 if (!cell.hasDefaultContent()) {
2166 // Row-wise filling of PointStorages is faster than column-wise filling.
2167 QSharedPointer<QTextDocument> richText = cell.richText();
2168 for (int r = rowIndex; r <= endRow; ++r) {
2169 for (int c = 0; c < numberColumns; ++c) {
2170 Cell target(this, columnIndex + c, r);
2171 target.setFormula(cell.formula());
2172 target.setUserInput(cell.userInput());
2173 target.setRichText(richText);
2174 target.setValue(cell.value());
2175 if (cell.doesMergeCells()) {
2176 target.mergeCells(columnIndex + c, r, cell.mergedXCells(), cell.mergedYCells());
2177 }
2178 }
2179 }
2180 }
2181 columnIndex += numberColumns;
2182 }
2183
2184 cellStorage()->setRowsRepeated(rowIndex, number);
2185
2186 rowIndex += number;
2187 return columnMaximal;
2188}
2189
2190QRect Sheet::usedArea(bool onlyContent) const
2191{
2192 int maxCols = d->cellStorage->columns(!onlyContent);
2193 int maxRows = d->cellStorage->rows(!onlyContent);
2194
2195 if (!onlyContent) {
2196 maxRows = qMax(maxRows, d->rows.lastNonDefaultRow());
2197
2198 const ColumnFormat* col = firstCol();
2199 while (col) {
2200 if (col->column() > maxCols)
2201 maxCols = col->column();
2202
2203 col = col->next();
2204 }
2205 }
2206
2207 // flake
2208 QRectF shapesBoundingRect;
2209 for (int i = 0; i < d->shapes.count(); ++i)
2210 shapesBoundingRect |= d->shapes[i]->boundingRect();
2211 const QRect shapesCellRange = documentToCellCoordinates(shapesBoundingRect);
2212 maxCols = qMax(maxCols, shapesCellRange.right());
2213 maxRows = qMax(maxRows, shapesCellRange.bottom());
2214
2215 return QRect(1, 1, maxCols, maxRows);
2216}
2217
2218inline int compareCellInRow(const Cell &cell1, const Cell &cell2, int maxCols)
2219{
2220 if (cell1.isNull() != cell2.isNull())
2221 return 0;
2222 if (cell1.isNull())
2223 return 2;
2224 if (maxCols >= 0 && cell1.column() > maxCols)
2225 return 3;
2226 if (cell1.column() != cell2.column())
2227 return 0;
2228 if (!cell1.compareData(cell2))
2229 return 0;
2230 return 1;
2231}
2232
2233inline bool compareCellsInRows(CellStorage *cellStorage, int row1, int row2, int maxCols)
2234{
2235 Cell cell1 = cellStorage->firstInRow(row1);
2236 Cell cell2 = cellStorage->firstInRow(row2);
2237 while (true) {
2238 int r = compareCellInRow(cell1, cell2, maxCols);
2239 if (r == 0)
2240 return false;
2241 if (r != 1)
2242 break;
2243 cell1 = cellStorage->nextInRow(cell1.column(), cell1.row());
2244 cell2 = cellStorage->nextInRow(cell2.column(), cell2.row());
2245 }
2246 return true;
2247}
2248
2249bool Sheet::compareRows(int row1, int row2, int maxCols, OdfSavingContext& tableContext) const
2250{
2251#if 0
2252 if (!d->rows.rowsAreEqual(row1, row2)) {
2253 return false;
2254 }
2255 if (tableContext.rowHasCellAnchoredShapes(this, row1) != tableContext.rowHasCellAnchoredShapes(this, row2)) {
2256 return false;
2257 }
2258 return compareCellsInRows(cellStorage(), row1, row2, maxCols);
2259#else
2260 Q_UNUSED(maxCols);
2261
2262 // Optimized comparison by using the RowRepeatStorage to compare the content
2263 // rather then an expensive loop like compareCellsInRows.
2264 int row1repeated = cellStorage()->rowRepeat(row1);
2265 Q_ASSERT( row2 > row1 );
2266 if (row2 - row1 >= row1repeated) {
2267 return false;
2268 }
2269
2270 // The RowRepeatStorage does not take to-cell anchored shapes into account
2271 // so we need to check for them explicit.
2272 if (tableContext.rowHasCellAnchoredShapes(this, row1) != tableContext.rowHasCellAnchoredShapes(this, row2)) {
2273 return false;
2274 }
2275
2276 // Some sanity-checks to be sure our RowRepeatStorage works as expected.
2277 Q_ASSERT_X( d->rows.rowsAreEqual(row1, row2), __FUNCTION__, QString("Bug in RowRepeatStorage").toLocal8Bit() );
2278 //Q_ASSERT_X( compareCellsInRows(cellStorage(), row1, row2, maxCols), __FUNCTION__, QString("Bug in RowRepeatStorage").toLocal8Bit() );
2279 Q_ASSERT_X( compareCellInRow(cellStorage()->lastInRow(row1), cellStorage()->lastInRow(row2), -1), __FUNCTION__, QString("Bug in RowRepeatStorage").toLocal8Bit() );
2280
2281 // If we reached that point then the both rows are equal.
2282 return true;
2283#endif
2284}
2285
2286void Sheet::saveOdfHeaderFooter(KoXmlWriter &xmlWriter) const
2287{
2288 QString headerLeft = print()->headerFooter()->headLeft();
2289 QString headerCenter = print()->headerFooter()->headMid();
2290 QString headerRight = print()->headerFooter()->headRight();
2291
2292 QString footerLeft = print()->headerFooter()->footLeft();
2293 QString footerCenter = print()->headerFooter()->footMid();
2294 QString footerRight = print()->headerFooter()->footRight();
2295
2296 xmlWriter.startElement("style:header");
2297 if ((!headerLeft.isEmpty())
2298 || (!headerCenter.isEmpty())
2299 || (!headerRight.isEmpty())) {
2300 xmlWriter.startElement("style:region-left");
2301 xmlWriter.startElement("text:p");
2302 convertPart(headerLeft, xmlWriter);
2303 xmlWriter.endElement();
2304 xmlWriter.endElement();
2305
2306 xmlWriter.startElement("style:region-center");
2307 xmlWriter.startElement("text:p");
2308 convertPart(headerCenter, xmlWriter);
2309 xmlWriter.endElement();
2310 xmlWriter.endElement();
2311
2312 xmlWriter.startElement("style:region-right");
2313 xmlWriter.startElement("text:p");
2314 convertPart(headerRight, xmlWriter);
2315 xmlWriter.endElement();
2316 xmlWriter.endElement();
2317 } else {
2318 xmlWriter.startElement("text:p");
2319
2320 xmlWriter.startElement("text:sheet-name");
2321 xmlWriter.addTextNode("???");
2322 xmlWriter.endElement();
2323
2324 xmlWriter.endElement();
2325 }
2326 xmlWriter.endElement();
2327
2328
2329 xmlWriter.startElement("style:footer");
2330 if ((!footerLeft.isEmpty())
2331 || (!footerCenter.isEmpty())
2332 || (!footerRight.isEmpty())) {
2333 xmlWriter.startElement("style:region-left");
2334 xmlWriter.startElement("text:p");
2335 convertPart(footerLeft, xmlWriter);
2336 xmlWriter.endElement();
2337 xmlWriter.endElement(); //style:region-left
2338
2339 xmlWriter.startElement("style:region-center");
2340 xmlWriter.startElement("text:p");
2341 convertPart(footerCenter, xmlWriter);
2342 xmlWriter.endElement();
2343 xmlWriter.endElement();
2344
2345 xmlWriter.startElement("style:region-right");
2346 xmlWriter.startElement("text:p");
2347 convertPart(footerRight, xmlWriter);
2348 xmlWriter.endElement();
2349 xmlWriter.endElement();
2350 } else {
2351 xmlWriter.startElement("text:p");
2352
2353 xmlWriter.startElement("text:sheet-name");
2354 xmlWriter.addTextNode("Page "); // ???
2355 xmlWriter.endElement();
2356
2357 xmlWriter.startElement("text:page-number");
2358 xmlWriter.addTextNode("1"); // ???
2359 xmlWriter.endElement();
2360
2361 xmlWriter.endElement();
2362 }
2363 xmlWriter.endElement();
2364
2365
2366}
2367
2368void Sheet::addText(const QString & text, KoXmlWriter & writer) const
2369{
2370 if (!text.isEmpty())
2371 writer.addTextNode(text);
2372}
2373
2374void Sheet::convertPart(const QString & part, KoXmlWriter & xmlWriter) const
2375{
2376 QString text;
2377 QString var;
2378
2379 bool inVar = false;
2380 uint i = 0;
2381 uint l = part.length();
2382 while (i < l) {
2383 if (inVar || part[i] == '<') {
2384 inVar = true;
2385 var += part[i];
2386 if (part[i] == '>') {
2387 inVar = false;
2388 if (var == "<page>") {
2389 addText(text, xmlWriter);
2390 xmlWriter.startElement("text:page-number");
2391 xmlWriter.addTextNode("1");
2392 xmlWriter.endElement();
2393 } else if (var == "<pages>") {
2394 addText(text, xmlWriter);
2395 xmlWriter.startElement("text:page-count");
2396 xmlWriter.addTextNode("99"); //TODO I think that it can be different from 99
2397 xmlWriter.endElement();
2398 } else if (var == "<date>") {
2399 addText(text, xmlWriter);
2400 //text:p><text:date style:data-style-name="N2" text:date-value="2005-10-02">02/10/2005</text:date>, <text:time>10:20:12</text:time></text:p> "add style" => create new style
2401#if 0 //FIXME
2402 KoXmlElement t = dd.createElement("text:date");
2403 t.setAttribute("text:date-value", "0-00-00");
2404 // todo: "style:data-style-name", "N2"
2405 t.appendChild(dd.createTextNode(QDate::currentDate().toString()));
2406 parent.appendChild(t);
2407#endif
2408 } else if (var == "<time>") {
2409 addText(text, xmlWriter);
2410
2411 xmlWriter.startElement("text:time");
2412 xmlWriter.addTextNode(QTime::currentTime().toString());
2413 xmlWriter.endElement();
2414 } else if (var == "<file>") { // filepath + name
2415 addText(text, xmlWriter);
2416 xmlWriter.startElement("text:file-name");
2417 xmlWriter.addAttribute("text:display", "full");
2418 xmlWriter.addTextNode("???");
2419 xmlWriter.endElement();
2420 } else if (var == "<name>") { // filename
2421 addText(text, xmlWriter);
2422
2423 xmlWriter.startElement("text:title");
2424 xmlWriter.addTextNode("???");
2425 xmlWriter.endElement();
2426 } else if (var == "<author>") {
2427 DocBase* sdoc = doc();
2428 KoDocumentInfo* docInfo = sdoc->documentInfo();
2429
2430 text += docInfo->authorInfo("creator");
2431 addText(text, xmlWriter);
2432 } else if (var == "<email>") {
2433 DocBase* sdoc = doc();
2434 KoDocumentInfo* docInfo = sdoc->documentInfo();
2435
2436 text += docInfo->authorInfo("email");
2437 addText(text, xmlWriter);
2438
2439 } else if (var == "<org>") {
2440 DocBase* sdoc = doc();
2441 KoDocumentInfo* docInfo = sdoc->documentInfo();
2442
2443 text += docInfo->authorInfo("company");
2444 addText(text, xmlWriter);
2445
2446 } else if (var == "<sheet>") {
2447 addText(text, xmlWriter);
2448
2449 xmlWriter.startElement("text:sheet-name");
2450 xmlWriter.addTextNode("???");
2451 xmlWriter.endElement();
2452 } else {
2453 // no known variable:
2454 text += var;
2455 addText(text, xmlWriter);
2456 }
2457
2458 text.clear();
2459 var.clear();
2460 }
2461 } else {
2462 text += part[i];
2463 }
2464 ++i;
2465 }
2466 if (!text.isEmpty() || !var.isEmpty()) {
2467 //we don't have var at the end =>store it
2468 addText(text + var, xmlWriter);
2469 }
2470 kDebug(36003) << " text end :" << text << " var :" << var;
2471}
2472
2473void Sheet::saveOdfBackgroundImage(KoXmlWriter& xmlWriter) const
2474{
2475 const BackgroundImageProperties& properties = backgroundImageProperties();
2476 xmlWriter.startElement("style:backgroundImage");
2477
2478 //xmlWriter.addAttribute("xlink:href", fileName);
2479 xmlWriter.addAttribute("xlink:type", "simple");
2480 xmlWriter.addAttribute("xlink:show", "embed");
2481 xmlWriter.addAttribute("xlink:actuate", "onLoad");
2482
2483 QString opacity = QString("%1%").arg(properties.opacity);
2484 xmlWriter.addAttribute("draw:opacity", opacity);
2485
2486 QString position;
2487 if(properties.horizontalPosition == BackgroundImageProperties::Left) {
2488 position += "left";
2489 }
2490 else if(properties.horizontalPosition == BackgroundImageProperties::HorizontalCenter) {
2491 position += "center";
2492 }
2493 else if(properties.horizontalPosition == BackgroundImageProperties::Right) {
2494 position += "right";
2495 }
2496
2497 position += ' ';
2498
2499 if(properties.verticalPosition == BackgroundImageProperties::Top) {
2500 position += "top";
2501 }
2502 else if(properties.verticalPosition == BackgroundImageProperties::VerticalCenter) {
2503 position += "center";
2504 }
2505 else if(properties.verticalPosition == BackgroundImageProperties::Bottom) {
2506 position += "right";
2507 }
2508 xmlWriter.addAttribute("style:position", position);
2509
2510 QString repeat;
2511 if(properties.repeat == BackgroundImageProperties::NoRepeat) {
2512 repeat = "no-repeat";
2513 }
2514 else if(properties.repeat == BackgroundImageProperties::Repeat) {
2515 repeat = "repeat";
2516 }
2517 else if(properties.repeat == BackgroundImageProperties::Stretch) {
2518 repeat = "stretch";
2519 }
2520 xmlWriter.addAttribute("style:repeat", repeat);
2521
2522 xmlWriter.endElement();
2523}
2524
2525
2526void Sheet::loadOdfSettings(const KoOasisSettings::NamedMap &settings)
2527{
2528 // Find the entry in the map that applies to this sheet (by name)
2529 KoOasisSettings::Items items = settings.entry(sheetName());
2530 if (items.isNull())
2531 return;
2532 setHideZero(!items.parseConfigItemBool("ShowZeroValues"));
2533 setShowGrid(items.parseConfigItemBool("ShowGrid"));
2534 setFirstLetterUpper(items.parseConfigItemBool("FirstLetterUpper"));
2535
2536 int cursorX = qMin(KS_colMax, qMax(1, items.parseConfigItemInt("CursorPositionX") + 1));
2537 int cursorY = qMin(KS_rowMax, qMax(1, items.parseConfigItemInt("CursorPositionY") + 1));
2538 map()->loadingInfo()->setCursorPosition(this, QPoint(cursorX, cursorY));
2539
2540 double offsetX = items.parseConfigItemDouble("xOffset");
2541 double offsetY = items.parseConfigItemDouble("yOffset");
2542 map()->loadingInfo()->setScrollingOffset(this, QPointF(offsetX, offsetY));
2543
2544 setShowFormulaIndicator(items.parseConfigItemBool("ShowFormulaIndicator"));
2545 setShowCommentIndicator(items.parseConfigItemBool("ShowCommentIndicator"));
2546 setShowPageOutline(items.parseConfigItemBool("ShowPageOutline"));
2547 setLcMode(items.parseConfigItemBool("lcmode"));
2548 setAutoCalculationEnabled(items.parseConfigItemBool("autoCalc"));
2549 setShowColumnNumber(items.parseConfigItemBool("ShowColumnNumber"));
2550}
2551
2552void Sheet::saveOdfSettings(KoXmlWriter &settingsWriter) const
2553{
2554 //not into each page into oo spec
2555 settingsWriter.addConfigItem("ShowZeroValues", !getHideZero());
2556 settingsWriter.addConfigItem("ShowGrid", getShowGrid());
2557 //not define into oo spec
2558 settingsWriter.addConfigItem("FirstLetterUpper", getFirstLetterUpper());
2559 settingsWriter.addConfigItem("ShowFormulaIndicator", getShowFormulaIndicator());
2560 settingsWriter.addConfigItem("ShowCommentIndicator", getShowCommentIndicator());
2561 settingsWriter.addConfigItem("ShowPageOutline", isShowPageOutline());
2562 settingsWriter.addConfigItem("lcmode", getLcMode());
2563 settingsWriter.addConfigItem("autoCalc", isAutoCalculationEnabled());
2564 settingsWriter.addConfigItem("ShowColumnNumber", getShowColumnNumber());
2565}
2566
2567bool Sheet::saveOdf(OdfSavingContext& tableContext)
2568{
2569 KoXmlWriter & xmlWriter = tableContext.shapeContext.xmlWriter();
2570 KoGenStyles & mainStyles = tableContext.shapeContext.mainStyles();
2571 xmlWriter.startElement("table:table");
2572 xmlWriter.addAttribute("table:name", sheetName());
2573 xmlWriter.addAttribute("table:style-name", saveOdfSheetStyleName(mainStyles));
2574 QByteArray pwd;
2575 password(pwd);
2576 if (!pwd.isNull()) {
2577 xmlWriter.addAttribute("table:protected", "true");
2578 QByteArray str = KCodecs::base64Encode(pwd);
2579 // FIXME Stefan: see OpenDocument spec, ch. 17.3 Encryption
2580 xmlWriter.addAttribute("table:protection-key", QString(str));
2581 }
2582 QRect _printRange = printSettings()->printRegion().lastRange();
2583 if (!_printRange.isNull() &&_printRange != (QRect(QPoint(1, 1), QPoint(KS_colMax, KS_rowMax)))) {
2584 const Region region(_printRange, this);
2585 if (region.isValid()) {
2586 kDebug(36003) << region;
2587 xmlWriter.addAttribute("table:print-ranges", region.saveOdf());
2588 }
2589 }
2590
2591 // flake
2592 // Create a dict of cell anchored shapes with the cell as key.
2593 int sheetAnchoredCount = 0;
2594 foreach(KoShape* shape, d->shapes) {
2595 if (dynamic_cast<ShapeApplicationData*>(shape->applicationData())->isAnchoredToCell()) {
2596 qreal dummy;
2597 const QPointF position = shape->position();
2598 const int col = leftColumn(position.x(), dummy);
2599 const int row = topRow(position.y(), dummy);
2600 tableContext.insertCellAnchoredShape(this, row, col, shape);
2601 } else {
2602 sheetAnchoredCount++;
2603 }
2604 }
2605
2606 // flake
2607 // Save the remaining shapes, those that are anchored in the page.
2608 if (sheetAnchoredCount) {
2609 xmlWriter.startElement("table:shapes");
2610 foreach(KoShape* shape, d->shapes) {
2611 if (dynamic_cast<ShapeApplicationData*>(shape->applicationData())->isAnchoredToCell())
2612 continue;
2613 shape->saveOdf(tableContext.shapeContext);
2614 }
2615 xmlWriter.endElement();
2616 }
2617
2618 const QRect usedArea = this->usedArea();
2619 saveOdfColRowCell(usedArea.width(), usedArea.height(), tableContext);
2620
2621 xmlWriter.endElement();
2622 return true;
2623}
2624
2625QString Sheet::saveOdfSheetStyleName(KoGenStyles &mainStyles)
2626{
2627 KoGenStyle pageStyle(KoGenStyle::TableAutoStyle, "table"/*FIXME I don't know if name is sheet*/);
2628
2629 KoGenStyle pageMaster(KoGenStyle::MasterPageStyle);
2630 const QString pageLayoutName = printSettings()->saveOdfPageLayout(mainStyles,
2631 getShowFormula(),
2632 !getHideZero());
2633 pageMaster.addAttribute("style:page-layout-name", pageLayoutName);
2634
2635 QBuffer buffer;
2636 buffer.open(QIODevice::WriteOnly);
2637 KoXmlWriter elementWriter(&buffer); // TODO pass indentation level
2638 saveOdfHeaderFooter(elementWriter);
2639
2640 QString elementContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
2641 pageMaster.addChildElement("headerfooter", elementContents);
2642 pageStyle.addAttribute("style:master-page-name", mainStyles.insert(pageMaster, "Standard"));
2643
2644 pageStyle.addProperty("table:display", !isHidden());
2645
2646 if( !backgroundImage().isNull() ) {
2647 QBuffer bgBuffer;
2648 bgBuffer.open(QIODevice::WriteOnly);
2649 KoXmlWriter bgWriter(&bgBuffer); //TODO pass identation level
2650 saveOdfBackgroundImage(bgWriter);
2651
2652 const QString bgContent = QString::fromUtf8(bgBuffer.buffer(), bgBuffer.size());
2653 pageMaster.addChildElement("backgroundImage", bgContent);
2654 }
2655
2656 return mainStyles.insert(pageStyle, "ta");
2657}
2658
2659
2660void Sheet::saveOdfColRowCell(int maxCols, int maxRows, OdfSavingContext& tableContext)
2661{
2662 kDebug(36003) << "Sheet::saveOdfColRowCell:" << d->name;
2663
2664 KoXmlWriter & xmlWriter = tableContext.shapeContext.xmlWriter();
2665 KoGenStyles & mainStyles = tableContext.shapeContext.mainStyles();
2666
2667 // calculate the column/row default cell styles
2668 int maxMaxRows = maxRows; // includes the max row a column default style occupies
2669 // also extends the maximum column/row to include column/row styles
2670 styleStorage()->saveOdfCreateDefaultStyles(maxCols, maxMaxRows, tableContext);
2671 if (tableContext.rowDefaultStyles.count() != 0)
2672 maxRows = qMax(maxRows, (--tableContext.rowDefaultStyles.constEnd()).key());
2673 // Take the actual used area into account so we also catch shapes that are
2674 // anchored after any content.
2675 QRect r = usedArea(false);
2676 maxRows = qMax(maxRows, r.bottom());
2677 maxCols = qMax(maxCols, r.right());
2678 // OpenDocument needs at least one cell per sheet.
2679 maxCols = qMin(KS_colMax, qMax(1, maxCols));
2680 maxRows = qMin(KS_rowMax, qMax(1, maxRows));
2681 maxMaxRows = maxMaxRows;
2682 kDebug(36003) << "\t Sheet dimension:" << maxCols << " x" << maxRows;
2683
2684 // saving the columns
2685 //
2686 int i = 1;
2687 while (i <= maxCols) {
2688 const ColumnFormat* column = columnFormat(i);
2689// kDebug(36003) << "Sheet::saveOdfColRowCell: first col loop:"
2690// << "i:" << i
2691// << "column:" << (column ? column->column() : 0)
2692// << "default:" << (column ? column->isDefault() : false);
2693
2694 //style default layout for column
2695 const Style style = tableContext.columnDefaultStyles.value(i);
2696
2697 int j = i;
2698 int count = 1;
2699
2700 while (j <= maxCols) {
2701 const ColumnFormat* nextColumn = d->columns.next(j);
2702 const int nextColumnIndex = nextColumn ? nextColumn->column() : 0;
2703 const QMap<int, Style>::iterator nextColumnDefaultStyle = tableContext.columnDefaultStyles.upperBound(j);
2704 const int nextStyleColumnIndex = nextColumnDefaultStyle == tableContext.columnDefaultStyles.end()
2705 ? 0 : nextColumnDefaultStyle.key();
2706 // j becomes the index of the adjacent column
2707 ++j;
2708
2709// kDebug(36003) <<"Sheet::saveOdfColRowCell: second col loop:"
2710// << "j:" << j
2711// << "next column:" << (nextColumn ? nextColumn->column() : 0)
2712// << "next styled column:" << nextStyleColumnIndex;
2713
2714 // no next or not the adjacent column?
2715 if ((!nextColumn && !nextStyleColumnIndex) ||
2716 (nextColumnIndex != j && nextStyleColumnIndex != j)) {
2717 // if the origin column was a default column,
2718 if (column->isDefault() && style.isDefault()) {
2719 // we count the default columns
2720 if (!nextColumn && !nextStyleColumnIndex)
2721 count = maxCols - i + 1;
2722 else if (nextColumn && (!nextStyleColumnIndex || nextColumn->column() <= nextStyleColumnIndex))
2723 count = nextColumn->column() - i;
2724 else
2725 count = nextStyleColumnIndex - i;
2726 }
2727 // otherwise we just stop here to process the adjacent
2728 // column in the next iteration of the outer loop
2729 break;
2730 }
2731
2732 // stop, if the next column differs from the current one
2733 if ((nextColumn && (*column != *nextColumn)) || (!nextColumn && !column->isDefault()))
2734 break;
2735 if (style != tableContext.columnDefaultStyles.value(j))
2736 break;
2737 ++count;
2738 }
2739
2740 xmlWriter.startElement("table:table-column");
2741 if (!column->isDefault()) {
2742 KoGenStyle currentColumnStyle(KoGenStyle::TableColumnAutoStyle, "table-column");
2743 currentColumnStyle.addPropertyPt("style:column-width", column->width());
2744 if (column->hasPageBreak()) {
2745 currentColumnStyle.addProperty("fo:break-before", "page");
2746 }
2747 xmlWriter.addAttribute("table:style-name", mainStyles.insert(currentColumnStyle, "co"));
2748 }
2749 if (!column->isDefault() || !style.isDefault()) {
2750 if (!style.isDefault()) {
2751 KoGenStyle currentDefaultCellStyle; // the type is determined in saveOdfStyle
2752 const QString name = style.saveOdf(currentDefaultCellStyle, mainStyles,
2753 map()->styleManager());
2754 xmlWriter.addAttribute("table:default-cell-style-name", name);
2755 }
2756
2757 if (column->isHidden())
2758 xmlWriter.addAttribute("table:visibility", "collapse");
2759 else if (column->isFiltered())
2760 xmlWriter.addAttribute("table:visibility", "filter");
2761 }
2762 if (count > 1)
2763 xmlWriter.addAttribute("table:number-columns-repeated", count);
2764 xmlWriter.endElement();
2765
2766 kDebug(36003) << "Sheet::saveOdfColRowCell: column" << i
2767 << "repeated" << count - 1 << "time(s)";
2768
2769 i += count;
2770 }
2771
2772 // saving the rows and the cells
2773 // we have to loop through all rows of the used area
2774 for (i = 1; i <= maxRows; ++i) {
2775 // default cell style for row
2776 const Style style = tableContext.rowDefaultStyles.value(i);
2777
2778 xmlWriter.startElement("table:table-row");
2779
2780 const bool rowIsDefault = d->rows.isDefaultRow(i);
2781 if (!rowIsDefault) {
2782 KoGenStyle currentRowStyle(KoGenStyle::TableRowAutoStyle, "table-row");
2783 currentRowStyle.addPropertyPt("style:row-height", d->rows.rowHeight(i));
2784 if (d->rows.hasPageBreak(i)) {
2785 currentRowStyle.addProperty("fo:break-before", "page");
2786 }
2787 xmlWriter.addAttribute("table:style-name", mainStyles.insert(currentRowStyle, "ro"));
2788 }
2789
2790 // We cannot use cellStorage()->rowRepeat(i) here cause the RowRepeatStorage only knows
2791 // about the content but not about the shapes anchored to a cell. So, we need to check
2792 // for them here to be sure to catch them even when the content in the cell is repeated.
2793 int repeated = 1;
2794 // empty row?
2795 if (!d->cellStorage->firstInRow(i) && !tableContext.rowHasCellAnchoredShapes(this, i)) { // row is empty
2796// kDebug(36003) <<"Sheet::saveOdfColRowCell: first row loop:"
2797// << " i: " << i
2798// << " row: " << row->row();
2799 int j = i + 1;
2800
2801 // search for
2802 // next non-empty row
2803 // or
2804 // next row with different Format
2805 while (j <= maxRows && !d->cellStorage->firstInRow(j) && !tableContext.rowHasCellAnchoredShapes(this, j)) {
2806// kDebug(36003) <<"Sheet::saveOdfColRowCell: second row loop:"
2807// << " j: " << j
2808// << " row: " << nextRow->row();
2809
2810 // if the reference row has the default row format
2811 if (rowIsDefault && style.isDefault()) {
2812 // if the next is not default, stop here
2813 if (!d->rows.isDefaultRow(j) || !tableContext.rowDefaultStyles.value(j).isDefault())
2814 break;
2815 // otherwise, jump to the next
2816 ++j;
2817 continue;
2818 }
2819
2820 // stop, if the next row differs from the current one
2821 if (!d->rows.rowsAreEqual(i, j))
2822 break;
2823 if (style != tableContext.rowDefaultStyles.value(j))
2824 break;
2825 // otherwise, process the next
2826 ++j;
2827 }
2828 repeated = j - i;
2829
2830 if (repeated > 1)
2831 xmlWriter.addAttribute("table:number-rows-repeated", repeated);
2832 if (!style.isDefault()) {
2833 KoGenStyle currentDefaultCellStyle; // the type is determined in saveOdfCellStyle
2834 const QString name = style.saveOdf(currentDefaultCellStyle, mainStyles,
2835 map()->styleManager());
2836 xmlWriter.addAttribute("table:default-cell-style-name", name);
2837 }
2838 if (d->rows.isHidden(i)) // never true for the default row
2839 xmlWriter.addAttribute("table:visibility", "collapse");
2840 else if (d->rows.isFiltered(i)) // never true for the default row
2841 xmlWriter.addAttribute("table:visibility", "filter");
2842
2843 // NOTE Stefan: Even if paragraph 8.1 states, that rows may be empty, the
2844 // RelaxNG schema does not allow that.
2845 xmlWriter.startElement("table:table-cell");
2846 // Fill the row with empty cells, if there's a row default cell style.
2847 if (!style.isDefault())
2848 xmlWriter.addAttribute("table:number-columns-repeated", QString::number(maxCols));
2849 // Fill the row with empty cells up to the last column with a default cell style.
2850 else if (!tableContext.columnDefaultStyles.isEmpty()) {
2851 const int col = (--tableContext.columnDefaultStyles.constEnd()).key();
2852 xmlWriter.addAttribute("table:number-columns-repeated", QString::number(col));
2853 }
2854 xmlWriter.endElement();
2855
2856 kDebug(36003) << "Sheet::saveOdfColRowCell: empty row" << i
2857 << "repeated" << repeated << "time(s)";
2858
2859 // copy the index for the next row to process
2860 i = j - 1; /*it's already incremented in the for loop*/
2861 } else { // row is not empty
2862 if (!style.isDefault()) {
2863 KoGenStyle currentDefaultCellStyle; // the type is determined in saveOdfCellStyle
2864 const QString name = style.saveOdf(currentDefaultCellStyle, mainStyles,
2865 map()->styleManager());
2866 xmlWriter.addAttribute("table:default-cell-style-name", name);
2867 }
2868 if (d->rows.isHidden(i)) // never true for the default row
2869 xmlWriter.addAttribute("table:visibility", "collapse");
2870 else if (d->rows.isFiltered(i)) // never true for the default row
2871 xmlWriter.addAttribute("table:visibility", "filter");
2872
2873 int j = i + 1;
2874 while (j <= maxRows && compareRows(i, j, maxCols, tableContext)) {
2875 j++;
2876 repeated++;
2877 }
2878 repeated = j - i;
2879 if (repeated > 1) {
2880 kDebug(36003) << "Sheet::saveOdfColRowCell: NON-empty row" << i
2881 << "repeated" << repeated << "times";
2882
2883 xmlWriter.addAttribute("table:number-rows-repeated", repeated);
2884 }
2885
2886 saveOdfCells(i, maxCols, tableContext);
2887
2888 // copy the index for the next row to process
2889 i = j - 1; /*it's already incremented in the for loop*/
2890 }
2891 xmlWriter.endElement();
2892 }
2893
2894 // Fill in rows with empty cells, if there's a column default cell style.
2895 if (!tableContext.columnDefaultStyles.isEmpty()) {
2896 if (maxMaxRows > maxRows) {
2897 xmlWriter.startElement("table:table-row");
2898 if (maxMaxRows > maxRows + 1)
2899 xmlWriter.addAttribute("table:number-rows-repeated", maxMaxRows - maxRows);
2900 xmlWriter.startElement("table:table-cell");
2901 const int col = qMin(maxCols, (--tableContext.columnDefaultStyles.constEnd()).key());
2902 xmlWriter.addAttribute("table:number-columns-repeated", QString::number(col));
2903 xmlWriter.endElement();
2904 xmlWriter.endElement();
2905 }
2906 }
2907}
2908
2909void Sheet::saveOdfCells(int row, int maxCols, OdfSavingContext& tableContext)
2910{
2911 KoXmlWriter & xmlWriter = tableContext.shapeContext.xmlWriter();
2912
2913 int i = 1;
2914 Cell cell(this, i, row);
2915 Cell nextCell = d->cellStorage->nextInRow(i, row);
2916 // handle situations where the row contains shapes and nothing else
2917 if (cell.isDefault() && nextCell.isNull()) {
2918 int nextShape = tableContext.nextAnchoredShape(this, row, i);
2919 if (nextShape)
2920 nextCell = Cell(this, nextShape, row);
2921 }
2922 // while
2923 // the current cell is not a default one
2924 // or
2925 // we have a further cell in this row
2926 do {
2927// kDebug(36003) <<"Sheet::saveOdfCells:"
2928// << " i: " << i
2929// << " column: " << cell.column() << endl;
2930
2931 int repeated = 1;
2932 int column = i;
2933 cell.saveOdf(row, column, repeated, tableContext);
2934 i += repeated;
2935 // stop if we reached the end column
2936 if (i > maxCols || nextCell.isNull())
2937 break;
2938
2939 cell = Cell(this, i, row);
2940 // if we have a shape anchored to an empty cell, ensure that the cell gets also processed
2941 int nextShape = tableContext.nextAnchoredShape(this, row, column);
2942 if (nextShape && ((nextShape < i) || cell.isDefault())) {
2943 cell = Cell(this, nextShape, row);
2944 i = nextShape;
2945 }
2946
2947 nextCell = d->cellStorage->nextInRow(i, row);
2948 } while (!cell.isDefault() || tableContext.cellHasAnchoredShapes(this, cell.row(), cell.column()) || !nextCell.isNull());
2949
2950 // Fill the row with empty cells, if there's a row default cell style.
2951 if (tableContext.rowDefaultStyles.contains(row)) {
2952 if (maxCols >= i) {
2953 xmlWriter.startElement("table:table-cell");
2954 if (maxCols > i)
2955 xmlWriter.addAttribute("table:number-columns-repeated", QString::number(maxCols - i + 1));
2956 xmlWriter.endElement();
2957 }
2958 }
2959 // Fill the row with empty cells up to the last column with a default cell style.
2960 else if (!tableContext.columnDefaultStyles.isEmpty()) {
2961 const int col = (--tableContext.columnDefaultStyles.constEnd()).key();
2962 if (col >= i) {
2963 xmlWriter.startElement("table:table-cell");
2964 if (col > i)
2965 xmlWriter.addAttribute("table:number-columns-repeated", QString::number(col - i + 1));
2966 xmlWriter.endElement();
2967 }
2968 }
2969}
2970
2971bool Sheet::loadXML(const KoXmlElement& sheet)
2972{
2973 bool ok = false;
2974 QString sname = sheetName();
2975 if (!map()->loadingInfo()->loadTemplate()) {
2976 sname = sheet.attribute("name");
2977 if (sname.isEmpty()) {
2978 doc()->setErrorMessage(i18n("Invalid document. Sheet name is empty."));
2979 return false;
2980 }
2981 }
2982
2983 bool detectDirection = true;
2984 QString layoutDir = sheet.attribute("layoutDirection");
2985 if (!layoutDir.isEmpty()) {
2986 if (layoutDir == "rtl") {
2987 detectDirection = false;
2988 setLayoutDirection(Qt::RightToLeft);
2989 } else if (layoutDir == "ltr") {
2990 detectDirection = false;
2991 setLayoutDirection(Qt::LeftToRight);
2992 } else
2993 kDebug() << " Direction not implemented :" << layoutDir;
2994 }
2995 if (detectDirection)
2996 checkContentDirection(sname);
2997
2998 /* older versions of KSpread allowed all sorts of characters that
2999 the parser won't actually understand. Replace these with '_'
3000 Also, the initial character cannot be a space.
3001 */
3002 while (sname[0] == ' ') {
3003 sname.remove(0, 1);
3004 }
3005 for (int i = 0; i < sname.length(); i++) {
3006 if (!(sname[i].isLetterOrNumber() ||
3007 sname[i] == ' ' || sname[i] == '.' || sname[i] == '_')) {
3008 sname[i] = '_';
3009 }
3010 }
3011
3012 // validate sheet name, if it differs from the current one
3013 if (sname != sheetName()) {
3014 /* make sure there are no name collisions with the altered name */
3015 QString testName = sname;
3016 QString baseName = sname;
3017 int nameSuffix = 0;
3018
3019 /* so we don't panic over finding ourself in the following test*/
3020 sname.clear();
3021 while (map()->findSheet(testName) != 0) {
3022 nameSuffix++;
3023 testName = baseName + '_' + QString::number(nameSuffix);
3024 }
3025 sname = testName;
3026
3027 kDebug(36001) << "Sheet::loadXML: table name =" << sname;
3028 setObjectName(sname);
3029 setSheetName(sname, true);
3030 }
3031
3032// (dynamic_cast<SheetIface*>(dcopObject()))->sheetNameHasChanged();
3033
3034 if (sheet.hasAttribute("grid")) {
3035 setShowGrid((int)sheet.attribute("grid").toInt(&ok));
3036 // we just ignore 'ok' - if it didn't work, go on
3037 }
3038 if (sheet.hasAttribute("printGrid")) {
3039 print()->settings()->setPrintGrid((bool)sheet.attribute("printGrid").toInt(&ok));
3040 // we just ignore 'ok' - if it didn't work, go on
3041 }
3042 if (sheet.hasAttribute("printCommentIndicator")) {
3043 print()->settings()->setPrintCommentIndicator((bool)sheet.attribute("printCommentIndicator").toInt(&ok));
3044 // we just ignore 'ok' - if it didn't work, go on
3045 }
3046 if (sheet.hasAttribute("printFormulaIndicator")) {
3047 print()->settings()->setPrintFormulaIndicator((bool)sheet.attribute("printFormulaIndicator").toInt(&ok));
3048 // we just ignore 'ok' - if it didn't work, go on
3049 }
3050 if (sheet.hasAttribute("hide")) {
3051 setHidden((bool)sheet.attribute("hide").toInt(&ok));
3052 // we just ignore 'ok' - if it didn't work, go on
3053 }
3054 if (sheet.hasAttribute("showFormula")) {
3055 setShowFormula((bool)sheet.attribute("showFormula").toInt(&ok));
3056 // we just ignore 'ok' - if it didn't work, go on
3057 }
3058 //Compatibility with KSpread 1.1.x
3059 if (sheet.hasAttribute("formular")) {
3060 setShowFormula((bool)sheet.attribute("formular").toInt(&ok));
3061 // we just ignore 'ok' - if it didn't work, go on
3062 }
3063 if (sheet.hasAttribute("showFormulaIndicator")) {
3064 setShowFormulaIndicator((bool)sheet.attribute("showFormulaIndicator").toInt(&ok));
3065 // we just ignore 'ok' - if it didn't work, go on
3066 }
3067 if (sheet.hasAttribute("showCommentIndicator")) {
3068 setShowCommentIndicator((bool)sheet.attribute("showCommentIndicator").toInt(&ok));
3069 // we just ignore 'ok' - if it didn't work, go on
3070 }
3071 if (sheet.hasAttribute("borders")) {
3072 setShowPageOutline((bool)sheet.attribute("borders").toInt(&ok));
3073 // we just ignore 'ok' - if it didn't work, go on
3074 }
3075 if (sheet.hasAttribute("lcmode")) {
3076 setLcMode((bool)sheet.attribute("lcmode").toInt(&ok));
3077 // we just ignore 'ok' - if it didn't work, go on
3078 }
3079 if (sheet.hasAttribute("autoCalc")) {
3080 setAutoCalculationEnabled((bool)sheet.attribute("autoCalc").toInt(&ok));
3081 // we just ignore 'ok' - if it didn't work, go on
3082 }
3083 if (sheet.hasAttribute("columnnumber")) {
3084 setShowColumnNumber((bool)sheet.attribute("columnnumber").toInt(&ok));
3085 // we just ignore 'ok' - if it didn't work, go on
3086 }
3087 if (sheet.hasAttribute("hidezero")) {
3088 setHideZero((bool)sheet.attribute("hidezero").toInt(&ok));
3089 // we just ignore 'ok' - if it didn't work, go on
3090 }
3091 if (sheet.hasAttribute("firstletterupper")) {
3092 setFirstLetterUpper((bool)sheet.attribute("firstletterupper").toInt(&ok));
3093 // we just ignore 'ok' - if it didn't work, go on
3094 }
3095
3096 // Load the paper layout
3097 KoXmlElement paper = sheet.namedItem("paper").toElement();
3098 if (!paper.isNull()) {
3099 KoPageLayout pageLayout;
3100 pageLayout.format = KoPageFormat::formatFromString(paper.attribute("format"));
3101 pageLayout.orientation = (paper.attribute("orientation") == "Portrait")
3102 ? KoPageFormat::Portrait : KoPageFormat::Landscape;
3103
3104 // <borders>
3105 KoXmlElement borders = paper.namedItem("borders").toElement();
3106 if (!borders.isNull()) {
3107 pageLayout.leftMargin = MM_TO_POINT(borders.attribute("left").toFloat());
3108 pageLayout.rightMargin = MM_TO_POINT(borders.attribute("right").toFloat());
3109 pageLayout.topMargin = MM_TO_POINT(borders.attribute("top").toFloat());
3110 pageLayout.bottomMargin = MM_TO_POINT(borders.attribute("bottom").toFloat());
3111 }
3112 print()->settings()->setPageLayout(pageLayout);
3113
3114 QString hleft, hright, hcenter;
3115 QString fleft, fright, fcenter;
3116 // <head>
3117 KoXmlElement head = paper.namedItem("head").toElement();
3118 if (!head.isNull()) {
3119 KoXmlElement left = head.namedItem("left").toElement();
3120 if (!left.isNull())
3121 hleft = left.text();
3122 KoXmlElement center = head.namedItem("center").toElement();
3123 if (!center.isNull())
3124 hcenter = center.text();
3125 KoXmlElement right = head.namedItem("right").toElement();
3126 if (!right.isNull())
3127 hright = right.text();
3128 }
3129 // <foot>
3130 KoXmlElement foot = paper.namedItem("foot").toElement();
3131 if (!foot.isNull()) {
3132 KoXmlElement left = foot.namedItem("left").toElement();
3133 if (!left.isNull())
3134 fleft = left.text();
3135 KoXmlElement center = foot.namedItem("center").toElement();
3136 if (!center.isNull())
3137 fcenter = center.text();
3138 KoXmlElement right = foot.namedItem("right").toElement();
3139 if (!right.isNull())
3140 fright = right.text();
3141 }
3142 print()->headerFooter()->setHeadFootLine(hleft, hcenter, hright, fleft, fcenter, fright);
3143 }
3144
3145 // load print range
3146 KoXmlElement printrange = sheet.namedItem("printrange-rect").toElement();
3147 if (!printrange.isNull()) {
3148 int left = printrange.attribute("left-rect").toInt();
3149 int right = printrange.attribute("right-rect").toInt();
3150 int bottom = printrange.attribute("bottom-rect").toInt();
3151 int top = printrange.attribute("top-rect").toInt();
3152 if (left == 0) { //whole row(s) selected
3153 left = 1;
3154 right = KS_colMax;
3155 }
3156 if (top == 0) { //whole column(s) selected
3157 top = 1;
3158 bottom = KS_rowMax;
3159 }
3160 const Region region(QRect(QPoint(left, top), QPoint(right, bottom)), this);
3161 printSettings()->setPrintRegion(region);
3162 }
3163
3164 // load print zoom
3165 if (sheet.hasAttribute("printZoom")) {
3166 double zoom = sheet.attribute("printZoom").toDouble(&ok);
3167 if (ok) {
3168 printSettings()->setZoom(zoom);
3169 }
3170 }
3171
3172 // load page limits
3173 if (sheet.hasAttribute("printPageLimitX")) {
3174 int pageLimit = sheet.attribute("printPageLimitX").toInt(&ok);
3175 if (ok) {
3176 printSettings()->setPageLimits(QSize(pageLimit, 0));
3177 }
3178 }
3179
3180 // load page limits
3181 if (sheet.hasAttribute("printPageLimitY")) {
3182 int pageLimit = sheet.attribute("printPageLimitY").toInt(&ok);
3183 if (ok) {
3184 const int horizontalLimit = printSettings()->pageLimits().width();
3185 printSettings()->setPageLimits(QSize(horizontalLimit, pageLimit));
3186 }
3187 }
3188
3189 // Load the cells
3190 KoXmlNode n = sheet.firstChild();
3191 while (!n.isNull()) {
3192 KoXmlElement e = n.toElement();
3193 if (!e.isNull()) {
3194 QString tagName = e.tagName();
3195 if (tagName == "cell")
3196 Cell(this, 1, 1).load(e, 0, 0); // col, row will get overridden in all cases
3197 else if (tagName == "row") {
3198 RowFormat *rl = new RowFormat();
3199 rl->setSheet(this);
3200 if (rl->load(e))
3201 insertRowFormat(rl);
3202 else
3203 delete rl;
3204 } else if (tagName == "column") {
3205 ColumnFormat *cl = new ColumnFormat();
3206 cl->setSheet(this);
3207 if (cl->load(e))
3208 insertColumnFormat(cl);
3209 else
3210 delete cl;
3211 }
3212#if 0 // CALLIGRA_SHEETS_KOPART_EMBEDDING
3213 else if (tagName == "object") {
3214 EmbeddedCalligraObject *ch = new EmbeddedCalligraObject(doc(), this);
3215 if (ch->load(e))
3216 insertObject(ch);
3217 else {
3218 ch->embeddedObject()->setDeleted(true);
3219 delete ch;
3220 }
3221 } else if (tagName == "chart") {
3222 EmbeddedChart *ch = new EmbeddedChart(doc(), this);
3223 if (ch->load(e))
3224 insertObject(ch);
3225 else {
3226 ch->embeddedObject()->setDeleted(true);
3227 delete ch;
3228 }
3229 }
3230#endif // CALLIGRA_SHEETS_KOPART_EMBEDDING
3231 }
3232 n = n.nextSibling();
3233 }
3234
3235 // load print repeat columns
3236 KoXmlElement printrepeatcolumns = sheet.namedItem("printrepeatcolumns").toElement();
3237 if (!printrepeatcolumns.isNull()) {
3238 int left = printrepeatcolumns.attribute("left").toInt();
3239 int right = printrepeatcolumns.attribute("right").toInt();
3240 printSettings()->setRepeatedColumns(qMakePair(left, right));
3241 }
3242
3243 // load print repeat rows
3244 KoXmlElement printrepeatrows = sheet.namedItem("printrepeatrows").toElement();
3245 if (!printrepeatrows.isNull()) {
3246 int top = printrepeatrows.attribute("top").toInt();
3247 int bottom = printrepeatrows.attribute("bottom").toInt();
3248 printSettings()->setRepeatedRows(qMakePair(top, bottom));
3249 }
3250
3251 if (!sheet.hasAttribute("borders1.2")) {
3252 convertObscuringBorders();
3253 }
3254
3255 loadXmlProtection(sheet);
3256
3257 return true;
3258}
3259
3260
3261bool Sheet::loadChildren(KoStore* _store)
3262{
3263 Q_UNUSED(_store);
3264#if 0 // CALLIGRA_SHEETS_KOPART_EMBEDDING
3265 foreach(EmbeddedObject* object, doc()->embeddedObjects()) {
3266 if (object->sheet() == this && (object->getType() == OBJECT_CALLIGRA_PART || object->getType() == OBJECT_CHART)) {
3267 kDebug() << "Calligra::Sheets::Sheet::loadChildren";
3268 if (!dynamic_cast<EmbeddedCalligraObject*>(object)->embeddedObject()->loadDocument(_store))
3269 return false;
3270 }
3271 }
3272#endif // CALLIGRA_SHEETS_KOPART_EMBEDDING
3273 return true;
3274}
3275
3276
3277void Sheet::setShowPageOutline(bool b)
3278{
3279 if (b == d->showPageOutline)
3280 return;
3281
3282 d->showPageOutline = b;
3283 // Just repaint everything visible; no need to invalidate the visual cache.
3284 if (!map()->isLoading()) {
3285 map()->addDamage(new SheetDamage(this, SheetDamage::ContentChanged));
3286 }
3287}
3288
3289QImage Sheet::backgroundImage() const
3290{
3291 return d->backgroundImage;
3292}
3293
3294void Sheet::setBackgroundImage(const QImage& image)
3295{
3296 d->backgroundImage = image;
3297}
3298
3299Sheet::BackgroundImageProperties Sheet::backgroundImageProperties() const
3300{
3301 return d->backgroundProperties;
3302}
3303
3304void Sheet::setBackgroundImageProperties(const Sheet::BackgroundImageProperties& properties)
3305{
3306 d->backgroundProperties = properties;
3307}
3308
3309void Sheet::insertColumnFormat(ColumnFormat *l)
3310{
3311 d->columns.insertElement(l, l->column());
3312 if (!map()->isLoading()) {
3313 map()->addDamage(new SheetDamage(this, SheetDamage::ColumnsChanged));
3314 }
3315}
3316
3317void Sheet::insertRowFormat(RowFormat *l)
3318{
3319 const int row = l->row();
3320 d->rows.setRowHeight(row, row, l->height());
3321 d->rows.setHidden(row, row, l->isHidden());
3322 d->rows.setFiltered(row, row, l->isFiltered());
3323 d->rows.setPageBreak(row, row, l->hasPageBreak());
3324 if (!map()->isLoading()) {
3325 map()->addDamage(new SheetDamage(this, SheetDamage::RowsChanged));
3326 }
3327}
3328
3329void Sheet::deleteColumnFormat(int column)
3330{
3331 d->columns.removeElement(column);
3332 if (!map()->isLoading()) {
3333 map()->addDamage(new SheetDamage(this, SheetDamage::ColumnsChanged));
3334 }
3335}
3336
3337void Sheet::deleteRowFormat(int row)
3338{
3339 d->rows.setDefault(row, row);
3340 if (!map()->isLoading()) {
3341 map()->addDamage(new SheetDamage(this, SheetDamage::RowsChanged));
3342 }
3343}
3344
3345
3346RowFormatStorage* Sheet::rowFormats()
3347{
3348 return &d->rows;
3349}
3350
3351const RowFormatStorage* Sheet::rowFormats() const
3352{
3353 return &d->rows;
3354}
3355
3356void Sheet::showStatusMessage(const QString &message, int timeout)
3357{
3358 emit statusMessage(message, timeout);
3359}
3360
3361void Sheet::hideSheet(bool _hide)
3362{
3363 setHidden(_hide);
3364 if (_hide)
3365 map()->addDamage(new SheetDamage(this, SheetDamage::Hidden));
3366 else
3367 map()->addDamage(new SheetDamage(this, SheetDamage::Shown));
3368}
3369
3370bool Sheet::setSheetName(const QString& name, bool init)
3371{
3372 Q_UNUSED(init);
3373 if (map()->findSheet(name))
3374 return false;
3375
3376 if (isProtected())
3377 return false;
3378
3379 if (d->name == name)
3380 return true;
3381
3382 QString old_name = d->name;
3383 d->name = name;
3384
3385 // FIXME: Why is the change of a sheet's name not supposed to be propagated here?
3386 // If it is not, we have to manually do so in the loading process, e.g. for the
3387 // SheetAccessModel in the document's data center map.
3388 //if (init)
3389 // return true;
3390
3391 foreach(Sheet* sheet, map()->sheetList()) {
3392 sheet->changeCellTabName(old_name, name);
3393 }
3394
3395 map()->addDamage(new SheetDamage(this, SheetDamage::Name));
3396
3397 setObjectName(name);
3398// (dynamic_cast<SheetIface*>(dcopObject()))->sheetNameHasChanged();
3399
3400 return true;
3401}
3402
3403
3404void Sheet::updateLocale()
3405{
3406 for (int c = 0; c < valueStorage()->count(); ++c) {
3407 Cell cell(this, valueStorage()->col(c), valueStorage()->row(c));
3408 QString text = cell.userInput();
3409 cell.parseUserInput(text);
3410 }
3411 // Affects the displayed value; rebuild the visual cache.
3412 const Region region(1, 1, KS_colMax, KS_rowMax, this);
3413 map()->addDamage(new CellDamage(this, region, CellDamage::Appearance));
3414}
3415
3416void Sheet::convertObscuringBorders()
3417{
3418 // FIXME Stefan: Verify that this is not needed anymore.
3419#if 0
3420 /* a word of explanation here:
3421 beginning with KSpread 1.2 (actually, cvs of Mar 28, 2002), border information
3422 is stored differently. Previously, for a cell obscuring a region, the entire
3423 region's border's data would be stored in the obscuring cell. This caused
3424 some data loss in certain situations. After that date, each cell stores
3425 its own border data, and prints it even if it is an obscured cell (as long
3426 as that border isn't across an obscuring border).
3427 Anyway, this function is used when loading a file that was stored with the
3428 old way of borders. All new files have the sheet attribute "borders1.2" so
3429 if that isn't in the file, all the border data will be converted here.
3430 It's a bit of a hack but I can't think of a better way and it's not *that*
3431 bad of a hack.:-)
3432 */
3433 Cell c = d->cellStorage->firstCell();
3434 QPen topPen, bottomPen, leftPen, rightPen;
3435 for (; c; c = c->nextCell()) {
3436 if (c->extraXCells() > 0 || c->extraYCells() > 0) {
3437 const Style* style = this->style(c->column(), c->row());
3438 topPen = style->topBorderPen();
3439 leftPen = style->leftBorderPen();
3440 rightPen = style->rightBorderPen();
3441 bottomPen = style->bottomBorderPen();
3442
3443 c->format()->setTopBorderStyle(Qt::NoPen);
3444 c->format()->setLeftBorderStyle(Qt::NoPen);
3445 c->format()->setRightBorderStyle(Qt::NoPen);
3446 c->format()->setBottomBorderStyle(Qt::NoPen);
3447
3448 for (int x = c->column(); x < c->column() + c->extraXCells(); x++) {
3449 Cell(this, x, c->row())->setTopBorderPen(topPen);
3450 Cell(this, x, c->row() + c->extraYCells())->
3451 setBottomBorderPen(bottomPen);
3452 }
3453 for (int y = c->row(); y < c->row() + c->extraYCells(); y++) {
3454 Cell(this, c->column(), y)->setLeftBorderPen(leftPen);
3455 Cell(this, c->column() + c->extraXCells(), y)->
3456 setRightBorderPen(rightPen);
3457 }
3458 }
3459 }
3460#endif
3461}
3462
3463void Sheet::applyDatabaseFilter(const Database &database)
3464{
3465 Sheet* const sheet = database.range().lastSheet();
3466 const QRect range = database.range().lastRange();
3467 const int start = database.orientation() == Qt::Vertical ? range.top() : range.left();
3468 const int end = database.orientation() == Qt::Vertical ? range.bottom() : range.right();
3469 for (int i = start + 1; i <= end; ++i) {
3470 const bool isFiltered = !database.filter().evaluate(database, i);
3471// kDebug() <<"Filtering column/row" << i <<"?" << isFiltered;
3472 if (database.orientation() == Qt::Vertical) {
3473 sheet->rowFormats()->setFiltered(i, i, isFiltered);
3474 } else { // database.orientation() == Qt::Horizontal
3475 sheet->nonDefaultColumnFormat(i)->setFiltered(isFiltered);
3476 }
3477 }
3478 if (database.orientation() == Qt::Vertical)
3479 sheet->map()->addDamage(new SheetDamage(sheet, SheetDamage::RowsChanged));
3480 else // database.orientation() == Qt::Horizontal
3481 sheet->map()->addDamage(new SheetDamage(sheet, SheetDamage::ColumnsChanged));
3482
3483 cellStorage()->setDatabase(database.range(), Database());
3484 cellStorage()->setDatabase(database.range(), database);
3485 map()->addDamage(new CellDamage(this, database.range(), CellDamage::Appearance));
3486}
3487
3488/**********************
3489 * Printout Functions *
3490 **********************/
3491
3492#ifndef NDEBUG
3493void Sheet::printDebug()
3494{
3495 int iMaxColumn = d->cellStorage->columns();
3496 int iMaxRow = d->cellStorage->rows();
3497
3498 kDebug(36001) << "Cell | Content | Value [UserInput]";
3499 Cell cell;
3500 for (int currentrow = 1 ; currentrow <= iMaxRow ; ++currentrow) {
3501 for (int currentcolumn = 1 ; currentcolumn <= iMaxColumn ; currentcolumn++) {
3502 cell = Cell(this, currentcolumn, currentrow);
3503 if (!cell.isEmpty()) {
3504 QString cellDescr = Cell::name(currentcolumn, currentrow).rightJustified(4) +
3505 //QString cellDescr = "Cell ";
3506 //cellDescr += QString::number(currentrow).rightJustified(3,'0') + ',';
3507 //cellDescr += QString::number(currentcolumn).rightJustified(3,'0') + ' ';
3508 " | ";
3509 QString valueType;
3510 QTextStream stream(&valueType);
3511 stream << cell.value().type();
3512 cellDescr += valueType.rightJustified(7) +
3513 " | " +
3514 map()->converter()->asString(cell.value()).asString().rightJustified(5) +
3515 QString(" [%1]").arg(cell.userInput());
3516 kDebug(36001) << cellDescr;
3517 }
3518 }
3519 }
3520}
3521#endif
3522
3523} // namespace Sheets
3524} // namespace Calligra
3525
3526#include "Sheet.moc"
3527