File indexing completed on 2024-04-28 16:21:30

0001 /* This file is part of the KDE project
0002    Copyright 2010 Marijn Kruisselbrink <mkruisselbrink@kde.org>
0003    Copyright 2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
0004    Copyright 1998,1999 Torben Weis <weis@kde.org>
0005    Copyright 1999-2007 The KSpread Team <calligra-devel@kde.org>
0006 
0007    This library is free software; you can redistribute it and/or
0008    modify it under the terms of the GNU Library General Public
0009    License as published by the Free Software Foundation; either
0010    version 2 of the License, or (at your option) any later version.
0011 
0012    This library is distributed in the hope that it will be useful,
0013    but WITHOUT ANY WARRANTY; without even the implied warranty of
0014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015    Library General Public License for more details.
0016 
0017    You should have received a copy of the GNU Library General Public License
0018    along with this library; see the file COPYING.LIB.  If not, write to
0019    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0020    Boston, MA 02110-1301, USA.
0021 */
0022 
0023 // Local
0024 #include "Sheet.h"
0025 
0026 #include <QApplication>
0027 
0028 #include <kcodecs.h>
0029 
0030 #include <KoShape.h>
0031 #include <KoDocumentResourceManager.h>
0032 #include <KoUnit.h>
0033 #include <KoCanvasResourceIdentities.h>
0034 
0035 #include "CellStorage.h"
0036 #include "Cluster.h"
0037 #include "Damages.h"
0038 #include "DependencyManager.h"
0039 #include "DocBase.h"
0040 #include "FormulaStorage.h"
0041 #include "HeaderFooter.h"
0042 #include "LoadingInfo.h"
0043 #include "Map.h"
0044 #include "NamedAreaManager.h"
0045 #include "PrintSettings.h"
0046 #include "RecalcManager.h"
0047 #include "RowColumnFormat.h"
0048 #include "RowFormatStorage.h"
0049 #include "ShapeApplicationData.h"
0050 #include "SheetPrint.h"
0051 #include "SheetModel.h"
0052 #include "StyleStorage.h"
0053 #include "Validity.h"
0054 #include "ValueConverter.h"
0055 #include "ValueStorage.h"
0056 #include "database/Filter.h"
0057 
0058 namespace Calligra
0059 {
0060 namespace Sheets
0061 {
0062 
0063 static QString createObjectName(const QString &sheetName)
0064 {
0065     QString objectName;
0066     for (int i = 0; i < sheetName.count(); ++i) {
0067         if (sheetName[i].isLetterOrNumber() || sheetName[i] == '_')
0068             objectName.append(sheetName[i]);
0069         else
0070             objectName.append('_');
0071     }
0072     return objectName;
0073 }
0074 
0075 
0076 class Q_DECL_HIDDEN Sheet::Private
0077 {
0078 public:
0079     Private(Sheet* sheet) : rows(sheet) {}
0080 
0081     Map* workbook;
0082     SheetModel *model;
0083 
0084     QString name;
0085 
0086     Qt::LayoutDirection layoutDirection;
0087 
0088     // true if sheet is hidden
0089     bool hide;
0090 
0091     bool showGrid;
0092     bool showFormula;
0093     bool showFormulaIndicator;
0094     bool showCommentIndicator;
0095     bool autoCalc;
0096     bool lcMode;
0097     bool showColumnNumber;
0098     bool hideZero;
0099     bool firstLetterUpper;
0100 
0101     // clusters to hold objects
0102     CellStorage* cellStorage;
0103     RowFormatStorage rows;
0104     ColumnCluster columns;
0105     QList<KoShape*> shapes;
0106 
0107     // hold the print object
0108     SheetPrint* print;
0109 
0110     // Indicates whether the sheet should paint the page breaks.
0111     // Doing so costs some time, so by default it should be turned off.
0112     bool showPageOutline;
0113 
0114     // Max range of canvas in x and y direction.
0115     //  Depends on KS_colMax/KS_rowMax and the width/height of all columns/rows
0116     QSizeF documentSize;
0117 
0118     QImage backgroundImage;
0119     Sheet::BackgroundImageProperties backgroundProperties;
0120 };
0121 
0122 
0123 Sheet::Sheet(Map* map, const QString &sheetName)
0124         : KoShapeUserData(map)
0125         , KoShapeBasedDocumentBase()
0126         , d(new Private(this))
0127 {
0128     d->workbook = map;
0129     if (map->doc()) {
0130         resourceManager()->setUndoStack(map->doc()->undoStack());
0131         QVariant variant;
0132         variant.setValue<void*>(map->doc()->sheetAccessModel());
0133         resourceManager()->setResource(::Sheets::CanvasResource::AccessModel, variant);
0134     }
0135     d->model = new SheetModel(this);
0136 
0137     d->layoutDirection = QApplication::layoutDirection();
0138 
0139     d->name = sheetName;
0140 
0141     // Set a valid object name, so that we can offer scripting.
0142     setObjectName(createObjectName(d->name));
0143 
0144     d->cellStorage = new CellStorage(this);
0145     d->columns.setAutoDelete(true);
0146 
0147     d->documentSize = QSizeF(KS_colMax * d->workbook->defaultColumnFormat()->width(),
0148                              KS_rowMax * d->workbook->defaultRowFormat()->height());
0149 
0150     d->hide = false;
0151     d->showGrid = true;
0152     d->showFormula = false;
0153     d->showFormulaIndicator = false;
0154     d->showCommentIndicator = true;
0155     d->showPageOutline = false;
0156 
0157     d->lcMode = false;
0158     d->showColumnNumber = false;
0159     d->hideZero = false;
0160     d->firstLetterUpper = false;
0161     d->autoCalc = true;
0162     d->print = new SheetPrint(this);
0163 
0164     // document size changes always trigger a visible size change
0165     connect(this, SIGNAL(documentSizeChanged(QSizeF)), SIGNAL(visibleSizeChanged()));
0166     // CellStorage connections
0167     connect(d->cellStorage, SIGNAL(insertNamedArea(Region,QString)),
0168             d->workbook->namedAreaManager(), SLOT(insert(Region,QString)));
0169     connect(d->cellStorage, SIGNAL(namedAreaRemoved(QString)),
0170             d->workbook->namedAreaManager(), SLOT(remove(QString)));
0171 }
0172 
0173 Sheet::Sheet(const Sheet &other)
0174         : KoShapeUserData(other.d->workbook)
0175         , KoShapeBasedDocumentBase()
0176         , ProtectableObject(other)
0177         , d(new Private(this))
0178 {
0179     d->workbook = other.d->workbook;
0180     d->model = new SheetModel(this);
0181 
0182     // create a unique name
0183     int i = 1;
0184     do
0185         d->name = other.d->name + QString("_%1").arg(i++);
0186     while (d->workbook->findSheet(d->name));
0187 
0188     // Set a valid object name, so that we can offer scripting.
0189     setObjectName(createObjectName(d->name));
0190 
0191     d->layoutDirection = other.d->layoutDirection;
0192     d->hide = other.d->hide;
0193     d->showGrid = other.d->showGrid;
0194     d->showFormula = other.d->showFormula;
0195     d->showFormulaIndicator = other.d->showFormulaIndicator;
0196     d->showCommentIndicator = other.d->showCommentIndicator;
0197     d->autoCalc = other.d->autoCalc;
0198     d->lcMode = other.d->lcMode;
0199     d->showColumnNumber = other.d->showColumnNumber;
0200     d->hideZero = other.d->hideZero;
0201     d->firstLetterUpper = other.d->firstLetterUpper;
0202 
0203     d->cellStorage = new CellStorage(*other.d->cellStorage, this);
0204     d->rows = other.d->rows;
0205     d->columns = other.d->columns;
0206 
0207     // flake
0208 #if 0 // CALLIGRA_SHEETS_WIP_COPY_SHEET_(SHAPES)
0209     //FIXME This does not work as copySettings does not work. Also createDefaultShapeAndInit without the correct settings can not work
0210     //I think this should use ODF load/save for copying
0211     KoShape* shape;
0212     const QList<KoShape*> shapes = other.d->shapes;
0213     for (int i = 0; i < shapes.count(); ++i) {
0214         KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(shapes[i]->shapeId());
0215         if (factory) {
0216             shape = factory->createDefaultShapeAndInit(0);
0217             shape->copySettings(shapes[i]);
0218             addShape(shape);
0219         }
0220     }
0221 #endif // CALLIGRA_SHEETS_WIP_COPY_SHEET_(SHAPES)
0222 
0223     d->print = new SheetPrint(this); // FIXME = new SheetPrint(*other.d->print);
0224 
0225     d->showPageOutline = other.d->showPageOutline;
0226     d->documentSize = other.d->documentSize;
0227 }
0228 
0229 Sheet::~Sheet()
0230 {
0231     //Disable automatic recalculation of dependencies on this sheet to prevent crashes
0232     //in certain situations:
0233     //
0234     //For example, suppose a cell in SheetB depends upon a cell in SheetA.  If the cell in SheetB is emptied
0235     //after SheetA has already been deleted, the program would try to remove dependencies from the cell in SheetA
0236     //causing a crash.
0237     setAutoCalculationEnabled(false);
0238 
0239     delete d->print;
0240     delete d->cellStorage;
0241     qDeleteAll(d->shapes);
0242     delete d;
0243 }
0244 
0245 QAbstractItemModel* Sheet::model() const
0246 {
0247     return d->model;
0248 }
0249 
0250 QString Sheet::sheetName() const
0251 {
0252     return d->name;
0253 }
0254 
0255 Map* Sheet::map() const
0256 {
0257     return d->workbook;
0258 }
0259 
0260 DocBase* Sheet::doc() const
0261 {
0262     return d->workbook->doc();
0263 }
0264 
0265 void Sheet::addShape(KoShape* shape)
0266 {
0267     if (!shape)
0268         return;
0269     d->shapes.append(shape);
0270     shape->setApplicationData(new ShapeApplicationData());
0271     emit shapeAdded(this, shape);
0272 }
0273 
0274 void Sheet::removeShape(KoShape* shape)
0275 {
0276     if (!shape)
0277         return;
0278     d->shapes.removeAll(shape);
0279     emit shapeRemoved(this, shape);
0280 }
0281 
0282 void Sheet::deleteShapes()
0283 {
0284     qDeleteAll(d->shapes);
0285     d->shapes.clear();
0286 }
0287 
0288 KoDocumentResourceManager* Sheet::resourceManager() const
0289 {
0290     return map()->resourceManager();
0291 }
0292 
0293 QList<KoShape*> Sheet::shapes() const
0294 {
0295     return d->shapes;
0296 }
0297 
0298 Qt::LayoutDirection Sheet::layoutDirection() const
0299 {
0300     return d->layoutDirection;
0301 }
0302 
0303 void Sheet::setLayoutDirection(Qt::LayoutDirection dir)
0304 {
0305     d->layoutDirection = dir;
0306 }
0307 
0308 bool Sheet::isHidden() const
0309 {
0310     return d->hide;
0311 }
0312 
0313 void Sheet::setHidden(bool hidden)
0314 {
0315     d->hide = hidden;
0316 }
0317 
0318 bool Sheet::getShowGrid() const
0319 {
0320     return d->showGrid;
0321 }
0322 
0323 void Sheet::setShowGrid(bool _showGrid)
0324 {
0325     d->showGrid = _showGrid;
0326 }
0327 
0328 bool Sheet::getShowFormula() const
0329 {
0330     return d->showFormula;
0331 }
0332 
0333 void Sheet::setShowFormula(bool _showFormula)
0334 {
0335     d->showFormula = _showFormula;
0336 }
0337 
0338 bool Sheet::getShowFormulaIndicator() const
0339 {
0340     return d->showFormulaIndicator;
0341 }
0342 
0343 void Sheet::setShowFormulaIndicator(bool _showFormulaIndicator)
0344 {
0345     d->showFormulaIndicator = _showFormulaIndicator;
0346 }
0347 
0348 bool Sheet::getShowCommentIndicator() const
0349 {
0350     return d->showCommentIndicator;
0351 }
0352 
0353 void Sheet::setShowCommentIndicator(bool _indic)
0354 {
0355     d->showCommentIndicator = _indic;
0356 }
0357 
0358 bool Sheet::getLcMode() const
0359 {
0360     return d->lcMode;
0361 }
0362 
0363 void Sheet::setLcMode(bool _lcMode)
0364 {
0365     d->lcMode = _lcMode;
0366 }
0367 
0368 bool Sheet::isAutoCalculationEnabled() const
0369 {
0370     return d->autoCalc;
0371 }
0372 
0373 void Sheet::setAutoCalculationEnabled(bool enable)
0374 {
0375     //Avoid possible recalculation of dependencies if the auto calc setting hasn't changed
0376     if (d->autoCalc == enable)
0377         return;
0378 
0379     d->autoCalc = enable;
0380     //If enabling automatic calculation, make sure that the dependencies are up-to-date
0381     if (enable == true) {
0382         map()->dependencyManager()->addSheet(this);
0383         map()->recalcManager()->recalcSheet(this);
0384     } else {
0385         map()->dependencyManager()->removeSheet(this);
0386     }
0387 }
0388 
0389 bool Sheet::getShowColumnNumber() const
0390 {
0391     return d->showColumnNumber;
0392 }
0393 
0394 void Sheet::setShowColumnNumber(bool _showColumnNumber)
0395 {
0396     d->showColumnNumber = _showColumnNumber;
0397 }
0398 
0399 bool Sheet::getHideZero() const
0400 {
0401     return d->hideZero;
0402 }
0403 
0404 void Sheet::setHideZero(bool _hideZero)
0405 {
0406     d->hideZero = _hideZero;
0407 }
0408 
0409 bool Sheet::getFirstLetterUpper() const
0410 {
0411     return d->firstLetterUpper;
0412 }
0413 
0414 void Sheet::setFirstLetterUpper(bool _firstUpper)
0415 {
0416     d->firstLetterUpper = _firstUpper;
0417 }
0418 
0419 bool Sheet::isShowPageOutline() const
0420 {
0421     return d->showPageOutline;
0422 }
0423 
0424 const ColumnFormat* Sheet::columnFormat(int _column) const
0425 {
0426     const ColumnFormat *p = d->columns.lookup(_column);
0427     if (p != 0)
0428         return p;
0429 
0430     return map()->defaultColumnFormat();
0431 }
0432 
0433 // needed in loading code
0434 ColumnFormat *Sheet::nextColumn(int col) const
0435 {
0436     return d->columns.next(col);
0437 }
0438 
0439 CellStorage* Sheet::cellStorage() const
0440 {
0441     return d->cellStorage;
0442 }
0443 
0444 const CommentStorage* Sheet::commentStorage() const
0445 {
0446     return d->cellStorage->commentStorage();
0447 }
0448 
0449 const ConditionsStorage* Sheet::conditionsStorage() const
0450 {
0451     return d->cellStorage->conditionsStorage();
0452 }
0453 
0454 const FormulaStorage* Sheet::formulaStorage() const
0455 {
0456     return d->cellStorage->formulaStorage();
0457 }
0458 
0459 const FusionStorage* Sheet::fusionStorage() const
0460 {
0461     return d->cellStorage->fusionStorage();
0462 }
0463 
0464 const LinkStorage* Sheet::linkStorage() const
0465 {
0466     return d->cellStorage->linkStorage();
0467 }
0468 
0469 const StyleStorage* Sheet::styleStorage() const
0470 {
0471     return d->cellStorage->styleStorage();
0472 }
0473 
0474 const ValidityStorage* Sheet::validityStorage() const
0475 {
0476     return d->cellStorage->validityStorage();
0477 }
0478 
0479 const ValueStorage* Sheet::valueStorage() const
0480 {
0481     return d->cellStorage->valueStorage();
0482 }
0483 
0484 SheetPrint* Sheet::print() const
0485 {
0486     return d->print;
0487 }
0488 
0489 PrintSettings* Sheet::printSettings() const
0490 {
0491     return d->print->settings();
0492 }
0493 
0494 void Sheet::setPrintSettings(const PrintSettings &settings)
0495 {
0496     d->print->setSettings(settings);
0497     // Repaint, if page borders are shown and this is the active sheet.
0498     if (isShowPageOutline()) {
0499         // Just repaint everything visible; no need to invalidate the visual cache.
0500         map()->addDamage(new SheetDamage(this, SheetDamage::ContentChanged));
0501     }
0502 }
0503 
0504 HeaderFooter *Sheet::headerFooter() const
0505 {
0506     return d->print->headerFooter();
0507 }
0508 
0509 QSizeF Sheet::documentSize() const
0510 {
0511     return d->documentSize;
0512 }
0513 
0514 void Sheet::adjustDocumentWidth(double deltaWidth)
0515 {
0516     d->documentSize.rwidth() += deltaWidth;
0517     emit documentSizeChanged(d->documentSize);
0518 }
0519 
0520 void Sheet::adjustDocumentHeight(double deltaHeight)
0521 {
0522     d->documentSize.rheight() += deltaHeight;
0523     emit documentSizeChanged(d->documentSize);
0524 }
0525 
0526 void Sheet::adjustCellAnchoredShapesX(qreal minX, qreal maxX, qreal delta)
0527 {
0528     foreach (KoShape* s, d->shapes) {
0529         if (dynamic_cast<ShapeApplicationData*>(s->applicationData())->isAnchoredToCell()) {
0530             if (s->position().x() >= minX && s->position().x() < maxX) {
0531                 QPointF p = s->position();
0532                 p.setX(qMax(minX, p.x() + delta));
0533                 s->setPosition(p);
0534             }
0535         }
0536     }
0537 }
0538 
0539 void Sheet::adjustCellAnchoredShapesX(qreal delta, int firstCol, int lastCol)
0540 {
0541     adjustCellAnchoredShapesX(columnPosition(firstCol), columnPosition(lastCol+1), delta);
0542 }
0543 
0544 void Sheet::adjustCellAnchoredShapesY(qreal minY, qreal maxY, qreal delta)
0545 {
0546     foreach (KoShape* s, d->shapes) {
0547         if (dynamic_cast<ShapeApplicationData*>(s->applicationData())->isAnchoredToCell()) {
0548             if (s->position().y() >= minY && s->position().y() < maxY) {
0549                 QPointF p = s->position();
0550                 p.setY(qMax(minY, p.y() + delta));
0551                 s->setPosition(p);
0552             }
0553         }
0554     }
0555 }
0556 
0557 void Sheet::adjustCellAnchoredShapesY(qreal delta, int firstRow, int lastRow)
0558 {
0559     adjustCellAnchoredShapesY(rowPosition(firstRow), rowPosition(lastRow+1), delta);
0560 }
0561 
0562 int Sheet::leftColumn(qreal _xpos, qreal &_left) const
0563 {
0564     _left = 0.0;
0565     int col = 1;
0566     double x = columnFormat(col)->visibleWidth();
0567     while (x < _xpos && col < KS_colMax) {
0568         _left += columnFormat(col)->visibleWidth();
0569         x += columnFormat(++col)->visibleWidth();
0570     }
0571     return col;
0572 }
0573 
0574 int Sheet::rightColumn(double _xpos) const
0575 {
0576     int col = 1;
0577     double x = columnFormat(col)->visibleWidth();
0578     while (x <= _xpos && col < KS_colMax)
0579         x += columnFormat(++col)->visibleWidth();
0580     return col;
0581 }
0582 
0583 int Sheet::topRow(qreal _ypos, qreal & _top) const
0584 {
0585     qreal top;
0586     int row = rowFormats()->rowForPosition(_ypos, &top);
0587     _top = top;
0588     return row;
0589 }
0590 
0591 int Sheet::bottomRow(double _ypos) const
0592 {
0593     return rowFormats()->rowForPosition(_ypos+1e-9);
0594 }
0595 
0596 QRectF Sheet::cellCoordinatesToDocument(const QRect& cellRange) const
0597 {
0598     // TODO Stefan: Rewrite to save some iterations over the columns/rows.
0599     QRectF rect;
0600     rect.setLeft(columnPosition(cellRange.left()));
0601     rect.setRight(columnPosition(cellRange.right()) + columnFormat(cellRange.right())->width());
0602     rect.setTop(rowPosition(cellRange.top()));
0603     rect.setBottom(rowPosition(cellRange.bottom()) + rowFormats()->rowHeight(cellRange.bottom()));
0604     return rect;
0605 }
0606 
0607 QRect Sheet::documentToCellCoordinates(const QRectF &area) const
0608 {
0609     double width = 0.0;
0610     int left = 0;
0611     while (width <= area.left())
0612         width += columnFormat(++left)->visibleWidth();
0613     int right = left;
0614     while (width < area.right())
0615         width += columnFormat(++right)->visibleWidth();
0616     int top = rowFormats()->rowForPosition(area.top());
0617     int bottom = rowFormats()->rowForPosition(area.bottom());
0618     return QRect(left, top, right - left + 1, bottom - top + 1);
0619 }
0620 
0621 double Sheet::columnPosition(int _col) const
0622 {
0623     const int max = qMin(_col, KS_colMax);
0624     double x = 0.0;
0625     for (int col = 1; col < max; ++col)
0626         x += columnFormat(col)->visibleWidth();
0627     return x;
0628 }
0629 
0630 
0631 double Sheet::rowPosition(int _row) const
0632 {
0633     const int max = qMin(_row, KS_rowMax+1);
0634     return rowFormats()->totalVisibleRowHeight(1, max-1);
0635 }
0636 
0637 ColumnFormat* Sheet::firstCol() const
0638 {
0639     return d->columns.first();
0640 }
0641 
0642 ColumnFormat* Sheet::nonDefaultColumnFormat(int _column, bool force_creation)
0643 {
0644     Q_ASSERT(_column >= 1 && _column <= KS_colMax);
0645     ColumnFormat *p = d->columns.lookup(_column);
0646     if (p != 0 || !force_creation)
0647         return p;
0648 
0649     p = new ColumnFormat(*map()->defaultColumnFormat());
0650     p->setSheet(this);
0651     p->setColumn(_column);
0652 
0653     d->columns.insertElement(p, _column);
0654 
0655     return p;
0656 }
0657 
0658 void Sheet::changeCellTabName(QString const & old_name, QString const & new_name)
0659 {
0660     for (int c = 0; c < formulaStorage()->count(); ++c) {
0661         if (formulaStorage()->data(c).expression().contains(old_name)) {
0662             int nb = formulaStorage()->data(c).expression().count(old_name + '!');
0663             QString tmp = old_name + '!';
0664             int len = tmp.length();
0665             tmp = formulaStorage()->data(c).expression();
0666 
0667             for (int i = 0; i < nb; ++i) {
0668                 int pos = tmp.indexOf(old_name + '!');
0669                 tmp.replace(pos, len, new_name + '!');
0670             }
0671             Cell cell(this, formulaStorage()->col(c), formulaStorage()->row(c));
0672             Formula formula(this, cell);
0673             formula.setExpression(tmp);
0674             cell.setFormula(formula);
0675             cell.makeFormula();
0676         }
0677     }
0678 }
0679 
0680 void Sheet::insertShiftRight(const QRect& rect)
0681 {
0682     foreach(Sheet* sheet, map()->sheetList()) {
0683         for (int i = rect.top(); i <= rect.bottom(); ++i) {
0684             sheet->changeNameCellRef(QPoint(rect.left(), i), false,
0685                                      Sheet::ColumnInsert, sheetName(),
0686                                      rect.right() - rect.left() + 1);
0687         }
0688     }
0689 }
0690 
0691 void Sheet::insertShiftDown(const QRect& rect)
0692 {
0693     foreach(Sheet* sheet, map()->sheetList()) {
0694         for (int i = rect.left(); i <= rect.right(); ++i) {
0695             sheet->changeNameCellRef(QPoint(i, rect.top()), false,
0696                                      Sheet::RowInsert, sheetName(),
0697                                      rect.bottom() - rect.top() + 1);
0698         }
0699     }
0700 }
0701 
0702 void Sheet::removeShiftUp(const QRect& rect)
0703 {
0704     foreach(Sheet* sheet, map()->sheetList()) {
0705         for (int i = rect.left(); i <= rect.right(); ++i) {
0706             sheet->changeNameCellRef(QPoint(i, rect.top()), false,
0707                                      Sheet::RowRemove, sheetName(),
0708                                      rect.bottom() - rect.top() + 1);
0709         }
0710     }
0711 }
0712 
0713 void Sheet::removeShiftLeft(const QRect& rect)
0714 {
0715     foreach(Sheet* sheet, map()->sheetList()) {
0716         for (int i = rect.top(); i <= rect.bottom(); ++i) {
0717             sheet->changeNameCellRef(QPoint(rect.left(), i), false,
0718                                      Sheet::ColumnRemove, sheetName(),
0719                                      rect.right() - rect.left() + 1);
0720         }
0721     }
0722 }
0723 
0724 void Sheet::insertColumns(int col, int number)
0725 {
0726     double deltaWidth = 0.0;
0727     for (int i = 0; i < number; i++) {
0728         deltaWidth -= columnFormat(KS_colMax)->width();
0729         d->columns.insertColumn(col);
0730         deltaWidth += columnFormat(col + i)->width();
0731     }
0732     // Adjust document width (plus widths of new columns; minus widths of removed columns).
0733     adjustDocumentWidth(deltaWidth);
0734 
0735     foreach(Sheet* sheet, map()->sheetList()) {
0736         sheet->changeNameCellRef(QPoint(col, 1), true,
0737                                  Sheet::ColumnInsert, sheetName(),
0738                                  number);
0739     }
0740     //update print settings
0741     d->print->insertColumn(col, number);
0742 }
0743 
0744 void Sheet::insertRows(int row, int number)
0745 {
0746     d->rows.insertRows(row, number);
0747 
0748     foreach(Sheet* sheet, map()->sheetList()) {
0749         sheet->changeNameCellRef(QPoint(1, row), true,
0750                                  Sheet::RowInsert, sheetName(),
0751                                  number);
0752     }
0753     //update print settings
0754     d->print->insertRow(row, number);
0755 }
0756 
0757 void Sheet::removeColumns(int col, int number)
0758 {
0759     double deltaWidth = 0.0;
0760     for (int i = 0; i < number; ++i) {
0761         deltaWidth -= columnFormat(col)->width();
0762         d->columns.removeColumn(col);
0763         deltaWidth += columnFormat(KS_colMax)->width();
0764     }
0765     // Adjust document width (plus widths of new columns; minus widths of removed columns).
0766     adjustDocumentWidth(deltaWidth);
0767 
0768     foreach(Sheet* sheet, map()->sheetList()) {
0769         sheet->changeNameCellRef(QPoint(col, 1), true,
0770                                  Sheet::ColumnRemove, sheetName(),
0771                                  number);
0772     }
0773     //update print settings
0774     d->print->removeColumn(col, number);
0775 }
0776 
0777 void Sheet::removeRows(int row, int number)
0778 {
0779     d->rows.removeRows(row, number);
0780 
0781     foreach(Sheet* sheet, map()->sheetList()) {
0782         sheet->changeNameCellRef(QPoint(1, row), true,
0783                                  Sheet::RowRemove, sheetName(),
0784                                  number);
0785     }
0786 
0787     //update print settings
0788     d->print->removeRow(row, number);
0789 }
0790 
0791 QString Sheet::changeNameCellRefHelper(const QPoint& pos, bool fullRowOrColumn, ChangeRef ref,
0792                                        int nbCol, const QPoint& point, bool isColumnFixed,
0793                                        bool isRowFixed)
0794 {
0795     QString newPoint;
0796     int col = point.x();
0797     int row = point.y();
0798     // update column
0799     if (isColumnFixed)
0800         newPoint.append('$');
0801     if (ref == ColumnInsert &&
0802             col + nbCol <= KS_colMax &&
0803             col >= pos.x() &&    // Column after the new one : +1
0804             (fullRowOrColumn || row == pos.y())) {  // All rows or just one
0805         newPoint += Cell::columnName(col + nbCol);
0806     } else if (ref == ColumnRemove &&
0807                col > pos.x() &&    // Column after the deleted one : -1
0808                (fullRowOrColumn || row == pos.y())) {  // All rows or just one
0809         newPoint += Cell::columnName(col - nbCol);
0810     } else
0811         newPoint += Cell::columnName(col);
0812 
0813     // Update row
0814     if (isRowFixed)
0815         newPoint.append('$');
0816     if (ref == RowInsert &&
0817             row + nbCol <= KS_rowMax &&
0818             row >= pos.y() &&   // Row after the new one : +1
0819             (fullRowOrColumn || col == pos.x())) {  // All columns or just one
0820         newPoint += QString::number(row + nbCol);
0821     } else if (ref == RowRemove &&
0822                row > pos.y() &&   // Row after the deleted one : -1
0823                (fullRowOrColumn || col == pos.x())) {  // All columns or just one
0824         newPoint += QString::number(row - nbCol);
0825     } else
0826         newPoint += QString::number(row);
0827 
0828     if (((ref == ColumnRemove
0829             && (col >= pos.x() && col < pos.x() + nbCol) // Column is the deleted one : error
0830             && (fullRowOrColumn || row == pos.y())) ||
0831             (ref == RowRemove
0832              && (row >= pos.y() && row < pos.y() + nbCol) // Row is the deleted one : error
0833              && (fullRowOrColumn || col == pos.x())) ||
0834             (ref == ColumnInsert
0835              && col + nbCol > KS_colMax
0836              && col >= pos.x()     // Column after the new one : +1
0837              && (fullRowOrColumn || row == pos.y())) ||
0838             (ref == RowInsert
0839              && row + nbCol > KS_rowMax
0840              && row >= pos.y() // Row after the new one : +1
0841              && (fullRowOrColumn || col == pos.x())))) {
0842         newPoint = '#' + i18n("Dependency") + '!';
0843     }
0844     return newPoint;
0845 }
0846 
0847 QString Sheet::changeNameCellRefHelper(const QPoint& pos, const QRect& rect, bool fullRowOrColumn, ChangeRef ref,
0848                                        int nbCol, const QPoint& point, bool isColumnFixed,
0849                                        bool isRowFixed)
0850 {
0851     const bool isFirstColumn = pos.x() == rect.left();
0852     const bool isLastColumn = pos.x() == rect.right();
0853     const bool isFirstRow = pos.y() == rect.top();
0854     const bool isLastRow = pos.y() == rect.bottom();
0855 
0856     QString newPoint;
0857     int col = point.x();
0858     int row = point.y();
0859     // update column
0860     if (isColumnFixed)
0861         newPoint.append('$');
0862     if (ref == ColumnInsert &&
0863             col + nbCol <= KS_colMax &&
0864             col >= pos.x() &&    // Column after the new one : +1
0865             (fullRowOrColumn || row == pos.y())) {  // All rows or just one
0866         newPoint += Cell::columnName(col + nbCol);
0867     } else if (ref == ColumnRemove &&
0868                (col > pos.x() ||
0869                 (col == pos.x() && isLastColumn)) &&    // Column after the deleted one : -1
0870                (fullRowOrColumn || row == pos.y())) {  // All rows or just one
0871         newPoint += Cell::columnName(col - nbCol);
0872     } else
0873         newPoint += Cell::columnName(col);
0874 
0875     // Update row
0876     if (isRowFixed)
0877         newPoint.append('$');
0878     if (ref == RowInsert &&
0879             row + nbCol <= KS_rowMax &&
0880             row >= pos.y() &&   // Row after the new one : +1
0881             (fullRowOrColumn || col == pos.x())) {  // All columns or just one
0882         newPoint += QString::number(row + nbCol);
0883     } else if (ref == RowRemove &&
0884                (row > pos.y() ||
0885                 (row == pos.y() && isLastRow)) &&   // Row after the deleted one : -1
0886                (fullRowOrColumn || col == pos.x())) {  // All columns or just one
0887         newPoint += QString::number(row - nbCol);
0888     } else
0889         newPoint += QString::number(row);
0890 
0891     if (((ref == ColumnRemove
0892             && col == pos.x() // Column is the deleted one : error
0893             && (fullRowOrColumn || row == pos.y())
0894             && (isFirstColumn && isLastColumn)) ||
0895             (ref == RowRemove
0896              && row == pos.y() // Row is the deleted one : error
0897              && (fullRowOrColumn || col == pos.x())
0898              && (isFirstRow && isLastRow)) ||
0899             (ref == ColumnInsert
0900              && col + nbCol > KS_colMax
0901              && col >= pos.x()     // Column after the new one : +1
0902              && (fullRowOrColumn || row == pos.y())) ||
0903             (ref == RowInsert
0904              && row + nbCol > KS_rowMax
0905              && row >= pos.y() // Row after the new one : +1
0906              && (fullRowOrColumn || col == pos.x())))) {
0907         newPoint = '#' + i18n("Dependency") + '!';
0908     }
0909     return newPoint;
0910 }
0911 
0912 void Sheet::changeNameCellRef(const QPoint& pos, bool fullRowOrColumn, ChangeRef ref,
0913                               const QString& tabname, int nbCol)
0914 {
0915     for (int c = 0; c < formulaStorage()->count(); ++c) {
0916         QString newText('=');
0917         const Tokens tokens = formulaStorage()->data(c).tokens();
0918         for (int t = 0; t < tokens.count(); ++t) {
0919             const Token token = tokens[t];
0920             switch (token.type()) {
0921             case Token::Cell:
0922             case Token::Range: {
0923                 if (map()->namedAreaManager()->contains(token.text())) {
0924                     newText.append(token.text()); // simply keep the area name
0925                     break;
0926                 }
0927                 const Region region(token.text(), map());
0928                 if (!region.isValid() || !region.isContiguous()) {
0929                     newText.append(token.text());
0930                     break;
0931                 }
0932                 if (!region.firstSheet() && tabname != sheetName()) {
0933                     // nothing to do here
0934                     newText.append(token.text());
0935                     break;
0936                 }
0937                 // actually only one element in here, but we need extended access to the element
0938                 Region::ConstIterator end(region.constEnd());
0939                 for (Region::ConstIterator it(region.constBegin()); it != end; ++it) {
0940                     Region::Element* element = (*it);
0941                     if (element->type() == Region::Element::Point) {
0942                         if (element->sheet())
0943                             newText.append(element->sheet()->sheetName() + '!');
0944                         QString newPoint = changeNameCellRefHelper(pos, fullRowOrColumn, ref,
0945                                            nbCol,
0946                                            element->rect().topLeft(),
0947                                            element->isColumnFixed(),
0948                                            element->isRowFixed());
0949                         newText.append(newPoint);
0950                     } else { // (element->type() == Region::Element::Range)
0951                         if (element->sheet())
0952                             newText.append(element->sheet()->sheetName() + '!');
0953                         QString newPoint;
0954                         newPoint = changeNameCellRefHelper(pos, element->rect(), fullRowOrColumn, ref,
0955                                                            nbCol, element->rect().topLeft(),
0956                                                            element->isColumnFixed(),
0957                                                            element->isRowFixed());
0958                         newText.append(newPoint + ':');
0959                         newPoint = changeNameCellRefHelper(pos, element->rect(), fullRowOrColumn, ref,
0960                                                            nbCol, element->rect().bottomRight(),
0961                                                            element->isColumnFixed(),
0962                                                            element->isRowFixed());
0963                         newText.append(newPoint);
0964                     }
0965                 }
0966                 break;
0967             }
0968             default: {
0969                 newText.append(token.text());
0970                 break;
0971             }
0972             }
0973         }
0974 
0975         Cell cell(this, formulaStorage()->col(c), formulaStorage()->row(c));
0976         Formula formula(this, cell);
0977         formula.setExpression(newText);
0978         cell.setFormula(formula);
0979     }
0980 }
0981 
0982 // helper function for Sheet::areaIsEmpty
0983 bool Sheet::cellIsEmpty(const Cell& cell, TestType _type)
0984 {
0985     if (!cell.isPartOfMerged()) {
0986         switch (_type) {
0987         case Text :
0988             if (!cell.userInput().isEmpty())
0989                 return false;
0990             break;
0991         case Validity:
0992             if (!cell.validity().isEmpty())
0993                 return false;
0994             break;
0995         case Comment:
0996             if (!cell.comment().isEmpty())
0997                 return false;
0998             break;
0999         case ConditionalCellAttribute:
1000             if (cell.conditions().conditionList().count() > 0)
1001                 return false;
1002             break;
1003         }
1004     }
1005     return true;
1006 }
1007 
1008 // TODO: convert into a manipulator, similar to the Dilation one
1009 bool Sheet::areaIsEmpty(const Region& region, TestType _type)
1010 {
1011     Region::ConstIterator endOfList = region.constEnd();
1012     for (Region::ConstIterator it = region.constBegin(); it != endOfList; ++it) {
1013         QRect range = (*it)->rect();
1014         // Complete rows selected ?
1015         if ((*it)->isRow()) {
1016             for (int row = range.top(); row <= range.bottom(); ++row) {
1017                 Cell cell = d->cellStorage->firstInRow(row);
1018                 while (!cell.isNull()) {
1019                     if (!cellIsEmpty(cell, _type))
1020                         return false;
1021                     cell = d->cellStorage->nextInRow(cell.column(), row);
1022                 }
1023             }
1024         }
1025         // Complete columns selected ?
1026         else if ((*it)->isColumn()) {
1027             for (int col = range.left(); col <= range.right(); ++col) {
1028                 Cell cell = d->cellStorage->firstInColumn(col);
1029                 while (!cell.isNull()) {
1030                     if (!cellIsEmpty(cell, _type))
1031                         return false;
1032                     cell = d->cellStorage->nextInColumn(col, cell.row());
1033                 }
1034             }
1035         } else {
1036             Cell cell;
1037             int right  = range.right();
1038             int bottom = range.bottom();
1039             for (int x = range.left(); x <= right; ++x)
1040                 for (int y = range.top(); y <= bottom; ++y) {
1041                     cell = Cell(this, x, y);
1042                     if (!cellIsEmpty(cell, _type))
1043                         return false;
1044                 }
1045         }
1046     }
1047     return true;
1048 }
1049 
1050 QDomElement Sheet::saveXML(QDomDocument& dd)
1051 {
1052     QDomElement sheet = dd.createElement("table");
1053 
1054     // backward compatibility
1055     QString sheetName;
1056     for (int i = 0; i < d->name.count(); ++i) {
1057         if (d->name[i].isLetterOrNumber() || d->name[i] == ' ' || d->name[i] == '.')
1058             sheetName.append(d->name[i]);
1059         else
1060             sheetName.append('_');
1061     }
1062     sheet.setAttribute("name", sheetName);
1063 
1064     //Laurent: for oasis format I think that we must use style:direction...
1065     sheet.setAttribute("layoutDirection", (layoutDirection() == Qt::RightToLeft) ? "rtl" : "ltr");
1066     sheet.setAttribute("columnnumber", QString::number((int)getShowColumnNumber()));
1067     sheet.setAttribute("borders", QString::number((int)isShowPageOutline()));
1068     sheet.setAttribute("hide", QString::number((int)isHidden()));
1069     sheet.setAttribute("hidezero", QString::number((int)getHideZero()));
1070     sheet.setAttribute("firstletterupper", QString::number((int)getFirstLetterUpper()));
1071     sheet.setAttribute("grid", QString::number((int)getShowGrid()));
1072     sheet.setAttribute("printGrid", QString::number((int)print()->settings()->printGrid()));
1073     sheet.setAttribute("printCommentIndicator", QString::number((int)print()->settings()->printCommentIndicator()));
1074     sheet.setAttribute("printFormulaIndicator", QString::number((int)print()->settings()->printFormulaIndicator()));
1075     sheet.setAttribute("showFormula", QString::number((int)getShowFormula()));
1076     sheet.setAttribute("showFormulaIndicator", QString::number((int)getShowFormulaIndicator()));
1077     sheet.setAttribute("showCommentIndicator", QString::number((int)getShowCommentIndicator()));
1078     sheet.setAttribute("lcmode", QString::number((int)getLcMode()));
1079     sheet.setAttribute("autoCalc", QString::number((int)isAutoCalculationEnabled()));
1080     sheet.setAttribute("borders1.2", "1");
1081     QByteArray pwd;
1082     password(pwd);
1083     if (!pwd.isNull()) {
1084         if (pwd.size() > 0) {
1085             QByteArray str = KCodecs::base64Encode(pwd);
1086             sheet.setAttribute("protected", QString(str.data()));
1087         } else
1088             sheet.setAttribute("protected", "");
1089     }
1090 
1091     // paper parameters
1092     QDomElement paper = dd.createElement("paper");
1093     paper.setAttribute("format", printSettings()->paperFormatString());
1094     paper.setAttribute("orientation", printSettings()->orientationString());
1095     sheet.appendChild(paper);
1096 
1097     QDomElement borders = dd.createElement("borders");
1098     KoPageLayout pageLayout = print()->settings()->pageLayout();
1099     borders.setAttribute("left", QString::number(pageLayout.leftMargin));
1100     borders.setAttribute("top", QString::number(pageLayout.topMargin));
1101     borders.setAttribute("right", QString::number(pageLayout.rightMargin));
1102     borders.setAttribute("bottom", QString::number(pageLayout.bottomMargin));
1103     paper.appendChild(borders);
1104 
1105     QDomElement head = dd.createElement("head");
1106     paper.appendChild(head);
1107     if (!print()->headerFooter()->headLeft().isEmpty()) {
1108         QDomElement left = dd.createElement("left");
1109         head.appendChild(left);
1110         left.appendChild(dd.createTextNode(print()->headerFooter()->headLeft()));
1111     }
1112     if (!print()->headerFooter()->headMid().isEmpty()) {
1113         QDomElement center = dd.createElement("center");
1114         head.appendChild(center);
1115         center.appendChild(dd.createTextNode(print()->headerFooter()->headMid()));
1116     }
1117     if (!print()->headerFooter()->headRight().isEmpty()) {
1118         QDomElement right = dd.createElement("right");
1119         head.appendChild(right);
1120         right.appendChild(dd.createTextNode(print()->headerFooter()->headRight()));
1121     }
1122     QDomElement foot = dd.createElement("foot");
1123     paper.appendChild(foot);
1124     if (!print()->headerFooter()->footLeft().isEmpty()) {
1125         QDomElement left = dd.createElement("left");
1126         foot.appendChild(left);
1127         left.appendChild(dd.createTextNode(print()->headerFooter()->footLeft()));
1128     }
1129     if (!print()->headerFooter()->footMid().isEmpty()) {
1130         QDomElement center = dd.createElement("center");
1131         foot.appendChild(center);
1132         center.appendChild(dd.createTextNode(print()->headerFooter()->footMid()));
1133     }
1134     if (!print()->headerFooter()->footRight().isEmpty()) {
1135         QDomElement right = dd.createElement("right");
1136         foot.appendChild(right);
1137         right.appendChild(dd.createTextNode(print()->headerFooter()->footRight()));
1138     }
1139 
1140     // print range
1141     QDomElement printrange = dd.createElement("printrange-rect");
1142     QRect _printRange = printSettings()->printRegion().lastRange();
1143     int left = _printRange.left();
1144     int right = _printRange.right();
1145     int top = _printRange.top();
1146     int bottom = _printRange.bottom();
1147     //If whole rows are selected, then we store zeros, as KS_colMax may change in future
1148     if (left == 1 && right == KS_colMax) {
1149         left = 0;
1150         right = 0;
1151     }
1152     //If whole columns are selected, then we store zeros, as KS_rowMax may change in future
1153     if (top == 1 && bottom == KS_rowMax) {
1154         top = 0;
1155         bottom = 0;
1156     }
1157     printrange.setAttribute("left-rect", QString::number(left));
1158     printrange.setAttribute("right-rect", QString::number(right));
1159     printrange.setAttribute("bottom-rect", QString::number(bottom));
1160     printrange.setAttribute("top-rect", QString::number(top));
1161     sheet.appendChild(printrange);
1162 
1163     // Print repeat columns
1164     QDomElement printRepeatColumns = dd.createElement("printrepeatcolumns");
1165     printRepeatColumns.setAttribute("left", QString::number(printSettings()->repeatedColumns().first));
1166     printRepeatColumns.setAttribute("right", QString::number(printSettings()->repeatedColumns().second));
1167     sheet.appendChild(printRepeatColumns);
1168 
1169     // Print repeat rows
1170     QDomElement printRepeatRows = dd.createElement("printrepeatrows");
1171     printRepeatRows.setAttribute("top", QString::number(printSettings()->repeatedRows().first));
1172     printRepeatRows.setAttribute("bottom", QString::number(printSettings()->repeatedRows().second));
1173     sheet.appendChild(printRepeatRows);
1174 
1175     //Save print zoom
1176     sheet.setAttribute("printZoom", QString::number(printSettings()->zoom()));
1177 
1178     //Save page limits
1179     const QSize pageLimits = printSettings()->pageLimits();
1180     sheet.setAttribute("printPageLimitX", QString::number(pageLimits.width()));
1181     sheet.setAttribute("printPageLimitY", QString::number(pageLimits.height()));
1182 
1183     // Save all cells.
1184     const QRect usedArea = this->usedArea();
1185     for (int row = 1; row <= usedArea.height(); ++row) {
1186         Cell cell = d->cellStorage->firstInRow(row);
1187         while (!cell.isNull()) {
1188             QDomElement e = cell.save(dd);
1189             if (!e.isNull())
1190                 sheet.appendChild(e);
1191             cell = d->cellStorage->nextInRow(cell.column(), row);
1192         }
1193     }
1194 
1195     // Save all RowFormat objects.
1196     int styleIndex = styleStorage()->nextRowStyleIndex(0);
1197     int rowFormatRow = 0, lastRowFormatRow = rowFormats()->lastNonDefaultRow();
1198     while (styleIndex || rowFormatRow <= lastRowFormatRow) {
1199         int lastRow;
1200         bool isDefault = rowFormats()->isDefaultRow(rowFormatRow, &lastRow);
1201         if (isDefault && styleIndex <= lastRow) {
1202             RowFormat rowFormat(*map()->defaultRowFormat());
1203             rowFormat.setSheet(this);
1204             rowFormat.setRow(styleIndex);
1205             QDomElement e = rowFormat.save(dd);
1206             if (e.isNull())
1207                 return QDomElement();
1208             sheet.appendChild(e);
1209             styleIndex = styleStorage()->nextRowStyleIndex(styleIndex);
1210         } else if (!isDefault) {
1211             RowFormat rowFormat(rowFormats(), rowFormatRow);
1212             QDomElement e = rowFormat.save(dd);
1213             if (e.isNull())
1214                 return QDomElement();
1215             sheet.appendChild(e);
1216             if (styleIndex == rowFormatRow)
1217                 styleIndex = styleStorage()->nextRowStyleIndex(styleIndex);
1218         }
1219         if (isDefault) rowFormatRow = qMin(lastRow+1, styleIndex == 0 ? KS_rowMax : styleIndex);
1220         else rowFormatRow++;
1221     }
1222 
1223     // Save all ColumnFormat objects.
1224     ColumnFormat* columnFormat = firstCol();
1225     styleIndex = styleStorage()->nextColumnStyleIndex(0);
1226     while (columnFormat || styleIndex) {
1227         if (columnFormat && (!styleIndex || columnFormat->column() <= styleIndex)) {
1228             QDomElement e = columnFormat->save(dd);
1229             if (e.isNull())
1230                 return QDomElement();
1231             sheet.appendChild(e);
1232             if (columnFormat->column() == styleIndex)
1233                 styleIndex = styleStorage()->nextColumnStyleIndex(styleIndex);
1234             columnFormat = columnFormat->next();
1235         } else if (styleIndex) {
1236             ColumnFormat columnFormat(*map()->defaultColumnFormat());
1237             columnFormat.setSheet(this);
1238             columnFormat.setColumn(styleIndex);
1239             QDomElement e = columnFormat.save(dd);
1240             if (e.isNull())
1241                 return QDomElement();
1242             sheet.appendChild(e);
1243             styleIndex = styleStorage()->nextColumnStyleIndex(styleIndex);
1244         }
1245     }
1246 #if 0 // CALLIGRA_SHEETS_KOPART_EMBEDDING
1247     foreach(EmbeddedObject* object, doc()->embeddedObjects()) {
1248         if (object->sheet() == this) {
1249             QDomElement e = object->save(dd);
1250 
1251             if (e.isNull())
1252                 return QDomElement();
1253             sheet.appendChild(e);
1254         }
1255     }
1256 #endif // CALLIGRA_SHEETS_KOPART_EMBEDDING
1257     return sheet;
1258 }
1259 
1260 bool Sheet::isLoading()
1261 {
1262     return map()->isLoading();
1263 }
1264 
1265 void Sheet::checkContentDirection(QString const & name)
1266 {
1267     /* set sheet's direction to RTL if sheet name is an RTL string */
1268     if ((name.isRightToLeft()))
1269         setLayoutDirection(Qt::RightToLeft);
1270     else
1271         setLayoutDirection(Qt::LeftToRight);
1272 }
1273 
1274 QRect Sheet::usedArea(bool onlyContent) const
1275 {
1276     int maxCols = d->cellStorage->columns(!onlyContent);
1277     int maxRows = d->cellStorage->rows(!onlyContent);
1278 
1279     if (!onlyContent) {
1280         maxRows = qMax(maxRows, d->rows.lastNonDefaultRow());
1281 
1282         const ColumnFormat* col = firstCol();
1283         while (col) {
1284             if (col->column() > maxCols)
1285                 maxCols = col->column();
1286 
1287             col = col->next();
1288         }
1289     }
1290 
1291     // flake
1292     QRectF shapesBoundingRect;
1293     for (int i = 0; i < d->shapes.count(); ++i)
1294         shapesBoundingRect |= d->shapes[i]->boundingRect();
1295     const QRect shapesCellRange = documentToCellCoordinates(shapesBoundingRect);
1296     maxCols = qMax(maxCols, shapesCellRange.right());
1297     maxRows = qMax(maxRows, shapesCellRange.bottom());
1298 
1299     return QRect(1, 1, maxCols, maxRows);
1300 }
1301 
1302 bool Sheet::loadXML(const KoXmlElement& sheet)
1303 {
1304     bool ok = false;
1305     QString sname = sheetName();
1306     if (!map()->loadingInfo()->loadTemplate()) {
1307         sname = sheet.attribute("name");
1308         if (sname.isEmpty()) {
1309             doc()->setErrorMessage(i18n("Invalid document. Sheet name is empty."));
1310             return false;
1311         }
1312     }
1313 
1314     bool detectDirection = true;
1315     QString layoutDir = sheet.attribute("layoutDirection");
1316     if (!layoutDir.isEmpty()) {
1317         if (layoutDir == "rtl") {
1318             detectDirection = false;
1319             setLayoutDirection(Qt::RightToLeft);
1320         } else if (layoutDir == "ltr") {
1321             detectDirection = false;
1322             setLayoutDirection(Qt::LeftToRight);
1323         } else
1324             debugSheets << " Direction not implemented :" << layoutDir;
1325     }
1326     if (detectDirection)
1327         checkContentDirection(sname);
1328 
1329     /* older versions of KSpread allowed all sorts of characters that
1330     the parser won't actually understand.  Replace these with '_'
1331     Also, the initial character cannot be a space.
1332     */
1333     while (sname[0] == ' ') {
1334         sname.remove(0, 1);
1335     }
1336     for (int i = 0; i < sname.length(); i++) {
1337         if (!(sname[i].isLetterOrNumber() ||
1338                 sname[i] == ' ' || sname[i] == '.' || sname[i] == '_')) {
1339             sname[i] = '_';
1340         }
1341     }
1342 
1343     // validate sheet name, if it differs from the current one
1344     if (sname != sheetName()) {
1345         /* make sure there are no name collisions with the altered name */
1346         QString testName = sname;
1347         QString baseName = sname;
1348         int nameSuffix = 0;
1349 
1350         /* so we don't panic over finding ourself in the following test*/
1351         sname.clear();
1352         while (map()->findSheet(testName) != 0) {
1353             nameSuffix++;
1354             testName = baseName + '_' + QString::number(nameSuffix);
1355         }
1356         sname = testName;
1357 
1358         debugSheets << "Sheet::loadXML: table name =" << sname;
1359         setObjectName(sname);
1360         setSheetName(sname, true);
1361     }
1362 
1363 //     (dynamic_cast<SheetIface*>(dcopObject()))->sheetNameHasChanged();
1364 
1365     if (sheet.hasAttribute("grid")) {
1366         setShowGrid((int)sheet.attribute("grid").toInt(&ok));
1367         // we just ignore 'ok' - if it didn't work, go on
1368     }
1369     if (sheet.hasAttribute("printGrid")) {
1370         print()->settings()->setPrintGrid((bool)sheet.attribute("printGrid").toInt(&ok));
1371         // we just ignore 'ok' - if it didn't work, go on
1372     }
1373     if (sheet.hasAttribute("printCommentIndicator")) {
1374         print()->settings()->setPrintCommentIndicator((bool)sheet.attribute("printCommentIndicator").toInt(&ok));
1375         // we just ignore 'ok' - if it didn't work, go on
1376     }
1377     if (sheet.hasAttribute("printFormulaIndicator")) {
1378         print()->settings()->setPrintFormulaIndicator((bool)sheet.attribute("printFormulaIndicator").toInt(&ok));
1379         // we just ignore 'ok' - if it didn't work, go on
1380     }
1381     if (sheet.hasAttribute("hide")) {
1382         setHidden((bool)sheet.attribute("hide").toInt(&ok));
1383         // we just ignore 'ok' - if it didn't work, go on
1384     }
1385     if (sheet.hasAttribute("showFormula")) {
1386         setShowFormula((bool)sheet.attribute("showFormula").toInt(&ok));
1387         // we just ignore 'ok' - if it didn't work, go on
1388     }
1389     //Compatibility with KSpread 1.1.x
1390     if (sheet.hasAttribute("formular")) {
1391         setShowFormula((bool)sheet.attribute("formular").toInt(&ok));
1392         // we just ignore 'ok' - if it didn't work, go on
1393     }
1394     if (sheet.hasAttribute("showFormulaIndicator")) {
1395         setShowFormulaIndicator((bool)sheet.attribute("showFormulaIndicator").toInt(&ok));
1396         // we just ignore 'ok' - if it didn't work, go on
1397     }
1398     if (sheet.hasAttribute("showCommentIndicator")) {
1399         setShowCommentIndicator((bool)sheet.attribute("showCommentIndicator").toInt(&ok));
1400         // we just ignore 'ok' - if it didn't work, go on
1401     }
1402     if (sheet.hasAttribute("borders")) {
1403         setShowPageOutline((bool)sheet.attribute("borders").toInt(&ok));
1404         // we just ignore 'ok' - if it didn't work, go on
1405     }
1406     if (sheet.hasAttribute("lcmode")) {
1407         setLcMode((bool)sheet.attribute("lcmode").toInt(&ok));
1408         // we just ignore 'ok' - if it didn't work, go on
1409     }
1410     if (sheet.hasAttribute("autoCalc")) {
1411         setAutoCalculationEnabled((bool)sheet.attribute("autoCalc").toInt(&ok));
1412         // we just ignore 'ok' - if it didn't work, go on
1413     }
1414     if (sheet.hasAttribute("columnnumber")) {
1415         setShowColumnNumber((bool)sheet.attribute("columnnumber").toInt(&ok));
1416         // we just ignore 'ok' - if it didn't work, go on
1417     }
1418     if (sheet.hasAttribute("hidezero")) {
1419         setHideZero((bool)sheet.attribute("hidezero").toInt(&ok));
1420         // we just ignore 'ok' - if it didn't work, go on
1421     }
1422     if (sheet.hasAttribute("firstletterupper")) {
1423         setFirstLetterUpper((bool)sheet.attribute("firstletterupper").toInt(&ok));
1424         // we just ignore 'ok' - if it didn't work, go on
1425     }
1426 
1427     // Load the paper layout
1428     KoXmlElement paper = sheet.namedItem("paper").toElement();
1429     if (!paper.isNull()) {
1430         KoPageLayout pageLayout;
1431         pageLayout.format = KoPageFormat::formatFromString(paper.attribute("format"));
1432         pageLayout.orientation = (paper.attribute("orientation")  == "Portrait")
1433                                  ? KoPageFormat::Portrait : KoPageFormat::Landscape;
1434 
1435         // <borders>
1436         KoXmlElement borders = paper.namedItem("borders").toElement();
1437         if (!borders.isNull()) {
1438             pageLayout.leftMargin   = MM_TO_POINT(borders.attribute("left").toFloat());
1439             pageLayout.rightMargin  = MM_TO_POINT(borders.attribute("right").toFloat());
1440             pageLayout.topMargin    = MM_TO_POINT(borders.attribute("top").toFloat());
1441             pageLayout.bottomMargin = MM_TO_POINT(borders.attribute("bottom").toFloat());
1442         }
1443         print()->settings()->setPageLayout(pageLayout);
1444 
1445         QString hleft, hright, hcenter;
1446         QString fleft, fright, fcenter;
1447         // <head>
1448         KoXmlElement head = paper.namedItem("head").toElement();
1449         if (!head.isNull()) {
1450             KoXmlElement left = head.namedItem("left").toElement();
1451             if (!left.isNull())
1452                 hleft = left.text();
1453             KoXmlElement center = head.namedItem("center").toElement();
1454             if (!center.isNull())
1455                 hcenter = center.text();
1456             KoXmlElement right = head.namedItem("right").toElement();
1457             if (!right.isNull())
1458                 hright = right.text();
1459         }
1460         // <foot>
1461         KoXmlElement foot = paper.namedItem("foot").toElement();
1462         if (!foot.isNull()) {
1463             KoXmlElement left = foot.namedItem("left").toElement();
1464             if (!left.isNull())
1465                 fleft = left.text();
1466             KoXmlElement center = foot.namedItem("center").toElement();
1467             if (!center.isNull())
1468                 fcenter = center.text();
1469             KoXmlElement right = foot.namedItem("right").toElement();
1470             if (!right.isNull())
1471                 fright = right.text();
1472         }
1473         print()->headerFooter()->setHeadFootLine(hleft, hcenter, hright, fleft, fcenter, fright);
1474     }
1475 
1476     // load print range
1477     KoXmlElement printrange = sheet.namedItem("printrange-rect").toElement();
1478     if (!printrange.isNull()) {
1479         int left = printrange.attribute("left-rect").toInt();
1480         int right = printrange.attribute("right-rect").toInt();
1481         int bottom = printrange.attribute("bottom-rect").toInt();
1482         int top = printrange.attribute("top-rect").toInt();
1483         if (left == 0) { //whole row(s) selected
1484             left = 1;
1485             right = KS_colMax;
1486         }
1487         if (top == 0) { //whole column(s) selected
1488             top = 1;
1489             bottom = KS_rowMax;
1490         }
1491         const Region region(QRect(QPoint(left, top), QPoint(right, bottom)), this);
1492         printSettings()->setPrintRegion(region);
1493     }
1494 
1495     // load print zoom
1496     if (sheet.hasAttribute("printZoom")) {
1497         double zoom = sheet.attribute("printZoom").toDouble(&ok);
1498         if (ok) {
1499             printSettings()->setZoom(zoom);
1500         }
1501     }
1502 
1503     // load page limits
1504     if (sheet.hasAttribute("printPageLimitX")) {
1505         int pageLimit = sheet.attribute("printPageLimitX").toInt(&ok);
1506         if (ok) {
1507             printSettings()->setPageLimits(QSize(pageLimit, 0));
1508         }
1509     }
1510 
1511     // load page limits
1512     if (sheet.hasAttribute("printPageLimitY")) {
1513         int pageLimit = sheet.attribute("printPageLimitY").toInt(&ok);
1514         if (ok) {
1515             const int horizontalLimit = printSettings()->pageLimits().width();
1516             printSettings()->setPageLimits(QSize(horizontalLimit, pageLimit));
1517         }
1518     }
1519 
1520     // Load the cells
1521     KoXmlNode n = sheet.firstChild();
1522     while (!n.isNull()) {
1523         KoXmlElement e = n.toElement();
1524         if (!e.isNull()) {
1525             QString tagName = e.tagName();
1526             if (tagName == "cell")
1527                 Cell(this, 1, 1).load(e, 0, 0); // col, row will get overridden in all cases
1528             else if (tagName == "row") {
1529                 RowFormat *rl = new RowFormat();
1530                 rl->setSheet(this);
1531                 if (rl->load(e))
1532                     insertRowFormat(rl);
1533                 delete rl;
1534             } else if (tagName == "column") {
1535                 ColumnFormat *cl = new ColumnFormat();
1536                 cl->setSheet(this);
1537                 if (cl->load(e))
1538                     insertColumnFormat(cl);
1539                 else
1540                     delete cl;
1541             }
1542 #if 0 // CALLIGRA_SHEETS_KOPART_EMBEDDING
1543             else if (tagName == "object") {
1544                 EmbeddedCalligraObject *ch = new EmbeddedCalligraObject(doc(), this);
1545                 if (ch->load(e))
1546                     insertObject(ch);
1547                 else {
1548                     ch->embeddedObject()->setDeleted(true);
1549                     delete ch;
1550                 }
1551             } else if (tagName == "chart") {
1552                 EmbeddedChart *ch = new EmbeddedChart(doc(), this);
1553                 if (ch->load(e))
1554                     insertObject(ch);
1555                 else {
1556                     ch->embeddedObject()->setDeleted(true);
1557                     delete ch;
1558                 }
1559             }
1560 #endif // CALLIGRA_SHEETS_KOPART_EMBEDDING
1561         }
1562         n = n.nextSibling();
1563     }
1564 
1565     // load print repeat columns
1566     KoXmlElement printrepeatcolumns = sheet.namedItem("printrepeatcolumns").toElement();
1567     if (!printrepeatcolumns.isNull()) {
1568         int left = printrepeatcolumns.attribute("left").toInt();
1569         int right = printrepeatcolumns.attribute("right").toInt();
1570         printSettings()->setRepeatedColumns(qMakePair(left, right));
1571     }
1572 
1573     // load print repeat rows
1574     KoXmlElement printrepeatrows = sheet.namedItem("printrepeatrows").toElement();
1575     if (!printrepeatrows.isNull()) {
1576         int top = printrepeatrows.attribute("top").toInt();
1577         int bottom = printrepeatrows.attribute("bottom").toInt();
1578         printSettings()->setRepeatedRows(qMakePair(top, bottom));
1579     }
1580 
1581     if (!sheet.hasAttribute("borders1.2")) {
1582         convertObscuringBorders();
1583     }
1584 
1585     loadXmlProtection(sheet);
1586 
1587     return true;
1588 }
1589 
1590 
1591 bool Sheet::loadChildren(KoStore* _store)
1592 {
1593     Q_UNUSED(_store);
1594 #if 0 // CALLIGRA_SHEETS_KOPART_EMBEDDING
1595     foreach(EmbeddedObject* object, doc()->embeddedObjects()) {
1596         if (object->sheet() == this && (object->getType() == OBJECT_CALLIGRA_PART || object->getType() == OBJECT_CHART)) {
1597             debugSheets << "Calligra::Sheets::Sheet::loadChildren";
1598             if (!dynamic_cast<EmbeddedCalligraObject*>(object)->embeddedObject()->loadDocument(_store))
1599                 return false;
1600         }
1601     }
1602 #endif // CALLIGRA_SHEETS_KOPART_EMBEDDING
1603     return true;
1604 }
1605 
1606 
1607 void Sheet::setShowPageOutline(bool b)
1608 {
1609     if (b == d->showPageOutline)
1610         return;
1611 
1612     d->showPageOutline = b;
1613     // Just repaint everything visible; no need to invalidate the visual cache.
1614     if (!map()->isLoading()) {
1615         map()->addDamage(new SheetDamage(this, SheetDamage::ContentChanged));
1616     }
1617 }
1618 
1619 QImage Sheet::backgroundImage() const
1620 {
1621     return d->backgroundImage;
1622 }
1623 
1624 void Sheet::setBackgroundImage(const QImage& image)
1625 {
1626     d->backgroundImage = image;
1627 }
1628 
1629 Sheet::BackgroundImageProperties Sheet::backgroundImageProperties() const
1630 {
1631     return d->backgroundProperties;
1632 }
1633 
1634 void Sheet::setBackgroundImageProperties(const Sheet::BackgroundImageProperties& properties)
1635 {
1636     d->backgroundProperties = properties;
1637 }
1638 
1639 void Sheet::insertColumnFormat(ColumnFormat *l)
1640 {
1641     d->columns.insertElement(l, l->column());
1642     if (!map()->isLoading()) {
1643         map()->addDamage(new SheetDamage(this, SheetDamage::ColumnsChanged));
1644     }
1645 }
1646 
1647 void Sheet::insertRowFormat(RowFormat *l)
1648 {
1649     const int row = l->row();
1650     d->rows.setRowHeight(row, row, l->height());
1651     d->rows.setHidden(row, row, l->isHidden());
1652     d->rows.setFiltered(row, row, l->isFiltered());
1653     d->rows.setPageBreak(row, row, l->hasPageBreak());
1654     if (!map()->isLoading()) {
1655         map()->addDamage(new SheetDamage(this, SheetDamage::RowsChanged));
1656     }
1657 }
1658 
1659 void Sheet::deleteColumnFormat(int column)
1660 {
1661     d->columns.removeElement(column);
1662     if (!map()->isLoading()) {
1663         map()->addDamage(new SheetDamage(this, SheetDamage::ColumnsChanged));
1664     }
1665 }
1666 
1667 void Sheet::deleteRowFormat(int row)
1668 {
1669     d->rows.setDefault(row, row);
1670     if (!map()->isLoading()) {
1671         map()->addDamage(new SheetDamage(this, SheetDamage::RowsChanged));
1672     }
1673 }
1674 
1675 
1676 RowFormatStorage* Sheet::rowFormats()
1677 {
1678     return &d->rows;
1679 }
1680 
1681 const RowFormatStorage* Sheet::rowFormats() const
1682 {
1683     return &d->rows;
1684 }
1685 
1686 void Sheet::showStatusMessage(const QString &message, int timeout)
1687 {
1688     emit statusMessage(message, timeout);
1689 }
1690 
1691 void Sheet::hideSheet(bool _hide)
1692 {
1693     setHidden(_hide);
1694     if (_hide)
1695         map()->addDamage(new SheetDamage(this, SheetDamage::Hidden));
1696     else
1697         map()->addDamage(new SheetDamage(this, SheetDamage::Shown));
1698 }
1699 
1700 bool Sheet::setSheetName(const QString& name, bool init)
1701 {
1702     Q_UNUSED(init);
1703     if (map()->findSheet(name))
1704         return false;
1705 
1706     if (isProtected())
1707         return false;
1708 
1709     if (d->name == name)
1710         return true;
1711 
1712     QString old_name = d->name;
1713     d->name = name;
1714 
1715     // FIXME: Why is the change of a sheet's name not supposed to be propagated here?
1716     // If it is not, we have to manually do so in the loading process, e.g. for the
1717     // SheetAccessModel in the document's data center map.
1718     //if (init)
1719     //    return true;
1720 
1721     foreach(Sheet* sheet, map()->sheetList()) {
1722         sheet->changeCellTabName(old_name, name);
1723     }
1724 
1725     map()->addDamage(new SheetDamage(this, SheetDamage::Name));
1726 
1727     setObjectName(name);
1728 //     (dynamic_cast<SheetIface*>(dcopObject()))->sheetNameHasChanged();
1729 
1730     return true;
1731 }
1732 
1733 
1734 void Sheet::updateLocale()
1735 {
1736     for (int c = 0; c < valueStorage()->count(); ++c) {
1737         Cell cell(this, valueStorage()->col(c), valueStorage()->row(c));
1738         QString text = cell.userInput();
1739         cell.parseUserInput(text);
1740     }
1741     // Affects the displayed value; rebuild the visual cache.
1742     const Region region(1, 1, KS_colMax, KS_rowMax, this);
1743     map()->addDamage(new CellDamage(this, region, CellDamage::Appearance));
1744 }
1745 
1746 void Sheet::convertObscuringBorders()
1747 {
1748     // FIXME Stefan: Verify that this is not needed anymore.
1749 #if 0
1750     /* a word of explanation here:
1751        beginning with KSpread 1.2 (actually, cvs of Mar 28, 2002), border information
1752        is stored differently.  Previously, for a cell obscuring a region, the entire
1753        region's border's data would be stored in the obscuring cell.  This caused
1754        some data loss in certain situations.  After that date, each cell stores
1755        its own border data, and prints it even if it is an obscured cell (as long
1756        as that border isn't across an obscuring border).
1757        Anyway, this function is used when loading a file that was stored with the
1758        old way of borders.  All new files have the sheet attribute "borders1.2" so
1759        if that isn't in the file, all the border data will be converted here.
1760        It's a bit of a hack but I can't think of a better way and it's not *that*
1761        bad of a hack.:-)
1762     */
1763     Cell c = d->cellStorage->firstCell();
1764     QPen topPen, bottomPen, leftPen, rightPen;
1765     for (; c; c = c->nextCell()) {
1766         if (c->extraXCells() > 0 || c->extraYCells() > 0) {
1767             const Style* style = this->style(c->column(), c->row());
1768             topPen = style->topBorderPen();
1769             leftPen = style->leftBorderPen();
1770             rightPen = style->rightBorderPen();
1771             bottomPen = style->bottomBorderPen();
1772 
1773             c->format()->setTopBorderStyle(Qt::NoPen);
1774             c->format()->setLeftBorderStyle(Qt::NoPen);
1775             c->format()->setRightBorderStyle(Qt::NoPen);
1776             c->format()->setBottomBorderStyle(Qt::NoPen);
1777 
1778             for (int x = c->column(); x < c->column() + c->extraXCells(); x++) {
1779                 Cell(this, x, c->row())->setTopBorderPen(topPen);
1780                 Cell(this, x, c->row() + c->extraYCells())->
1781                 setBottomBorderPen(bottomPen);
1782             }
1783             for (int y = c->row(); y < c->row() + c->extraYCells(); y++) {
1784                 Cell(this, c->column(), y)->setLeftBorderPen(leftPen);
1785                 Cell(this, c->column() + c->extraXCells(), y)->
1786                 setRightBorderPen(rightPen);
1787             }
1788         }
1789     }
1790 #endif
1791 }
1792 
1793 void Sheet::applyDatabaseFilter(const Database &database)
1794 {
1795     Sheet* const sheet = database.range().lastSheet();
1796     const QRect range = database.range().lastRange();
1797     const int start = database.orientation() == Qt::Vertical ? range.top() : range.left();
1798     const int end = database.orientation() == Qt::Vertical ? range.bottom() : range.right();
1799     for (int i = start + 1; i <= end; ++i) {
1800         const bool isFiltered = !database.filter().evaluate(database, i);
1801 //         debugSheets <<"Filtering column/row" << i <<"?" << isFiltered;
1802         if (database.orientation() == Qt::Vertical) {
1803             sheet->rowFormats()->setFiltered(i, i, isFiltered);
1804         } else { // database.orientation() == Qt::Horizontal
1805             sheet->nonDefaultColumnFormat(i)->setFiltered(isFiltered);
1806         }
1807     }
1808     if (database.orientation() == Qt::Vertical)
1809         sheet->map()->addDamage(new SheetDamage(sheet, SheetDamage::RowsChanged));
1810     else // database.orientation() == Qt::Horizontal
1811         sheet->map()->addDamage(new SheetDamage(sheet, SheetDamage::ColumnsChanged));
1812 
1813     cellStorage()->setDatabase(database.range(), Database());
1814     cellStorage()->setDatabase(database.range(), database);
1815     map()->addDamage(new CellDamage(this, database.range(), CellDamage::Appearance));
1816 }
1817 
1818 /**********************
1819  * Printout Functions *
1820  **********************/
1821 
1822 #ifndef NDEBUG
1823 void Sheet::printDebug()
1824 {
1825     int iMaxColumn = d->cellStorage->columns();
1826     int iMaxRow = d->cellStorage->rows();
1827 
1828     debugSheets << "Cell | Content | Value  [UserInput]";
1829     Cell cell;
1830     for (int currentrow = 1 ; currentrow <= iMaxRow ; ++currentrow) {
1831         for (int currentcolumn = 1 ; currentcolumn <= iMaxColumn ; currentcolumn++) {
1832             cell = Cell(this, currentcolumn, currentrow);
1833             if (!cell.isEmpty()) {
1834                 QString cellDescr = Cell::name(currentcolumn, currentrow).rightJustified(4) +
1835                 //QString cellDescr = "Cell ";
1836                 //cellDescr += QString::number(currentrow).rightJustified(3,'0') + ',';
1837                 //cellDescr += QString::number(currentcolumn).rightJustified(3,'0') + ' ';
1838                     " | ";
1839                 QString valueType;
1840                 QTextStream stream(&valueType);
1841                 stream << cell.value().type();
1842                 cellDescr += valueType.rightJustified(7) +
1843                              " | " +
1844                              map()->converter()->asString(cell.value()).asString().rightJustified(5) +
1845                              QString("  [%1]").arg(cell.userInput());
1846                 debugSheets << cellDescr;
1847             }
1848         }
1849     }
1850 }
1851 #endif
1852 
1853 } // namespace Sheets
1854 } // namespace Calligra