File indexing completed on 2024-05-12 16:36:10

0001 /* This file is part of the KDE project
0002    Copyright 2006 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
0003 
0004    This library is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LIB.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017    Boston, MA 02110-1301, USA.
0018 */
0019 
0020 // Local
0021 #include "SheetView.h"
0022 
0023 #include <QCache>
0024 #include <QRect>
0025 #include <QPainter>
0026 #include <QPainterPath>
0027 #ifdef CALLIGRA_SHEETS_MT
0028 #include <QMutex>
0029 #include <QMutexLocker>
0030 #include <QReadWriteLock>
0031 #include <QReadLocker>
0032 #include <QWriteLocker>
0033 #endif
0034 
0035 #include <KoViewConverter.h>
0036 
0037 #include "CellView.h"
0038 #include "calligra_sheets_limits.h"
0039 #include "PointStorage.h"
0040 #include "RectStorage.h"
0041 #include "Region.h"
0042 #include "RowColumnFormat.h"
0043 #include "RowFormatStorage.h"
0044 #include "Sheet.h"
0045 
0046 using namespace Calligra::Sheets;
0047 
0048 struct CellPaintData
0049 {
0050     CellPaintData(const CellView &cellView, const Cell &cell, const QPointF &coordinate)
0051         : cellView(cellView)
0052           , cell(cell)
0053           , coordinate(coordinate)
0054     {}
0055     CellView cellView;
0056     Cell cell;
0057     QPointF coordinate;
0058 };
0059 
0060 class Q_DECL_HIDDEN SheetView::Private
0061 {
0062 public:
0063     Private()
0064 #ifdef CALLIGRA_SHEETS_MT
0065         : cacheMutex(QMutex::Recursive)
0066 #endif
0067     {}
0068     const Sheet* sheet;
0069     const KoViewConverter* viewConverter;
0070     QRect visibleRect;
0071     QCache<QPoint, CellView> cache;
0072 #ifdef CALLIGRA_SHEETS_MT
0073     QMutex cacheMutex;
0074 #endif
0075     QRegion cachedArea;
0076     CellView* defaultCellView;
0077     // The maximum accessed cell range used for the scrollbar ranges.
0078     QSize accessedCellRange;
0079     FusionStorage* obscuredInfo;
0080     QSize obscuredRange; // size of the bounding box of obscuredInfo
0081 #ifdef CALLIGRA_SHEETS_MT
0082     QReadWriteLock obscuredLock;
0083 #endif
0084 
0085     PointStorage<bool> highlightedCells;
0086     QPoint activeHighlight;
0087 #ifdef CALLIGRA_SHEETS_MT
0088     QReadWriteLock highlightLock;
0089 #endif
0090     QColor highlightColor;
0091     QColor highlightMaskColor;
0092     QColor activeHighlightColor;
0093 public:
0094     Cell cellToProcess(int col, int row, QPointF& coordinate, QSet<Cell>& processedMergedCells, const QRect& visRect);
0095 #ifdef CALLIGRA_SHEETS_MT
0096     CellView cellViewToProcess(Cell& cell, QPointF& coordinate, QSet<Cell>& processedObscuredCells,
0097                                SheetView* sheetView, const QRect& visRect);
0098 #else
0099     const CellView& cellViewToProcess(Cell& cell, QPointF& coordinate, QSet<Cell>& processedObscuredCells,
0100                                SheetView* sheetView, const QRect& visRect);
0101 #endif
0102 };
0103 
0104 Cell SheetView::Private::cellToProcess(int col, int row, QPointF& coordinate,
0105                                        QSet<Cell>& processedMergedCells,
0106                                        const QRect& visRect)
0107 {
0108     Cell cell(sheet, col, row);
0109     if (cell.isPartOfMerged()) {
0110         cell = cell.masterCell();
0111         // if the rect of visible cells contains this master cell, it was already painted
0112         if (visRect.contains(cell.cellPosition())) {
0113             coordinate.setY(coordinate.y() + sheet->rowFormats()->rowHeight(row));
0114             return Cell(); // next row
0115         }
0116         // if the out of bounds master cell was already painted, there's nothing more to do
0117         if (processedMergedCells.contains(cell)) {
0118             coordinate.setY(coordinate.y() + sheet->rowFormats()->rowHeight(row));
0119             return Cell(); // next row
0120         }
0121         processedMergedCells.insert(cell);
0122         // take the coordinate of the master cell
0123         if (sheet->layoutDirection() == Qt::RightToLeft) {
0124             for (int c = cell.column()+1; c <= col; ++c)
0125                 coordinate.setX(coordinate.x() + sheet->columnFormat(c)->width());
0126         } else {
0127             for (int c = cell.column(); c < col; ++c)
0128                 coordinate.setX(coordinate.x() - sheet->columnFormat(c)->width());
0129         }
0130         for (int r = cell.row(); r < row; ++r)
0131             coordinate.setY(coordinate.y() - sheet->rowFormats()->rowHeight(r));
0132     }
0133     return cell;
0134 }
0135 
0136 #ifdef CALLIGRA_SHEETS_MT
0137 CellView SheetView::Private::cellViewToProcess(Cell& cell, QPointF& coordinate,
0138         QSet<Cell>& processedObscuredCells, SheetView* sheetView, const QRect& visRect)
0139 #else
0140 const CellView& SheetView::Private::cellViewToProcess(Cell& cell, QPointF& coordinate,
0141         QSet<Cell>& processedObscuredCells, SheetView* sheetView, const QRect& visRect)
0142 #endif
0143 {
0144     const int col = cell.column();
0145     const int row = cell.row();
0146     const QPoint cellPos = cell.cellPosition();
0147 #ifdef CALLIGRA_SHEETS_MT
0148     CellView cellView = sheetView->cellView(col, row);
0149 #else
0150     const CellView& cellView = sheetView->cellView(col, row);
0151 #endif
0152     if (sheetView->isObscured(cellPos)) {
0153         // if the rect of visible cells contains the obscuring cell, it was already painted
0154         if (visRect.contains(sheetView->obscuringCell(cellPos))) {
0155             coordinate.setY(coordinate.y() + sheet->rowFormats()->rowHeight(row));
0156             cell = Cell();
0157             return cellView; // next row
0158         }
0159         cell = Cell(sheet, sheetView->obscuringCell(cellPos));
0160         if (processedObscuredCells.contains(cell)) {
0161             coordinate.setY(coordinate.y() + sheet->rowFormats()->rowHeight(row));
0162             cell = Cell();
0163             return cellView; // next row
0164         }
0165         processedObscuredCells.insert(cell);
0166         // take the coordinate of the obscuring cell
0167         if (sheet->layoutDirection() == Qt::RightToLeft) {
0168             for (int c = cell.column()+1; c <= col; ++c)
0169                 coordinate.setX(coordinate.x() + sheet->columnFormat(c)->width());
0170         } else {
0171             for (int c = cell.column(); c < col; ++c)
0172                 coordinate.setX(coordinate.x() - sheet->columnFormat(c)->width());
0173         }
0174         for (int r = cell.row(); r < row; ++r)
0175             coordinate.setY(coordinate.y() - sheet->rowFormats()->rowHeight(r));
0176         // use the CellView of the obscuring cell
0177         return sheetView->cellView(cell.column(), cell.row());
0178     }
0179     return cellView;
0180 }
0181 
0182 
0183 SheetView::SheetView(const Sheet* sheet)
0184         : QObject(const_cast<Sheet*>(sheet))
0185         , d(new Private)
0186 {
0187     d->sheet = sheet;
0188     d->viewConverter = 0;
0189     d->visibleRect = QRect(1, 1, 0, 0);
0190     d->cache.setMaxCost(10000);
0191     d->defaultCellView = createDefaultCellView();
0192     d->accessedCellRange =  sheet->usedArea().size().expandedTo(QSize(256, 256));
0193     d->obscuredInfo = new FusionStorage(sheet->map());
0194     d->obscuredRange = QSize(0, 0);
0195     d->highlightMaskColor = QColor(0, 0, 0, 128);
0196     d->activeHighlightColor = QColor(255, 127, 0, 128);
0197 }
0198 
0199 SheetView::~SheetView()
0200 {
0201     delete d->defaultCellView;
0202     delete d->obscuredInfo;
0203     delete d;
0204 }
0205 
0206 const Sheet* SheetView::sheet() const
0207 {
0208     return d->sheet;
0209 }
0210 
0211 void SheetView::setViewConverter(const KoViewConverter* viewConverter)
0212 {
0213     Q_ASSERT(viewConverter);
0214     d->viewConverter = viewConverter;
0215 }
0216 
0217 const KoViewConverter* SheetView::viewConverter() const
0218 {
0219     Q_ASSERT(d->viewConverter);
0220     return d->viewConverter;
0221 }
0222 
0223 #ifdef CALLIGRA_SHEETS_MT
0224 CellView SheetView::cellView(const QPoint& pos)
0225 #else
0226 const CellView& SheetView::cellView(const QPoint& pos)
0227 #endif
0228 {
0229     return cellView(pos.x(), pos.y());
0230 }
0231 
0232 #ifdef CALLIGRA_SHEETS_MT
0233 CellView SheetView::cellView(int col, int row)
0234 #else
0235 const CellView& SheetView::cellView(int col, int row)
0236 #endif
0237 {
0238     Q_ASSERT(1 <= col && col <= KS_colMax);
0239     Q_ASSERT(1 <= row && col <= KS_rowMax);
0240 #ifdef CALLIGRA_SHEETS_MT
0241     QMutexLocker ml(&d->cacheMutex);
0242 #endif
0243     CellView *v = d->cache.object(QPoint(col, row));
0244     if (!v) {
0245         v = createCellView(col, row);
0246         d->cache.insert(QPoint(col, row), v);
0247         d->cachedArea += QRect(col, row, 1, 1);
0248     }
0249 #ifdef CALLIGRA_SHEETS_MT
0250     // create a copy as long as the mutex is locked
0251     CellView cellViewCopy = *v;
0252     return cellViewCopy;
0253 #else
0254     return *v;
0255 #endif
0256 }
0257 
0258 void SheetView::setPaintCellRange(const QRect& rect)
0259 {
0260 #ifdef CALLIGRA_SHEETS_MT
0261     QMutexLocker ml(&d->cacheMutex);
0262 #endif
0263     d->visibleRect = rect & QRect(1, 1, KS_colMax, KS_rowMax);
0264     d->cache.setMaxCost(2 * rect.width() * rect.height());
0265 }
0266 
0267 QRect SheetView::paintCellRange() const
0268 {
0269     return d->visibleRect;
0270 }
0271 
0272 void SheetView::invalidateRegion(const Region& region)
0273 {
0274     QRegion qregion;
0275     Region::ConstIterator end(region.constEnd());
0276     for (Region::ConstIterator it(region.constBegin()); it != end; ++it) {
0277         qregion += (*it)->rect();
0278     }
0279     // reduce to the cached area
0280     qregion &= d->cachedArea;
0281     QVector<QRect> rects = qregion.rects();
0282     for (int i = 0; i < rects.count(); ++i)
0283         invalidateRange(rects[i]);
0284 }
0285 
0286 void SheetView::invalidate()
0287 {
0288 #ifdef CALLIGRA_SHEETS_MT
0289     QMutexLocker ml(&d->cacheMutex);
0290 #endif
0291     delete d->defaultCellView;
0292     d->defaultCellView = createDefaultCellView();
0293     d->cache.clear();
0294     d->cachedArea = QRegion();
0295     delete d->obscuredInfo;
0296     d->obscuredInfo = new FusionStorage(d->sheet->map());
0297     d->obscuredRange = QSize(0, 0);
0298 }
0299 
0300 void SheetView::paintCells(QPainter& painter, const QRectF& paintRect, const QPointF& topLeft, CanvasBase*, const QRect& visibleRect)
0301 {
0302     const QRect& visRect = visibleRect.isValid() ? visibleRect : d->visibleRect;
0303     // paintRect:   the canvas area, that should be painted; in document coordinates;
0304     //              no layout direction consideration; scrolling offset applied;
0305     //              independent from painter transformations
0306     // topLeft:     the document coordinate of the top left cell's top left corner;
0307     //              no layout direction consideration; independent from painter
0308     //              transformations
0309 
0310     // NOTE Stefan: The painting is split into several steps. In each of these all cells in
0311     //              d->visibleRect are traversed. This may appear suboptimal at the first look, but
0312     //              ensures that the borders are not erased by the background of adjacent cells.
0313 
0314 // debugSheets << "paintRect:" << paintRect;
0315 // debugSheets << "topLeft:" << topLeft;
0316 
0317     QRegion clipRect(painter.clipRegion());
0318     // 0. Paint the sheet background
0319     if (!sheet()->backgroundImage().isNull()) {
0320         //TODO support all the different properties
0321         Sheet::BackgroundImageProperties properties = sheet()->backgroundImageProperties();
0322         if( properties.repeat == Sheet::BackgroundImageProperties::Repeat ) {
0323             const int firstCol = visRect.left();
0324             const int firstRow = visRect.top();
0325             const int firstColPosition = d->sheet->columnPosition(firstCol);
0326             const int firstRowPosition = d->sheet->rowPosition(firstRow);
0327 
0328             const int imageWidth = sheet()->backgroundImage().rect().width();
0329             const int imageHeight = sheet()->backgroundImage().rect().height();
0330 
0331             int xBackground = firstColPosition - (firstColPosition % imageWidth);
0332             int yBackground = firstRowPosition - (firstRowPosition % imageHeight);
0333 
0334             const int lastCol = visRect.right();
0335             const int lastRow = visRect.bottom();
0336             const int lastColPosition = d->sheet->columnPosition(lastCol);
0337             const int lastRowPosition = d->sheet->rowPosition(lastRow);
0338 
0339             while( xBackground < lastColPosition ) {
0340                 int y = yBackground;
0341                 while( y < lastRowPosition ) {
0342                     painter.drawImage(QRect(xBackground, y, imageWidth, imageHeight), sheet()->backgroundImage());
0343                     y += imageHeight;
0344                 }
0345                 xBackground += imageWidth;
0346             }
0347         }
0348     }
0349 
0350     // 1. Paint the cell background
0351 
0352     // Handle right-to-left layout.
0353     // In an RTL sheet the cells have to be painted at their opposite horizontal
0354     // location on the canvas, meaning that column A will be the rightmost column
0355     // on screen, column B will be to the left of it and so on. Here we change
0356     // the horizontal coordinate at which we start painting the cell in case the
0357     // sheet's direction is RTL.
0358     const bool rightToLeft = sheet()->layoutDirection() == Qt::RightToLeft;
0359     const QPointF startCoordinate(rightToLeft ? paintRect.width() - topLeft.x() : topLeft.x(), topLeft.y());
0360     QPointF coordinate(startCoordinate);
0361 // debugSheets << "start coordinate:" << coordinate;
0362     QSet<Cell> processedMergedCells;
0363     QSet<Cell> processedObscuredCells;
0364     QList<CellPaintData> cached_cells;
0365     for (int col = visRect.left(); col <= visRect.right(); ++col) {
0366         if (d->sheet->columnFormat(col)->isHiddenOrFiltered())
0367             continue;
0368         if (rightToLeft)
0369             coordinate.setX(coordinate.x() - d->sheet->columnFormat(col)->width());
0370 // debugSheets <<"coordinate:" << coordinate;
0371         for (int row = visRect.top(); row <= visRect.bottom(); ++row) {
0372             int lastHiddenRow;
0373             if (d->sheet->rowFormats()->isHiddenOrFiltered(row, &lastHiddenRow)) {
0374                 row = lastHiddenRow;
0375                 continue;
0376             }
0377             // save the coordinate
0378             const QPointF savedCoordinate = coordinate;
0379             // figure out, if any and which cell has to be painted (may be a master cell)
0380             Cell cell = d->cellToProcess(col, row, coordinate, processedMergedCells, visRect);
0381             if (!cell)
0382                 continue;
0383             // figure out, which CellView to use (may be one for an obscuring cell)
0384             CellPaintData cpd(d->cellViewToProcess(cell, coordinate, processedObscuredCells, this, visRect), cell, coordinate);
0385             if (!cell)
0386                 continue;
0387             cpd.cellView.paintCellBackground(painter, clipRect, coordinate);
0388             cached_cells.append(cpd);
0389             // restore coordinate
0390             coordinate = savedCoordinate;
0391             coordinate.setY(coordinate.y() + d->sheet->rowFormats()->rowHeight(row));
0392         }
0393         coordinate.setY(topLeft.y());
0394         if (!rightToLeft)
0395             coordinate.setX(coordinate.x() + d->sheet->columnFormat(col)->width());
0396     }
0397 
0398     // 2. Paint the cell content including markers (formula, comment, ...)
0399     for (QList<CellPaintData>::ConstIterator it(cached_cells.constBegin()); it != cached_cells.constEnd(); ++it) {
0400         it->cellView.paintCellContents(paintRect, painter, clipRect, it->coordinate, it->cell, this);
0401     }
0402 
0403     // 3. Paint the default borders
0404     coordinate = startCoordinate;
0405     processedMergedCells.clear();
0406     for (int col = visRect.left(); col <= visRect.right(); ++col) {
0407         if (d->sheet->columnFormat(col)->isHiddenOrFiltered())
0408             continue;
0409         if (rightToLeft)
0410             coordinate.setX(coordinate.x() - d->sheet->columnFormat(col)->width());
0411         for (int row = visRect.top(); row <= visRect.bottom(); ++row) {
0412             int lastHiddenRow;
0413             if (d->sheet->rowFormats()->isHiddenOrFiltered(row, &lastHiddenRow)) {
0414                 row = lastHiddenRow;
0415                 continue;
0416             }
0417             // For borders even cells, that are merged in, need to be traversed.
0418             // Think of a merged cell with a set border and one its neighbours has a thicker border.
0419             // but: also the master cell of a merged cell always needs to be processed
0420             const QPointF savedCoordinate = coordinate;
0421             Cell cell = d->cellToProcess(col, row, coordinate, processedMergedCells, visRect);
0422             if (!!cell && (cell.column() != col || cell.row() != row)) {
0423                 const CellView cellView = this->cellView(cell.cellPosition());
0424                 cellView.paintDefaultBorders(painter, clipRect, paintRect, coordinate,
0425                                              CellView::LeftBorder | CellView::RightBorder |
0426                                              CellView::TopBorder | CellView::BottomBorder,
0427                                              visRect, cell, this);
0428             }
0429             coordinate = savedCoordinate;
0430             const CellView cellView = this->cellView(col, row);
0431             cellView.paintDefaultBorders(painter, clipRect, paintRect, coordinate,
0432                                          CellView::LeftBorder | CellView::RightBorder |
0433                                          CellView::TopBorder | CellView::BottomBorder,
0434                                          visRect, Cell(d->sheet, col, row), this);
0435             coordinate.setY(coordinate.y() + d->sheet->rowFormats()->rowHeight(row));
0436         }
0437         coordinate.setY(topLeft.y());
0438         if (!rightToLeft)
0439             coordinate.setX(coordinate.x() + d->sheet->columnFormat(col)->width());
0440     }
0441 
0442     // 4. Paint the custom borders, diagonal lines and page borders
0443     coordinate = startCoordinate;
0444     processedMergedCells.clear();
0445     processedObscuredCells.clear();
0446     for (int col = visRect.left(); col <= visRect.right(); ++col) {
0447         if (d->sheet->columnFormat(col)->isHiddenOrFiltered())
0448             continue;
0449         if (rightToLeft)
0450             coordinate.setX(coordinate.x() - d->sheet->columnFormat(col)->width());
0451         for (int row = visRect.top(); row <= visRect.bottom(); ++row) {
0452             int lastHiddenRow;
0453             if (d->sheet->rowFormats()->isHiddenOrFiltered(row, &lastHiddenRow)) {
0454                 row = lastHiddenRow;
0455                 continue;
0456             }
0457             // For borders even cells, that are merged in, need to be traversed.
0458             // Think of a merged cell with a set border and one its neighbours has a thicker border.
0459             // but: also the master cell of a merged cell always needs to be processed
0460             const QPointF savedCoordinate = coordinate;
0461             Cell cell = d->cellToProcess(col, row, coordinate, processedMergedCells, visRect);
0462             if (!!cell && (cell.column() != col || cell.row() != row)) {
0463                 const CellView cellView = this->cellView(cell.cellPosition());
0464                 cellView.paintCellBorders(paintRect, painter, clipRect, coordinate,
0465                                           visRect,
0466                                           cell, this);
0467             }
0468             coordinate = savedCoordinate;
0469             Cell theCell(sheet(), col, row);
0470             const CellView cellView = d->cellViewToProcess(theCell, coordinate, processedObscuredCells, this, visRect);
0471             if (!!theCell && (theCell.column() != col || theCell.row() != row)) {
0472                 cellView.paintCellBorders(paintRect, painter, clipRect, coordinate,
0473                                           visRect,
0474                                           theCell, this);
0475             }
0476             const CellView cellView2 = this->cellView(col, row);
0477             coordinate = savedCoordinate;
0478             cellView2.paintCellBorders(paintRect, painter, clipRect, coordinate,
0479                                       visRect,
0480                                       Cell(sheet(), col, row), this);
0481             coordinate.setY(coordinate.y() + d->sheet->rowFormats()->rowHeight(row));
0482         }
0483         coordinate.setY(topLeft.y());
0484         if (!rightToLeft)
0485             coordinate.setX(coordinate.x() + d->sheet->columnFormat(col)->width());
0486     }
0487 
0488     // 5. Paint cell highlighting
0489     if (hasHighlightedCells()) {
0490         QPointF active = activeHighlight();
0491         QPainterPath p;
0492         const CellPaintData* activeData = 0;
0493         for (QList<CellPaintData>::ConstIterator it(cached_cells.constBegin()); it != cached_cells.constEnd(); ++it) {
0494             if (isHighlighted(it->cell.cellPosition())) {
0495                 p.addRect(it->coordinate.x(), it->coordinate.y(), it->cellView.cellWidth(), it->cellView.cellHeight());
0496                 if (it->cell.cellPosition() == active) {
0497                     activeData = &*it;
0498                 }
0499             }
0500         }
0501         painter.setPen(Qt::NoPen);
0502         if (d->highlightColor.isValid()) {
0503             painter.setBrush(QBrush(d->highlightColor));
0504             painter.drawPath(p);
0505         }
0506         if (d->highlightMaskColor.isValid()) {
0507             QPainterPath base;
0508             base.addRect(painter.clipPath().boundingRect().adjusted(-5, -5, 5, 5));
0509             p = base.subtracted(p);
0510             painter.setBrush(QBrush(d->highlightMaskColor));
0511             painter.drawPath(p);
0512         }
0513 
0514         if (activeData && d->activeHighlightColor.isValid()) {
0515             painter.setBrush(QBrush(d->activeHighlightColor));
0516             painter.setPen(QPen(Qt::black, 0));
0517             painter.drawRect(QRectF(activeData->coordinate.x(), activeData->coordinate.y(), activeData->cellView.cellWidth(), activeData->cellView.cellHeight()));
0518         }
0519     }
0520 }
0521 
0522 void SheetView::invalidateRange(const QRect& range)
0523 {
0524 #ifdef CALLIGRA_SHEETS_MT
0525     QMutexLocker ml(&d->cacheMutex);
0526 #endif
0527     QRegion obscuredRegion;
0528     const int right  = range.right();
0529     for (int col = range.left(); col <= right; ++col) {
0530         const int bottom = range.bottom();
0531         for (int row = range.top(); row <= bottom; ++row) {
0532             const QPoint p(col, row);
0533             if (!d->cache.contains(p))
0534                 continue;
0535             if (obscuresCells(p) || isObscured(p)) {
0536                 obscuredRegion += obscuredArea(p);
0537                 obscureCells(p, 0, 0);
0538             }
0539             d->cache.remove(p);
0540         }
0541     }
0542     d->cachedArea -= range;
0543     obscuredRegion &= d->cachedArea;
0544     foreach (const QRect& rect, obscuredRegion.rects()) {
0545         invalidateRange(rect);
0546     }
0547 }
0548 
0549 void SheetView::obscureCells(const QPoint &position, int numXCells, int numYCells)
0550 {
0551 #ifdef CALLIGRA_SHEETS_MT
0552     QWriteLocker(&d->obscuredLock);
0553 #endif
0554     // Start by un-obscuring cells that we might be obscuring right now
0555     const QPair<QRectF, bool> pair = d->obscuredInfo->containedPair(position);
0556     if (!pair.first.isNull())
0557         d->obscuredInfo->insert(Region(pair.first.toRect()), false);
0558     // Obscure the cells
0559     if (numXCells != 0 || numYCells != 0)
0560         d->obscuredInfo->insert(Region(position.x(), position.y(), numXCells + 1, numYCells + 1), true);
0561 
0562     QRect obscuredArea = d->obscuredInfo->usedArea();
0563     QSize newObscuredRange(obscuredArea.right(), obscuredArea.bottom());
0564     if (newObscuredRange != d->obscuredRange) {
0565         d->obscuredRange = newObscuredRange;
0566         emit obscuredRangeChanged(d->obscuredRange);
0567     }
0568 }
0569 
0570 QPoint SheetView::obscuringCell(const QPoint &obscuredCell) const
0571 {
0572 #ifdef CALLIGRA_SHEETS_MT
0573     QReadLocker(&d->obscuredLock);
0574 #endif
0575     const QPair<QRectF, bool> pair = d->obscuredInfo->containedPair(obscuredCell);
0576     if (pair.first.isNull())
0577         return obscuredCell;
0578     if (pair.second == false)
0579         return obscuredCell;
0580     return pair.first.toRect().topLeft();
0581 }
0582 
0583 QSize SheetView::obscuredRange(const QPoint &obscuringCell) const
0584 {
0585 #ifdef CALLIGRA_SHEETS_MT
0586     QReadLocker(&d->obscuredLock);
0587 #endif
0588     const QPair<QRectF, bool> pair = d->obscuredInfo->containedPair(obscuringCell);
0589     if (pair.first.isNull())
0590         return QSize(0, 0);
0591     if (pair.second == false)
0592         return QSize(0, 0);
0593     // Not the master cell?
0594     if (pair.first.toRect().topLeft() != obscuringCell)
0595         return QSize(0, 0);
0596     return pair.first.toRect().size() - QSize(1, 1);
0597 }
0598 
0599 QRect SheetView::obscuredArea(const QPoint &cell) const
0600 {
0601 #ifdef CALLIGRA_SHEETS_MT
0602     QReadLocker(&d->obscuredLock);
0603 #endif
0604     const QPair<QRectF, bool> pair = d->obscuredInfo->containedPair(cell);
0605     if (pair.first.isNull())
0606         return QRect(cell, QSize(1, 1));
0607     if (pair.second == false)
0608         return QRect(cell, QSize(1, 1));
0609     // Not the master cell?
0610     return pair.first.toRect();
0611 }
0612 
0613 bool SheetView::isObscured(const QPoint &cell) const
0614 {
0615 #ifdef CALLIGRA_SHEETS_MT
0616     QReadLocker(&d->obscuredLock);
0617 #endif
0618     const QPair<QRectF, bool> pair = d->obscuredInfo->containedPair(cell);
0619     if (pair.first.isNull())
0620         return false;
0621     if (pair.second == false)
0622         return false;
0623     // master cell?
0624     if (pair.first.toRect().topLeft() == cell)
0625         return false;
0626     return true;
0627 }
0628 
0629 bool SheetView::obscuresCells(const QPoint &cell) const
0630 {
0631 #ifdef CALLIGRA_SHEETS_MT
0632     QReadLocker(&d->obscuredLock);
0633 #endif
0634     const QPair<QRectF, bool> pair = d->obscuredInfo->containedPair(cell);
0635     if (pair.first.isNull())
0636         return false;
0637     if (pair.second == false)
0638         return false;
0639     // master cell?
0640     if (pair.first.toRect().topLeft() != cell)
0641         return false;
0642     return true;
0643 }
0644 
0645 QSize SheetView::totalObscuredRange() const
0646 {
0647 #ifdef CALLIGRA_SHEETS_MT
0648     QReadLocker(&d->obscuredLock);
0649 #endif
0650     return d->obscuredRange;
0651 }
0652 
0653 #ifdef CALLIGRA_SHEETS_MT
0654 CellView SheetView::defaultCellView() const
0655 #else
0656 const CellView& SheetView::defaultCellView() const
0657 #endif
0658 {
0659     return *d->defaultCellView;
0660 }
0661 
0662 void SheetView::updateAccessedCellRange(const QPoint& location)
0663 {
0664     const QSize cellRange = d->accessedCellRange.expandedTo(QSize(location.x(), location.y()));
0665     if (d->accessedCellRange != cellRange || location.isNull()) {
0666         d->accessedCellRange = cellRange;
0667         const int col = qMin(KS_colMax, cellRange.width() + 10);
0668         const int row = qMin(KS_rowMax, cellRange.height() + 10);
0669         const double width = sheet()->columnPosition(col) + sheet()->columnFormat(col)->width();
0670         const double height = sheet()->rowPosition(row) + sheet()->rowFormats()->rowHeight(row);
0671         emit visibleSizeChanged(QSizeF(width, height));
0672     }
0673 }
0674 
0675 CellView* SheetView::createDefaultCellView()
0676 {
0677     return new CellView(this);
0678 }
0679 
0680 CellView* SheetView::createCellView(int col, int row)
0681 {
0682     return new CellView(this, col, row);
0683 }
0684 
0685 bool SheetView::isHighlighted(const QPoint &cell) const
0686 {
0687 #ifdef CALLIGRA_SHEETS_MT
0688     QReadLocker(&d->highlightLock);
0689 #endif
0690     return d->highlightedCells.lookup(cell.x(), cell.y());
0691 }
0692 
0693 void SheetView::setHighlighted(const QPoint &cell, bool isHighlighted)
0694 {
0695 #ifdef CALLIGRA_SHEETS_MT
0696     QWriteLocker(&d->highlightLock);
0697 #endif
0698     bool oldHadHighlights = d->highlightedCells.count() > 0;
0699     bool oldVal;
0700     if (isHighlighted) {
0701         oldVal = d->highlightedCells.insert(cell.x(), cell.y(), true);
0702     } else {
0703         oldVal = d->highlightedCells.take(cell.x(), cell.y());
0704     }
0705     if (oldHadHighlights != (d->highlightedCells.count() > 0)) {
0706         invalidate();
0707     } else if (oldVal != isHighlighted) {
0708         invalidateRegion(Region(cell));
0709     }
0710 }
0711 
0712 bool SheetView::hasHighlightedCells() const
0713 {
0714 #ifdef CALLIGRA_SHEETS_MT
0715     QReadLocker(&d->highlightLock);
0716 #endif
0717     return d->highlightedCells.count() > 0;
0718 }
0719 
0720 void SheetView::clearHighlightedCells()
0721 {
0722 #ifdef CALLIGRA_SHEETS_MT
0723     QWriteLocker(&d->highlightLock);
0724 #endif
0725     d->activeHighlight = QPoint();
0726     if (d->highlightedCells.count()) {
0727         d->highlightedCells.clear();
0728         invalidate();
0729     }
0730 }
0731 
0732 QPoint SheetView::activeHighlight() const
0733 {
0734     return d->activeHighlight;
0735 }
0736 
0737 void SheetView::setActiveHighlight(const QPoint &cell)
0738 {
0739     QPoint oldVal = d->activeHighlight;
0740     d->activeHighlight = cell;
0741     if (oldVal != cell) {
0742         Region r;
0743         if (!oldVal.isNull()) r.add(oldVal);
0744         if (!cell.isNull()) r.add(cell);
0745         invalidateRegion(r);
0746     }
0747 }
0748 
0749 void SheetView::setHighlightColor(const QColor &color)
0750 {
0751     d->highlightColor = color;
0752     if (hasHighlightedCells()) {
0753         invalidate();
0754     }
0755 }
0756 
0757 void SheetView::setHighlightMaskColor(const QColor &color)
0758 {
0759     d->highlightMaskColor = color;
0760     if (hasHighlightedCells()) {
0761         invalidate();
0762     }
0763 }
0764 
0765 void SheetView::setActiveHighlightColor(const QColor &color)
0766 {
0767     d->activeHighlightColor = color;
0768     if (hasHighlightedCells()) {
0769         invalidate();
0770     }
0771 }