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 | |
100 | namespace Calligra |
101 | { |
102 | namespace Sheets |
103 | { |
104 | |
105 | template<typename T> class IntervalMap |
106 | { |
107 | public: |
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 | } |
121 | private: |
122 | QMap<int, QPair<int, T> > m_data; |
123 | }; |
124 | |
125 | static 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 | |
138 | class Sheet::Private |
139 | { |
140 | public: |
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 ; |
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 | |
185 | Sheet::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 | |
235 | Sheet::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 | |
288 | Sheet::~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 | |
304 | QAbstractItemModel* Sheet::model() const |
305 | { |
306 | return d->model; |
307 | } |
308 | |
309 | QString Sheet::sheetName() const |
310 | { |
311 | return d->name; |
312 | } |
313 | |
314 | Map* Sheet::map() const |
315 | { |
316 | return d->workbook; |
317 | } |
318 | |
319 | DocBase* Sheet::doc() const |
320 | { |
321 | return d->workbook->doc(); |
322 | } |
323 | |
324 | void 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 | |
333 | void Sheet::removeShape(KoShape* shape) |
334 | { |
335 | if (!shape) |
336 | return; |
337 | d->shapes.removeAll(shape); |
338 | emit shapeRemoved(this, shape); |
339 | } |
340 | |
341 | void Sheet::deleteShapes() |
342 | { |
343 | qDeleteAll(d->shapes); |
344 | d->shapes.clear(); |
345 | } |
346 | |
347 | KoDocumentResourceManager* Sheet::resourceManager() const |
348 | { |
349 | return map()->resourceManager(); |
350 | } |
351 | |
352 | QList<KoShape*> Sheet::shapes() const |
353 | { |
354 | return d->shapes; |
355 | } |
356 | |
357 | Qt::LayoutDirection Sheet::layoutDirection() const |
358 | { |
359 | return d->layoutDirection; |
360 | } |
361 | |
362 | void Sheet::setLayoutDirection(Qt::LayoutDirection dir) |
363 | { |
364 | d->layoutDirection = dir; |
365 | } |
366 | |
367 | bool Sheet::isHidden() const |
368 | { |
369 | return d->hide; |
370 | } |
371 | |
372 | void Sheet::setHidden(bool hidden) |
373 | { |
374 | d->hide = hidden; |
375 | } |
376 | |
377 | bool Sheet::getShowGrid() const |
378 | { |
379 | return d->showGrid; |
380 | } |
381 | |
382 | void Sheet::setShowGrid(bool _showGrid) |
383 | { |
384 | d->showGrid = _showGrid; |
385 | } |
386 | |
387 | bool Sheet::getShowFormula() const |
388 | { |
389 | return d->showFormula; |
390 | } |
391 | |
392 | void Sheet::setShowFormula(bool _showFormula) |
393 | { |
394 | d->showFormula = _showFormula; |
395 | } |
396 | |
397 | bool Sheet::getShowFormulaIndicator() const |
398 | { |
399 | return d->showFormulaIndicator; |
400 | } |
401 | |
402 | void Sheet::setShowFormulaIndicator(bool _showFormulaIndicator) |
403 | { |
404 | d->showFormulaIndicator = _showFormulaIndicator; |
405 | } |
406 | |
407 | bool Sheet::() const |
408 | { |
409 | return d->showCommentIndicator; |
410 | } |
411 | |
412 | void Sheet::(bool _indic) |
413 | { |
414 | d->showCommentIndicator = _indic; |
415 | } |
416 | |
417 | bool Sheet::getLcMode() const |
418 | { |
419 | return d->lcMode; |
420 | } |
421 | |
422 | void Sheet::setLcMode(bool _lcMode) |
423 | { |
424 | d->lcMode = _lcMode; |
425 | } |
426 | |
427 | bool Sheet::isAutoCalculationEnabled() const |
428 | { |
429 | return d->autoCalc; |
430 | } |
431 | |
432 | void 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 | |
448 | bool Sheet::getShowColumnNumber() const |
449 | { |
450 | return d->showColumnNumber; |
451 | } |
452 | |
453 | void Sheet::setShowColumnNumber(bool _showColumnNumber) |
454 | { |
455 | d->showColumnNumber = _showColumnNumber; |
456 | } |
457 | |
458 | bool Sheet::getHideZero() const |
459 | { |
460 | return d->hideZero; |
461 | } |
462 | |
463 | void Sheet::setHideZero(bool _hideZero) |
464 | { |
465 | d->hideZero = _hideZero; |
466 | } |
467 | |
468 | bool Sheet::getFirstLetterUpper() const |
469 | { |
470 | return d->firstLetterUpper; |
471 | } |
472 | |
473 | void Sheet::setFirstLetterUpper(bool _firstUpper) |
474 | { |
475 | d->firstLetterUpper = _firstUpper; |
476 | } |
477 | |
478 | bool Sheet::isShowPageOutline() const |
479 | { |
480 | return d->showPageOutline; |
481 | } |
482 | |
483 | const 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 | |
492 | CellStorage* Sheet::cellStorage() const |
493 | { |
494 | return d->cellStorage; |
495 | } |
496 | |
497 | const CommentStorage* Sheet::() const |
498 | { |
499 | return d->cellStorage->commentStorage(); |
500 | } |
501 | |
502 | const ConditionsStorage* Sheet::conditionsStorage() const |
503 | { |
504 | return d->cellStorage->conditionsStorage(); |
505 | } |
506 | |
507 | const FormulaStorage* Sheet::formulaStorage() const |
508 | { |
509 | return d->cellStorage->formulaStorage(); |
510 | } |
511 | |
512 | const FusionStorage* Sheet::fusionStorage() const |
513 | { |
514 | return d->cellStorage->fusionStorage(); |
515 | } |
516 | |
517 | const LinkStorage* Sheet::linkStorage() const |
518 | { |
519 | return d->cellStorage->linkStorage(); |
520 | } |
521 | |
522 | const StyleStorage* Sheet::styleStorage() const |
523 | { |
524 | return d->cellStorage->styleStorage(); |
525 | } |
526 | |
527 | const ValidityStorage* Sheet::validityStorage() const |
528 | { |
529 | return d->cellStorage->validityStorage(); |
530 | } |
531 | |
532 | const ValueStorage* Sheet::valueStorage() const |
533 | { |
534 | return d->cellStorage->valueStorage(); |
535 | } |
536 | |
537 | SheetPrint* Sheet::print() const |
538 | { |
539 | return d->print; |
540 | } |
541 | |
542 | PrintSettings* Sheet::printSettings() const |
543 | { |
544 | return d->print->settings(); |
545 | } |
546 | |
547 | void 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 | |
557 | HeaderFooter *Sheet::() const |
558 | { |
559 | return d->print->headerFooter(); |
560 | } |
561 | |
562 | QSizeF Sheet::documentSize() const |
563 | { |
564 | return d->documentSize; |
565 | } |
566 | |
567 | void Sheet::adjustDocumentWidth(double deltaWidth) |
568 | { |
569 | d->documentSize.rwidth() += deltaWidth; |
570 | emit documentSizeChanged(d->documentSize); |
571 | } |
572 | |
573 | void Sheet::adjustDocumentHeight(double deltaHeight) |
574 | { |
575 | d->documentSize.rheight() += deltaHeight; |
576 | emit documentSizeChanged(d->documentSize); |
577 | } |
578 | |
579 | void 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 | |
592 | void Sheet::adjustCellAnchoredShapesX(qreal delta, int firstCol, int lastCol) |
593 | { |
594 | adjustCellAnchoredShapesX(columnPosition(firstCol), columnPosition(lastCol+1), delta); |
595 | } |
596 | |
597 | void 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 | |
610 | void Sheet::adjustCellAnchoredShapesY(qreal delta, int firstRow, int lastRow) |
611 | { |
612 | adjustCellAnchoredShapesY(rowPosition(firstRow), rowPosition(lastRow+1), delta); |
613 | } |
614 | |
615 | int 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 | |
627 | int 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 | |
636 | int 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 | |
644 | int Sheet::bottomRow(double _ypos) const |
645 | { |
646 | return rowFormats()->rowForPosition(_ypos+1e-9); |
647 | } |
648 | |
649 | QRectF 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 | |
660 | QRect 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 | |
674 | double 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 | |
684 | double Sheet::rowPosition(int _row) const |
685 | { |
686 | const int max = qMin(_row, KS_rowMax+1); |
687 | return rowFormats()->totalVisibleRowHeight(1, max-1); |
688 | } |
689 | |
690 | ColumnFormat* Sheet::firstCol() const |
691 | { |
692 | return d->columns.first(); |
693 | } |
694 | |
695 | ColumnFormat* 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 | |
711 | void 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 | |
733 | void 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 | |
744 | void 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 | |
755 | void 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 | |
766 | void 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 | |
777 | void 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 | |
797 | void 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 | |
810 | void 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 | |
830 | void 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 | |
844 | QString 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 | |
900 | QString 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 | |
965 | void 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 |
1036 | bool 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 |
1062 | bool 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 | |
1103 | QDomElement 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 = 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 | |
1313 | bool Sheet::isLoading() |
1314 | { |
1315 | return map()->isLoading(); |
1316 | } |
1317 | |
1318 | void 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 | |
1327 | bool Sheet::loadSheetStyleFormat(KoXmlElement *style) |
1328 | { |
1329 | QString hleft, hmiddle, hright; |
1330 | QString fleft, fmiddle, fright; |
1331 | KoXmlNode = 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 = 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 = 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 = 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 | |
1400 | void 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 | |
1407 | QString 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 | |
1452 | void 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 | |
1475 | void 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 | |
1505 | bool 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 | |
1775 | void 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 | |
1784 | void 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 | |
1894 | bool 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 | |
1995 | void 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 | |
2029 | int 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 | |
2190 | QRect 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 | |
2218 | inline 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 | |
2233 | inline 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 | |
2249 | bool 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 | |
2286 | void Sheet::(KoXmlWriter &xmlWriter) const |
2287 | { |
2288 | QString = print()->headerFooter()->headLeft(); |
2289 | QString = print()->headerFooter()->headMid(); |
2290 | QString = print()->headerFooter()->headRight(); |
2291 | |
2292 | QString = print()->headerFooter()->footLeft(); |
2293 | QString = print()->headerFooter()->footMid(); |
2294 | QString = 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 | |
2368 | void Sheet::addText(const QString & text, KoXmlWriter & writer) const |
2369 | { |
2370 | if (!text.isEmpty()) |
2371 | writer.addTextNode(text); |
2372 | } |
2373 | |
2374 | void 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 | |
2473 | void 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 | |
2526 | void 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 | |
2552 | void 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 | |
2567 | bool 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 | |
2625 | QString 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 | |
2660 | void 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 | |
2909 | void 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 | |
2971 | bool 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 = 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 | |
3261 | bool 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 | |
3277 | void 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 | |
3289 | QImage Sheet::backgroundImage() const |
3290 | { |
3291 | return d->backgroundImage; |
3292 | } |
3293 | |
3294 | void Sheet::setBackgroundImage(const QImage& image) |
3295 | { |
3296 | d->backgroundImage = image; |
3297 | } |
3298 | |
3299 | Sheet::BackgroundImageProperties Sheet::backgroundImageProperties() const |
3300 | { |
3301 | return d->backgroundProperties; |
3302 | } |
3303 | |
3304 | void Sheet::setBackgroundImageProperties(const Sheet::BackgroundImageProperties& properties) |
3305 | { |
3306 | d->backgroundProperties = properties; |
3307 | } |
3308 | |
3309 | void 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 | |
3317 | void 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 | |
3329 | void 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 | |
3337 | void 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 | |
3346 | RowFormatStorage* Sheet::rowFormats() |
3347 | { |
3348 | return &d->rows; |
3349 | } |
3350 | |
3351 | const RowFormatStorage* Sheet::rowFormats() const |
3352 | { |
3353 | return &d->rows; |
3354 | } |
3355 | |
3356 | void Sheet::showStatusMessage(const QString &message, int timeout) |
3357 | { |
3358 | emit statusMessage(message, timeout); |
3359 | } |
3360 | |
3361 | void 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 | |
3370 | bool 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 | |
3404 | void 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 | |
3416 | void 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 | |
3463 | void 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 |
3493 | void 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 | |